云原生CI/CD流水线发布

文章目录

  • 前言
  • k8s组件与操作流程
    • k8s组件
    • 创建pod
  • k8s
  • 代码&&打包
  • k8s yaml
    • deployment
    • service
    • k8s volumes
      • demo
  • CI
    • gitlab
    • CI runner
  • CD
    • 配置git repository
    • 安装argo
    • 创建argo cd的配置yaml
    • argocd和helm结合
    • argocd hook
    • argocd 发布
  • RBAC
  • operator
  • helm
  • prometheus && grafna
    • prometheus
      • metric

前言

本文章主要是记录一套完善的CICD流水线,从golang代码到打包—>到发布(argo)到k8s上,其中包含日志的存储(es),服务监控(Prometheus),监控可视化(grafana)

k8s组件与操作流程

k8s组件

k8s分成control panel和data panel,这2个panel都是node组成,control panel顾名思义就是我们的master节点,data panel就是我们的工作(work)节点…
在data panel中主要是由kubelet,kubelet用于和master节点(master的api server)进行通讯
而在control panel中有多个组件分别为etcd,api server,scheduler,controller manager

  • etcd:用于存储所有配置,比如我们的pod的状态等各个resource obj的理想状态(通过yaml设置的)
  • scheduler:被api-server激活后用于调度resource obj到work node上
  • controller manager:一个大的loop,用于监控集群中所有的对象的状态,并且和etcd中存储的状态做比较,不一样就按照etcd中存储的理想状态进行动态调整
  • api-server:非常重要,api-server链接所有的组件(etcd,scheduler,controller manager,kubelet which peer),所有的组件都要通过这个api-server进行通讯,且kubectl用户端也是通过api-server与k8s集群通讯

在这里插入图片描述

如上图

注意!!!,我们的集群是kubeadm一键搭建的,什么coredns,etcd,kube-apiserver都以pod的形式搭建在k8s集群中,我们当然可以将其拿出来在liuux上额外的安装

创建pod

当我们通过yaml文件(kube apply -f ...)创建多个pod的时候流程如下

  • 首先客户端通过连接api-server运行yaml文件生成manifest,这manifest有我们指定的pod配置,比如多少份(replicas 3)等等传递给etcd,etcd存储这些配置文件,并且创建3个pod(manifest配置的),此时pod状态为pending
  • etcd创建完对应的pod后回传消息给api-server,告诉他完成pod初始创建,需要将他调度到work node上,api-server此时唤醒scheduler
  • scheduler被唤醒后选择work node(也是通过api-server查找etcd看node的信息),然后将选择的node发送给api-server,最后scheduler接着沉睡
  • api-server接到消息后传递给对应worker node的kubelet,让这些worker node通过kubelet创建pod,此时pod的状态是creating(更新到etcd中)
  • 当work-node的kubelet创建完pod后将创建完的消息发送给api-server,api-server再通知etcd,让etcd将对应的pod状态改为running

k8s

不介绍了,教程一大堆,这里默认安装成功,注意的是,我的环境下,使用的是containerd(container runtime), docker(container engine),一台master,2台slaver

container engine vs container runtime
首先container runtime是container engine的重要组成部分
我们一个容器生命周期包含,拉镜像,运行,存储,销毁等等,这个容器的生命周期都是由container engine来维护,但是容器最终是要运行在os上,既然要运行在os上那么必不可少的一些syscall(unix为例子),比如mount(),clone(),这些syscall是container engine必须要操作的,因此这一部分与os密切相关(如何在os上存储如何在os上运行等)的功能叫做container runtime,必须要注意的是,container runtime必须要遵守OCI标准,OCI标准是k8s所提出来的(不确定),containerd就是一个非常出色的container runtime,其核心功能用runc完成
containerddocker的一部分,之前的新闻说k8s不再支持docker,而是转向支持containerd是什么情况,首先docker是先于k8s发行,在k8s发布后,没有多余可以选择的产品,所以选择了docker作为容器引擎(当时的docker的runtime就是containerd),此后因为k8s产品理念(模块化),使用者可以自己搭配组件,以达到自己的使用目的,并且当时市面上出现了多个container runtime,此时k8s提出了CRI(container runtime interface)标准,k8s的流程是由kubelete发送CRI给container runtime进行容器运行,生命周期由k8s掌管,但是为了兼容之前的版本(docker作为container runtime),k8s为docker额外写一个组件叫做dockerdim(因为之前的版本CRI是直接发送给docker而非containerd,虽然说containerd支持oci且是docker的一部分,但是docker不支持,所以需要dockerdim将oci翻译成docker的api进行下发),之后在1.21版本后k8s不再支持dockerdim,解决方案是绕过docker直接将oci下发给containerd(containerd支持oci),因为k8s不需要docker去操作容器生命周期,k8s可以直接通过oci直接与containerd去接管
在这里插入图片描述

代码&&打包

很简单的golang代码,实现功能就是提供get请求,并且服务端记录日志等待后续的输出

package main

import(
        "net/http"
        "fmt"
        "log"
        "os"
)

func handler_for_hello_get(rw http.ResponseWriter, r *http.Request){
        file,_ := os.OpenFile("access_log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
        infologger := log.New(file, "INFO", log.Ldate|log.Ltime|log.Lshortfile)

        if r.Method != "GET"{
                fmt.Fprintf(rw, "handle get method only!!!\n")
                infologger.Println("connect from ", r.RemoteAddr, " Failed!!!")
                file.Close()
                return
        }

        fmt.Fprintf(rw, "hello!!!\n")
        infologger.Println("connect from ", r.RemoteAddr, " Success!!!")

        file.Close()
}

func main(){
        add := ":8080"
        fmt.Printf("start server at %v\n", add)
        http.HandleFunc("/hello", handler_for_hello_get)
        log.Fatal(http.ListenAndServe(add, nil))
}

当我们写完代码后需要写dockerfile,然后打包,这里注意的是,我们就一个代码,没有设计到第三方库,所以不需要什么go.mod和go.sum文件,所以dockerfile的时候不用copy go.mod和go.sum(本来也没有),dockerfile如下

#golang 1.20 image
FROM golang:1.20

# set workdir
WORKDIR /app

#copy go.mod and go.sum but golang code we have doesn't import third-part package, so we #
#COPY go.mod go.sum ./

# same as front
#RUN go mod download

# copy golang source code to image
COPY *.go ./

EXPOSE 8081

CMD ["go", "run", "server.go"]

开始build打包,打包后的镜像名字为honkytonkman/server_in_k8s,注意/后是镜像名字,/前是用户名字,后面我们docker login的时候login的用户名一定要是待推送image /前面的名字,不然就出错

root@k8s-master:~/k8s_demo# docker build  . -t honkytonkman/server_in_k8s
[+] Building 2.0s (8/8) FINISHED                                                                                                                          docker:default
 => [internal] load build definition from Dockerfile                                                                                                                0.0s
 => => transferring dockerfile: 351B                                                                                                                                0.0s
 => [internal] load .dockerignore                                                                                                                                   0.0s
 => => transferring context: 2B                                                                                                                                     0.0s
 => [internal] load metadata for docker.io/library/golang:1.20                                                                                                      2.0s
 => [1/3] FROM docker.io/library/golang:1.20@sha256:bc5f0b5e43282627279fe5262ae275fecb3d2eae3b33977a7fd200c7a760d6f1                                                0.0s
 => [internal] load build context                                                                                                                                   0.0s
 => => transferring context: 31B                                                                                                                                    0.0s
 => CACHED [2/3] WORKDIR /app                                                                                                                                       0.0s
 => CACHED [3/3] COPY *.go ./                                                                                                                                       0.0s
 => exporting to image                                                                                                                                              0.0s
 => => exporting layers                                                                                                                                             0.0s
 => => writing image sha256:2f86a57b12cc98f087c73ad5cfe126129131718bcc7b64a2a01567f8b513a26e                                                                        0.0s
 => => naming to docker.io/zhr/server_in_k8s                                                                                                                        0.0s

打包后查看镜像

root@k8s-master:~/k8s_demo# docker images
REPOSITORY                   TAG       IMAGE ID       CREATED         SIZE
honkytonkman/server_in_k8s   latest    2f86a57b12cc   5 minutes ago   845MB

如果生产环境下这些都是自动化的(CI)然后传入公司的镜像仓库,但是我们这里是自己的环境所以我打算上传到dockerhub的公共仓库中,再在k8s中拉取下来

root@k8s-master:~/k8s_demo# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: honkytonkman
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

push镜像

root@k8s-master:~/k8s_demo# docker push honkytonkman/server_in_k8s

k8s yaml

deployment

k8s有多个object,比如什么Pod,Deployment,Service等等,他们可以通过yaml进行配置,可以通过命令行进行配置,这里主要讲述通过yaml进行配置

pod和Deployment有啥区别呢?首先在生产环境我们会很少碰到只用Pod的情况,因为一个服务不仅仅是Pod,他需要指定replicate的数量,他需要指定持久存储(可能),他需要指定template(就是Pod)等等,而Pod就仅仅是Pod,而Service object主要是为多个replicate的Pod分配一个虚拟ip供外部访问

无论什么object的yaml,他们都分为3类,分别是

  • metadata

    metadata就是创建pod指定的metadata比如label,name之类的

  • specification

    在yaml中简写为spec,主要是pod的配置,根据kind的不同而不同

  • status

    这个是k8s自己生成的,我们期望Pod的status在spec:中指定了,但是运行过程中Pod的真正status由k8s查看Pod得出,k8s不断地比较这2个status,不过不一样比如我们规定replicats为2但是实际的replicats为1,所以k8s会发现status不一样,然后自动调整以达到我们spec规定的状态,具体实现这个操作的k8s组件是controller

下面是我们的deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: server
  labels:
    app: web
spec:
  selector:
    matchLabels:
      app: web
  replicas: 5
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: server
        image: honkytonkman/server_in_k8s
        ports:
        - containerPort: 8080

上述的Pod只有一个container,假如一个Pod有多个container,那么多个container共享IPC和Network namespace,也就是说一个Pod中多个container可以通过localhost进行访问,process namespace默认不共享,但是我们可以通过设置

spec: #这个是pod的spec
 shareProcessNamespace: true
 containers: 
 ...

当然deployment可也通过kubectl进行设置

是不是很奇怪metadata里面的labelspec里面的selector,还有selector中的matchlabel,这里会详细的解释
首先要明确的是labelmetadata里面的属性,且label是k/v格式,一个node可以有label,一个service之类的object可以有label,在外面的label是设置整个deplyment的属性,而在template中的label是设置pod的属性(template就是设置pod的container还有镜像什么的),在selector中matchLabels选中kv属性为app: web的pod运行,正好我们template中设置了labels为app: web,所以运行他

然后我们运行这个deployment

root@k8s-master:~/k8s_demo# kubectl apply -f server_deplyment.yaml
deployment.apps/server configured

然后查看deplyment,发现我们创建的5个pod都正常运行,然后我们看是那些什么pod

root@k8s-master:~/k8s_demo# kubectl get deployments
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
server   5/5     5            5           47m

我们可以通过刚刚给pod设置的label进行pod查找

root@k8s-master:~/k8s_demo# kubectl get pods --selector app=web
NAME                      READY   STATUS    RESTARTS   AGE
server-7776bd5fb9-48lck   1/1     Running   0          17m
server-7776bd5fb9-7t4mh   1/1     Running   0          18m
server-7776bd5fb9-8pjvc   1/1     Running   0          17m
server-7776bd5fb9-ftw8n   1/1     Running   0          18m
server-7776bd5fb9-x8knr   1/1     Running   0          17m

也可以根据名字进行查找,因为pod的命明规则是deployment.yaml中设置的名字再加上pod id,我们的deployment的名字是server所以

root@k8s-master:~/k8s_demo# kubectl get pods | grep ^server-
server-7776bd5fb9-48lck   1/1     Running   0          20m
server-7776bd5fb9-7t4mh   1/1     Running   0          20m
server-7776bd5fb9-8pjvc   1/1     Running   0          20m
server-7776bd5fb9-ftw8n   1/1     Running   0          20m
server-7776bd5fb9-x8knr   1/1     Running   0          20m

我们查看pod的详细信息

root@k8s-master:~/k8s_demo# kubectl describe pod server-7776bd5fb9-48lck
Name:             server-7776bd5fb9-48lck
Namespace:        default
Priority:         0
Service Account:  default
Node:             k8s-slaver1/192.168.152.132
Start Time:       Wed, 09 Aug 2023 11:52:12 +0800
Labels:           app=web
                  pod-template-hash=7776bd5fb9
Annotations:      <none>
Status:           Running
IP:               10.244.1.6
IPs:
  IP:           10.244.1.6
Controlled By:  ReplicaSet/server-7776bd5fb9
Containers:
  server:
    Container ID:   containerd://3668c62a0caa0d2caad516cf46b3b3115e6f52ec3196a0a041fb6ee438f42f00
    Image:          honkytonkman/server_in_k8s
    Image ID:       docker.io/honkytonkman/server_in_k8s@sha256:9ca2b7dc0aaace89f0e914a1b87bfd5eab24cfbf87ff1bb61a45962eab9ff825
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Wed, 09 Aug 2023 11:52:17 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-xnmnx (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  kube-api-access-xnmnx:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  22m   default-scheduler  Successfully assigned default/server-7776bd5fb9-48lck to k8s-slaver1
  Normal  Pulling    22m   kubelet            Pulling image "honkytonkman/server_in_k8s"
  Normal  Pulled     22m   kubelet            Successfully pulled image "honkytonkman/server_in_k8s" in 2.46795589s (3.418282616s including waiting)
  Normal  Created    22m   kubelet            Created container server
  Normal  Started    22m   kubelet            Started container server

然后发现ip和端口后我们直接测试链接是否pod提供的服务正常

root@k8s-master:~/k8s_demo# curl 10.244.1.6:8080/hello
hello!!!

我们详细(没有describe那么详细)的看deployment中pod的信息

root@k8s-master:~/k8s_demo# kubectl get pods -o wide --selector app=web
NAME                      READY   STATUS    RESTARTS   AGE   IP           NODE          NOMINATED NODE   READINESS GATES
server-7776bd5fb9-48lck   1/1     Running   0          28m   10.244.1.6   k8s-slaver1   <none>           <none>
server-7776bd5fb9-7t4mh   1/1     Running   0          28m   10.244.1.4   k8s-slaver1   <none>           <none>
server-7776bd5fb9-8pjvc   1/1     Running   0          28m   10.244.1.5   k8s-slaver1   <none>           <none>
server-7776bd5fb9-ftw8n   1/1     Running   0          28m   10.244.2.6   k8s-slaver2   <none>           <none>
server-7776bd5fb9-x8knr   1/1     Running   0          28m   10.244.2.7   k8s-slaver2   <none>           <none>

发现有3个node被调度到k8s-server1上,如果k8s-slaver2是一台高新能机器,且deployment的pod运行时也需要高性能(或者说k8s-slaver2属于一个database服务的专属服务器,这个depolyment是用于部署database服务的)我们需要将deployment的所有pod都转移到k8s-slaver2中,这怎实现呢?用labels

首先给k8s-slaver2打上标签

root@k8s-master:~/k8s_demo# kubectl get nodes -o wide
NAME          STATUS   ROLES           AGE   VERSION   INTERNAL-IP       EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
k8s-master    Ready    control-plane   41h   v1.27.4   192.168.152.131   <none>        Ubuntu 20.04.6 LTS   5.15.0-78-generic   containerd://1.6.21
k8s-slaver1   Ready    <none>          41h   v1.27.4   192.168.152.132   <none>        Ubuntu 20.04.6 LTS   5.15.0-78-generic   containerd://1.7.2
k8s-slaver2   Ready    <none>          41h   v1.27.4   192.168.152.133   <none>        Ubuntu 20.04.6 LTS   5.15.0-78-generic   containerd://1.7.2

root@k8s-master:~/k8s_demo# kubectl label nodes k8s-slaver2 group=database
node/k8s-slaver2 labeled

然后在deployment上加nodeSelector

apiVersion: apps/v1
kind: Deployment
metadata:
  name: server
  labels:
    app: web
spec:
  selector:
    matchLabels:
      app: web
  replicas: 5
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        group: database
        app: web
    spec:
      containers:
      - name: server
        image: honkytonkman/server_in_k8s
        ports:
        - containerPort: 8080
      nodeSelector:
        group: database

重新apply deployment,因为在spec中设置了策略是滚动更新(RollingUpdate),也就是说,先创建新pod再删除原有的pod

如果我们设置了滚动更新,假设有5个Pod副本(replicas:5),手动删除一个,那么删除后会立马自动创建一个新的pod,以达到replicas=5这个status,因为开始讲了k8s会无时无刻的比较生产环境中的pod的状态(因为刚刚删除了一个pod此时replicas为4),和我们配置的理想状态(replicas为5)k8s发现不一样立马调整生产环境中的状态以达到我们配置的理想状态,这也叫self-heal

root@k8s-master:~/k8s_demo# kubectl apply -f server_deplyment.yaml
deployment.apps/server configured
root@k8s-master:~/k8s_demo# kubectl get pods -o wide --show-labels
NAME                      READY   STATUS    RESTARTS   AGE   IP            NODE          NOMINATED NODE   READINESS GATES   LABELS
server-59f9bd6776-9tlpq   1/1     Running   0          42s   10.244.2.15   k8s-slaver2   <none>           <none>            app=web,group=database,pod-template-hash=59f9bd6776
server-59f9bd6776-bjlw4   1/1     Running   0          50s   10.244.2.12   k8s-slaver2   <none>           <none>            app=web,group=database,pod-template-hash=59f9bd6776
server-59f9bd6776-xtsvr   1/1     Running   0          50s   10.244.2.11   k8s-slaver2   <none>           <none>            app=web,group=database,pod-template-hash=59f9bd6776
server-59f9bd6776-xwkkv   1/1     Running   0          45s   10.244.2.14   k8s-slaver2   <none>           <none>            app=web,group=database,pod-template-hash=59f9bd6776
server-59f9bd6776-z8gg6   1/1     Running   0          50s   10.244.2.13   k8s-slaver2   <none>           <none>            app=web,group=database,pod-template-hash=59f9bd6776

最后发现所有的pod都被调度到k8s-slaver2上了

service

因为deployment创建了5个pod每个pod都有一个单独的ip,并且提供相同的功能,我们目前的需求是要5个pod同时通过一个ip对外提供服务,所以此时service就出现了,简单讲service就是为一个服务的多个pod提供一个虚拟ip供外界访问
不过service有2种提供虚拟ip的方式,一个是NodePort,一个是ClusterIP

  • NodePort:就是使用本node的ip,不过会随机开一个端口,通过访问本node的端口达到对service的访问
  • ClusterIP:故名思意就是分配一个集群ip,通过集群ip访问

可能你还有疑问,如果用yaml文件进行部署,service和deployment是2个不同的yaml文件,如何确定service和deployment这2个object可以匹配上?注意deployment的最外层的matedata中我们设置了labels,这个labels是针对这个deployment文件的label,而在service的yaml中也有一个key是selector,这个selector可以选中特定deployment的label,这样2者就匹配上了,上面的deployment的label是app: web,这里我们的service就选中他即可
service.yaml文件如下

apiVersion: v1
kind: Service
metadata:
  name: service-obj-for-service
  labels:
    app: web
    type: service
spec:
  type: ClusterIP #use cluster ip
  selector:
    app: web
  ports:
  - protocol: TCP
    port: 8081 #对外的ip端口
    targetPort: 8080 #pod内需要映射出去的端口

然后使用这个yaml

root@k8s-master:~/k8s_demo# kubectl apply -f server_service.yaml
service/service-obj-for-service created
root@k8s-master:~/k8s_demo#
root@k8s-master:~/k8s_demo#
root@k8s-master:~/k8s_demo# kubectl get service
NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes                ClusterIP   10.96.0.1       <none>        443/TCP    2d16h
service-obj-for-service   ClusterIP   10.107.202.64   <none>        8081/TCP   8s

通过分配的集群ip进行访问

root@k8s-master:~/k8s_demo# curl 10.107.202.64:8081/hello
hello!!!

但是我们并不能确定是否后端的5个pod是否平均分担负载,因为代码只返回hello,没有返回当前主机名,所以等后面会通过gitlab+argo的方式搭建一条完整的CICD流水线,当代码改动自动的CI(打包dockerfile–>push打包后的镜像到镜像仓库中),自动的CD(使用helm2模板自动部署到argo中)

然后我们尝试使用nodeport的方式进行访问
更改service的yaml为NodePort模式

apiVersion: v1
kind: Service
metadata:
  name: service-obj-for-service
  labels:
    app: web
    type: service
spec:
  type: NodePort #use cluster ip
  selector:
    app: web
  ports:
  - protocol: TCP
    port: 8081 #对外的ip端口
    targetPort: 8080 #pod内需要映射出去的端口

使用yaml

root@k8s-master:~/k8s_demo# kubectl apply -f server_service.yaml

然后查看

root@k8s-master:~/k8s_demo# kubectl describe service service-obj-for-service
root@k8s-master:~/k8s_demo# kubectl describe service service-obj-for-service
Name:                     service-obj-for-service
Namespace:                default
Labels:                   app=web
                          type=service
Annotations:              <none>
Selector:                 app=web
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.107.202.64
IPs:                      10.107.202.64
Port:                     <unset>  8081/TCP
TargetPort:               8080/TCP
NodePort:                 <unset>  31168/TCP
Endpoints:                10.244.1.15:8080,10.244.1.18:8080,10.244.1.19:8080 + 2 more...
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
root@k8s-master:~/k8s_demo#

发现本机自动地暴露了一个tcp端口(31168),分配的cluster的ip没有变还是10.107.202.64:8081
这个时候我们再通过本机node的ip访问成功

root@k8s-master:~/k8s_demo# curl 192.168.152.131:31168/hello
hello!!!

此时流量访问的流程是

client----->k8s_master_IP:31168------>cluster_virtual_ip:8081--->Multiple_Pod:8080

type还可以设置为LoadBalancer这个我们后面实验

k8s volumes

这里我们只是学习学习,并不发布到cicd中去

首先我们直到容器时stateless的,它并不保存他运行时的状态,所以传统上来说,容器并不用作数据库应用,但是在k8s中则不一样,k8s中提供了一个object叫做PersistentVolume,它可以使用外部的存储,比如nfs,比如公有云提供的云存储服务,也可以使用本地磁盘等等存储服务进行pod的数据存储,并且当pod被删除和重新创建后可以通过configmap等object再重新的指向PresistentVolume,在k8s volumes中有3个重要的object分别是PersistentVolume,PersistentVolumeClaim,StorageClass

Ephemeral volume types have a lifetime of a pod, but persistent volumes exist beyond the lifetime of a pod. When a pod ceases to exist, Kubernetes destroys ephemeral volumes; however, Kubernetes does not destroy persistent volumes. For any kind of volume in a given pod, data is preserved across container restarts.

PersistentVolume并不属于任何一个namespace,而是属于整个集群!!!,所以我们的Pod在使用的时候需要使用PersistentVolumeClaim(PVC),PVC向PersistentVolume申请存储,然后给Pod用(PVC有namespace)

StorageClass和PersistentVolume一样游离于namespace之外

demo

本地临时存储
demo的过程,我们的deplyment只会创造一个pod,然后在slaver1创建,然后在pod中写入文件,最后我们再将pod迁移到slaver2中,再查看pod的零时存储的文件是否还在,我们迁移不会使用kubectl drain驱逐node上的pod,再kubectl cordon使节点不可调度,因为我们的node上还有其他的pod需要使用,让slaver2承受所有太难了,所以我们的方案是在deployment上使用matchlabel匹配slaver2的label

deployment如下

apiVersion: apps/v1
kind: Deployment
metadata: 
  name: test-pod-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-pod
  template:
    metadata:
      labels: #set pod label
        app: test-pod
    spec:
      volumes:
      - name: cache-volume
        emptyDir: {}
      containers:
      - image: nginx
        name: test-pod-emptydir
        volumeMounts:
        - mountPath: /cache #mount to pod /cache
          name: cache-volume

然后apply

root@k8s-master:~/storage# kubectl apply -f nginx-localstorage-deployment.yml
deployment.apps/test-pod-deployment created
root@k8s-master:~/storage# kubectl get pod -o wide  | grep test-pod
test-pod-deployment-74f78694b9-9wshr   1/1     Running   0              70s    10.244.2.141   k8s-slaver2   <none>           <none>

发现存在于slaver2上,我们进去看看,并且在dir中存入

root@k8s-master:~/storage# kubectl exec -it test-pod-deployment-74f78694b9-9wshr -- /bin/bash 
root@test-pod-deployment-74f78694b9-9wshr:/# cd /cache/
root@test-pod-deployment-74f78694b9-9wshr:/cache# echo "for test emptydir" > test.txt
root@test-pod-deployment-74f78694b9-9wshr:/cache# cat /cache/test.txt 
for test emptydir
root@test-pod-deployment-74f78694b9-9wshr:/cache# exit
exit

然后我们进行驱逐操作,首先是看slaver1的labels(以免我们使用kubectl label 手动给node打标签)

root@k8s-master:~/storage# kubectl get nodes --show-labels
NAME          STATUS   ROLES           AGE   VERSION   LABELS
k8s-master    Ready    control-plane   54d   v1.27.4   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node.kubernetes.io/exclude-from-external-load-balancers=
k8s-slaver1   Ready    <none>          54d   v1.27.4   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-slaver1,kubernetes.io/os=linux
k8s-slaver2   Ready    <none>          54d   v1.27.4   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,group=database,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-slaver2,kubernetes.io/os=linux

然后我们决定使用slaver1的kubernetes.io/hostname=k8s-slaver1这个标签。所以改动我们的deployment加上nodeSelector

apiVersion: apps/v1
kind: Deployment
metadata: 
  name: test-pod-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-pod
  template:
    metadata:
      labels: #set pod label
        app: test-pod
    spec:
      nodeSelector:
        kubernetes.io/hostname: k8s-slaver1
      volumes:
      - name: cache-volume
        emptyDir: {}
      containers:
      - image: nginx
        name: test-pod-emptydir
        volumeMounts:
        - mountPath: /cache #mount to pod /cache
          name: cache-volume

再进行apply,最后查看

root@k8s-master:~/storage# kubectl get pod -o wide  | grep test-pod
test-pod-deployment-564f5bf74f-tg2md   1/1     Running   0              2m45s   10.244.1.160   k8s-slaver1   <none>           <none>

pod已经跑到我们的slaver1上了,最后我们看test.txt还在吗

root@k8s-master:~/storage# kubectl exec -it test-pod-deployment-564f5bf74f-tg2md -- /bin/bash
root@test-pod-deployment-564f5bf74f-tg2md:/# cd /cache
root@test-pod-deployment-564f5bf74f-tg2md:/cache# ls
root@test-pod-deployment-564f5bf74f-tg2md:/cache# 

啥也没有

说明我们的local存储再经过调度到其他的节点后,原本存储的东西已经都没有了
因为pod是stateless的,经过调度,原pod宿主机上的存储数据都会被删除,要想数据还保存在,我们推荐使用pv和pvc实现持久化存储

持久化存储
我们首先在master上配置nfs,所有的数据都会被存入nfs中,然后再通过pvc和pv的方式共享出来,实现stateful,无论pod怎么调度,数据都还在
安装nfs不讲了
直接开始写pv如下

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-data
  namespace: default
spec:
  accessModes:
  - ReadWriteMany
  capacity:
    storage: 1Gi
  persistentVolumeReclaimPolicy: Retain #retain代表对应pvc被删除的时候,保留pv声明的数据,且不被其他pvc重复使用,delete代表对应的pvc被删除的时候对应的pv和pv指向的存储对象(aws等对象)也被删除,recycle代表pvc被删除的时候pv对应的存储对象数据被删除,且被其他的pvc复用
  nfs:
    server: 192.168.152.131
    path: /data
root@k8s-master:~/storage# kubectl apply -f nfs-pv.yml 
persistentvolume/pv-nfs-data created
root@k8s-master:~/storage# 
root@k8s-master:~/storage# 
root@k8s-master:~/storage# kubectl get pv 
NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv-nfs-data   1Gi        RWX            Retain           Available                                   25m

然后我们再声明对应的pvc,pvc就有namespace的概念了,且pvc必须要和pv一个名字不然不能bound

apiVersion: v1
kind: PersistentVolumeClaim
metadata: 
  name: pvc-nfs-data # same as pv's name
  namespace: default
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

查看

root@k8s-master:~/storage# kubectl apply -f nfs-pvc.yml 
persistentvolumeclaim/pvc-nfs-data1 created
root@k8s-master:~/storage# kubectl get pvc
NAME              STATUS    VOLUME        CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-nfs-data      Bound     pv-nfs-data   1Gi        RWX                           27s                                                   6s

然后我们直接在nfs中创建文件

root@k8s-master:~/storage# showmount -e
Export list for k8s-master:
/data *
root@k8s-master:~/storage# cd /data
root@k8s-master:/data# echo "presistent storage test" > test.txt

然后改动deployment,再apply

apiVersion: apps/v1
kind: Deployment
metadata: 
  name: test-pod-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-pod
  template:
    metadata:
      labels: #set pod label
        app: test-pod
    spec:
      nodeSelector:
        kubernetes.io/hostname: k8s-slaver1
      volumes:
      - name: persistent-volume
        #emptyDir: {}
        persistentVolumeClaim:
          claimName: pvc-nfs-data
      containers:
      - image: nginx
        name: test-pod-emptydir
        volumeMounts:
        - mountPath: /cache #mount to pod /cache
          name: persistent-volume

发现文件已经在里面

root@k8s-master:~/storage# kubectl apply -f nginx-localstorage-deployment.yml
deployment.apps/test-pod-deployment configured
root@k8s-master:~/storage# 
root@k8s-master:~/storage# 
root@k8s-master:~/storage# 
root@k8s-master:~/storage# 
root@k8s-master:~/storage# kubectl get pod -o wide | grep test
test-pod-deployment-66cb996cbb-jb9xr   1/1     Running   0               48s    10.244.1.161   k8s-slaver1   <none>           <none>
test-pv-pod                            0/1     Pending   0               41d    <none>         <none>        <none>           <none>
root@k8s-master:~/storage# kubectl exec -it test-pod-deployment-66cb996cbb-jb9xr -- /bin/bash
root@test-pod-deployment-66cb996cbb-jb9xr:/# cd /cache/
root@test-pod-deployment-66cb996cbb-jb9xr:/cache# ls
test.txt
root@test-pod-deployment-66cb996cbb-jb9xr:/cache# cat test.txt 
presistent storage test

然后再将pod迁移到slaver2中(更改deployment文件的nodeselect)

apiVersion: apps/v1
kind: Deployment
metadata: 
  name: test-pod-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-pod
  template:
    metadata:
      labels: #set pod label
        app: test-pod
    spec:
      nodeSelector:
        kubernetes.io/hostname: k8s-slaver2
      volumes:
      - name: persistent-volume
        #emptyDir: {}
        persistentVolumeClaim:
          claimName: pvc-nfs-data
      containers:
      - image: nginx
        name: test-pod-emptydir
        volumeMounts:
        - mountPath: /cache #mount to pod /cache
          name: persistent-volume

最后查看发现无论pod怎么迁移数据还是存在

root@k8s-master:~/storage# kubectl apply -f nginx-localstorage-deployment.yml
deployment.apps/test-pod-deployment configured
root@k8s-master:~/storage# 
root@k8s-master:~/storage# 
root@k8s-master:~/storage# kubectl get pod -o wide | grep test-pod
test-pod-deployment-74df69669c-cbvzz   1/1     Running   0               14s    10.244.2.142   k8s-slaver2   <none>           <none>
root@k8s-master:~/storage# kubectl exec -it test-pod-deployment-74df69669c-cbvzz -- /bin/bash
root@test-pod-deployment-74df69669c-cbvzz:/# cd /cache/
root@test-pod-deployment-74df69669c-cbvzz:/cache# ls
test.txt
root@test-pod-deployment-74df69669c-cbvzz:/cache# cat test.txt 
presistent storage test
root@test-pod-deployment-74df69669c-cbvzz:/cache# 

动态pv
这里主要是讲sc(storageclass),sc更加的具有弹性,sc可以根据存储类型去定义,比如一个ssd存储一个sc,一个nfs一个sc,然后用户在使用的时候(pvc)可以指定sc,然后由sc自动的生成pv(根据sc设置的类别)
但是sc需要依赖第三方的插件,比如nfs的第三方插件,我们先通过helm下载nfs的插件


CI

这里CI就是用gitlab进行自动化的构建打包镜像,然后推送到dockerhub中,而gitlab我们就不自己搭建了,而是直接用gitlab.com

gitlab

首先gitlab有着极其丰富的功能,包含代码的版本管理,还有就是今天的重头戏CI(当然gitlab也支持CD,不过我们只用gitlab做CI)
在CI之前,我们的业务代码就是上面的go语言写的web server要先上传到gitlab中,具体怎么上传教程非常多,就不介绍了
当代码到了gitlab仓库后进入gitlab主页的代码项目中,点击左边的Build再点击Pipeline Editor进行CI Pipeline的编写(如果是自己搭建,且版本有点老Pipeline Editor在左边的CICD选项中,点击即可下拉选中Pipeline Editor)
点击后会出现一个在线编辑的界面,让我编辑Pipeline的yml文件,这个文件会自动的创建

Gitlab的CI Pipeline的yml配置文件非常简单,Pipeline由2部分组成,分别是stage和job,一个stage中包含多个job,比如我们一套代码流程(Pipeline)包含编译–>测试–>发布,其中编译,测试,发布都可以看成stage,而Job就是具体执行的操作,比如一个测试stage中可以分成2个job,分别是单元测试job和压力测试job,编译stage可以就只有一个job,比如build-job类似,在gitlab Pipeline yml语法中job和stages都是key,其中stages定义了多个stage(数组形式),job定义了从属的stage和做的具体操作(script),在test的时候job运行具体的命令,当命令返回非0的时候pipeline才会停止

我们开始明确了Gitlab只做CI,那么Pipeline stage可以分成4部分,分别是build–>test–>package–>push_to_dockerhub

注意!!!每个job执行完毕后都会恢复现场,比如编译后的文件会一一删除,进入的目录会一一退出

因为我们要将golang的源代码编译成二进制文件,所以简单的go run就不行了,这里我们改一下golang的源码,添加go.mod
go.mod文件如下

module main

然后就可以进行编译成二进制文件了,此时只需要push这个新的文件到gitlab中即可,然后我们更新需求,因为有2个branch,一个dev,一个main,我们设置了main不准被commit,只准commit到dev,再由dev发merge request到main才行,所以前2个stage可以main和dev

.gitlab-ci.yml文件如下


stages:          # List of stages for jobs, and their order of execution
  - build
  - test
  - package
  - push_to_dockerhub

build-job:       # This job runs in the build stage, which runs first.
  stage: build
  script:
    - echo ${CI_PIPELINE_SOURCE}
    - echo ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}
    - cd src
    - echo "build golang code to binary file"
    - /usr/local/go/bin/go build # 因为我们的runner在本地的机器shell运行(excutor是shell)此时会以gitlab-ci这个用户登录然后运行我们的pipeline,我们最好加上go的全路径因为我的PATH写在root用户的bashrc下

test-job:   # This job runs in the test stage.
  stage: test    # It only starts when the job in the build stage completes successfully.
  script:
    - echo "Running tests... "
    - pwd && ls -al
    - cd src
    - /usr/local/go/bin/go build #因为上一个job执行完毕后会删除job在运行中产生的文件
    - ./main& #后台运行编译后的二进制文件
    - echo "access url and get respond_file"
    - curl -I 127.0.0.1:8080/hello  >> respond_file
    - echo "check if file exist and return 200"
    - test -f  respond_file &&  grep  "HTTP/1.1 200 OK" respond_file
    - echo "test complete,and kill ./main process"
    - kill  `ps -aux | grep ./main | head -1 | awk   '{print $2}'`

package-job:   # This job also runs in the package stage.
  stage: package  
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_REF_NAME == "main"'   
      when: manual #手动执行,代表着这个job不会自动执行需要工作人员手动点执行才会执行
      allow_failure: false
  script:
    - echo "start to package"
    - cd src
    - docker build  . -t honkytonkman/server_in_k8s

push-job:      # This job runs in the deploy stage.
  stage: push_to_dockerhub  # It only runs when *both* jobs in the test stage complete successfully.
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_REF_NAME == "main"'   
      when: manual #手动执行,代表着这个job不会自动执行需要工作人员手动点执行才会执行
      allow_failure: false
  script:
    - echo "login to dockerhub"
    - docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD}  #login dockerhub
    - echo "push image"
    - docker push honkytonkman/server_in_k8s


我们可以看到CI Pipeline的yml文件中有2个变量,这个变量并没有在yml文件中设置,因为这些都是机密变量分别保存了密码和账号,所以我们将这些变量设置为预变量,预变量在gitlab的setting的cicd中 设置,变量可以选择环境范围(mask variable开启将会对这些变量在job log中加密,protect variable代表将会在受保护的分支上运行)

我们也可以通过设置rules来规定我们的job由什么branch执行,什么时候执行,匹配commit的log,匹配等等信息官网的文章解释的非常好

为什么package-job和push-job中的CI_PIPELINE_SOURCE 设置的是push而不是merge_request_event,因为merge_request_event指的是这个分指发起了mr才会触发pipeline,而我们是merge合并后触发,所以是push,如果要用merge_request_event就可以在dev分支中使用,当dev发起mr的时候触发merge_request_event,关于这种predefine的变量在官网解释的非常好

还有一点需要注意的是job的rules里面如果if匹配到并且后面没有跟when: never那么就执行此job,没有匹配到if就不执行这个job,如果if后面跟了个when: never,说明if后面的条件被匹配到了就不执行这个job,反之执行执行

CI runner

当我们更改后发现pipeline执行失败,说是账户没有绑定信用卡…这个时候Gitlab给了2个选项要么绑定信用卡说明你是valid user,要么自己搭建CI runner然后绑定gitlab…

开始安装CI runner,我们将CI runner安装在k8s master机器上(注意是装在裸机上,而非容器)

root@k8s-master:~# curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
root@k8s-master:~# sudo apt-get install gitlab-runner

然后在gitlab官网上创建gitlab runner(左边的bar的Settings–>CICD—>Runners)上面有3种runner,分别是

  • group runner

    一个group 可以包含多个project,这个project共享这个group runner,换句话说由这个group种的所有project由这个group runner调度

  • project runner

    针对每个project单独的runner,我们就用这个,用于我们的project

  • shared runner

    对所有的project共享

创建完成后会有注册命令,后面包含了url和token我们复制粘贴到待运行runner的机器上就行如下

root@k8s-master:~# gitlab-runner register  --url https://gitlab.com  --token XXXXXXXX

记住我们是本地机器直接运行ci runner,要选择excutor为shell而非container,virtual machine,当注册后,机器上会自动的创建gitlab-runner用户,当job在机器上跑的时候以这个用户的身份运行job中的命令(scrip下面)
因为我们的job有打包镜像,push镜像到dockerhub的操作,所以其中需要使用到docker命令,但是gitlab-runner用户没有权限执行docker命令所以我们要将gitlab-runner用户加入到docker组中

root@k8s-master:/home# usermod -aG docker gitlab-runner

然后可以查看CI runner

gitlab-runner@k8s-master:/home$ gitlab-ci-multi-runner list
Runtime platform                                    arch=amd64 os=linux pid=49375 revision=782e15da version=16.2.0
Listing configured runners                          ConfigFile=/home/gitlab-runner/.gitlab-runner/config.toml

然后就可以在gitlab上运行pipeline(当我们mr之后的时候就会自动的运行)

CD

入前面介绍过了,我们CD使用的工具是argo,为什么不用gitlab继续做部署?首先想一个问题,我们的gitlab runner在其他的机器上运行(非k8s集群机器),如果要部署k8s,还不是通过kubectl命令进行部署,但是runner运行的机器需要由k8s集群的权限才能运行,假如有50台机器运行gitlab runner,那么意味着要给这50台机器上k8s的权限…而argo避免了这个问题的产生,因为argo直接部署在K8S之中,一个完整的CICD流程如下

       commit code                                repository change triger gitCI pipeline                        after CI done push image to dockerhub or change manifest file
coder ----------------> gitlab  code repository-------------------------------------------->gitlab CI Runner---------------------------------------------------------------------->dockerhub/k8s manifest file

上述的流程都是通过预先配置自动触发集成
要将每个project(应用)的配置文件与源代码放在不同的git repository中
配置文件指的是deployment.yaml,service.yaml,secret.yaml等等,这样做的好处是,当我们的配置文件做了更改,此时我们不应该跑上述的CI(因为project的源代码没变),所以我们要将应用的配置文件和source code放在不同的git repository中,所以我们也要对这不同的git repository有不同业务逻辑的pipeline,当装载配置文件的给git repository改变了(k8s manifest file,这个无论是helm还是其他方式生成改动的)都会触发argo进行CD操作

argo的好处

  • 只认git上的配置文件,也就是说当我们在集群中手动更改应用的状态(比如多加了一个pod),argo发现后会自动的还原(还原成git上配置文件的设定),换句话说argo CD无时不刻的在sync,git上的配置和k8s真正的状态
  • 通过git管理配置文件,因为如果多个人同时操作k8s非常危险,而用git可以分多个branch,当要改动配置(merge到main branch,由main branch触发cd)此时需要提mr,由上级审核mr才行
  • argo直接跑在k8s中,所以认证非常容易,且argo使用原生的k8s api,比如上述说的状态对比直接用的k8s的controller,等等这些操作对于cluster都是可见的,对于我们定位问题非常有帮助

配置git repository

这里我们将配制2个git repository,分别用于存储项目代码(golang_web_service)和k8s,argo配置代码(golang_web_service_config)

具体配置不做详细的叙述,这里直接看结果
golang_web_service_config repository的目录如下

.
├── argo_cd_config
└── k8s_config
    ├── server_deplyment.yaml
    └── server_service.yaml

golang_web_service repository的目录如下

.
└── src
    ├── access_log
    ├── Dockerfile
    ├── go.mod
    ├── main
    ├── respond_file
    └── server.go

安装argo

首先argo cd需要运行在argo自己的namespace中,我们先创建argo cd的namespace

root@k8s-master:~# kubectl create namespace argocd
namespace/argocd created

然后apply argo cd的安装yaml文件

root@k8s-master:~# kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

首先看这个文件,我们发现他的apiServer是apiextensions.k8s.io/v1这是个啥?我们看一下手上的k8s集群有没有这个api

root@k8s-master:~# kubectl api-versions
admissionregistration.k8s.io/v1
apiextensions.k8s.io/v1
apiregistration.k8s.io/v1
apps/v1
argoproj.io/v1alpha1
authentication.k8s.io/v1
authorization.k8s.io/v1
autoscaling/v1
autoscaling/v2
batch/v1
certificates.k8s.io/v1
coordination.k8s.io/v1
discovery.k8s.io/v1
events.k8s.io/v1
flowcontrol.apiserver.k8s.io/v1beta2
flowcontrol.apiserver.k8s.io/v1beta3
networking.k8s.io/v1
node.k8s.io/v1
policy/v1
rbac.authorization.k8s.io/v1
scheduling.k8s.io/v1
storage.k8s.io/v1
v1

还真有,查了一下发现是自定义配置的,搭配kind为CustomResourceDefinition使用,当我们使用这个后,根据apiVersion: apiextensions.k8s.io/v1的配置k8s的api server会自动的创建一个RESTful api,具体的api地址是

/apis/<group>/<version>/<plural>

有了上述的配置再apply后,就可以更具这个apiVersion和kind自定义的创建yaml配置
具体看官网链接

有机会独开一章讲这个

因为argocd有ui,我们要登录,可以先看argocd的ip和端口,直接查看service

root@k8s-master:~# kubectl get service -n argocd
NAME                                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
argocd-applicationset-controller          ClusterIP   10.104.161.245   <none>        7000/TCP,8080/TCP            52m
argocd-dex-server                         ClusterIP   10.97.137.16     <none>        5556/TCP,5557/TCP,5558/TCP   52m
argocd-metrics                            ClusterIP   10.105.1.82      <none>        8082/TCP                     52m
argocd-notifications-controller-metrics   ClusterIP   10.96.102.116    <none>        9001/TCP                     52m
argocd-redis                              ClusterIP   10.101.167.132   <none>        6379/TCP                     52m
argocd-repo-server                        ClusterIP   10.96.231.179    <none>        8081/TCP,8084/TCP            52m
argocd-server                             ClusterIP   10.104.18.36     <none>        80/TCP,443/TCP               52m
argocd-server-metrics                     ClusterIP   10.100.207.255   <none>        8083/TCP                     52m

发现在namespace为argocd中有个service是argocd-server,且他开放80和443端口,这一看就是web地址,但是我们要从外面访问,所以将service的443端口转发到k8s-master的8080端口上

root@k8s-master:~# kubectl port-forward -n argocd services/argocd-server 8080:443 --address="192.168.152.131" &

如果我们不指定–address那么默认绑定localhost,不能通过node的ip地址进行访问

当我们输入ip和端口后,需要输入用户名,用户名的账号为admin,密码需要进入secret里看

root@k8s-master:~# kubectl get secrets -n argocd
NAME                          TYPE     DATA   AGE
argocd-initial-admin-secret   Opaque   1      93m
argocd-notifications-secret   Opaque   0      94m
argocd-secret                 Opaque   5      94m

发现有一个secrets叫做argocd-inital-admin-secret,这个就是我们初次登录的密码,密码写在他的yaml配置中,我们查看这个secret的yaml

root@k8s-master:~# kubectl get secrets -n argocd argocd-initial-admin-secret -o yaml
apiVersion: v1
data:
  password: aWwxa2lGVjhlWGtzT1VNVg==
kind: Secret
metadata:
  creationTimestamp: "2023-08-14T02:39:15Z"
  name: argocd-initial-admin-secret
  namespace: argocd
  resourceVersion: "288124"
  uid: 770c86e7-53dc-4ce0-9c81-3bd77ab8fcf8
type: Opaque

因为密码是base64加密(官网说的),所以我们对密码进行base64 decode得到我们的密码

root@k8s-master:~# echo "aWwxa2lGVjhlWGtzT1VNVg==" | base64 --decode
il1kiFV8eXksOUMV

输入后就可以登录

在这里插入图片描述

创建argo cd的配置yaml

首先这个配置的yaml放在config的repository中,argo应用这个repository后,默认3分钟sync一次config repository,sync就是比较这个config repository和当前k8s环境的配置有什么不同,有不同就根据配置自动的调整(self-heal)

在一切开始前我们查看当前有没有argo相关的api

root@k8s-master:~/k8s_demo_config/argo_cd_config# kubectl api-versions  | grep argo
argoproj.io/v1alpha1

发现的确有,因为当我们安装argo的时候他自动的创建了相应的argo的api,我们随后argo的yaml配置文件就用这个api

然后在argo的ui中添加argocd需要sync的repository,并且输入账号和密码(因为我们是私密repository,不添加就会出错),在argo ui左边的bar中选中settings然后再选中Repositories,再点击左上角CONNECT REPO,在新的弹窗中输入repo的地址(可以通过https和ssh方式当作需要被sync的repo的url),最后输入账号和密码

首先在本地的config repository 创建argocd的application.yaml,最后push到远端

root@k8s-master:~/k8s_demo_config# ls
argo_cd_config  k8s_config
root@k8s-master:~/k8s_demo_config# cd argo_cd_config/
root@k8s-master:~/k8s_demo_config/argo_cd_config# vim application.yaml

application.yaml如下

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: golang-server
  namespace: argocd
spec:
  project: default

  source:
    repoURL: https://gitlab.com/k8s_learn/golang_web_service_config.git
    targetRevision: HEAD
    path: k8s_config
  destination:
    server: https://kubernetes.default.svc
    namespace: default

  syncPolicy:
    syncOptions:
    - CreateNamespace=true #假如destination指定的部署namespace不存在那么我们创建一个

    automated:
      selfHeal: true
      prune: true #当sync的时候argocd发现现环境k8s的实际的状态和,被sync的目录预设的的目录状态不同,那么argo会改变k8s环境中的状态和sync目录预设定的同步,如果是k8s实际分配资源多了,prune允许argocd删除资源

首先api argoproj.io/v1alpha1和kind Application之前讲了在安装argo的时候通过api apiextensions.k8s.io/v1和kind CustomResourceDefinition创建的object,所以我们可以使用k8s这种风格的yaml去配置argo
在matadata中定义argo服务位于那个namespace中,在argo application创建后通过这条命令查看

root@k8s-master:~/k8s_demo_config# kubectl get applications.argoproj.io -n argocd
NAME            SYNC STATUS   HEALTH STATUS
golang-server   Synced        Healthy

然后再spec中最重要的2个是source和destination

  • source:定义我们需要sync的repo路径,HEAD指明我们跟踪的当前branch最新的commit,path定义我们的repo中具体的目录,这样我们可以只sync指定目录的变
    destination:定义我们的服务部署到那里,比如指定namespace,server

syncPolicy指定了我们sync的过程中的策略

然后我们push到远端的仓库中,具体不做详细介绍

然后我们本地apply这个yaml

root@k8s-master:~/k8s_demo_config# kubectl apply -f argo_cd_config/argocd_application.yaml

然后通过argo ui中就可以快速的定位application

在这里插入图片描述
然后我们更改deployment的配置(将replicas改为3,也就是只有3个pod副本)然后push到远端的repo中看是否会自动的触发CD
在这里插入图片描述
因为每个3分钟argocd才会同步一次所以我们点了手动sync并且刷新最后发现的确触发了CD。

argocd和helm结合

argocd好像可以直接指定helm的repository,但是我试了一下午没有找到方法…但是其他的就比较简单,如下我们应用对应的配置在另外的仓库中,如下

root@k8s-master:~/k8s_demo_config# tree
.
├── argo_cd_config
│   └── argocd_application.yaml
├── golang_web_application
│   ├── charts
│   ├── Chart.yaml
│   ├── templates
│   │   ├── k8s_config
│   │   │   ├── NOTES.txt
│   │   │   ├── server_deplyment.yaml
│   │   │   └── server_service.yaml
│   │   └── NOTES.txt
│   └── values.yaml
└── promethues_monitor
    └── service.monitor.yaml

6 directories, 8 files

我们的配置仓库就这3个目录,然后我们的argocd的配置文件(argocd_application.yaml)如下

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: golang_server
  namespace: argocd
spec:
  project: default

  source:
    repoURL: https://gitlab.com/k8s_learn/golang_web_service_config.git
    targetRevision: HEAD
    path: golang_web_application
    helm:
      valueFiles:
        - values.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: default

  syncPolicy:
    syncOptions:
    - CreateNamespace=true

    automated:
      selfHeal: true
      prune: true

当然上述可以通过argo ui直接配置(点application—>new application)更简单…

argocd hook

TODO

argocd 发布

TODO

RBAC

RBAC全称(Role-base access control)
首先我们客户端的命令到达k8s的control plant后,先是api-server,api-serve去看用户是否有权限操作这些资源,RBAC指定的资源对象就是常见的resource object,而操作有以下几个

  • get
  • list
  • watch
  • create
  • patch
  • update
  • delete
  • deletecollection

RBAC的操作也非常简单,首先是创建账户(serviceaccount),再是创建规则(role)规定那些apigroup的那些资源可以执行那些操作(verb),最后再用RoleBinding进行账户和role的绑定即可,看下面简单的例子

我们先是创建一个serviceaccount,名字为sa-example

apiVersion: v1
kind: ServiceAccount
metadata: 
  name: sa-example

然后我们创建规则,允许对pod,service,endpoint做所有操作,但是对deployment只能create不能查看

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: role-example
rules:
- apiGroups:
  - ""
  resource:
  - pods
  - services
  - endpoints
  verbs:
  - '*'
  
- apiGroups:
  - apps
  resource:
  - deployments
  verbs:
  - create

最后我们绑定他们

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: 
  name: rolebind-example
roleRef:
  kind: Role
  apiGroup: rbac.authorization.k8s.io
  name: role-example
subjects:
- kind: ServiceAccount
  name: sa-example
  namespace: default

然后再deployment的spec(最里面的)指定serviceAccountName:即可

operator

operator按照一句简单的话来说就是一组resource,比如CRD,deployment,service等等,opereator根据部署内容和功能也有自己的分级,级别越高,说明这个operator越厉害,稳定,智能,越低说明这个operator只能满足基本的需求,比如安装

  • level 1: 基本安装
  • level 2: level1之上支持对应用补丁和小的升级
  • level 3: 构建整个应用完整的生命周期,包括应用生命周期,存储生命周期(什么时候使用存储,什么时候free使用的存储,还有什么备份,failure recover)
  • level 4: 在level3之上又支持监控,日志分析,workload分析等等
  • level 5: 在level3之上支持横向,纵向扩容,实现自动配置,等等高级功能

这么一看operator就像是软件的sre

operator前面说了是用户使用k8s的定义的一个扩展api进行resource object的创建自定义,具体流程如下
在这里插入图片描述
具体流程就是我们自己定义的operator yaml文件交由controller的api server,api-server发现api是CRD,然后根据yaml自定义的对象在集群中创建resource object

helm

helm在k8s中广泛应用,他到底是啥呢?官方解释helm是一个包管理器,包管理器我们第一反应是yum,apt之类的linux包管理器,也许是pip或者go model之类的包管理器,helm也是包管理器,包管理器到底是个什么东西呢?首选包管理器应该是一个集合,这个集合可以由二进制可执行文件组成(yum,apt之类的包管理器),也可以是代码组成(pip,go model),helm显然是由多个yaml配置文件组成的包管理器,我们可以通过helm手动的生成本地的repository(本地仓库或者叫做本地包管理器),或者直接使用远端的repository(别人提供的,或者是官方的)

包管理器这个概念运用于计算机各个领域,比如上述提到的yum这种成熟应用,或者运用于编写代码,引用第三方库的时候,他们都可以用各自的包管理器管理,helm这个project的目标就是对于k8s的yaml进行集中管理

helm还有一个非常常用的功能就是提供模板,比如一个yaml的某个值可以直接使用"宏"代替,这个"宏"的具体值定义在我们helm repository根目录的value.yaml或者其他的yaml中,并且helm提供直接安装被其管理的k8s配置yaml文件(helm自动的处理了依赖关系)

这么一看helm的确非常强大,所以我们开始将我们的gitlab上的config repository配置成helm repository

root@k8s-master:~/k8s_demo_config# helm create golang_web_application
Creating golang_web_application

然后我们查看helm自动为我们创建的文件

root@k8s-master:~/k8s_demo_config# tree
.
├── argo_cd_config
│   └── argocd_application.yaml
├── golang_web_application
│   ├── charts
│   ├── Chart.yaml
│   ├── templates
│   │   ├── deployment.yaml
│   │   ├── _helpers.tpl
│   │   ├── hpa.yaml
│   │   ├── ingress.yaml
│   │   ├── NOTES.txt
│   │   ├── serviceaccount.yaml
│   │   ├── service.yaml
│   │   └── tests
│   │       └── test-connection.yaml
│   └── values.yaml
└── k8s_config
    ├── server_deplyment.yaml
    └── server_service.yaml

6 directories, 13 files

发现他为我们自动的创建了一个叫做goalng_web_application的目录,进去看有template目录还有values.yaml,template就是我们放置我们k8s或者argo的配置yaml的,外面的values.yaml就是上述存储"宏"对应的值的文件,我们不用这个文件,取而代之的是自己创建的values-stage.yaml(后面我们会创建一个value-prod.yaml),charts目录用于存访依赖的目录,我们将本地的配置放到其正确位置中,确保我们后面push到gitlab中正确无误,文件目录如下

root@k8s-master:~/k8s_demo_config# tree
.
├── argo_cd_config
│   └── argocd_application.yaml
└── golang_web_application
    ├── charts
    ├── Chart.yaml
    ├── templates
    │   ├── k8s_config
    │   │   ├── NOTES.txt
    │   │   ├── server_deplyment.yaml
    │   │   └── server_service.yaml
    │   └── NOTES.txt
    └── values-stage.yaml

5 directories, 7 files

首先我们要了解template的编写规则,格式示例为{{ .Values.appName }},template都是以.Values开头表示helm repository的values.yaml之类存访值的文件,appName,代表values.yaml之类的文件中key的名字,所以{{ .Values.appName }}的具体值定义在values.yaml之类的文件中的

appName: XXX

{{ .Values.configmap.name }}的具体值定义在values.yaml之类的文件中的

configmap:
  name: XXX

了解后开始改动我们的k8s配置文件

root@k8s-master:~/k8s_demo_config/golang_web_application/templates/k8s_config# vim server_deplyment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.K8sConfig.ServerName }}
  labels:
    app: {{ .Values.K8sConfig.ServerName }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.K8sConfig.ServerName }}
  replicas: {{ .Values.K8sConfig.Replicas }}
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        group: database
        app: {{ .Values.K8sConfig.ServerName }}
    spec:
      containers:
      - name: {{ .Values.K8sConfig.ServerName }}
        image: "{{ .Values.K8sConfig.Image.Name }}:{{ .Values.K8sConfig.Image.Tag }}"
        ports:
        - containerPort: {{ .Values.K8sConfig.ContainerPort }}
          #nodeSelector:
          #group: database
root@k8s-master:~/k8s_demo_config/golang_web_application/templates/k8s_config# vim server_service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.K8sConfig.ServerName }}
  namespace: {{ .Values.K8sConfig.NameSpace }}
  labels:
    app: web
    type: service
spec:
  type: NodePort #use cluster ip
  selector:
    app: {{ .Values.K8sConfig.ServerName }}
  ports:
  - protocol: TCP
    port: {{ .Values.K8sConfig.ServicePort }} #对外的ip端口
    targetPort: {{ .Values.K8sConfig.ContainerPort }} #pod内需要映射出去的端口

现在开始改values-stage.yaml文件

root@k8s-master:~/k8s_demo_config/golang_web_application# vim values-stage.yaml
K8sConfig:
  ServerName: golang_server
  Replicas: 2
  ContainerPort: 8080
  ServicePort: 8081
  NameSpace: stage
  Image:
    Name: honkytonkman/server_in_k8s
    Tag: latest

然后我们创建stage和prod2个namespace,用于存储2个不同的环境,我们的目的是先在stage上发布,再在prod上发布

root@k8s-master:~/k8s_demo_config# kubectl create namespace stage
namespace/stage created
root@k8s-master:~/k8s_demo_config# kubectl create namespace prod
namespace/prod created

然后我们指定我们的value文件直接运行安装我们的helm仓库(install会自动的部署我们的temple的object)

root@k8s-master:~/k8s_demo_config# helm install golang-web-application golang_web_application -f golang_web_application/values-stage.yaml
NAME: golang-web-application
LAST DEPLOYED: Wed Aug 16 12:55:05 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

然后我们查看我们的仓库再查看stage namespace下的所有pod和service是否已经创建

root@k8s-master:~/k8s_demo_config# helm ls
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                           APP VERSION
golang-web-application  default         1               2023-08-16 12:55:05.205674113 +0800 CST deployed        golang_web_application-0.1.0    1.16.0
root@k8s-master:~/k8s_demo_config#
root@k8s-master:~/k8s_demo_config#
root@k8s-master:~/k8s_demo_config# kubectl get pods -n stage
NAME                            READY   STATUS    RESTARTS   AGE
golang-server-564b599b7-nzt2k   1/1     Running   0          5m6s
golang-server-564b599b7-wmnt8   1/1     Running   0          5m6s
root@k8s-master:~/k8s_demo_config# kubectl get service -n stage
NAME            TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
golang-server   NodePort   10.106.159.22   <none>        8081:31024/TCP   5m12s

发现都已经创建,此时我们有个问题,假如stage环境下的服务经过压力测试等一系列测试后发现没问题,我们要发布到生产环境中(prod)也就是prod这个namespace下,我们该怎么办,创建一个values-prod.yaml如下

root@k8s-master:~/k8s_demo_config# vim golang_web_application/values-prod.yaml
K8sConfig:
  NameSpace: prod

然后我们apply他,注意看我们的操作在install的时候必须指定2个文件分别是values-stageyaml和values-prod,且values-prod必须要在后面,因为helm规定了如果-f或者–values指定2个文件,且2个文件的某一个key/value有冲突,那么后面文件的冲突的key/value覆盖前面的value文件,所以我们这样写了后会用.Values.K8sConfig.NameSpace: prod覆盖前面的.Value.K8sConfig.NameSpace: stage这样我们的obj就发布到了prod环境中

root@k8s-master:~/k8s_demo_config# helm upgrade golang-web-application golang_web_application -f golang_web_application/values-stage.yaml  -f golang_web_application/values-prod.yaml
Release "golang-web-application" has been upgraded. Happy Helming!
NAME: golang-web-application
LAST DEPLOYED: Wed Aug 16 13:08:23 2023
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None

然后我们查看prod环境,发现成果

root@k8s-master:~/k8s_demo_config# kubectl get pods -n prod
NAME                            READY   STATUS    RESTARTS   AGE
golang-server-564b599b7-84hh2   1/1     Running   0          26s
golang-server-564b599b7-9h7qx   1/1     Running   0          26s
root@k8s-master:~/k8s_demo_config# kubectl get service -n prod
NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
golang-server   NodePort   10.96.38.211   <none>        8081:30810/TCP   32s

假设我们更改了服务的k8s obj配置只需要改对应的value即可,如下我们改了deplyment的数量只需要helm upgrade即可(helm update是更新库)

root@k8s-master:~/k8s_demo_config# vim golang_web_application/values-stage.yaml
K8sConfig:
  ServerName: golang-server
  Replicas: 3
  ContainerPort: 8080
  ServicePort: 8081
  NameSpace: stage
  Image:
    Name: honkytonkman/server_in_k8s
    Tag: latest

然后upgrade

root@k8s-master:~/k8s_demo_config# helm upgrade golang-web-application golang_web_application -f golang_web_application/values-stage.yaml  -f golang_web_application/values-prod.yaml
Release "golang-web-application" has been upgraded. Happy Helming!
NAME: golang-web-application
LAST DEPLOYED: Wed Aug 16 13:15:40 2023
NAMESPACE: default
STATUS: deployed
REVISION: 3
TEST SUITE: None

发现pod变成了3个

root@k8s-master:~/k8s_demo_config# kubectl get pods -n prod                                                                                                              NAME                            READY   STATUS    RESTARTS   AGE
golang-server-564b599b7-84hh2   1/1     Running   0          7m23s
golang-server-564b599b7-9h7qx   1/1     Running   0          7m23s
golang-server-564b599b7-f7c4j   1/1     Running   0          7s

假设新版本出现了故障,我们需要立马回退然后debug直接helm rollback加上release name即可如下
先查看helm release name

root@k8s-master:~/k8s_demo_config# helm ls
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                           APP VERSION
golang-web-application  default         3               2023-08-16 13:15:40.319255029 +0800 CST deployed        golang_web_application-0.1.0    1.16.0

release name就是golang-web-application
然后rollback到最近的一版

root@k8s-master:~/k8s_demo_config# helm rollback golang-web-application
Rollback was a success! Happy Helming!

然后查看pod

root@k8s-master:~/k8s_demo_config# kubectl get pods -n prod
NAME                            READY   STATUS    RESTARTS   AGE
golang-server-564b599b7-84hh2   1/1     Running   0          22m
golang-server-564b599b7-9h7qx   1/1     Running   0          22m

发现rollback成功

我们用git push的时候可以为我们待push的代码加上–commit描述我们的这次push的message,同理,helm也可以在helm install的时候–description即可

我们再改pod数量为1此时加上description描述我们此次的改动

root@k8s-master:~/k8s_demo_config# helm upgrade golang-web-application golang_web_application -f golang_web_application/values-stage.yaml  -f golang_web_application/values-prod.yaml --description "set podv number to 1"
Release "golang-web-application" has been upgraded. Happy Helming!
NAME: golang-web-application
LAST DEPLOYED: Wed Aug 16 13:36:10 2023
NAMESPACE: default
STATUS: deployed
REVISION: 5
TEST SUITE: None

再查看history可以看到我们upgrade的message

root@k8s-master:~/k8s_demo_config# helm history golang-web-application
REVISION        UPDATED                         STATUS          CHART                           APP VERSION     DESCRIPTION
1               Wed Aug 16 12:55:05 2023        superseded      golang_web_application-0.1.0    1.16.0          Install complete
2               Wed Aug 16 13:08:23 2023        superseded      golang_web_application-0.1.0    1.16.0          Upgrade complete
3               Wed Aug 16 13:15:40 2023        superseded      golang_web_application-0.1.0    1.16.0          Upgrade complete
4               Wed Aug 16 13:30:19 2023        superseded      golang_web_application-0.1.0    1.16.0          Rollback to 2
5               Wed Aug 16 13:36:10 2023        deployed        golang_web_application-0.1.0    1.16.0          set podv number to 1

prometheus && grafna

prometheuse我们使用operator来进行搭建

prometheus

首先将Prometheus-operator从github下载到服务器上,再进入主目录执行下面命令安装Prometheus-operator自定义的crd(),后面我们要用这些crd资源创建对象管理Prometheus服务,就和deployment,pod,service一样

root@k8s-master:~/prometheus/prometheus-operator# kubectl create  -f bundle.yaml
customresourcedefinition.apiextensions.k8s.io/alertmanagerconfigs.monitoring.coreos.com created
customresourcedefinition.apiextensions.k8s.io/alertmanagers.monitoring.coreos.com created
customresourcedefinition.apiextensions.k8s.io/podmonitors.monitoring.coreos.com created
customresourcedefinition.apiextensions.k8s.io/probes.monitoring.coreos.com created
customresourcedefinition.apiextensions.k8s.io/prometheuses.monitoring.coreos.com created
customresourcedefinition.apiextensions.k8s.io/prometheusrules.monitoring.coreos.com created
customresourcedefinition.apiextensions.k8s.io/servicemonitors.monitoring.coreos.com created
customresourcedefinition.apiextensions.k8s.io/thanosrulers.monitoring.coreos.com created
clusterrolebinding.rbac.authorization.k8s.io/prometheus-operator created
clusterrole.rbac.authorization.k8s.io/prometheus-operator created
deployment.apps/prometheus-operator created
serviceaccount/prometheus-operator created
service/prometheus-operator created

配置rbac,因为Prometheus operator(任何operator都一样)想接入到k8s集群中都需要配置权限
先创建serviceaccount(因为github里面有写好的安装包我们直接使用)

root@k8s-master:~/prometheus/prometheus-operator# kubectl apply -f  example/rbac/prometheus-operator/prometheus-operator-service-account.yaml

创建role,同上,Prometheus-operator的github官网为我们提供了

root@k8s-master:~/prometheus/prometheus-operator# kubectl apply -f  example/rbac/prometheus-operator/prometheus-operator-cluster-role.yaml

创建role bind,绑定service和role

root@k8s-master:~/prometheus/prometheus-operator# kubectl apply -f  example/rbac/prometheus-operator/prometheus-operator-cluster-role-binding.yaml

查看一下rolebind是否绑定成功

root@k8s-master:~/prometheus/prometheus-operator# kubectl describe clusterrolebinding prometheus-operator
Name:         prometheus-operator
Labels:       app.kubernetes.io/component=controller
              app.kubernetes.io/name=prometheus-operator
              app.kubernetes.io/version=0.63.0
Annotations:  <none>
Role:
  Kind:  ClusterRole
  Name:  prometheus-operator
Subjects:
  Kind            Name                 Namespace
  ----            ----                 ---------
  ServiceAccount  prometheus-operator  default

然后使用Prometheus-operator自定义的Prometheus资源创造Prometheus obj

root@k8s-master:~/prometheus/prometheus-operator# kubectl apply -f prometheus.yaml
prometheus.monitoring.coreos.com/prometheus-operator created

这个是自己写的如下

apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
  name: prometheus-operator
spec:
  serviceAccountName: prometheus-operator #一定要写之前创建绑定的serviceaccount,不然就会使用默认的serviceaccount default(创建namespace的时候默认创建)
  serviceMonitorNamespaceSelector: {} #指定监控的namespace范围{}匹配所有,可以往下面加mathchLabels指定标签
  serviceMonitorSelector: {}  #指定监控的service范围{}匹配所有,可以往下面加mathchLabels指定标签
  podMonitorSelector: {} #指定监控的pod范围{}匹配所有,可以往下面加mathchLabels指定标签
  resources:
    requests:
      memory: 400Mi

然后进入代码目录中根据前面的service(golang-server)写对应的监控配置(当然也是用Prometheus-operator创建的crd resource)

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor #Prometheus crd定义的
metadata:
  name: golang-service-monitor
spec:
  endpoints:
    - port: web #这个是我们被监控service创建的endpoint指定
  namespaceSelector:
    any: true #找所有的namespace,也可以指定特定的namespace
  selector:
    matchLabels:
      app: web #基于前面指定的namespace,再匹配特定的labels

然后apply他

root@k8s-master:~/k8s_demo_config/prometheus-monitor-config# kubectl apply  -f service.monitor.yaml

这里非常关键
我们先看Prometheus-operator的pod(创建Prometheus资源对象的时候创建的)的日志

root@k8s-master:~/k8s_demo_config/prometheus-monitor-config# kubectl logs prometheus-prometheus-operator-0  | tail -f

输出如下的日志

ts=2023-08-30T08:55:57.341Z caller=klog.go:116 level=error component=k8s_client_runtime func=ErrorDepth 
msg="pkg/mod/k8s.io/client-go@v0.26.1/tools/cache/reflector.go:169: Failed to watch *v1.Endpoints: failed to list 
*v1.Endpoints: endpoints is forbidden: User \"system:serviceaccount:default:prometheus-operator\" cannot list 
resource \"endpoints\" in API group \"\" at the cluster scope"

很明了的告诉了你,你名称为Prometheus-operator的serviceaccount没有权限去list service和endpoint对象!所以我们要在之前apply的role yaml中加上对应的verb(list操作)

- apiGroups:
  - ""
  resources:
  - services
  - services/finalizers
  - endpoints
  verbs:
  - list  #这里
  - get
  - create
  - update
  - delete

然后再apply,发现还是报错,这个报错

ts=2023-08-30T09:18:36.476Z caller=klog.go:116 level=error component=k8s_client_runtime func=ErrorDepth 
msg="pkg/mod/k8s.io/client-go@v0.26.1/tools/cache/reflector.go:169: Failed to watch *v1.Pod: unknown (get pods)"

这个报错是因为我们没有在服务中暴露/mertics,不管他,直接进Prometheus的web界面,进之前先转发端口(9090端口)

root@k8s-master:~/k8s_demo_config/prometheus-monitor-config# kubectl get svc/prometheus-operated
NAME                  TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
prometheus-operated   ClusterIP   None         <none>        9090/TCP   50m
root@k8s-master:~/k8s_demo_config/prometheus-monitor-config# kubectl port-forward svc/prometheus-operated 9090:9090 --address=${YOUR_HOST_IP}

登录后发现的确存在这个target

metric

Prometheus有4个类型的metric

  • Counter:counter只能增加或者被设为0或者重新回归初值再增加
  • Gauge:也代表一个数字,可以增加或者下降,比如代表温度之类的
  • Histogram:
  • Summary:

使用也非常的简单,在go中主要使用这2个库

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"

首先我们创建metric需要先注册metric,我们的这个metric是counter类型所以要注册counter类型metric(prometheus.NewCounter())

var http_request_total = prometheus.NewCounter(prometheus.CounterOpts{
    Name:        "METRIC_NAME",
    Help:        "METRIC_HELPER",
  },
  )
prometheus.MustRegister(http_request_total)

那么我们具体怎么对这个metric设置具体值呢?因为是counter所以他是从0开始增加且,只能增加,或者reset成0,我们需要在我们具体的处理或者统计函数中使用http_request_total.Inc()就可以完成metric的自增,http_request_total 就是之前prometheus.NewCounter的返回值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/195248.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

家政预约服务管理系统,轻松搭建专属家政小程序

家政预约服务管理系统&#xff0c;轻松搭建专属家政小程序app&#xff1b; 家政服务app开发架构包括&#xff1a; 1. 后台管理端&#xff1a;全面管理家政服务、门店、员工、阿姨信息、订单及优惠促销等数据&#xff0c;并进行统计分析。 2. 门店端&#xff1a;助力各门店及员工…

2023.11.27【读书笔记】|医疗科技创新流程(前言)

目录 注重价值关键要素如何解决价值问题&#xff1f;注重三个关键点价值探索价值预测价值定位 中国视角背景挑战战术 洞察过程发现需求发现需求筛选 发明概念产生概念选择 发挥战略发展商业计划 注重价值 在美国&#xff0c;医疗费用的增长率已经多年超过GDP增长率&#xff1b…

不用render_template函数,把html代码放在py文件里,不用单独写html文件

3.猜拳游戏&#xff1a;石头、剪刀、布的游戏 ##不用render_template函数&#xff0c;把html代码放在py文件里&#xff0c;不用单独写html文件 from flask import Flask, request import randomapp Flask(__name__)app.route(/) def index():#下面form标签虽然放在注释里&…

基于JavaWeb+SSM+Vue校园综合服务小程序系统的设计和实现

基于JavaWebSSMVue校园综合服务小程序系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 Lun文目录 摘 要 I Abstract II 第一章 绪 论 1 1.1选题背景 2 1.2研究现状 3 1.3研究内容 …

30.0/集合/ArrayList/LinkedList

目录 30.1什么是集合? 30.1.2为什么使用集合 30.1.3自己创建一个集合类 30.1.3 集合框架有哪些? 30.1.2使用ArrayList集合 30.2增加元素 30.3查询的方法 30.4删除 30.5 修改 30.6泛型 30.1什么是集合? 我们之前讲过数组&#xff0c;数组中它也可以存放多个元素。集合…

05_属性描述符

05_属性描述符 文章目录 05_属性描述符一、属性描述符是什么&#xff1f;二、属性描述符①&#xff1a;查看属性描述②&#xff1a;设置属性描述符③&#xff1a;案例01.代码实现02.代码实现&#xff08;优化&#xff09; 一、属性描述符是什么&#xff1f; 属性描述符的结构 在…

值得收藏的 6 个顶级 Mac 数据恢复软件榜单

对于 Mac 用户来说&#xff0c;丢失重要数据可能是一场真正的噩梦。无论是意外删除、系统崩溃还是狡猾的恶意软件&#xff0c;后果都可能是毁灭性的。幸运的是&#xff0c;Mac 数据恢复软件带来了一线希望。这些工具旨在帮助您轻松恢复珍贵的文件&#xff0c;无论是什么原因导致…

入侵redis之准备---Centos7上面部署redis

入侵redis之准备—Centos7上面部署redis 编写这个部署redis&#xff0c;只是为了另一个文章入侵redis做准备&#xff0c;网上还有好多类似的文章&#xff0c;这个单纯的就是部署安装&#xff0c;并简单的测试使用以下 关联其他文章 [1]VMware上面安装部署centos7镜像系统【详细…

knife4j集合化postman

knife4j集合化postman 01 knife4j的介绍 基于 JavaMVC的集成框架swagger的进一步强化&#xff0c;在原有通过注释就能生成文档的前身swagger-bootstrap-ui之上&#xff0c;增加了postman的测试功能&#xff0c;优化了文档的UI界面&#xff0c;在测试api接口的方面有了极大的进…

C 语言-循环嵌套-函数

C 语言 - 循环嵌套、函数 1. 循环嵌套 1.1 作用 循环 套 循环。 1.2 使用 需求1&#xff1a; 打印以下图形&#xff1a; * * * * * * * * * * * * * * * *代码&#xff1a; 1、使用循环打印 #include <stdio.h> int main(int argc, char const *argv[]) {for (int i…

Pycharm在debug问题解决方案

Pycharm在debug问题解决方案 前言一、Frames are not available二、查看变量时一直显示collecting data并显示不了任何内容 前言 Pycharm在debug时总是出现一些恼人的问题&#xff0c;以下是博主在训练中遇到的问题及在网上找到的可用解决方案&#xff1a; 一、Frames are not…

洪泽湖流域建筑物、人口密度与土地利用数据技术服务

一&#xff0e;背景介绍 人类社会发展离不开土地&#xff0c;没有土地就没有人类&#xff0c;土地利用随着人类的出现而发生。人类为了一定的社会或经济方面的目的&#xff0c;会通过利用、改造等活动。从土地上获得更多的资源。土地利用既要受自然条件的制约&#xff0c;同时也…

【密码学引论】序列密码

第五章 序列密码 1、序列密码 定义&#xff1a; 加密过程&#xff1a;把明文与密钥序列进行异或运算得到密文解密过程&#xff1a;把密文与密钥序列进行异或运算得到明文以字/字节为单位加解密密钥&#xff1a;采用一个比特流发生器随机产生二进制比特流 2、序列密码和分组密…

常用Web安全扫描工具合集

漏洞扫描是一种安全检测行为&#xff0c;更是一类重要的网络安全技术&#xff0c;它能够有效提高网络的安全性&#xff0c;而且漏洞扫描属于主动的防范措施&#xff0c;可以很好地避免黑客攻击行为&#xff0c;做到防患于未然。那么好用的漏洞扫描工具有哪些&#xff1f; 答案…

优雅使用docker-compose部署Skywalking

Skywalking使用docker-compose部署 version: 3.1 services: // 部署elasetic search 用于存储获取的应用信息与日志elasticsearch:image: elasticsearch:7.13.3container_name: elasticsearchprivileged: trueenvironment:- "cluster.nameelasticsearch" #设置集群名…

2019年10月17日: Go生态洞察:在Go 1.13中处理错误

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

2023.11.26 关于 Spring Boot 单元测试

目录 单元测试 优势 单元测试的使用 具体步骤 实现不污染数据库 阅读下面文章之前 建议点击下方链接了解 MyBatis 的创建与使用 MyBatis 的配置与使用 单元测试 单元测试 指对软件中的最小可测试单元进行检查和验证的过程单元测试 由开发人员在编码阶段完成&#xff0c;…

免费部署开源大模型 ChatGLM-6B

参考&#xff1a;【大模型-第一篇】在阿里云上部署ChatGLM3-CSDN博客 ChatGLM 是一个开源的、支持中英双语的对话语言模型&#xff0c;由智谱 AI 和清华大学 KEG 实验室联合发布&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。ChatGLM3-6B 更…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑碳排放分摊的综合能源服务商交易策略》

这篇文章的标题表明它将讨论一个关于综合能源服务商交易策略的主题&#xff0c;而在这个策略中&#xff0c;特别考虑了碳排放分摊的因素。以下是对标题中各关键词的解读&#xff1a; 综合能源服务商&#xff1a; 这指的是在能源领域提供多种服务的企业或组织&#xff0c;可能涵…

数据分析工具比较:Excel vs Python vs R

写在开头 在数据分析的世界里,选择合适的工具至关重要。本篇博客将深入比较常用的数据分析工具,包括Excel、Python和R,以帮助读者更好地选择适合自己需求的工具。 1.Excel:经典易用的电子表格 优势: 用户友好: Excel是大多数人熟悉的电子表格工具,使用简单,无需编程…