基於jenkins+kubernetes的cicd流程實踐一:環境搭建及方案原理

2022-12-04 12:00:30

1.基礎環境:Centos7.9,kubernetes:v1.21.5

node-1@112(master):docker,containerd,harbornginx(80),git,etcd

node-2@109(master/worker):docker,containerd,ingress_nginx(80),etcd,glusterfs

node-3@107(worker):docker,containerd,harbor(80),etcd,glusterfs

node-4@106(worker):docker,containerd,harbor(80),glusterfs

node-6@121(單體/應用):docker,containerd,nginx(80/前端),consul,nacos,yapi

2.公有程式碼倉庫,gitee,設定網路勾點

3.私有映象倉庫,harbor雙主複製,node-3/node-4,地址:myhub.com

參考:https://github.com/goharbor/harbor

4.持久化儲存 ,gluster-kubernetes ,node-2/node-3/node-4,heketi使用Ingress四層代理

參考:https://github.com/gluster/gluster-kubernetes

http://docs.kubernetes.org.cn/803.html

5.jenkins,k8s Deployment方式部署,推薦本地部署

參考:https://www.jenkins.io/doc/book/installing/kubernetes/

(1)Local Persistent Volume 替換為 GlusterFs(基於Local Persistent Volume搭建的分散式檔案),儲存設定資訊以及workspace

volume.yaml:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: jenkins-glusterfs-storage-class
provisioner: kubernetes.io/glusterfs
parameters:
  # 這裡heketi使用ingress四層代理
  # 192.168.0.109為ingress-nginx-controller監聽地址
  resturl: "http://192.168.0.109:30001"
  restauthenabled: "true"
  restuser: "admin"
  restuserkey: "admin123"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-pv-claim
  namespace: devops-tools
spec:
  storageClassName: jenkins-glusterfs-storage-class
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
---
# agent workspace-volume
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-agent-pv-claim
  namespace: devops-tools
spec:
  storageClassName: jenkins-glusterfs-storage-class
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 30Gi

(2)jenkins-server服務暴露方式NodePort替換為Ingress七層代理+四層代理

$ kubectl patch cm tcp-services -n ingress-nginx --patch='{"data": {"32000": "devops-tools/jenkins-service:80"}}'

service.yaml

apiVersion: v1
kind: Service
metadata:
  name: jenkins-service
  namespace: devops-tools
  annotations:
      prometheus.io/scrape: 'true'
      prometheus.io/path:   /
      prometheus.io/port:   '8080'
spec:
  selector:
    app: jenkins-server
  ports:
  - name: web
    port: 80
    targetPort: 8080
  - name: agent
    port: 50000
    targetPort: 50000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: jenkins-service
  namespace: devops-tools
spec:
  rules:
  - host: myjenkins.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: jenkins-service
            port:
              name: web

(3)sa的叢集角色中增加對叢集中deployment,ingress資源的所有操作許可權

serviceAccount.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: jenkins-admin
rules:
	#"":core
  - apiGroups: [""]
    resources: ["*"]
    verbs: ["*"]
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["*"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["*"]  
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins-admin
  namespace: devops-tools
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: jenkins-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: jenkins-admin
subjects:
- kind: ServiceAccount
  name: jenkins-admin
  namespace: devops-tools

(4)安裝外掛:

​ 參考: https://plugins.jenkins.io/kubernetes/

https://plugins.jenkins.io/kubernetes-cli/

https://gitee.com/help/articles/4193

​ (a)kubernetes:用於和k8s互動,設定建立agent pod

​ (b)Kubernetes Cli:提供agent使用kubectl與k8s互動環境

​ (c)Gitee:用於自動觸發構建

​ (d)其他:系統開始建議安裝的外掛都裝一下

(5)工作原理:

​ (a)pod中的容器掛載infra容器的Namespace,共用Mount Namespace,工作目錄/home/jenkins/agent都掛載workspace-volume資料卷,git拉下來的專案檔案都是可見的

​ (b)agent主容器:jnlp預設自動生成,jenkins+kubernetes的cicd整個流程是定義sidecar容器,通過jenkins及其外掛,手動一步步的實現sidecar容器對專案檔案的具體操作

(6)編譯環境準備-build專案時:

​ golang與alpine映象對應版本:https://hub.docker.com/_/golang

​ (a)用的是go專案,需要go程序install專案,使用容器化方式,讓k8s去管理

​ (b)kubernetes 外掛pod模板新增容器,編譯環境映象: golang:1.19.1-alpine,上傳到harbor

​ (c)容器啟動程序:/bin/sh -c cat,防止容器程序關閉

​ (d)其他語言專案,使用對應語言環境映象編譯即可

(7)agent pod系統使用者許可權處理-docker建立映象時:

​ (a)由於每個節點都裝有docker,root使用者登入,所以使用本地資料卷掛載方式,agent pod 的User Namespace相同才能執行即系統使用者需要root執行許可權才能使用當前節點docker

​ (b)uid(使用者id)和gid(使用者組id),securityContext設定針對controller的uid和gid,agent無效,controller和agent預設是jenkins和1000

​ (c)可以在kubernetes 外掛pod模板中修改,使用root使用者,uid=0,gid=0,或者Raw YAML for the Pod覆蓋主容器

​ (d)也可以在構建shell指令碼中將jenkins使用者新增到root使用者組中

​ (e)製作映象的基礎映象:alpine:3.16,go語言在雲原生優勢體現,不需要虛擬機器器,直接編譯成對應系統的二進位制檔案執行,映象可以瘦身很小

​ (f)寫shell指令碼定時查詢節點建立的專案映象並清理一定時間段的映象,以DaemonSet部署方式親和到每個worker節點,master節點存在NoSchedule汙點,或者映象上傳後立即刪除

​ (g)映象上傳harbor,第一次登陸需要docker login使用者註冊認證,本地儲存在~/.docker/config.json,每個節點不一定都存在,使用cm方式掛載到該路徑

$ kubectl create cm docker-hub-credential --from-file=config=config.json -n devops-tools

​ (h)也可以使用docker in docker 好處是生命週期和agent pod一致,缺點是問題排查難度變大

注:使用docker的多階段編譯製作映象,可以將(4)的go build過程放在(5)build image中構建,一個build階段完成

(8)agent pod與k8s api server互動-專案部署到k8s叢集時:

​ (a)使用kubectl,當前只存在master節點中,需要在worker節點使用,為了不汙染worker節點,使用容器化方式,讓k8s去管理

​ (b)k8s叢集外kubectl與k8s api server互動,使用者端證書TLS雙向認證,需要useraccount設定資訊,使用者端儲存在~/.kube/config,可以將憑證以cm/pvc方式掛載進去,api server地址一般設定為叢集地址或本地代理地址進行解耦

​ (c)k8s叢集內pod可以通過ServiceAccount與k8s k8s api server進行互動,ServiceAccount會建立對應secret自動掛載到pod檔案系統中:/var/run/secrets/kubernetes.io/serviceaccount ,包含有token資訊,為通訊憑證

​ (d)設定的ServiceAccount具有對叢集中core所有資源,deployment,ingress的所有操作許可權,獲取jenkins-admin的token:

$ kubectl -ndevops-tools   describe secret $(kubectl -ndevops-tools   get secret | grep jenkins-admin | awk '{print $1}')

​ (e)可以使用Kubernetes Cli外掛進行設定token與k8s api server叢集內部地址,自動設定並使用上下文,實現叢集內kubectl 與k8s api server互動

​ (f)kubernetes 外掛pod模板新增容器,映象: registry.cn-shanghai.aliyuncs.com/mydlq/kubectl:1.15.3,上傳到harbor

​ (g)容器啟動程序:/bin/sh -c cat,防止容器程序關閉

(9)pipline測試指令碼:

def label = "golang1.19.1"
def credential = "global-kubernetes-credential"

timeout(time: 900, unit: 'SECONDS') {
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git階段'){
                sh '''echo "開始拉取程式碼"
                     echo "拉取程式碼完成"'''
            }
            stage('Build階段'){
                container('golang') {  
                    sh '''echo "構建開始"
                          id
                          go version
                          echo "構建完成"'''
                }
            }
            stage('Docker階段'){
                sh '''echo "建立映象開始"
                     id
                     docker images
                     echo "建立映象完成"'''
            }
            stage('Kubernetes 階段'){
                container('kubectl') {
                    withKubeConfig([credentialsId: credential,serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                        sh '''echo "k8s部署開始"
                              id
                              kubectl get nodes
                              kubectl get pods
                              echo "k8s部署完成"'''
                    }
                }
            }
        }
    }
}

注:jenkins全域性變數會自動注入到容器環境變數中,每個容器都可以獲取使用