Jenkins 與 Kubernetes 的 CI 與 CD & Git + Maven + Docker+Kubectl

廣大電子人 發表於 2021-01-05

目錄[-]


系統環境:

  • Jenkins 版本:2.199
  • Kubernetes 版本:1.15.3

參考地址:

CI/CD 流程圖:

 

整個流程:

  • (1)、介紹瞭如何在 Kubernetes 部署 Jenkins。
  • (2)、介紹 Jenkins 中需要安裝什麼相關外掛。
  • (3)、配置憑據,例如 Docker 倉庫憑據、K8S 連線憑據、Git 認證憑據。
  • (4)、在 Jenkins 中儲存執行流水線過程中的指令碼,例如 Docker 的 Dockerfile、Maven 的 Settings.xml。
  • (5)、簡介描述瞭如何寫 “指令碼式” 的流水線指令碼,以及指令碼中如何使用各種常用外掛。
  • (6)、建立一個用於當做模板的 Job,對其進行一些引數化構建變數配置,方便後續全部的 Job 通過複製該模板 Job 來新建。
  • (7)、寫流水線指令碼,將分為 Git、Maven、Docker、Kubectl、Http 等幾個階段。寫完指令碼後放置到上面建立模板 Job 的指令碼框框中。
  • (8)、通過複製模板 Job 來新建立用於測試的專案 Job,並且修改其中從模板 Job 複製過來的變數的引數,將其改成適用於該測試專案的引數值。
  • (9)、執行上面建立的測試專案的 Job,觀察它是否能夠正常執行完整個指令碼,並且結果為成功。

一、Kubernetes 部署 Jenkins

下面是以 NFS 為儲存卷的示例,將在 NFS 儲存捲上建立 Jenkins 目錄,然後建立 NFS 型別的 PV、PVC。

1、NFS 儲存卷建立 Jenkins 目錄

進入 NFS Server 伺服器,然後再其儲存目錄下建立 Jenkins 目錄,並且確保目錄對其它使用者有讀寫許可權。

$ mkdir /nfs/data/jenkins

2、建立 Jenkins 用於儲存的 PV、PVC

建立 Kubernetes 的 PV、PVC 資源,其中 PV 用於與 NFS 關聯,需要設定 NFS Server 伺服器地址和掛載的路徑,修改佔用空間大小。而 PVC 則是與應用關聯,方便應用與 NFS 繫結掛載,下面是 PV、PVC 的資源物件 yaml 檔案。

jenkins-storage.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins
  labels:
    app: jenkins
spec:
  capacity:          
    storage: 50Gi
  accessModes:       
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain  
  mountOptions:         #NFS掛載選項
    - hard
    - nfsvers=4.1    
  nfs:                  #NFS設定
    path: /nfs/data/jenkins   
    server: 192.168.2.11
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: jenkins
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi     #儲存空間大小
  selector:
    matchLabels:
      app: jenkins

將 PV 與 PVC 部署到 Kubernetes 中:

  • -n:指定 namespace
$ kubectl apply -f jenkins-storage.yaml -n public

3、建立 ServiceAccount & ClusterRoleBinding

Kubernetes 叢集一般情況下都預設開啟了 RBAC 許可權,所以需要建立一個角色和服務賬戶,設定角色擁有一定許可權,然後將角色與 ServiceAccount 繫結,最後將 ServiceAccount 與 Jenkins 繫結,這樣來賦予 Jenkins 一定的許可權,使其能夠執行一些需要許可權才能進行的操作。這裡為了方便,將 cluster-admin 繫結到 ServiceAccount 來保證 Jenkins 擁有足夠的許可權。

  • 注意: 請修改下面的 Namespace 引數,改成部署的 Jenkins 所在的 Namespace。

jenkins-rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins-admin       #ServiceAccount名
  namespace: mydlqcloud     #指定namespace,一定要修改成你自己的namespace
  labels:
    name: jenkins
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: jenkins-admin
  labels:
    name: jenkins
subjects:
  - kind: ServiceAccount
    name: jenkins-admin
    namespace: mydlqcloud
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

將 Jenkins 的 RBAC 部署到 Kubernetes 中:

  • -n:指定 namespace
$ kubectl apply -f jenkins-rbac.yaml -n public

4、建立 Service & Deployment

在 Kubernetes 中部署服務需要部署檔案,這裡部署 Jenkins 需要建立 Service 與 Deployment 物件,其中兩個物件需要做一些配置,如下:

  • Service:Service 暴露兩個介面 8080 與 50000,其中 8080 是 Jenkins API 和 UI 的埠,而 50000 則是供代理使用的埠。
  • Deployment: Deployment 中,需要設定容器安全策略為 runAsUser: 0 賦予容器以 Root 許可權執行,並且暴露 8080 與 50000 兩個埠與 Service 對應,而且還要注意的是,還要設定上之前建立的服務賬戶 “jenkins-admin”。

jenkins-deployment.yaml

apiVersion: v1
kind: Service
metadata:
  name: jenkins
  labels:
    app: jenkins
spec:
  type: NodePort
  ports:
  - name: http
    port: 8080                      #服務埠
    targetPort: 8080
    nodePort: 32001                 #NodePort方式暴露 Jenkins 埠
  - name: jnlp
    port: 50000                     #代理埠
    targetPort: 50000
    nodePort: 32002
  selector:
    app: jenkins
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  labels:
    app: jenkins
spec:
  selector:
    matchLabels:
      app: jenkins
  replicas: 1
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      serviceAccountName: jenkins-admin
      containers:
      - name: jenkins
        image: jenkins/jenkins:2.199
        securityContext:                     
          runAsUser: 0                      #設定以ROOT使用者執行容器
          privileged: true                  #擁有特權
        ports:
        - name: http
          containerPort: 8080
        - name: jnlp
          containerPort: 50000
        resources:
          limits:
            memory: 2Gi
            cpu: "2000m"
          requests:
            memory: 2Gi
            cpu: "2000m"
        env:
        - name: LIMITS_MEMORY
          valueFrom:
            resourceFieldRef:
              resource: limits.memory
              divisor: 1Mi
        - name: "JAVA_OPTS"                 #設定變數,指定時區和 jenkins slave 執行者設定
          value: " 
                   -Xmx$(LIMITS_MEMORY)m 
                   -XshowSettings:vm 
                   -Dhudson.slaves.NodeProvisioner.initialDelay=0
                   -Dhudson.slaves.NodeProvisioner.MARGIN=50
                   -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
                   -Duser.timezone=Asia/Shanghai
                 "    
        - name: "JENKINS_OPTS"
          value: "--prefix=/jenkins"         #設定路徑字首加上 Jenkins
        volumeMounts:                        #設定要掛在的目錄
        - name: data
          mountPath: /var/jenkins_home
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: jenkins                 #設定PVC

引數說明:

  • JAVA_OPTS: JVM 引數設定
  • JENKINS_OPTS: Jenkins 引數設定
  • 其它引數: 預設情況下,Jenkins 生成代理是保守的。例如,如果佇列中有兩個構建,它不會立即生成兩個執行器。它將生成一個執行器,並等待某個時間釋放第一個執行器,然後再決定生成第二個執行器。Jenkins 確保它生成的每個執行器都得到了最大限度的利用。如果你想覆蓋這個行為,並生成一個執行器為每個構建佇列立即不等待,所以在 Jenkins 啟動時候新增這些引數:
-Dhudson.slaves.NodeProvisioner.initialDelay=0
-Dhudson.slaves.NodeProvisioner.MARGIN=50
-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85

有了上面的部署檔案後,再將 Jenkins 部署到 Kuberntes 中:

  • -n:指定應用啟動的 namespace
$ kubectl create -f jenkins-deployment.yaml -n mydlqcloud

5、獲取 Jenkins 生成的 Token

在安裝 Jenkins 時候,它預設生成一段隨機字串在控制檯日誌中,用於安裝時驗證。這裡需要獲取它輸出在控制檯中的日誌資訊,來獲取 Token 字串。

檢視 Jenkins Pod 啟動日誌

  • -n:指定應用啟動的 namespace
$ kubectl log $(kubectl get pods -n mydlqcloud | awk '{print $1}' | grep jenkins) -n mydlqcloud

在日誌中可以看到,預設給的token為:

*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

96b19967a2aa4e7ab7d2ea5c6f55db8d

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
*************************************************************

6、啟動 Jenkins 進行初始化

輸入 Kubernetes 叢集地址和 Jenkins Service 設定的 NodePort 埠號,訪問 Jenkins UI 介面進行初始化,按以下步驟執行:

進入Jenkins

輸入 Kubernetes 叢集地址和上面設定的 Nodeport 方式的埠號 32001,然後輸入上面獲取的 Token 字串。例如,本人 Kubernetes 叢集 IP 為 192.168.2.11 ,所以就可以訪問地址 http://192.168.2.11:32001/jenkins 進入 Jenkins 初始化介面。

 

安裝外掛

安裝外掛,選擇 推薦安裝 方式進行安裝即可,後續再安裝需要的外掛。

 

設定使用者名稱、密碼

在這裡輸入一個使用者名稱、密碼,方便後續登入,如果不設定可能下次登入需要使用之前日誌中預設的 Token 串來登入。

 

配置 Jenkins 地址

配置 Jenkins URL 地址,來告知 Jenkins 自己的 URL,在傳送郵件、觸發鉤子等可能用到。

 

進入 Jenkins 介面

到此 Jenkins 初始化就配置完成,成功進入 Jenkins 介面。

 

二、Jenkins 安裝相關外掛

Jenkins 中可以開啟 系統管理->外掛管理->可選外掛 來安裝下面的一些外掛:

  • Git: Jenkins 安裝中預設安裝 Git 外掛,所以不需要單獨安裝。利用 git 工具可以將 github、gitlab 等等的地址下載原始碼。
  • Docker: Jenkins 安裝中預設安裝 Docker 外掛,所以不需要單獨安裝。利用 Docker 外掛可以設定 Docker 環境,執行 Docker 命令,配置遠端 Docker 倉庫憑據等。
  • Kubernetes: Kubernetes 外掛的目的是能夠使用 Kubernetes 叢集動態配置 Jenkins 代理(使用Kubernetes排程機制來優化負載),執行單個構建,等構建完成後刪除該代理。這裡我們需要用到這個外掛來啟動 Jenkins Slave 代理映象,讓代理執行 Jenkins 要執行的 Job。
  • Kubernetes Cli: Kubernetes Cli 外掛作用是在執行 Jenkins Job 時候提供 kubectl 與 Kubernetes 叢集互動環境。可以在 Pipeline 或自由式專案中允許執行 kubectl 相關命令。它的主要作用是提供 kubectl 執行環境,當然也可以提供 helm 執行環境。
  • Config File Provider: Config File Provider 外掛作用就是提供在 Jenkins 中儲存 properties、xml、json、settings.xml 等資訊,可以在執行 Pipeline 過程中可以寫入儲存的配置。例如,存入一個 Maven 全域性 Settings.xml 檔案,在執行 Pipeline Job 時候引入該 Settings.xml ,這樣 Maven 編譯用的就是該全域性的 Settings.xml。
  • Pipeline Utility Steps: 這是一個操作檔案的外掛,例如讀寫 json、yaml、pom.xml、Properties 等等。在這裡主要用這個外掛讀取 pom.xml 檔案的引數設定,獲取變數,方便構建 Docker 映象。
  • Git Parameter: 能夠與 Git 外掛結合使用,動態獲取 Git 專案中分支資訊,在 Jenkins Job 構建前提供分支選項,來讓專案執行人員選擇拉取對應分支的程式碼。

三、配置相關憑據

選擇 憑據->系統->全域性憑據->新增憑據 來新增 Git、Docker Hub、Kubernetes 等認證憑據。

1、新增 Git 認證憑據

配置的引數值:

  • 型別:Username with password
  • 範圍:全域性
  • 使用者名稱(Git 使用者名稱): 略
  • 密碼(Git 密碼):略
  • ID:global-git-credential
  • 描述:全域性 Git 憑據

 

2、新增 Kubernetes Token 憑據

配置的引數值:

  • 型別:Secret text
  • 範圍:全域性
  • Secret(K8S Token 串):略
  • ID:global-kubernetes-credential
  • 描述:全域性的 K8S Token

 

3、新增 Docker 倉庫認證憑據

配置的引數值:

  • 型別:Username with password
  • 範圍:全域性
  • 使用者名稱(Docker 倉庫使用者名稱):略
  • 密碼(Docker 倉庫密碼):略
  • ID:docker-hub-credential
  • 描述:Docker 倉庫認證憑據

 

四、Jenkins 配置 Kubernetes 外掛

進入 系統管理->系統設定->雲 中,點選 新增一個雲 選項,來新建一個與 Kubernetes 的連線,然後按照下面各個配置項進行配置。

1、Kubernetes Plugin 基本配置

(1)、配置連線 Kubernetes 引數

配置 Kubernetes API 地址,然後再選擇 Kubernetes Token 憑據。

 

注意: 如果你的 Jenkins 也是安裝在 Kubernetes 環境中,那麼可以直接使用 Kubernetes 叢集內的 Kubernetes API 地址,如果 Jnekins 是在安裝在正常物理機或者虛擬機器環境中,那麼使用叢集外的 Kubernetes API 地址,兩個地址如下:

然後點選連線測試,檢視是否能成功連通 Kubernetes,如果返回結果 Successful 則代表連線成功,否則失敗。

 

(2)、配置 Jenkins 地址

 

注意: 這裡的 Jenkins 地址是供 Slave 節點連線 Jenkins Master 節點用的,所以這裡需要配置 Jenkins Master 的 URL 地址。這裡和上面一樣,也是考慮 Jenkins 是部署在 Kubernetes 叢集內還是叢集外,兩個地址如下:

  • 叢集內地址:https://{Jenkins Pod 名稱}.{Jenkins Pod 所在 Namespace}/{Jenkins 字首}
  • 叢集外地址:https://{Kubernetes 叢集 IP}:{Jenkins NodePort 埠}/{Jenkins 字首}

如果 Jnekins 中配置了 /jenkins 字首,則 URL 後面加上 /jenkins,否則不加,這個地址根據自己的 Jnekins 實際情況來判斷。

2、Kubernetes 外掛 Pod 模板配置

(1)、配置 Pod 名稱和標籤列表

配置 Pod 模板的名稱和標籤列表名,Pod 模板名可用於子模板繼承,標籤列表可用於 Jenkins Job 中指定,使用此 Pod 模板來執行任務。

 

(2)、配置 Pod 的原始 yaml

在 Pod 的原始 yaml 配置中,加入一段配置,用於改變 Kubernetes Plugin 自帶的 JNLP 映象,並指定 RunAsUser=0 來使容器以 Root 身份執行任務,並設定 privileged=true 來讓 Slave Pod 在 Kubernetes 中擁有特權。

Jenkins Slave JNLP 映象官方地址 https://hub.docker.com/r/jenkins/slave 可以從中下載相關 JNLP 代理映象。

 

yaml 內容如下:

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: jenkins-slave
spec:
  securityContext:
    runAsUser: 0
    privileged: true
  containers:
  - name: jnlp
    tty: true
    workingDir: /home/jenkins/agent
    image: registry.cn-shanghai.aliyuncs.com/mydlq/jnlp-slave:3.35-5-alpine

3、Kubernetes 外掛 Container 配置

將配置 Jenkins Slave 在 Kubernetes 中的 Pod 中所包含容器資訊,這裡映象都可以從官方 Docker Hub 下載,由於網速原因,本人已經將其下載到 Aliyun 映象倉庫。

(1)、配置 Maven 映象

  • 名稱:maven
  • Docker 映象:registry.cn-shanghai.aliyuncs.com/mydlq/maven:3.6.0-jdk8-alpine
  • 其它引數:預設值即可

Maven 映象可以從官方 Docker Hub 下載,地址:https://hub.docker.com/_/maven

 

(2)、配置 Docker In Docker 映象

  • 名稱:docker
  • Docker 映象:registry.cn-shanghai.aliyuncs.com/mydlq/docker:18.06.3-dind
  • 其它引數:預設值即可

Docker-IN-Docker 映象可以從官方 Docker Hub 下載,地址:https://hub.docker.com/_/docker

 

(3)、配置 Kubectl 映象

  • 名稱:kubectl
  • Docker 映象:registry.cn-shanghai.aliyuncs.com/mydlq/kubectl:1.15.3
  • 其它引數:預設值即可

Kubectl 映象可以從官方 Docker Hub 下載,地址:https://hub.docker.com/r/bitnami/kubectl

 

4、Container 儲存掛載配置

由於上面配置的 Maven、Docker 等都需要掛載儲存,Maven 中是將中央倉庫下載的 Jar 儲存到共享目錄,而 Docker 則是需要將宿主機的 Docker 配置掛載到 Docker In Docker 容器內部,所以我們要對掛載進行配置。

(1)、建立 Maven 儲存使用的 PV、PVC

提前在 NFS 卷中,建立用於儲存 Maven 相關 Jar 的目錄:

建立的目錄要確保其它使用者有讀寫許可權。

$ mkdir /nfs/data/maven

然後,Kubernetes 下再建立 Maven 的 PV、PVC 部署檔案:

maven-storage.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: maven
  labels:
    app: maven
spec:
  capacity:          
    storage: 100Gi
  accessModes:       
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain  
  mountOptions:         #NFS掛在選項
    - hard
    - nfsvers=4.1    
  nfs:                  #NFS設定
    path: /nfs/data/maven   
    server: 192.168.2.11
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: maven
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Gi     #儲存空間大小
  selector:
    matchLabels:
      app: maven

部署 PV、PVC 到 Kubernetes 中:

  • -n:指定 namespace
$ kubectl apply -f maven-storage.yaml -n public

(2)、配置 Maven 掛載

在卷選項中,選擇新增捲,選擇 Persistent Volume Claim 按如下新增配置:

  • 申明值(PVC 名稱):maven
  • 掛在路徑(容器內的目錄):/root/.m2

 

(3)、配置 Docker 掛載

Kubernetes 中 Pod 的容器是啟動在各個節點上,每個節點就是一臺宿主機,裡面進行了很多 Docker 配置,所以我們這裡將宿主機的 Docker 配置掛載進入 Docker 映象。選擇新增捲,選擇 Host Path Volume 按如下新增配置:

① 路徑 /usr/bin/docker:

  • 主機路徑(宿主機目錄):/usr/bin/docker
  • 掛載路徑(容器內的目錄):/usr/bin/docker

② 路徑 /var/run/docker.sock:

  • 主機路徑(宿主機目錄):/var/run/docker.sock
  • 掛載路徑(容器內的目錄):/var/run/docker.sock

③ 路徑 /etc/docker:

  • 主機路徑(宿主機目錄):/etc/docker
  • 掛載路徑(容器內的目錄):/etc/docker

 

五、建立相關檔案

之前安裝了 Config File Provider 外掛,該外掛功能就是可以在 Jenkins 上儲存一些配置檔案,例如,我們經常使用到的 yaml、properties、Dockerfile、Maven 的 Settings.xml 等檔案,都可以儲存到 Jenkins 該外掛中。

開啟 系統管理->Managed files ,在其中新增幾個檔案:

  • Maven 配置檔案: Maven 的 Settings.xml 配置檔案。
  • Dockerfile 檔案: Dockerfile 指令碼。
  • Kubernetes 部署檔案: 將應用部署到 kubernetes 的 Deployment 檔案。

1、新增 Maven 配置檔案

選擇 Add a new Config—>Global Maven settings.xml 來新增一個 Maven 全域性 Settings.xml 檔案:

  • ID: global-maven-settings
  • Name: MavenGlobalSettings
  • Comment: 全域性 Maven Settings.xml 檔案
  • Content: 內容如下↓:

為了加快 jar 包的下載速度,這裡將倉庫地址指向 aliyun Maven 倉庫地址。

<?xml version="1.0" encoding="UTF-8"?>

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <pluginGroups>
  </pluginGroups>

  <proxies>
  </proxies>

  <servers>
  </servers>
  
  <mirrors>
    <!--Aliyun Maven-->
    <mirror>
        <id>alimaven</id>
        <name>aliyun maven</name>
        <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        <mirrorOf>central</mirrorOf>
    </mirror>
  </mirrors>
  
  <profiles>
  </profiles>

</settings>

2、新增 Dockerfile 檔案

選擇 Add a new Config—>Custom file 來新增一個 Dockerfile 檔案:

  • ID: global-dockerfile-file
  • Name: Dockerfile
  • Comment: 全域性 Dockerfile 檔案
  • Content: 內容如下↓:
FROM openjdk:8u222-jre-slim
VOLUME /tmp
ADD target/*.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JVM_OPTS="-Xss256k -Duser.timezone=Asia/Shanghai -Djava.security.egd=file:/dev/./urandom"
ENV JAVA_OPTS=""
ENV APP_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JVM_OPTS $JAVA_OPTS -jar /app.jar $APP_OPTS" ]

3、新增 Kubernetes 部署檔案

選擇 Add a new Config—>Custom file 來新增一個 Kubernetes 部署檔案

  • ID: global-kubernetes-deployment
  • Name: deployment.yaml
  • Comment: 全域性 Kubernetes 部署檔案
  • Content: 內容如下↓:
apiVersion: v1
kind: Service
metadata:
  name: #APP_NAME
  labels:
    app: #APP_NAME
spec:
  type: NodePort
  ports:
  - name: server          #服務埠
    port: 8080  
    targetPort: 8080
  - name: management      #監控及監控檢查的埠 
    port: 8081
    targetPort: 8081
  selector:
    app: #APP_NAME
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: #APP_NAME
  labels:
    app: #APP_NAME
spec:
  replicas: #APP_REPLICAS
  selector:
    matchLabels:
      app: #APP_NAME
  strategy:
    type: Recreate          #設定更新策略為刪除策略
  template:
    metadata:
      labels:
        app: #APP_NAME
    spec:
      containers:
      - name: #APP_NAME
        image: #APP_IMAGE_NAME
        imagePullPolicy: Always
        ports:
        - containerPort: 8080   #服務埠
          name: server
        - containerPort: 8081   #監控及監控檢查的埠 
          name: management
        env:
        - name: "update_uuid"
          value: "#APP_UUID"    #生成的隨機值,放置執行kubectl apply時能夠執行
        resources: 
          limits:
            cpu: 2000m
            memory: 1024Mi
          requests:
            cpu: 1000m
            memory: 512Mi

為了模板能夠動態替換某些值,上面模板中設定了幾個可替換的引數,用 #變數名稱 來標記,後面我們在執行 Pipeline 時候將裡面的 #xxx變數 標記替換掉,上面配置的變數有:

  • #APP_NAME: 應用名稱。
  • #APP_REPLICAS: 應用副本數。
  • #APP_IMAGE_NAME: 映象名稱。
  • #APP_UUID: 生成的隨機值,因為後續 Kubectl 在執行命令時候,如果部署物件中的值沒有一點變化的話,將不會執行 kubectl apply 命令,所以這裡設定了一個隨機值,以確保每次部署檔案都不一致。

並且還有一點就是要注意,設定更新策略為 Recreate(刪除再建立) 策略,否則後面的健康檢查階段將不能正常檢查更新後的專案。

Kubernetes 預設為 RollingUpdate 策略,該策略為應用啟動時,先將新例項啟動,再刪除舊的例項,就是因為這樣,在後面健康檢查階段,健康檢查 URL 地址還是未更新前的舊例項的 URL 地址,會導致健康檢查不準確,所以必須改為 Recreate 策略,先刪除舊例項,再建立新例項。

六、如何寫流水線指令碼和使用外掛

1、指令碼中設定全域性超時時間

設定任務超時時間,如果在規定時間內任務沒有完成,則進行失敗操作,格式如下:

timeout(time: 60, unit: 'SECONDS') {
    // 指令碼
}

2、指令碼中使用 Git 外掛

Git 外掛方法使用格式,及其部分引數:

  • changelog: 是否檢測變化日誌
  • url: Git 專案地址
  • branch: Git 分支
  • credentialsId: Jenkins 存的 Git 憑據 ID 值
git changelog: true,
    url: "http://gitlab.xxxx/xxx.git"
    branch: "master",
    credentialsId: "xxxx-xxxx-xxxx-xxxx",

3、指令碼中使用 Kubernetes 外掛

Kubernetes 外掛中存在 PodTemplate 方法,在執行指令碼時候,會自動在 Kubernetes 中建立 Pod Template 配置的 Slave Pod,在其中執行 podTemplate 程式碼塊中的指令碼。

def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
    node (label) {
        print "在 Slave Pod 中執行任務"  
    }
}

podTemplate 方法引數簡介:

  • cloud: 之前 Kuberntes 外掛配置的 Cloud 名稱
  • label: 之前 Kuberntes 外掛配置的 Cloud 中 Pod Template 裡面的 Label 標籤名稱。

4、指令碼中使用 Docker 映象

在之前配置了 Kubernetes 外掛的 Pod Template 配置中,配置了幾個容器,每個容器中都有特定的功能的環境,例如:

  • Maven 容器中能夠執行 mvn 命令。
  • Kuberctl 容器能夠執行 kubectl 命令。
  • Docker In Docker 容器中能夠執行 Docker 命令。

既然每個容器都能提供特定的環境,那麼再執行執行 Pipleline 指令碼時候,就可以在不同的映象中使用不同的環境的命令:

  • Maven 映象
container('maven') {  
    sh "mvn install
}
  • Docker In Docker 映象
container('docker') {  
    sh "docker build -t xxxxx:1.0.0 .
}
  • Kubectl 映象
container('kubectl') {  
    sh "kubectl apply -f xxxx.yaml"
}

5、指令碼中引入 Jenkins 中預先儲存的檔案

在之前的 系統設定->File Manager 中,儲存了很多檔案,例如:

  • Docker 的映象構建指令碼檔案 Dockerfile。
  • Maven 的全域性設定檔案 Settings.xml
  • Kubernetes 的部署檔案 deployment.yaml

在使用 Pipleline 指令碼時候,我們需要將這些檔案文字提取出來,建立在執行任務的流程中,建立這些檔案可以使用 Config File Provider 外掛提供的 configFileProvider 方法,如下所示:

  • 建立 settings.xml 檔案
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
    sh "cat settings.xml"
}
  • 建立 Dockerfile 檔案
configFileProvider([configFile(fileId: "global-dockerfile-file", targetLocation: "Dockerfile")]){
    sh "cat Dockerfile"
}
  • 建立 Dockerfile 檔案
configFileProvider([configFile(fileId: "global-kubernetes-deployment", targetLocation: "deployment.yaml")]){
    sh "cat deployment.yaml"
}

6、指令碼建立檔案

在使用 Groovy 寫 Pipleline 指令碼時候,經常要將變數的文字生成檔案,方便在執行流水線過程中操作文字檔案使用,如何將檔案轉換為檔案,可以使用 Pipeline Utility Steps 外掛的 writeFile 方法,如下:

writeFile encoding: 'UTF-8', file: './test.txt', text: "寫入檔案的文字內容"

7、指令碼中使用 Http Rrequest 外掛

指令碼中可以使用 HttpRequest 來對某一地址進行請求,這裡簡單使用 Get 請求地址,複雜的可以檢視 Jenkins 外掛的官網檢視使用示例。

下面是使用 Http Request 的 Get 請求示例:

result = httpRequest "http:www.baidu.com"

if ("${result.status}" == "200") {
    print "Http 請求成功"
} 

8、指令碼中使用 Kubernetes Cli 外掛

在之前說過,在 kubectl 映象中能夠使用 kubectl 命令,不過由於執行 Kubectl 命令一般需要在映象的 $HOME/.kube/ 目錄中存在連線 Kubernetes API 的 config 檔案,使其 kubectl 命令有明確請求 kubernetes API 的地址和使用者許可權,不過將 config 檔案掛入映象內部是一件比較繁瑣的事情。

好在 Jenkins 提供的 Kubectl Cli 外掛,只要在其中配置連線 Kubernetes 的 Token 憑據,就能夠在 Kubectl Cli 提供的 withKubeConfig 方法,擁有類似存在 config 一樣的功能,在 kubectl 映象中的 withKubeConfig 方法塊內執行 kubectl 就可以操作配置的 Kubectl Cli 的憑據的 K8S 叢集。

container('kubectl') {
    withKubeConfig([credentialsId: "Kubernetes Token 憑據 ID",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
        sh "kubectl get nodes"
    }
}

9、指令碼中操作字串替換值

在使用 Groovy 語法寫 Pipleline 指令碼時候,我們經常要替換先前設定好的一些文字的值,這裡我們簡單示例一下,如何替換字串。

// 測試的字串
sourceStr = "這是要替換的值:#value1,這是要替換的值:#value2"
// 替換#value1與#value2連個值
afterStr = deploy.replaceAll("#value1","AAA").replaceAll("#value2","BBB")
// 輸出替換後的字串
print "${afterStr}"

10、指令碼中讀取 pom.xml 引數

在執行 Java 專案的流水線時,我們經常要動態獲取專案中的屬性,很多屬性都配置在專案的 pom.xml 中,還好 Pipeline Utility Steps 外掛提供能夠讀取 pom.xml 的方法,示例如下:

stage('讀取pom.xml引數階段'){
    // 讀取 Pom.xml 引數
    pom = readMavenPom file: './pom.xml'
    // 輸出讀取的引數
    print "${pom.artifactId}"
    print = "${pom.version}"
}

11、指令碼中使用 Docker 外掛構建與推送映象

在流水線指令碼中,我們一般不直接使用 Docker 命令,而是使用 Docker 外掛提供的 docker.withRegistry(“”) 方法來構建與推送映象,並且還能在方法中配置登入憑據資訊,來讓倉庫驗證許可權,這點是非常方便的。使用示例如下:

docker.withRegistry("http://xxxx Docker 倉庫地址", "Docker 倉庫憑據 ID") {
        // 構建 Docker 映象
        def customImage = docker.build("${dockerImageName}")
        // 推送 Docker 映象
        customImage.push()
    }

七、在 Jenkins 建立模板任務

建立一個 Pipeline Job 來充當各個 Jenkins Job 的模板,方便後續建立 Job 時,直接複製模板專案,然後修改配置就能使用。所以這裡我們建立一個模板 Pipeline Job,在 Job 配置中需要新增一些引數和環境變數,方便我們動態替換一些值。

1、建立 Pipeline 任務

  • 任務名稱: my-template
  • 任務型別: 流水線專案

 

2、配置專案構建基本引數

配置同一時間一個 Job 只能構建一個,不允許多個併發構建。另外需要設定專案構建後,包的保留時間,以防止包過多且大佔用大量空間(一個包很肯能佔 10MB~200MB 大小)導致儲不足。

 

3、配置 Git 變數

在 Job 配置的 引數化構建過程 中,新增下面引數:

Git 專案地址變數

  • 變數名稱:GIT_PROJECT_URL
  • 型別:String
  • 描述:專案 Git 地址
  • 預設值:”https://xxxxxxxxxxxx"

 

Git 分支變數

  • 變數名稱:GIT_BRANCH
  • 型別:Git Parameter
  • 描述:選擇 Git 分支
  • 預設值:master

 

Git 憑據變數

  • 變數名稱:GIT_CREADENTIAL
  • 型別:Credentials
  • 描述:Git 憑據
  • 預設值:global-git-credential

 

4、配置 Maven 變數

Maven 構建命令變數

  • 變數名稱:MAVEN_BUILD_OPTION
  • 型別:Choices
  • 描述:要執行的執行 Maven 命令選擇
  • 可選值:[‘package’, ‘install’, ‘deploy’]
  • 預設值:install

 

5、配置 Docker 變數

Docker 專案地址變數

  • 變數名稱:DOCKER_HUB_URL
  • 型別:String
  • 描述:Docker 倉庫地址
  • 預設值(預設 Docker 倉庫地址):”10.71.164.28:5000”

 

Docker 倉庫專案組變數

  • 變數名稱:DOCKER_HUB_GROUP
  • 型別:String
  • 描述:Docker 倉庫專案組名
  • 預設值:””

 

Docker 倉庫認證憑據變數

  • 變數名稱:DOCKER_HUB_CREADENTIAL
  • 型別:Credentials
  • 描述:Docker 倉庫認證憑據
  • 預設值:docker-hub-credential

 

Docker Dockerfile 檔案 ID 變數

  • 變數名稱:DOCKER_DOCKERFILE_ID
  • 型別:String
  • 描述:存於 Jenkins “Managed files” 的 Dockerfile 檔案的 ID
  • 預設值:”global-dockerfile-file”

 

6、配置 Kubernetes 變數

Kubernetes 認證憑據變數

  • 變數名稱:KUBERNETES_CREADENTIAL
  • 型別:Credentials
  • 描述:Kubernetes 認證 Token
  • 預設值:global-kubernetes-credential

 

Kubernetes Namespace 變數

  • 變數名稱:KUBERNETES_NAMESPACE
  • 型別:String
  • 描述:Kubernetes 名稱空間 Namespace
  • 預設值:””

 

Kubernetes 應用例項副本數

  • 變數名稱:KUBERNETES_APP_REPLICAS
  • 型別:String
  • 描述:應用例項副本數
  • 預設值:1

 

Kubernetes 應用部署 yaml 檔案ID

  • 變數名稱:KUBERNETES_DEPLOYMENT_ID
  • 型別:String
  • 描述:存於 Jenkins “Managed files” 的 K8S 部署檔案的 ID
  • 預設值:”global-kubernetes-deployment”

 

7、配置 HTTP 變數

HTTP 健康檢查埠

  • 變數名稱:HTTP_REQUEST_PORT
  • 型別:String
  • 描述:Http Request 埠(健康檢測埠)
  • 預設值:8081

 

HTTP 健康檢查地址

  • 變數名稱:HTTP_REQUEST_URL
  • 型別:String
  • 描述:Http Request 專案中的相對路徑(健康檢測路徑)
  • 預設值:/actuator/health

 

HTTP 健康檢查次數

  • 變數名稱:HTTP_REQUEST_NUMBER
  • 型別:Choices
  • 描述:Http Request 請求次數
  • 可選值:[‘10’, ‘5’, ‘10’, ‘15’, ‘20’, ‘25’, ‘30’]
  • 預設值:10

 

HTTP 健康檢查時間間隔

  • 變數名稱:HTTP_REQUEST_INTERVAL
  • 型別:Choices
  • 描述:Http Request 時間間隔
  • 可選值:[‘10’, ‘5’, ‘15’, ‘20’, ‘25’, ‘30’]
  • 預設值:10

 

八、建立 Pipeline 指令碼

接下將使用 Groovy 語法建立一個為 SpringBoot 專案準備的 CI/CD 的指令碼式的流水線指令碼。其中,指令碼中包含多個階段,分別為 Git 拉取映象,Maven 編譯 Java 專案,Docker 構建與推送映象,Kubectl 部署應用到 Kubernetes 中,最後使用 Http 請求進行健康檢查,下面是各個階段指令碼及其介紹。

1、指令碼中使用 Kubernetes 外掛及設定超時時間

使用 Kubernetes 外掛執行任務,並設定超時時間為 10 分鐘,指令碼如下:

// 設定超時時間 600 SECONDS,方法塊內的方法執行超時,任務就標記為失敗
timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            print "在 Slave Pod 中執行任務"  
        }
    }
}

2、指令碼中 Git 拉取專案階段

接下來接著往整體的指令碼中新增 Git 模組,其中需要引用上面配置的變數,將變數填入指令碼中的方法,如下:

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git階段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
        }
    }
}

變數介紹:

  • GIT_BRANCH: Git 專案分支變數。
  • GIT_PROJECT_URL: Git 專案 URL 變數。
  • GIT_CREADENTIAL: Git 憑據 ID 變數。

3、指令碼中 Maven 編譯專案階段

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git階段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('Maven階段'){
                container('maven') {  
                    // 建立 Maven 需要的 Settings.xml 檔案
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 執行 Maven 命令構建專案,並且設定 Maven 配置為剛剛建立的 Settings.xml 檔案
                        sh "mvn -T 1C clean ${MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
        }
    }
}

變數介紹:

  • MAVEN_BUILD_OPTION: Maven 執行的構建命令,package、install 或 deploy。
  • global-maven-settings: 全域性 Maven 的 Settings.xml 檔案的 ID 值,這裡是使用 configFileProvider 外掛來建立該檔案。

4、指令碼中讀取 pom.xml 引數階段

這裡使用 Pipeline Utility Steps 的 readMavenPom 方法讀取專案的 pom.xml 檔案,並設定 appName 與 appVersion 兩個全域性引數。

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git階段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('Maven階段'){
                container('maven') {  
                    // 建立 Maven 需要的 Settings.xml 檔案
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 執行 Maven 命令構建專案
                        sh "mvn -T 1C clean ${MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
            stage('讀取pom.xml引數階段'){
                // 讀取 Pom.xml 引數
                pom = readMavenPom file: './pom.xml'
                // 設定 appName 和 appVersion 兩個全域性引數
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
        }
    }
}

變數介紹:

  • pom.artifactId: 從 pom.xml 檔案中讀取的 artifactId 引數值。
  • pom.version: 從 pom.xml 檔案中讀取的 version 引數值。

5、指令碼中 Docker 映象構建與推送模組

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git階段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('Maven階段'){
                container('maven') {  
                    // 建立 Maven 需要的 Settings.xml 檔案
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 執行 Maven 命令構建專案
                        sh "mvn -T 1C clean ${MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
            stage('讀取pom.xml引數階段'){
                // 讀取 Pom.xml 引數
                pom = readMavenPom file: './pom.xml'
                // 設定 appName 和 appVersion 兩個全域性引數
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
            stage('Docker階段'){
                container('docker') {
                    // 建立 Dockerfile 檔案,但只能在方法塊內使用
                    configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
                        // 設定 Docker 映象名稱
                        dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
                        // 判斷 DOCKER_HUB_GROUP 是否為空,有些倉庫是不設定倉庫組的
                        if ("${params.DOCKER_HUB_GROUP}" == '') {
                            dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
                        }
                        // 提供 Docker 環境,使用 Docker 工具來進行 Docker 映象構建與推送
                        docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
                            def customImage = docker.build("${dockerImageName}")
                            customImage.push()
                        }
                    }
                }
            }
        }
    }
}

變數介紹:

  • DOCKER_DOCKERFILE_ID: Dockerfile 檔案的 ID。
  • DOCKER_HUB_URL: Docker 倉庫 URL 地址。
  • DOCKER_HUB_GROUP: Docker 倉庫專案組名。
  • DOCKER_HUB_CREADENTIAL: Docker 倉庫認證憑據。
  • appName: 從 pom.xml 中讀取的應用名稱。
  • appVersion: 從 pom.xml 中讀取的應用版本號。

6、Kubernetes 模組

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git階段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('Maven階段'){
                container('maven') {  
                    // 建立 Maven 需要的 Settings.xml 檔案
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 執行 Maven 命令構建專案
                        sh "mvn -T 1C clean ${MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
            stage('讀取pom.xml引數階段'){
                // 讀取 Pom.xml 引數
                pom = readMavenPom file: './pom.xml'
                // 設定 appName 和 appVersion 兩個全域性引數
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
            stage('Docker階段'){
                container('docker') {
                    // 建立 Dockerfile 檔案,但只能在方法塊內使用
                    configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
                        // 設定 Docker 映象名稱
                        dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
                        // 判斷 DOCKER_HUB_GROUP 是否為空,有些倉庫是不設定倉庫組的
                        if ("${params.DOCKER_HUB_GROUP}" == '') {
                            dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
                        }
                        // 提供 Docker 環境,使用 Docker 工具來進行 Docker 映象構建與推送
                        docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
                            def customImage = docker.build("${dockerImageName}")
                            customImage.push()
                        }
                    }
                }
            }
            stage('Kubernetes 階段'){
                container('kubectl') {
                    // 使用 Kubectl Cli 外掛的方法,提供 Kubernetes 環境,在其方法塊內部能夠執行 kubectl 命令
                    withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                        // 使用 configFile 外掛,建立 Kubernetes 部署檔案 deployment.yaml
                        configFileProvider([configFile(fileId: "${params.KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
                            // 讀取 Kubernetes 部署檔案
                            deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
                            // 替換部署檔案中的變數,並將替換後的文字賦予 deployfile 變數
                            deployfile = deploy.replaceAll("#APP_NAME","${appName}")
                                           .replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
                                           .replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
                                           .replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
                            // 生成新的 Kubernetes 部署檔案,內容為 deployfile 變數中的文字,檔名稱為 "deploy.yaml"
                            writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
                            // 輸出新建立的部署 yaml 檔案內容
                            sh "cat deploy.yaml"
                            // 執行 Kuberctl 命令進行部署操作
                            sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
                        }
                    }
                }
            }
        }
    }
}

變數介紹:

  • KUBERNETES_DEPLOYMENT_ID: Kubernetes 部署檔案的 ID。
  • KUBERNETES_CREADENTIAL: Kubernetes API 認證憑據。
  • KUBERNETES_NAMESPACE: Kubernetes 部署應用的 Namespace。
  • KUBERNETES_APP_REPLICAS: Kubernetes 部署應用的副本數。
  • appName: 從 pom.xml 中讀取的應用名稱。
  • dockerImageName: Docker 映象名稱。

7、HTTP 健康檢查模組

timeout(time: 600, unit: 'SECONDS') {
    def label = "jnlp-agent"
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git階段'){
                git changelog: true,
                    url: "${params.GIT_PROJECT_URL}",
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}"
            }
            stage('Maven階段'){
                container('maven') {  
                    // 建立 Maven 需要的 Settings.xml 檔案
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 執行 Maven 命令構建專案
                        sh "mvn -T 1C clean ${MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
            stage('讀取pom.xml引數階段'){
                // 讀取 Pom.xml 引數
                pom = readMavenPom file: './pom.xml'
                // 設定 appName 和 appVersion 兩個全域性引數
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
            stage('Docker階段'){
                container('docker') {
                    // 建立 Dockerfile 檔案,但只能在方法塊內使用
                    configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
                        // 設定 Docker 映象名稱
                        dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
                        // 判斷 DOCKER_HUB_GROUP 是否為空,有些倉庫是不設定倉庫組的
                        if ("${params.DOCKER_HUB_GROUP}" == '') {
                            dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
                        }
                        // 提供 Docker 環境,使用 Docker 工具來進行 Docker 映象構建與推送
                        docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
                            def customImage = docker.build("${dockerImageName}")
                            customImage.push()
                        }
                    }
                }
            }
            stage('Kubernetes 階段'){
                container('kubectl') {
                    // 使用 Kubectl Cli 外掛的方法,提供 Kubernetes 環境,在其方法塊內部能夠執行 kubectl 命令
                    withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                        // 使用 configFile 外掛,建立 Kubernetes 部署檔案 deployment.yaml
                        configFileProvider([configFile(fileId: "${params.KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
                            // 讀取 Kubernetes 部署檔案
                            deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
                            // 替換部署檔案中的變數,並將替換後的文字賦予 deployfile 變數
                            deployfile = deploy.replaceAll("#APP_NAME","${appName}")
                                           .replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
                                           .replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
                                           .replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
                            // 生成新的 Kubernetes 部署檔案,內容為 deployfile 變數中的文字,檔名稱為 "deploy.yaml"
                            writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
                            // 輸出新建立的部署 yaml 檔案內容
                            sh "cat deploy.yaml"
                            // 執行 Kuberctl 命令進行部署操作
                            sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
                        }
                    }
                }
            }
            stage('健康檢查階段'){
                // 設定檢測延遲時間 10s,10s 後再開始檢測
                sleep 10
                // 健康檢查地址
                httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
                // 迴圈使用 httpRequest 請求,檢測服務是否啟動
                for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
                    try{
                        // 輸出請求資訊和請求次數
                        print "訪問服務:${appName} \n" +
                              "訪問地址:${httpRequestUrl} \n" +
                              "訪問次數:${n}"
                        // 如果非第一次檢測,就睡眠一段時間,等待再次執行 httpRequest 請求
                        if(n > 1){
                            sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
                        }
                        // 使用 HttpRequest 外掛的 httpRequest 方法檢測對應地址
                        result = httpRequest "${httpRequestUrl}"
                        // 判斷是否返回 200
                        if ("${result.status}" == "200") {
                            print "Http 請求成功,流水線結束"
                            break
                        } 
                    }catch(Exception e){
                        print "監控檢測失敗,將在 ${params.HTTP_REQUEST_INTERVAL} 秒後將再次檢測。"
                        // 判斷檢測次數是否為最後一次檢測,如果是最後一次檢測,並且還失敗了,就對整個 Jenkins 任務標記為失敗
                        if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
                            currentBuild.result = "FAILURE"
                        }
                    }
                }
            }
        }
    }
}

變數介紹:

  • HTTP_REQUEST_PORT: HTTP 健康檢查埠。
  • HTTP_REQUEST_URL: HTTP 健康檢查 URL 地址。
  • HTTP_REQUEST_NUMBER: HTTP 健康檢查次數。
  • HTTP_REQUEST_INTERVAL: HTTP 健康檢查間隔。
  • KUBERNETES_NAMESPACE: Kubernetes 的 Namespace。
  • appName: 從 pom.xml 中讀取的應用名稱。

8、完整指令碼

def label = "jnlp-agent"
timeout(time: 900, unit: 'SECONDS') {
    podTemplate(label: label,cloud: 'kubernetes' ){
        node (label) {
            stage('Git階段'){
                // 執行 Git 命令進行 Clone 專案
                git changelog: true,
                    branch: "${params.GIT_BRANCH}",
                    credentialsId: "${params.GIT_CREADENTIAL}",
                    url: "${GIT_PROJECT_URL}"
            }
            stage('Maven階段'){
                container('maven') {  
                    // 建立 Maven 需要的 Settings.xml 檔案
                    configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
                        // 執行 Maven 命令構建專案,並且設定 Maven 配置為剛剛建立的 Settings.xml 檔案
                        sh "mvn -T 1C clean ${MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
                    }
                }
            }
            stage('讀取pom.xml引數階段'){
                // 讀取 Pom.xml 引數
                pom = readMavenPom file: './pom.xml'
                // 設定 appName 和 appVersion 兩個全域性引數
                appName = "${pom.artifactId}"
                appVersion = "${pom.version}"
            }
            stage('Docker階段'){
                container('docker') {
                    // 建立 Dockerfile 檔案,但只能在方法塊內使用
                    configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
                        // 設定 Docker 映象名稱
                        dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
                        if ("${params.DOCKER_HUB_GROUP}" == '') {
                            dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
                        }
                        // 提供 Docker 環境,使用 Docker 工具來進行 Docker 映象構建與推送
                        docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
                            def customImage = docker.build("${dockerImageName}")
                            customImage.push()
                        }
                    }
                }
            }
            stage('Kubernetes 階段'){
                // kubectl 映象
                container('kubectl') {
                    // 使用 Kubectl Cli 外掛的方法,提供 Kubernetes 環境,在其方法塊內部能夠執行 kubectl 命令
                    withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
                        // 使用 configFile 外掛,建立 Kubernetes 部署檔案 deployment.yaml
                        configFileProvider([configFile(fileId: "${params.KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
                            // 讀取 Kubernetes 部署檔案
                            deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
                            // 替換部署檔案中的變數,並將替換後的文字賦予 deployfile 變數
                            deployfile = deploy.replaceAll("#APP_NAME","${appName}")
                                           .replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
                                           .replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
                                           .replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
                            // 生成新的 Kubernetes 部署檔案,內容為 deployfile 變數中的文字,檔名稱為 "deploy.yaml"
                            writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
                            // 輸出新建立的部署 yaml 檔案內容
                            sh "cat deploy.yaml"
                            // 執行 Kuberctl 命令進行部署操作
                            sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
                        }
                    }
                }
            }
            stage('應用啟動檢查'){
                // 設定檢測延遲時間 10s,10s 後再開始檢測
                sleep 10
                // 健康檢查地址
                httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
                // 迴圈使用 httpRequest 請求,檢測服務是否啟動
                for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
                    try{
                        // 輸出請求資訊和請求次數
                        print "訪問服務:${appName} \n" +
                              "訪問地址:${httpRequestUrl} \n" +
                              "訪問次數:${n}"
                        // 如果非第一次檢測,就睡眠一段時間,等待再次執行 httpRequest 請求
                        if(n > 1){
                            sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
                        }
                        // 使用 HttpRequest 外掛的 httpRequest 方法檢測對應地址
                        result = httpRequest "${httpRequestUrl}"
                        // 判斷是否返回 200
                        if ("${result.status}" == "200") {
                            print "Http 請求成功,流水線結束"
                            break
                        } 
                    }catch(Exception e){
                        print "監控檢測失敗,將在 ${params.HTTP_REQUEST_INTERVAL} 秒後將再次檢測。"
                        // 判斷檢測次數是否為最後一次檢測,如果是最後一次檢測,並且還失敗了,就對整個 Jenkins 任務標記為失敗
                        if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
                            currentBuild.result = "FAILURE"
                        }
                    }
                }
            }
        }
    }
}

將該流水線程式碼,配置到之前的模板 Job 的流水線指令碼中,方便後續專案以此專案為模板。

九、建立任務從模板任務複製配置

這裡我們新建立一個測試的示例專案 Job,命名為 new-test,除了新建命名外,其它配置直接複製上面的模板 Job,然後修改配置中的預設的 Git 地址、Git 憑據、Kubernetes Namespace 等變數引數值。

1、建立新的 Job 並複製模板專案配置

 

2、修改新建 Job 的部分配置項

修改 Git 專案地址

 

修改 Git 憑據

 

修改 Kubernetes Namespace

 

一般情況下就需要修改上面這些引數,其它預設即可,不過特殊專案特殊處理,例如,健康檢查埠非 8081 就需要單獨改埠變數配置,檢查地址非 /actuator/health 就需要檢查健康檢查地址,Docker hub 憑據非預設設定就需要配置新的憑據等等,這些都需要根據專案的不同單獨修改的。

十、執行 pipeline 任務進行測試

執行上面建立的 Pipeline Job,點選 Build with Parameters 檢視配置的引數是否有誤,沒有錯誤就開始執行任務。

 

檢視整個執行的各個節點,是否哪部都能夠成功構建,如果出現錯誤,需要檢視控制檯輸出的日誌查詢錯誤點,然後對指令碼進行修改。

 

—END—