Jenkins CI自動發佈SprintBoot項目到Kubernetes集羣

一. 環境準備

本文示例如何使用 Jenkins 自動編譯打包, 構建 Docker 鏡像, 提交到本地 Docker Registry 鏡像服務, 並經過 Helm 發佈到 K8S 集羣.java

總體的流程圖以下:
整理流程node

本文涉及到的環境:linux

  • 服務端 ttg12:git

    • OS: Ubuntu Server 18.04
    • IP: 192.168.31.12
    • 應用:web

      • Docker v19.03.6
      • Docker Registry v2.6.1
      • K8S kubectl v1.18.5.
      • Jenkins Docker v2.235.1-lts
  • 本地工做電腦spring

    • OS: MacOS Catalina 10.15.5
    • IP: 192.168.31.22
    • 應用:docker

      • K8S kubectl 工具, 鏈接到 k8s master

二. 安裝和配置相關應用

2.1 Docker 準備

ttg12 上安裝好 docker, 並配置好國內鏡像.數據庫

ttg12 上經過 docker 安裝本地 Docker Registry (v2.6.1). 參考私有化部署極簡 Docker Registry, 並將自簽名證書複製到 /etc/docker/certs.d/<registry_domain>:<registry_port>/ca.crt.segmentfault

2.2 K8S 準備

準備一個 K8S 集羣或者單節點 K8S, 在 ttg12 上安裝好 kubectl 工具, 並配置好 /etc/.kube/config.tomcat

ttg12 上安裝好 helm 3 客戶端. Linux 下 Helm 3 的安裝很簡單, 直接下載二進制文件下來, 解壓, 移動到/usr/local/bin/目錄下便可.

# 這裏以 helm v3.3.0 爲例
curl https://get.helm.sh/helm-v3.3.0-rc.1-linux-amd64.tar.gz > /tmp/helm-v3.3.0-rc.1-linux-amd64.tar.gz
tar zxf /tmp/helm-v3.3.0-rc.1-linux-amd64.tar.gz -C /tmp/helm-v3.3.0-rc.1-linux-amd64/helm `/usr/local/bin/helm`
sudo cp /tmp/helm-v3.3.0-rc.1-linux-amd64/helm /usr/local/bin/helm
sudo rm -rf /tmp/helm-v3.3.0-rc.1-linux-amd64.tar.gz /tmp/helm-v3.3.0-rc.1-linux-amd64/helm

2.3 安裝 Jenkins

本文使用 Docker 方式安裝 Jenkins. 具體步驟參考本文另一篇博文 Docker安裝Jenkins並支持Maven,Docker,Helm.

啓動容器後, 登陸 Jenkins, 安裝插件 Docker plugin and Docker Pipeline. 不然會報異常:

Obtained Jenkinsfile from git [https://gitee.com/facelessdemos/SpringBootDemo.git](https://gitee.com/facelessdemos/SpringBootDemo.git)
Running in Durability level: MAX\_SURVIVABILITY
org.codehaus.groovy.control.MultipleCompilationErrorsExcept.ion: startup failed:
WorkflowScript: 34: Invalid agent type "docker" specified. Must be one of \[any, label, none\] @ line 34, column 17.
                   docker {
                   ^

參考 Gitee 官方文檔, 安裝 Gitee 插件.

三. 代碼準備

在項目根目錄下新建 Dockerfile, Jenkinsfile 文件 以及 helm 目錄.
image.png

3.1 Dockerfile

FROM frolvlad/alpine-java:jdk8-slim
#在build鏡像時能夠經過 --build-args profile=xxx 進行修改
ARG profile
ENV SPRING_PROFILES_ACTIVE=${profile}
#項目的端口
EXPOSE 8080
WORKDIR /mnt

#修改時區
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
    && apk add --no-cache tzdata \
    && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone \
    && apk del tzdata \
    && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* $HOME/.cache

COPY ./web/target/web-1.0-SNAPSHOT.jar ./app.jar
ENTRYPOINT ["java", "-jar", "/mnt/app.jar"]

3.2 Jenkinsfile

image_tag = "1.0.0-snapshot"  //定一個全局變量,存儲Docker鏡像的tag(版本)
pipeline {
    agent any
    environment {
        //GIT_REPO = "${env.gitlabSourceRepoName}"  //從Jenkins Gitlab插件中獲取Git項目的名稱
        GIT_REPO = "springbootdemo"
        GIT_BRANCH = "${env.gitlabTargetBranch}"  //項目的分支
        GIT_TAG = sh(returnStdout: true,script: 'git describe --tags --always').trim()  //commit id或tag名稱
        KUBE_CONFIG_LOCAL = credentials('local-k8s-kube-config')  //開發測試環境的kube憑證
        KUBE_CONFIG_PROD = "" //credentials('prod-k8s-kube-config') //生產環境的kube憑證

        DOCKER_REGISTRY = "registry.faceless.com:5443" //Docker倉庫地址
        DOCKER_NAMESPACE = "facelessdemo"  //命名空間
        DOCKER_IMAGE = "${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${GIT_REPO}" //Docker鏡像地址

        INGRESS_HOST_DEV = "dev.springbootdemo.cn"    //開發環境的域名
        INGRESS_HOST_TEST = "test.springbootdemo.cn"  //測試環境的域名
        INGRESS_HOST_PROD = "prod.springbootdemo.cn"  //生產環境的域名

        K8S_NAMESPACE = "demo-dev"
    }
    parameters {
        string(name: 'ingress_path', defaultValue: '/', description: '服務上下文路徑')
        string(name: 'replica_count', defaultValue: '2', description: '容器副本數量')
    }

    stages {
        stage('Code Analyze') {
            agent any
            steps {
               echo "1. 代碼靜態檢查"
            }
        }
        stage('Maven Build') {
            agent any
            steps {
                echo "2. 代碼編譯打包"
                sh 'mvn clean package -Dfile.encoding=UTF-8 -Dmaven.test.skip=true'
            }
        }
        stage('Docker Build') {
            agent any
            steps {
                echo "3. 構建Docker鏡像"
                echo "鏡像地址: ${DOCKER_IMAGE}"
                script {
                    def profile = "dev"
                    image_tag = "dev." + env.GIT_TAG
                    if (env.gitlabTargetBranch == "develop") {
                        image_tag = "dev." + env.GIT_TAG
                        profile = "dev"
                    } else if (env.gitlabTargetBranch == "pre-release") {
                        image_tag = "test." + env.GIT_TAG
                        profile = "test"
                    } else if (env.gitlabTargetBranch == "master"){
                        // master分支則直接使用Tag
                        image_tag = env.GIT_TAG
                        profile = "prod"
                    }
                    //經過--build-arg將profile進行設置,以區分不一樣環境進行鏡像構建
                    sh "docker build  --build-arg profile=${profile} -t ${DOCKER_IMAGE}:${image_tag} ."
                    sh "docker push ${DOCKER_IMAGE}:${image_tag}"
                    sh "docker rmi ${DOCKER_IMAGE}:${image_tag}"
                }
            }
        }
        stage('Helm Deploy') {
            agent any
            steps {
                echo "4. 部署到K8s"
                sh "helm upgrade -i --namespace=${K8S_NAMESPACE} --set image.repository=${DOCKER_IMAGE} --set image.tag=${image_tag} ${GIT_REPO} ./helm/"
            }
        }
    }
}

3.3 Helm配置

經過helm 客戶端建立對應版本的 chart 配置文件清單:

helm create springbootdemo

獲得相應的文件清單以下:
image.png

1) 咱們須要修改其中的 values.yaml 文件. 注意如下帶註釋的部分.

# 啓動容器副本數
replicaCount: 2
image:
  # 項目打包出來的 docker image 提交在本地鏡像倉庫中
  repository: registry.faceless.com:5443/facelessdemo/springbootdemo
  pullPolicy: IfNotPresent
  # 默認image標籤. 需在Jenkinsfile中的 `sh helm upgrade` 命令中覆蓋.
  tag: "dev.latest"

imagePullSecrets: []
# 項目名
nameOverride: "springbootdemo"
fullnameOverride: ""

# 容器的端口暴露及環境變量配置
container:
  port: 8080
  env: []

serviceAccount:
  create: true
  annotations: {}
  name: ""

podAnnotations: {}
podSecurityContextext: {}
securityContext: {}

# 本示例經過Ingress提供對外服務, 因此Service使用ClusterIP模式便可
service:
  type: ClusterIP
  port: 8080

# Ingress配置域名和路徑. 測試時須要將`dev.springbootdemo.cn`域名指向k8s的任一臺worker的IP.
ingress:
  enabled: true
  annotations: {}
  hosts:
    - host: dev.springbootdemo.cn
      paths: [/]
  tls: []

resources: {}
autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 4

nodeSelector: {}
tolerations: []
affinity: {}

# Probe延時參數
probe:
  livenessDelaySeconds: 120
  readinessDelaySeconds: 120

2) templates/depolyment.yaml 文件. templates 文件夾下的文件是 K8S deploymnet, service, ingress 等通用配置模板, 通常狀況下無需修改. 可是對於 SrpingBoot 項目, 特別是涉及到啓動過程當中須要鏈接數據庫/消息隊列/其餘第三方服務的狀況, 默認在啓動後就進行服務探測(Probe). 而此時可能 tomcat 還沒起來, 因此容易誤報爲服務失敗, 致使無故重啓容器. 所以咱們須要修改 templates/depolyment.yaml 文件, 將其中 Probe 延時改成一個相對比較合理的時間.

咱們修改 templates/depolyment.yaml文件, 在 .spec.template.spec.containers 節點下增長 .livenessProbe.readinessProbe 兩個節點. 其餘部分沒有任何改動, 因此這裏只給出這一段代碼片斷:

containers:
        - name: {{ .Chart.Name }}
          # 省略...
          ports:
            - name: http
              containerPort: {{ .Values.container.port }}
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds:  {{ .Values.probe.livenessDelaySeconds }}
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds:  {{ .Values.probe.readinessDelaySeconds }}

 四. 配置 Jenkins

1) 新建一個 Item, 取一個名字 (好比: SpringBootDemo-K8S), 選擇 Pipeline 類型.
image.png

2) 進入到配置頁面, General -> Description字段輸入項目描述.
3) 若是你的 Jenkins 能夠外網訪問, 或者跟你的 Gitlab 處於同一局域網, 能夠由 Gitee 或者 Gitlab 觸發 WebHook, 那麼在 Build Triggers 模塊進行相應的配置. 本示例由於 Gitee 沒法觸達內網部署的 Jenkins, 因此沒有配置 Build Triggers. 後面須要手動觸發編譯流程.
4) Pipeline的定義以下圖所示 (注意, 我這裏取的是base分支, 若是你須要取master分支, 將下圖中的3處base改成master:
image.png

最後, 手動觸發編譯, Pipeline 的 Stage View 以下圖所示:
image.png

相關文章
相關標籤/搜索