­

最全教程 | Kubernetes + Jenkins + Helm + Springboot 實踐

  • 2019 年 12 月 27 日
  • 筆記

目錄[-]

一、Kubernetes 環境安裝 Jenkins

二、Jenkins 安裝插件

1、Git 插件

2、Docker 插件

3、Kubernetes

4、Kubernetes Cli

5、Config File Provider

6、Pipeline Utility Steps

三、Jenkins 配置插件

1、Git 插件配置及使用

2、Docker 插件配置及使用

3、Kubernetes 插件配置及使用

4、Kubernetes Cli 插件配置及使用

5、Config File Provider 插件

6、Pipeline Utility Steps 插件

四、測試插件

1、創建流水線任務

2、配置流水線任務

3、查看流水線日誌

五、部署前準備

1、配置文件存放位置比較

2、設置配置文件到項目中

3、測試運行環境是否可用

六、開始寫 Pipeline 腳本

1、Git 拉取

2、Maven 編譯

3、Docker 編譯

4、Helm 啟動應用

5、測試介面

七、完善 Pipeline 腳本

1、設置超時時間

2、設置郵箱通知

3、判斷成功失敗來發送郵件

4、將腳本放入到項目中

八、完整程式碼


環境介紹

  • Jenkins: 2.172
  • Helm: 2.13.1
  • Kubernetes: 1.14.0
  • chart repo倉庫地址: http://chart.mydlq.club
  • 項目Github地址: https://github.com/my-dlq/springboot-helloworld
  • Helm的chart模板Github地址: https://github.com/my-dlq/springboot-chart

一、Kubernetes 環境安裝 Jenkins

詳情請看 Kubernetes 中安裝 Jenkins ,這裡不過多敘述。

二、Jenkins 安裝插件

為了方便集成 Maven、Kubernetes、配置文件等等,這裡需要安裝幾個別的插件,這裡插件可以在 系統管理—>插件管理—>可選插件 裡面安裝下面列出的插件。

  • ① Git 插件
  • ② Docker 插件
  • ③ Kubernetes
  • ④ Kubernetes Cli
  • ⑤ Config File Provider
  • ⑥ Pipeline Utility Steps

1、Git 插件

Jenkins 安裝中默認安裝 Git 插件,所以不需要單獨安裝。利用 git 工具可以將 github、gitlab 等等的地址下載源碼。

2、Docker 插件

Jenkins 安裝中默認安裝 Docker 插件,所以不需要單獨安裝。利用 Docker 插件可以設置 Docker 環境,運行 Docker 命令,配置遠程 Docker 倉庫憑據等。

3、Kubernetes

Kubernetes 插件的目的是能夠使用 Kubernetes 集群動態配置 Jenkins 代理(使用Kubernetes調度機制來優化負載),運行單個構建,等構建完成後刪除該代理。這裡我們需要用到這個插件來啟動 Jenkins Slave 代理鏡像,讓代理執行 Jenkins 要執行的 Job。

4、Kubernetes Cli

Kubernetes Cli 插件作用是在執行 Jenkins Job 時候提供 kubectl 與 Kubernetes 集群交互環境。可以在 Pipeline 或自由式項目中允許執行 kubectl 相關命令。它的主要作用是提供 kubectl 運行環境,當然也可以提供 helm 運行環境。

5、Config File Provider

Config File Provider 插件作用就是提供在 Jenkins 中存儲 properties、xml、json、settings.xml 等資訊,可以在執行 Pipeline 過程中可以寫入存儲的配置。

例如,存入一個 Maven 全局 Settings.xml 文件,在執行 Pipeline Job 時候引入該 Settings.xml ,這樣 Maven 編譯用的就是該全局的 Settings.xml。

6、Pipeline Utility Steps

這是一個操作文件的插件,例如讀寫 json、yaml、pom.xml、Properties 等等。在這裡主要用這個插件讀取 pom.xml 文件的參數設置,獲取變數,方便構建 Docker 鏡像。

三、Jenkins 配置插件

1、Git 插件配置及使用

(1)、配置憑據:

如果是私有項目 Git 一般需要配置一個憑據用於驗證,如果是公開項目,則無需任何配置。

憑據->系統->全局憑據->添加憑據

(2)、Pipeline 腳本中使用:

利用 Git 插件拉取源碼,分別可以設置拉取的「分支」、「顯示拉取日誌」、「拉取的憑據」、「拉取的地址」,可以將上面設置的憑據ID設置到 credentialsId 參數上

參考:https://jenkins.io/doc/pipeline/steps/git/#-git-%20git

git branch: "master" ,changelog: true , credentialsId: "xxxx-xxxx-xxxx", url: "https://github.com/xxxxxx"  

2、Docker 插件配置及使用

(1)、功能描述:

此插件將提供一下功能:

  • 記錄FROM中使用的Docker鏡像的跟蹤
  • 記錄在容器中運行的Docker鏡像的跟蹤
  • 在Docker容器中運行構建步驟
  • 設置Docker註冊表端點,用於推送鏡像驗證
  • 設置Docker伺服器端點,用於執行遠程Docker API

(2)、Pipeline 腳本中使用:

安裝 Jenkins 時候默認會安上此插件,這裡主要是利用插件提供一個 docker 登錄了的環境,以及執行一些 Docker 命令,具體請看參考,下面將寫一個簡單的執行例子來描述 Docker 鏡像的構建過程。

參考:https://jenkins.io/doc/pipeline/steps/docker-workflow/

// 此方法是設置docker倉庫地址,然後選擇存了用戶名、密碼的憑據ID進行驗證。注意,只有在此方法之中才生效。  docker.withRegistry("https://hub.docker.com/", "xxxxx-xxxx-xxxx-xxxx") {      echo "構建鏡像"      def customImage = docker.build("hub.mydlq.club/myproject/springboot-helloworld:0.0.1")      echo "推送鏡像"      customImage.push()      echo "刪除鏡像"      sh "docker rmi hub.mydlq.club/myproject/springboot-helloworld:0.0.1"  }  

3、Kubernetes 插件配置及使用

(1)、配置憑據:

配置連接 kubernetes 集群的憑據(Kubernetes ServiceAccount token),此憑據的賬戶許可權最好設置較大點,避免出現未知問題。配置完成後,需要在後面的 Cloud 雲配置中設置這個憑據。

(2)、雲配置

系統管理—>系統設置—>

參考:https://github.com/jenkinsci/kubernetes-plugin

這裡是配置連接Kubernetes集群,啟動 Jenkins Slave 代理的相關配置。

  • 名稱: kubernetes
  • Kubernetes 地址: https://kubernetes.default.svc.cluster.local (默認集群內調用 k8s api 地址)
  • 禁用 HTTPS 證書檢查: 勾選 (不驗證https)
  • 憑據: 新增憑據—>Secret text—>Secret 設置 kubernetes 的 Token (進入 k8s dashboard 的 token 等都行)
  • Jenkins地址: http://jenkins.mydlqcloud:8080/jenkins (用於代理與 Jenkins 連接的地址,用的是 k8s 集群中 jenkins 服務的地址為「http://jenkins服務名.jenkins所在namespace:jenkins埠號/jenkins後綴」)
  • 其他: 默認即可

(3)、Template 模板配置

這裡配置 Jenkins Slave 在 kubernetes 集群中啟動的 Pod 的配置,這裡將設置四個鏡像,分別是:

  • Jenkins Slave: 用於執行 Jenkins Job 命令。
  • Helm-Kuberctl: 用於執行 Helm 命令。
  • Docker 用於編譯、推送 Docker 鏡像
  • Maven: 用於Maven編譯、打包。

這裡將這四個鏡像融入到一個 Pod 之中,方便執行各種命令來完成持續部署交互過程。

Template 基本配置:

原始 Yaml 設置:

在 Pod 的原始 yaml 那欄中,填寫下面的 yaml 文件內容進行配置,將會以下面的 yaml 配置作為 Jenkins Slave Pod 的基本配置,如果上面介面上配置了某些資訊,會自動替換 yaml 中設置的值,相當於此 yaml 文件作為了一個默認(預設)配置了。

apiVersion: v1  kind: Pod  metadata:    labels:      app: jenkins-slave  spec:    serviceAccountName: jenkins-admin    securityContext:                  #容器安全設置      runAsUser: 0                    #以ROOT用戶運行容器      privileged: true                #賦予特權執行容器    containers:    - name: jnlp                      #Jenkins Slave鏡像      image: registry.cn-shanghai.aliyuncs.com/mydlq/jenkins-jnlp-slave:3.27-1      #設置工作目錄      workingDir: /home/jenkins      tty: true    - name: docker                    #Docker鏡像      image: registry.cn-shanghai.aliyuncs.com/mydlq/docker:18.06.2-dind      command: ['cat']      tty: true      volumeMounts:      - name: docker        mountPath: /usr/bin/docker      - name: docker-sock        mountPath: /var/run/docker.sock      - name: docker-config        mountPath: /etc/docker    - name: maven                     #Maven鏡像      image: registry.cn-shanghai.aliyuncs.com/mydlq/maven:3.6.0-jdk8-alpine      command:      - cat      tty: true      volumeMounts:      - name: maven-m2        mountPath: /root/.m2    - name: helm-kubectl              #Kubectl & Helm鏡像      image: registry.cn-shanghai.aliyuncs.com/mydlq/helm-kubectl:2.13.1      command:      - cat      tty: true    volumes:    - name: docker                    #將宿主機 Docker 文件夾掛進容器,方便存儲&拉取本地鏡像      hostPath:        path: /usr/bin/docker    - name: docker-sock               #將宿主機 Docker.sock 掛進容器      hostPath:        path: /var/run/docker.sock    - name: docker-config             #將宿主機 Docker 配置掛在進入容器      hostPath:        path: /etc/docker    - name: maven-m2                  #Maven 本地倉庫掛在到 NFS 共享存儲,方便不同節點能同時訪問與存儲      nfs:        server: 192.168.2.11        path: "/nfs/data/maven-m2"  #  nodeSelector:  #    kubernetes.io/hostname: node-2-12  

4、Kubernetes Cli 插件配置及使用

(1)、配置憑據:

配置連接 kubernetes 集群的憑據,這個憑據可以和上面 kubernetes 插件的憑據一致,都是用於連接 Kubernetes 集群

(2)、Pipeline 腳本中使用:

此插件主要功能就是提供執行 kubectl 的環境設置,在此插件方法中相當於有 kubectl、helm 等環境設置,然後用相關鏡像就可以執行相關命令。

參考:https://jenkins.io/doc/pipeline/steps/kubernetes-cli/

// 提供 kubectl 執行的環境,其中得設置存儲了 token 的憑據ID和 kubernetes api 地址  withKubeConfig([credentialsId: "xxxx-xxxx-xxxx-xxxx",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {      sh "kubectl get nodes"  }  

5、Config File Provider 插件

(1)、配置 Maven settings.xml

在 Jenkins 安裝時候安裝了「config File Provider」插件,這個插件的作用就是提供在 Jenkins 中存儲properties、xml、json、settings.xml 等資訊,這裡打開下面列表,配置一個全局的 Maven 的 settings.xml 文件。

系統管理—>Managed files—>Add a new Config—>Global Maven settings.xml

在裡面添加一個全局的 setting.xml 設置,為了加快 jar 包的下載速度,這裡將倉庫地址指向 aliyun Maven 倉庫地址。

<mirror>      <id>alimaven</id>      <name>aliyun maven</name>      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>      <mirrorOf>central</mirrorOf>  </mirror>  

(2)、Pipeline 腳本中使用:

參考:https://jenkins.io/doc/pipeline/steps/config-file-provider/

可以在 Pipeline 腳本中,用於生成上面設置的文件,用法如下:

// 生成 settings.xml 文件,這個方法第一個參數是引用文件ID,第二個是生成的文件名  configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]) {      // 只有在方法裡面該文件才存在      echo "cat settings.xml"  }  

6、Pipeline Utility Steps 插件

(1)、功能描述

此插件將提供一下功能:

  • 提取Zip文件
  • 創建Zip文件
  • 創建一個普通文件。
  • 生成一個Yaml文件。
  • 編寫maven項目文件。
  • 在工作區中查找文件。
  • 讀取 properties 文件參數。
  • 從工作區中的文件中讀取JSON。
  • 讀取 maven 項目的 pom.xml 文件
  • ……

(2)、Pipeline 腳本中使用:

這裡主要是用此插件讀取 pom.xml 的項目有關的參數,用於 docker 編譯鏡像時使用。另一個功能是在腳本進行時候用於生成文件,例如 yaml 文件、helm 證書等。

參考:https://jenkins.io/doc/pipeline/steps/pipeline-utility-steps/

// 讀取 pom.xml 文件  pom = readMavenPom file: "./pom.xml"  echo "${pom.artifactId}:${pom.version}"  

四、測試插件

為了保證插件配置正確且值執行,在 kubernetes 環境下啟動 Jenkins 代理執行任務,這裡將進行測試。

1、創建流水線任務

創建一個名為 「k8s-test」 的任務,類型選擇「流水線」。

2、配置流水線任務

(1)、常規配置

  • 為了安全,禁止並發構建。
  • 為了提升效率,這裡設置流水線效率,持久保存設置覆蓋。

(2)、流水線腳本

這裡寫一個簡單的腳本,將 Kubernetes 插件提供的 Pipeline 的方法引入,如下:

// 代理名稱,填寫系統設置中設置的 Cloud 中 Template 模板的 label  def label = "jnlp-agent"    // 調用Kubernetes提供的方法  podTemplate(label: label,cloud: 'kubernetes' ){      // 在代理節點上運行腳本      node (label) {          echo "測試 kubernetes 中 jenkins slave 代理!~"      }  }  

(3)、運行流水線任務

回到任務介面,點擊立即構造來執行任務。

3、查看流水線日誌

然後點擊執行歷史欄中點擊,查看控制台輸出的日誌資訊。

五、部署前準備

1、配置文件存放位置比較

以下僅是個人看法,有更好的方式,希望告知。

這裡涉及到一個問題,在 Jenkins 中,我們的 Jenkinsfile 腳本存放在哪比較方便,這裡本人想到三種:

  • 1、新建 Git 項目,專門存放不同的 jenkinsfile 腳本,Jenkins 創建任務時候指定腳本存放的 Git 地址;
  • 2、放到各個項目中,當在執行 Jenkins 任務時候讀取 Git項目,從中檢測 jenkinsfile 腳本從而執行;
  • 3、每個腳本都放置到 Jenkins 每個任務的配置中,每次都執行配置中設置的腳本;

比較三者:

  • 第1種方式方便統一管理,一改動git上的配置,jenkins 任務的流水線腳本都會跟著變化;
  • 第2種方式可以針對每個項目單獨設置,更靈活,就是不方便統一管理,維護需要各個項目組;
  • 第3種方式需要每次都新建項目時候在配置中設置腳本,比較費力不方便維護,不太推薦;

2、設置配置文件到項目中

這裡需要將將一些配置文件存入項目源碼中,用於在執行流水線中讀取對應的配置參數,比如:

  • SpringBoot源碼: 用於測試的 helloworld 的SpringBoot項目。
  • Dockerfile: 用於 Docker 編譯鏡像的文件,比如打包的基礎鏡像等等。
  • values.yaml: 用於 Helm 啟動的chart的配置文件,裡面設置了一些chart的配置資訊,告知該如何啟動應用程式。

項目 Github 地址:https://github.com/my-dlq/springboot-helloworld

(1)、Dockerfile

FROM registry.cn-shanghai.aliyuncs.com/mydlq/openjdk:8u201-jdk-alpine3.9  VOLUME /tmp  ADD target/*.jar app.jar  RUN sh -c 'touch /app.jar'  ENV JAVA_OPTS="-Xmx512M -Xms256M -Xss256k -Duser.timezone=Asia/Shanghai"  ENV APP_OPTS=""  ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]  

(2)、values.yaml

kind: Deployment  image:    pullPolicy: "Always"  replicas: 1  resources:    limits:      memory: 512Mi      cpu: 1000m    requests:      memory: 256Mi      cpu: 500m    #***java && app 環境變數設置  env:    - name: "JAVA_OPTS"      value: "-Xmx512M -Xms355M -Xss256k -Duser.timezone=Asia/Shanghai"    - name: "APP_OPTS"      value: ""    envFrom:    #- configMapRef:    #  name: env-config    service:    type: NodePort                     #Service type設置 (可以設置為ClusterIP、NodePort、None)    labels:      svcEndpoints: actuator    annotations: {}    ports:      - name: server        port: 8080        targetPort: 8080        protocol: TCP        nodePort: 30080      - name: management        port: 8081        targetPort: 8081        protocol: TCP        nodePort: 30081  

3、測試運行環境是否可用

這裡先寫一個簡單的腳本,用於測試各個環境是否都能用,例如slave鏡像的git命令能否執行、maven鏡像的mvn命令是否能用等等。

這裡可以用 container(『docker』) 方式,來引用 kubernetes 插件中設置的容器,利用各個容器不同的客戶端功能,來執行對應的命令。

將之前創建的任務配置中的 pipeline 腳本改成下面:

def label = "jnlp-agent"    podTemplate(label: label,cloud: 'kubernetes' ){      node (label) {          stage('Git階段'){              echo "1、開始拉取程式碼"              sh "git version"          }          stage('Maven階段'){              container('maven') {                  echo "2、開始Maven編譯、推送到本地庫"                  sh "mvn -version"              }          }          stage('Docker階段'){              container('docker') {                  echo "3、開始讀取Maven pom變數,並執行Docker編譯、推送、刪除"                  sh "docker version"              }          }           stage('Helm階段'){              container('helm-kubectl') {                  echo "4、開始檢測Kubectl環境,測試執行Helm部署,與執行部署"                  sh "helm version"              }          }      }  }  

jenkins slave 容器中默認集成 git 客戶端,該整體流水線執行就在 Jenkins slave 容器中,任務默認在 Jenkins Slave 執行,所以不需要設置容器名稱。

然後執行查看日誌,日誌內容如下:

Running on jnlp-agent-g7qk5 in /home/jenkins/workspace/k8s-test  [Pipeline] {  [Pipeline] stage  [Pipeline] { (Git階段)  [Pipeline] echo  1、開始拉取程式碼  [Pipeline] sh  + git version  git version 2.11.0  [Pipeline] }  [Pipeline] // stage  [Pipeline] stage  [Pipeline] { (Maven階段)  [Pipeline] container  [Pipeline] {  [Pipeline] echo  2、開始Maven編譯、推送到本地庫  [Pipeline] sh  + mvn -version  Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-24T18:41:47Z)  Maven home: /usr/share/maven  Java version: 1.8.0_201, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-1.8-openjdk/jre  Default locale: en_US, platform encoding: UTF-8  OS name: "linux", version: "3.10.0-957.1.3.el7.x86_64", arch: "amd64", family: "unix"  [Pipeline] }  [Pipeline] // container  [Pipeline] }  [Pipeline] // stage  [Pipeline] stage  [Pipeline] { (Docker階段)  [Pipeline] container  [Pipeline] {  [Pipeline] echo  3、開始讀取Maven pom變數,並執行Docker編譯、推送、刪除  [Pipeline] sh  + docker version  Client:   Version:           18.06.2-ce   API version:       1.38   Go version:        go1.10.4   Git commit:        6d37f41   Built:             Sun Feb 10 03:43:40 2019   OS/Arch:           linux/amd64   Experimental:      false  Server: Docker Engine - Community   Engine:    Version:          18.09.3    API version:      1.39 (minimum version 1.12)    Go version:       go1.10.8    Git commit:       774a1f4    Built:            Thu Feb 28 06:02:24 2019    OS/Arch:          linux/amd64    Experimental:     false  [Pipeline] }  [Pipeline] // container  [Pipeline] }  [Pipeline] // stage  [Pipeline] stage  [Pipeline] { (Helm階段)  [Pipeline] container  [Pipeline] {  [Pipeline] echo  4、開始檢測Kubectl環境,測試執行Helm部署,與執行部署  [Pipeline] sh  + helm version  Client: &version.Version{SemVer:"v2.13.1", GitCommit:"618447cbf203d147601b4b9bd7f8c37a5d39fbb4", GitTreeState:"clean"}  Server: &version.Version{SemVer:"v2.13.1", GitCommit:"79d07943b03aea2b76c12644b4b54733bc5958d6", GitTreeState:"clean"}  [Pipeline] // podTemplate  [Pipeline] End of Pipeline  Finished: SUCCESS  

最後看見執行狀態為 SUCCESS 則證明環境可用,否則有問題,請檢測問題所在。

六、開始寫 Pipeline 腳本

這裡進行分階段性的腳本編寫,然後一步步測試,最後合併在一起。這裡新建一個名稱為 k8s-pipeline 的任務,然後在配置項腳本框匯總輸入 Pipleline 腳本。

1、Git 拉取

這裡拉取本人 Github 上的一個簡單的 SpringBoot Demo 項目進行實踐。

Groovy腳本

def label = "jnlp-agent"    podTemplate(label: label,cloud: 'kubernetes' ){      node (label) {          stage('Git階段'){              echo "Git 階段"              git branch: "master" ,changelog: true , url: "https://github.com/my-dlq/springboot-helloworld.git"          }      }  }  

查看執行日誌

......  Running on jnlp-agent-dhr1h in /home/jenkins/workspace/k8s-pipeline  [Pipeline] {  [Pipeline] stage  [Pipeline] { (Git階段)  [Pipeline] echo  1、開始拉取程式碼  [Pipeline] sh  + git clone https://github.com/my-dlq/springboot-helloworld.git  Cloning into 'springboot-helloworld'...  [Pipeline] sh  + ls -l  total 0  drwxr-xr-x 4 root root 79 Apr 28 07:00 springboot-helloworld  [Pipeline] }  ......  Finished: SUCCESS  

可以通過控制台輸出的日誌看到,已經拉取成功。繼續進行下一步,Maven 階段。

2、Maven 編譯

這裡將進行 Maven 編譯,將 Java 源碼編譯成一個 Jar 項目,方便後續打包進入 Docker 鏡像。( Maven 中也可以進行單元測試,由於某些原因,這裡不進行闡述,可以自己執行測試命令進行測試 )

Groovy腳本

def label = "jnlp-agent"    podTemplate(label: label,cloud: 'kubernetes' ){      node (label) {          stage('Git階段'){              echo "Git 階段"              git branch: "master" ,changelog: true , url: "https://github.com/my-dlq/springboot-helloworld.git"          }          stage('Maven階段'){              container('maven') {                  //這裡引用上面設置的全局的 settings.xml 文件,根據其ID將其引入並創建該文件                  configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]){                      sh "mvn clean install -Dmaven.test.skip=true --settings settings.xml"                  }              }          }      }  }  

查看執行日誌

......  [INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ springboot-helloword ---  [INFO]  [INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ springboot-helloword ---  [INFO] Using 'UTF-8' encoding to copy filtered resources.  [INFO] Copying 1 resource  [INFO] Copying 0 resource  [INFO]  [INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ springboot-helloword ---  [INFO] Changes detected - recompiling the module!  [INFO] Compiling 2 source files to /home/jenkins/workspace/k8s-pipeline/target/classes  [INFO]  [INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ springboot-helloword ---  [INFO] Not copying test resources  [INFO]  [INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ springboot-helloword ---  [INFO] Not compiling test sources  [INFO]  [INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ springboot-helloword ---  [INFO] Tests are skipped.  [INFO]  [INFO] --- maven-jar-plugin:3.1.1:jar (default-jar) @ springboot-helloword ---  [INFO] Building jar: /home/jenkins/workspace/k8s-pipeline/target/springboot-helloword-0.0.1.jar  [INFO]  [INFO] --- spring-boot-maven-plugin:2.1.4.RELEASE:repackage (repackage) @ springboot-helloword ---  [INFO] Replacing main artifact with repackaged archive  [INFO]  [INFO] --- maven-install-plugin:2.5.2:install (default-install) @ springboot-helloword ---  [INFO] Installing /home/jenkins/workspace/k8s-pipeline/target/springboot-helloword-0.0.1.jar to /root/.m2/repository/club/mydlq/springboot-helloword/0.0.1/springboot-helloword-0.0.1.jar  [INFO] Installing /home/jenkins/workspace/k8s-pipeline/pom.xml to /root/.m2/repository/club/mydlq/springboot-helloword/0.0.1/springboot-helloword-0.0.1.pom  [INFO] ------------------------------------------------------------------------  [INFO] BUILD SUCCESS  [INFO] ------------------------------------------------------------------------  [INFO] Total time:  7.989 s  [INFO] Finished at: 2019-04-28T09:38:03Z  [INFO] ------------------------------------------------------------------------  ......  [Pipeline] // podTemplate  [Pipeline] End of Pipeline  Finished: SUCCESS  

3、Docker 編譯

Groovy腳本

def label = "jnlp-agent"    podTemplate(label: label,cloud: 'kubernetes' ){      node (label) {          stage('Git階段'){              echo "Git 階段"              git branch: "master" ,changelog: true , url: "https://github.com/my-dlq/springboot-helloworld.git"          }          stage('Maven階段'){              echo "Maven 階段"              container('maven') {                  //這裡引用上面設置的全局的 settings.xml 文件,根據其ID將其引入並創建該文件                  configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]){                      sh "mvn clean install -Dmaven.test.skip=true --settings settings.xml"                  }              }          }          stage('Docker階段'){              echo "Docker 階段"              container('docker') {                  // 讀取pom參數                  echo "讀取 pom.xml 參數"                  pom = readMavenPom file: './pom.xml'                  // 設置鏡像倉庫地址                  hub = "registry.cn-shanghai.aliyuncs.com"                  // 設置倉庫項目名                  project_name = "mydlq"                  echo "編譯 Docker 鏡像"                  docker.withRegistry("http://${hub}", "ffb3b544-108e-4851-b747-b8a00bfe7ee0") {                      echo "構建鏡像"                      // 設置推送到aliyun倉庫的mydlq項目下,並用pom裡面設置的項目名與版本號打標籤                      def customImage = docker.build("${hub}/${project_name}/${pom.artifactId}:${pom.version}")                      echo "推送鏡像"                      customImage.push()                      echo "刪除鏡像"                      sh "docker rmi ${hub}/${project_name}/${pom.artifactId}:${pom.version}"                  }              }          }      }  }  

查看執行日誌

編譯 Docker 鏡像  [Pipeline] withEnv  [Pipeline] {  [Pipeline] withDockerRegistry  Executing shell script inside container [docker] of pod [jnlp-agent-v6f1f]  Executing command: "docker" "login" "-u" "3******7@qq.com" "-p" ******** "http://registry.cn-shanghai.aliyuncs.com"  /home/jenkins/workspace/k8s-pipeline2@tmp/b52e213b-a730-4120-b004-decd8e16b246/config.json.  Configure a credential helper to remove this warning. See  https://docs.docker.com/engine/reference/commandline/login/#credentials-store    Login Succeeded  [Pipeline] {  [Pipeline] echo  構建鏡像  [Pipeline] sh  + docker build -t registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1 .  Sending build context to Docker daemon  18.37MB    Step 1/7 : FROM registry.cn-shanghai.aliyuncs.com/mydlq/openjdk:8u201-jdk-alpine3.9   ---> 3675b9f543c5  Step 2/7 : VOLUME /tmp   ---> Running in 7fc4af80e6ce  Removing intermediate container 7fc4af80e6ce   ---> 4e4224d3b50b  Step 3/7 : ADD target/*.jar app.jar   ---> 0c24118d522f  Step 4/7 : RUN sh -c 'touch /app.jar'   ---> Running in 8836cb91e1ca  Removing intermediate container 8836cb91e1ca   ---> 389e604851b6  Step 5/7 : ENV JAVA_OPTS="-Xmx512M -Xms256M -Xss256k -Duser.timezone=Asia/Shanghai"   ---> Running in 5126902b1e6b  Removing intermediate container 5126902b1e6b   ---> 055ad2b9c49d  Step 6/7 : ENV APP_OPTS=""   ---> Running in cf8ea4b61eea  Removing intermediate container cf8ea4b61eea   ---> 07dd4fdda44a  Step 7/7 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]   ---> Running in 93c4d0d859e1  Removing intermediate container 93c4d0d859e1   ---> d29e092f2c17  Successfully built d29e092f2c17  Successfully tagged registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1  [Pipeline] dockerFingerprintFrom  [Pipeline] echo  推送鏡像  [Pipeline] sh  + docker tag registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1 registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1  [Pipeline] sh  + docker push registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1  The push refers to repository [registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword]  8d45ad1172aa: Preparing  aba126d6a94c: Preparing  a464c54f93a9: Mounted from mydlq/openjdk  dee6aef5c2b6: Mounted from mydlq/openjdk  aba126d6a94c: Pushed  8d45ad1172aa: Pushed  0.0.1: digest: sha256:2c661931a3c08a1cad1562ec4936c68f06b4b3ffcec5de14c390ae793cf5b53b size: 1371  [Pipeline] echo  刪除鏡像  [Pipeline] sh  + docker rmi registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1  Untagged: registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword:0.0.1  Untagged: registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloword@sha256:2c661931a3c08a1cad1562ec4936c68f06b4b3ffcec5de14c390ae793cf5b53b  Deleted: sha256:d29e092f2c175a3662af0175e62462238583330ba7d46b84d89134056ba14027  Deleted: sha256:07dd4fdda44a12d7749e5e7f60b1943c83e6d0a3da2e4f279ce4d53f3b04f27e  ......  Finished: SUCCESS  

4、Helm 啟動應用

創建Helm執行方法

這裡提前創建好執行 helm 的方法,將其簡單封裝一下用於執行流水線時候,調用此方法,執行對應的 Helm 操作。

  • 方法名:helmDeploy()
  • 可配參數

參數

描述

– init:

是否為執行 helm 初始化

– url:

初始化 chart 倉庫地址

– dry:

是否為嘗試部署

– name:

部署的應用 Release 名

– namespace:

應用啟動到哪個Namespace

– image:

鏡像名

– tag:

鏡像標籤

– template:

選用的chart模板

// 執行Helm的方法  def helmDeploy(Map args) {      // Helm 初始化      if(args.init){          sh "helm init --client-only --stable-repo-url ${args.url}"      }      // Helm 嘗試部署      else if (args.dry_run) {          println "嘗試 Helm 部署,驗證是否能正常部署"          sh "helm upgrade --install ${args.name} --namespace ${args.namespace} -f values.yaml --set ${args.repository},${args.tag} stable/${args.template} --dry-run --debug"      }      // Helm 正式部署      else {          println "正式 Helm 部署"          sh "helm upgrade --install ${args.name} --namespace ${args.namespace} -f values.yaml --set ${args.repository},${args.tag} stable/${args.template}"      }  }    // 方法調用  stage() {      echo "Helm 初始化 http://chart.mydlq.club"      helmDeploy(init: true ,url: "Helm 倉庫地址");      echo "Helm 嘗試執行部署"      helmDeploy(init: false ,dry: true ,name: "應用名" ,namespace: "應用啟動的Namespace" ,image: "鏡像名",tag: "鏡像標籤" ,template: "選用的chart模板")      echo "Helm 正式執行部署"      helmDeploy(init: false ,dry: false ,name: "應用名" ,namespace: "應用啟動的Namespace" ,image: "鏡像名",tag: "鏡像標籤" ,template: "選用的chart模板")  }  

完整Groovy腳本

def label = "jnlp-agent"    // 執行Helm的方法  def helmDeploy(Map args) {      if(args.init){          println "Helm 初始化"          sh "helm init --client-only --stable-repo-url ${args.url}"      } else if (args.dry_run) {          println "嘗試 Helm 部署,驗證是否能正常部署"          sh "helm upgrade --install ${args.name} --namespace ${args.namespace} ${args.values} --set ${args.image},${args.tag} stable/${args.template} --dry-run --debug"      } else {          println "正式 Helm 部署"          sh "helm upgrade --install ${args.name} --namespace ${args.namespace} ${args.values} --set ${args.image},${args.tag} stable/${args.template}"      }  }    // jenkins slave 執行流水線任務  podTemplate(label: label,cloud: 'kubernetes' ){      node (label) {          stage('Git階段'){              echo "Git 階段"              git branch: "master" ,changelog: true , url: "https://github.com/my-dlq/springboot-helloworld.git"          }          stage('Maven階段'){              echo "Maven 階段"              container('maven') {                  //這裡引用上面設置的全局的 settings.xml 文件,根據其ID將其引入並創建該文件                  configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]){                      sh "mvn clean install -Dmaven.test.skip=true --settings settings.xml"                  }              }          }          stage('Docker階段'){              echo "Docker 階段"              container('docker') {                  // 讀取pom參數                  echo "讀取 pom.xml 參數"                  pom = readMavenPom file: './pom.xml'                  // 設置鏡像倉庫地址                  hub = "registry.cn-shanghai.aliyuncs.com"                  // 設置倉庫項目名                  project_name = "mydlq"                  echo "編譯 Docker 鏡像"                  docker.withRegistry("http://${hub}", "ffb3b544-108e-4851-b747-b8a00bfe7ee0") {                      echo "構建鏡像"                      // 設置推送到aliyun倉庫的mydlq項目下,並用pom裡面設置的項目名與版本號打標籤                      def customImage = docker.build("${hub}/${project_name}/${pom.artifactId}:${pom.version}")                      echo "推送鏡像"                      customImage.push()                      echo "刪除鏡像"                      sh "docker rmi ${hub}/${project_name}/${pom.artifactId}:${pom.version}"                  }              }          }          stage('Helm階段'){              container('helm-kubectl') {                  withKubeConfig([credentialsId: "8510eda6-e1c7-4535-81af-17626b9575f7",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {                      // 設置參數                      image = "image.repository=${hub}/${project_name}/${pom.artifactId}"                      tag = "image.tag=${pom.version}"                      template = "spring-boot"                      repo_url = "http://chart.mydlq.club"                      app_name = "${pom.artifactId}"                      // 檢測是否存在yaml文件                      def values = ""                      if (fileExists('values.yaml')) {                          values = "-f values.yaml"                      }                      // 執行 Helm 方法                      echo "Helm 初始化"                      helmDeploy(init: true ,url: "${repo_url}");                      echo "Helm 執行部署測試"                      helmDeploy(init: false ,dry_run: true ,name: "${app_name}" ,namespace: "mydlqcloud" ,image: "${image}" ,tag: "${tag}" , values: "${values}" ,template: "${template}")                      echo "Helm 執行正式部署"                      helmDeploy(init: false ,dry_run: false ,name: "${app_name}" ,namespace: "mydlqcloud",image: "${image}" ,tag: "${tag}" , values: "${values}" ,template: "${template}")                  }              }          }      }  }  

查看執行日誌

.....  Executing shell script inside container [helm-kubectl] of pod [jnlp-agent-r3c8h]  Executing command: "kubectl" "config" "set-cluster" "k8s" "--server=https://kubernetes.default.svc.cluster.local" "--insecure-skip-tls-verify=true"  exit  Cluster "k8s" set.  Executing shell script inside container [helm-kubectl] of pod [jnlp-agent-r3c8h]  Executing command: "kubectl" "config" "set-credentials" "cluster-admin" ********  Switched to context "k8s".  [Pipeline] {  [Pipeline] fileExists  [Pipeline] echo  Helm 初始化  [Pipeline] echo  Helm 初始化  [Pipeline] sh  + helm init --client-only --stable-repo-url http://chart.mydlq.club  Creating /root/.helm/repository/repositories.yaml  Adding stable repo with URL: http://chart.mydlq.club  Adding local repo with URL: http://127.0.0.1:8879/charts  $HELM_HOME has been configured at /root/.helm.  Not installing Tiller due to 'client-only' flag having been set  Happy Helming!  [Pipeline] echo  Helm 執行部署測試  [Pipeline] echo  嘗試 Helm 部署,驗證是否能正常部署  [Pipeline] sh  + helm upgrade --install springboot-helloworld --namespace mydlqcloud -f values.yaml --set 'image.repository=registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloworld,image.tag=0.0.1' stable/spring-boot --dry-run --debug  [debug] Created tunnel using local port: '37001'    [debug] SERVER: "127.0.0.1:37001"    [debug] Fetched stable/spring-boot to /root/.helm/cache/archive/spring-boot-1.0.4.tgz    Release "springboot-helloworld" does not exist. Installing it now.  [debug] CHART PATH: /root/.helm/cache/archive/spring-boot-1.0.4.tgz  [Pipeline] echo  Helm 執行正式部署  [Pipeline] echo  正式 Helm 部署  [Pipeline] sh  + helm upgrade --install springboot-helloworld --namespace mydlqcloud -f values.yaml --set 'image.repository=registry.cn-shanghai.aliyuncs.com/mydlq/springboot-helloworld,image.tag=0.0.1' stable/spring-boot  Release "springboot-helloworld" does not exist. Installing it now.  NAME:   springboot-helloworld  LAST DEPLOYED: Mon Apr 29 07:31:39 2019  NAMESPACE: mydlqcloud  STATUS: DEPLOYED    RESOURCES:  ==> v1/Pod(related)  NAME                                    READY  STATUS             RESTARTS  AGE  springboot-helloworld-7cd66cf74d-vfjr6  0/1    ContainerCreating  0         0s    ==> v1/Service  NAME                   TYPE      CLUSTER-IP   EXTERNAL-IP  PORT(S)                        AGE  springboot-helloworld  NodePort  10.10.87.61  <none>       8080:30080/TCP,8081:30081/TCP  0s    ==> v1beta1/Deployment  NAME                   READY  UP-TO-DATE  AVAILABLE  AGE  springboot-helloworld  0/1    1           0          0s  ......  Finished: SUCCESS  

5、測試介面

上面的 Helm步驟執行完成後,就可以進行簡單測試了,其中此項目引用的chart是一個簡單的 SpringBoot 項目,其中用 NodePort 方式暴露了兩個埠,30080 & 30081,分別對應8080、8081倆個埠,切提供了一個 Hello World 介面為「/hello」,所以我們這裡訪問一下這個介面地址:

http://192.168.2.11:30080/hello

七、完善 Pipeline 腳本

1、設置超時時間

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

格式:

timeout(time: 20, unit: 'SECONDS') {      // 流水線程式碼  }  

例子:

設置超時時間為 60s 來讓 Jenkins Slave 節點執行任務。

def label = "jnlp-agent"  timeout(time: 60, unit: 'SECONDS') {      podTemplate(label: label,cloud: 'kubernetes' ){          node (label) {              stage('Git階段'){                  echo "Git 階段"              }              stage('Maven階段'){                  echo "Maven 階段"              }              stage('Docker階段'){                  echo "Docker 階段"              }              stage('Helm階段'){                  echo "Helm 階段"              }          }      }  }  

2、設置郵箱通知

(1)、設置郵箱開啟 POP3/SMTP/IMAP 設置

這裡用的是 163 郵箱

(2)、安裝 Email Extension Template 插件

這裡安裝插件「Email Extension Template」用於設置郵件模板。

(3)、配置系統默認郵件參數

系統管理->系統設置:

配置「Jenkins Location」和「Extended E-mail Notification」,其中系統管理員郵件地址一定要和「User Name」值一致。

Jenkins Location 設置:

參數名稱

描述

– Jenkins URL:

Jenkins 地址,用於發送郵件時寫入內容之中

– 系統管理員郵件地址:

郵件伺服器賬戶


Extended E-mail Notification 設置:

參數名稱

描述

– SMTP server:

smtp 郵箱服務的地址

– Default user E-mail suffix:

郵件伺服器後綴

– User Name:

郵件伺服器賬戶

– Password:

郵件伺服器 SMTP 授權碼

– Default Content Type:

設置郵件文本格式

– Enable Debug Mode:

啟用 Debug 模式

(4)、創建流水線項目

創建一個流水線項目,用於寫 Pipeline 腳本測試郵件發送,並配置 Pipeline 腳本,這裡寫一個簡單的 Pipeline 腳本,調用 emailext 方法執行發送郵件。

腳本內容:

def label = "jnlp-agent"  podTemplate(label: label,cloud: 'kubernetes' ){      node (label) {          stage('Git階段'){              echo "Git 階段"          }          stage('Maven階段'){              echo "Maven 階段"          }          stage('Docker階段'){              echo "Docker 階段"          }          stage('Helm階段'){              echo "Helm 階段"          }          stage('email'){              echo "測試發送郵件"              emailext(subject: '任務執行失敗',to: '324******47@qq.com',body: '''測試郵件內容...''')          }      }  }  

(5)、運行項目查看日誌

查看日誌,看是否執行發送操作以及運行狀況。

Started by user admin  Running in Durability level: PERFORMANCE_OPTIMIZED  [Pipeline] Start of Pipeline  [Pipeline] node  Running on Jenkins in /var/jenkins_home/workspace/email-test  [Pipeline] {  [Pipeline] stage  [Pipeline] { (email)  [Pipeline] echo  測試發送郵件  [Pipeline] emailext  Sending email to: 32*****47@qq.com  [Pipeline] }  [Pipeline] // stage  [Pipeline] }  [Pipeline] // node  [Pipeline] End of Pipeline  Finished: SUCCESS  

(6)、查看郵件

查看郵件,可以看到已經收到設置郵箱發來的郵件資訊。

3、判斷成功失敗來發送郵件

(1)、流水線過程判斷成功失敗

這裡加 try、catch、finally 進行流水線,黨執行 finally 時候,進行判斷此任務執行到此是否成功構建,如果成功,則發送成功郵件通知。如果失敗,則發送失敗郵件通知。

try{      def label = "jnlp-agent"      podTemplate(label: label,cloud: 'kubernetes' ){          node (label) {              stage('Git階段'){                  echo "Git 階段"              }              stage('Maven階段'){                  echo "Maven 階段"              }              stage('Docker階段'){                  echo "Docker 階段"              }              stage('Helm階段'){                  echo "Helm 階段"              }          }      }  }catch(Exception e) {      currentBuild.result = "FAILURE"  }finally {      // 獲取執行狀態      def currResult = currentBuild.result ?: 'SUCCESS'      // 判斷執行任務狀態,根據不同狀態發送郵件      stage('email'){          if (currResult == 'SUCCESS') {              echo "發送成功郵件"              emailext(subject: '任務執行成功',to: '32*****7@qq.com',body: '''任務已經成功構建完成...''')          }else {              echo "發送失敗郵件"              emailext(subject: '任務執行失敗',to: '32*****7@qq.com',body: '''任務執行失敗構建失敗...''')          }      }  }  

(2)、測試成功失敗執行時發送郵件

成功:

讓其正常成功跑完流程後發送郵件。

失敗:

模擬故意執行錯誤發送郵件。

4、將腳本放入到項目中

將腳本放入項目之中,方便後續調用時直接設置項目所在的Git地址即可。

八、完整程式碼

完整程式碼如下:

// 執行Helm的方法  def helmDeploy(Map args) {      if(args.init){          println "Helm 初始化"          sh "helm init --client-only --stable-repo-url ${args.url}"      } else if (args.dry_run) {          println "嘗試 Helm 部署,驗證是否能正常部署"          sh "helm upgrade --install ${args.name} --namespace ${args.namespace} ${args.values} --set ${args.image},${args.tag} stable/${args.template} --dry-run --debug"      } else {          println "正式 Helm 部署"          sh "helm upgrade --install ${args.name} --namespace ${args.namespace} ${args.values} --set ${args.image},${args.tag} stable/${args.template}"      }  }    // jenkins slave 執行流水線任務  timeout(time: 600, unit: 'SECONDS') {      try{          def label = "jnlp-agent"          podTemplate(label: label,cloud: 'kubernetes' ){              node (label) {                  stage('Git階段'){                      echo "Git 階段"                      git branch: "master" ,changelog: true , url: "https://github.com/my-dlq/springboot-helloworld.git"                  }                  stage('Maven階段'){                      echo "Maven 階段"                      container('maven') {                          //這裡引用上面設置的全局的 settings.xml 文件,根據其ID將其引入並創建該文件                          configFileProvider([configFile(fileId: "75884c5a-4ec2-4dc0-8d87-58b6b1636f8a", targetLocation: "settings.xml")]){                              sh "mvn clean install -Dmaven.test.skip=true --settings settings.xml"                          }                      }                  }                  stage('Docker階段'){                      echo "Docker 階段"                      container('docker') {                          // 讀取pom參數                          echo "讀取 pom.xml 參數"                          pom = readMavenPom file: './pom.xml'                          // 設置鏡像倉庫地址                          hub = "registry.cn-shanghai.aliyuncs.com"                          // 設置倉庫項目名                          project_name = "mydlq"                          echo "編譯 Docker 鏡像"                          docker.withRegistry("http://${hub}", "ffb3b544-108e-4851-b747-b8a00bfe7ee0") {                              echo "構建鏡像"                              // 設置推送到aliyun倉庫的mydlq項目下,並用pom裡面設置的項目名與版本號打標籤                              def customImage = docker.build("${hub}/${project_name}/${pom.artifactId}:${pom.version}")                              echo "推送鏡像"                              customImage.push()                              echo "刪除鏡像"                              sh "docker rmi ${hub}/${project_name}/${pom.artifactId}:${pom.version}"                          }                      }                  }                  stage('Helm階段'){                      container('helm-kubectl') {                          withKubeConfig([credentialsId: "8510eda6-e1c7-4535-81af-17626b9575f7",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {                              // 設置參數                              image = "image.repository=${hub}/${project_name}/${pom.artifactId}"                              tag = "image.tag=${pom.version}"                              template = "spring-boot"                              repo_url = "http://chart.mydlq.club"                              app_name = "${pom.artifactId}"                              // 檢測是否存在yaml文件                              def values = ""                              if (fileExists('values.yaml')) {                                  values = "-f values.yaml"                              }                              // 執行 Helm 方法                              echo "Helm 初始化"                              helmDeploy(init: true ,url: "${repo_url}");                              echo "Helm 執行部署測試"                              helmDeploy(init: false ,dry_run: true ,name: "${app_name}" ,namespace: "mydlqcloud" ,image: "${image}" ,tag: "${tag}" , values: "${values}" ,template: "${template}")                              echo "Helm 執行正式部署"                              helmDeploy(init: false ,dry_run: false ,name: "${app_name}" ,namespace: "mydlqcloud",image: "${image}" ,tag: "${tag}" , values: "${values}" ,template: "${template}")                          }                      }                  }              }          }      }catch(Exception e) {          currentBuild.result = "FAILURE"      }finally {          // 獲取執行狀態          def currResult = currentBuild.result ?: 'SUCCESS'          // 判斷執行任務狀態,根據不同狀態發送郵件          stage('email'){              if (currResult == 'SUCCESS') {                  echo "發送成功郵件"                  emailext(subject: '任務執行成功',to: '32******7@qq.com',body: '''任務已經成功構建完成...''')              }else {                  echo "發送失敗郵件"                  emailext(subject: '任務執行失敗',to: '32******7@qq.com',body: '''任務執行失敗構建失敗...''')              }          }      }  }