1. Gitlab+Jenkins+k8s+Helm 的自動化部署實踐

        共 11245字,需瀏覽 23分鐘

         ·

        2021-11-05 15:44

        來源:https://segmentfault.com/a/1190000022637144

        本文從實踐角度介紹如何結(jié)合我們常用的 Gitlab 與 Jenkins,通過 K8s 來實現(xiàn)項目的自動化部署,示例將包括基于 SpringBoot 的服務(wù)端項目與基于 Vue.js 的 Web 項目。

        本文涉及到的工具與技術(shù)包括:
        • Gitlab —— 常用的源代碼管理系統(tǒng)
        • Jenkins,Jenkins Pipeline —— 常用的自動化構(gòu)建、部署工具,Pipeline 以流水線的方式將構(gòu)建、部署的各個步驟組織起來
        • Docker,Dockerfile —— 容器引擎,所有應(yīng)用最終都要以 Docker 容器運行,Dockerfile 是 Docker 鏡像定義文件
        • Kubernetes —— Google 開源的容器編排管理系統(tǒng)
        • Helm —— Kubernetes 的包管理工具,類似 Linux 的 yum,apt,或 Node 的 npm 等包管理工具,能將 Kubernetes 中的應(yīng)用及相關(guān)依賴服務(wù)以包(Chart)的形式組織管理
        環(huán)境背景:
        1. 已使用 Gitlab 做源碼管理,源碼按不同的環(huán)境建立了 develop(對應(yīng)開發(fā)環(huán)境),pre-release(對應(yīng)測試環(huán)境),master(對應(yīng)生產(chǎn)環(huán)境)分支
        2. 已搭建了 Jenkins 服務(wù)
        3. 已有 Docker Registry 服務(wù),用于 Docker 鏡像存儲(基于 Docker Registry 或Harbor 自建,或使用云服務(wù),本文使用阿里云容器鏡像服務(wù))
        4. 已搭建了 K8s 集群

        預(yù)期效果:

        1. 分環(huán)境部署應(yīng)用,開發(fā)環(huán)境、測試環(huán)境、生產(chǎn)環(huán)境分開來,部署在同一集群的不同namespace,或不同集群中(比如開發(fā)測試部署在本地集群的不同 namespace中,生產(chǎn)環(huán)境部署在云端集群)

        2. 配置盡可能通用化,只需要通過修改少量配置文件的少量配置屬性,就能完成新項目的自動化部署配置

        3. 開發(fā)測試環(huán)境在push代碼時自動觸發(fā)構(gòu)建與部署,生產(chǎn)環(huán)境在 master 分支上添加版本 tag 并且 push tag 后觸發(fā)自動部署

        4. 整體交互流程如下圖

        項目配置文件

        首先我們需要在項目的根路徑中添加一些必要的配置文件,如下圖所示


        包括:

        1. Dockerfile 文件,用于構(gòu)建 Docker 鏡像的文件(參考 Docker筆記(十一):

          Dockerfile 詳解與最佳實踐)

        2. Helm 相關(guān)配置文件,Helm 是 Kubernetes 的包管理工具,可以將應(yīng)用部署相關(guān)的Deployment,Service,Ingress 等打包進行發(fā)布與管理(Helm 的具體介紹我們后面再補充)

        3. Jenkinsfile 文件,Jenkins 的 pipeline 定義文件,定義了各個階段需執(zhí)行的任務(wù)

        Dockerfile

        在項目根目錄中添加一個 Dockerfile 文件(文件名就叫 Dockerfile),定義如何構(gòu)建Docker 鏡像,以 Spring Boot 項目為例,
        FROM frolvlad/alpine-java:jdk8-slim#在build鏡像時可以通過 --build-args profile=xxx 進行修改ARG profileENV SPRING_PROFILES_ACTIVE=${profile}#項目的端口EXPOSE 8000 WORKDIR /mnt
        #修改時區(qū)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 ./target/your-project-name-1.0-SNAPSHOT.jar ./app.jarENTRYPOINT ["java", "-jar", "/mnt/app.jar"]

        將 SPRING_PROFILES_ACTIVE 通過參數(shù) profile 暴露出來,在構(gòu)建的時候可以通過 —build-args profile=xxx 來進行動態(tài)設(shè)定,以滿足不同環(huán)境的鏡像構(gòu)建要求。

        SPRING_PROFILES_ACTIVE 本可以在 Docker 容器啟動時通過 docker run -e SPRING_PROFILES_ACTIVE=xxx 來設(shè)定,因這里使用 Helm 進行部署不直接通過docker run 運行,因此通過 ARG 在鏡像構(gòu)建時指定

        Helm 配置文件

        Helm 是 Kubernetes 的包管理工具,將應(yīng)用部署相關(guān)的 Deployment,Service,Ingress 等打包進行發(fā)布與管理(可以像 Docker 鏡像一樣存儲于倉庫中)。如上圖中Helm 的配置文件包括:

        helm                                    - chart包的目錄名├── templates                           - k8s配置模版目錄│   ├── deployment.yaml                 - Deployment配置模板,定義如何部署Pod│   ├── _helpers.tpl                    - 以下劃線開頭的文件,helm視為公共庫定義文件,用于定義通用的子模版、函數(shù)、變量等│   ├── ingress.yaml                    - Ingress配置模板,定義外部如何訪問Pod提供的服務(wù),類似于Nginx的域名路徑配置│   ├── NOTES.txt                       - chart包的幫助信息文件,執(zhí)行helm install命令成功后會輸出這個文件的內(nèi)容│   └── service.yaml                    - Service配置模板,配置訪問Pod的服務(wù)抽象,有NodePort與ClusterIp等|── values.yaml                         - chart包的參數(shù)配置文件,各模版文件可以引用這里的參數(shù)├── Chart.yaml                          - chart定義,可以定義chart的名字,版本號等信息├── charts                              - 依賴的子包目錄,里面可以包含多個依賴的chart包,一般不存在依賴,我這里將其刪除了
        我們可以在 Chart.yaml 中定義每個項目的 chart 名稱(類似安裝包名),如
        apiVersion: v2name: your-chart-namedescription: A Helm chart for Kubernetes
        type: applicationversion: 1.0.0appVersion: 1.16.0

        在 values.yaml 中定義模板文件中需要用到的變量,如

        #部署Pod的副本數(shù),即運行多少個容器replicaCount: 1#容器鏡像配置image:  repository: registry.cn-hangzhou.aliyuncs.com/demo/demo  pullPolicy: Always  # Overrides the image tag whose default is the chart version.  tag: "dev"#鏡像倉庫訪問憑證imagePullSecrets:  - name: aliyun-registry-secret#覆蓋啟動容器名稱nameOverride: ""fullnameOverride: ""#容器的端口暴露及環(huán)境變量配置container:  port: 8000  env: []#ServiceAccount,默認不創(chuàng)建serviceAccount:  # Specifies whether a service account should be created  create: false  # Annotations to add to the service account  annotations: {}  name: ""
        podAnnotations: {}
        podSecurityContext: {} # fsGroup: 2000
        securityContext: {} # capabilities: # drop: # - ALL # readOnlyRootFilesystem: true # runAsNonRoot: true # runAsUser: 1000#使用NodePort的service,默認為ClusterIpservice: type: NodePort port: 8000#外部訪問Ingress配置,需要配置hosts部分ingress: enabled: true annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" hosts: - host: demo.com paths: ["/demo"] tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.local
        #.... 省略了其它默認參數(shù)配置

        這里在默認生成的基礎(chǔ)上添加了 container 部分,可以在這里指定容器的端口號而不用去改模板文件(讓模板文件在各個項目通用,通常不需要做更改),同時添加env的配置,可以在helm部署時往容器里傳入環(huán)境變量。將Service type從默認的ClusterIp改為了NodePort。部署同類型的不同項目時,只需要根據(jù)項目情況配置Chart.yaml與values.yaml兩個文件的少量配置項,templates目錄下的模板文件可直接復(fù)用。

        部署時需要在K8s環(huán)境中從 Docker 鏡像倉庫拉取鏡像,因此需要在K8s中創(chuàng)建鏡像倉庫訪問憑證(imagePullSecrets)
        # 登錄Docker Registry生成/root/.docker/config.json文件sudo docker login --username=your-username registry.cn-shenzhen.aliyuncs.com# 創(chuàng)建 namespace develop(我這里是根據(jù)項目的環(huán)境分支名稱建立namespacekubectl create namespace develop# 在 namespace develop中創(chuàng)建一個secretkubectl create secret generic aliyun-registry-secret --from-file=.dockerconfigjson=/root/.docker/config.json  --type=kubernetes.io/dockerconfigjson --namespace=develop

        Jenkinsfile

        Jenkinsfile 是 Jenkins pipeline 配置文件,遵循 Groovy 語法,對于 Spring Boot 項目的構(gòu)建部署, 編寫 Jenkinsfile 腳本文件如下,
        image_tag = "default"  //定一個全局變量,存儲Docker鏡像的tag(版本)pipeline {    agent any    environment {        GIT_REPO = "${env.gitlabSourceRepoName}"  //從Jenkins Gitlab插件中獲取Git項目的名稱        GIT_BRANCH = "${env.gitlabTargetBranch}"  //項目的分支        GIT_TAG = sh(returnStdout: true,script: 'git describe --tags --always').trim()  //commit id或tag名稱        DOCKER_REGISTER_CREDS = credentials('aliyun-docker-repo-creds') //docker registry憑證        KUBE_CONFIG_LOCAL = credentials('local-k8s-kube-config')  //開發(fā)測試環(huán)境的kube憑證        KUBE_CONFIG_PROD = "" //credentials('prod-k8s-kube-config') //生產(chǎn)環(huán)境的kube憑證
        DOCKER_REGISTRY = "registry.cn-hangzhou.aliyuncs.com" //Docker倉庫地址 DOCKER_NAMESPACE = "your-namespace" //命名空間 DOCKER_IMAGE = "${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${GIT_REPO}" //Docker鏡像地址
        INGRESS_HOST_DEV = "dev.your-site.com" //開發(fā)環(huán)境的域名 INGRESS_HOST_TEST = "test.your-site.com" //測試環(huán)境的域名 INGRESS_HOST_PROD = "prod.your-site.com" //生產(chǎn)環(huán)境的域名 } parameters { string(name: 'ingress_path', defaultValue: '/your-path', description: '服務(wù)上下文路徑') string(name: 'replica_count', defaultValue: '1', description: '容器副本數(shù)量') }
        stages { stage('Code Analyze') { agent any steps { echo "1. 代碼靜態(tài)檢查" } } stage('Maven Build') { agent { docker { image 'maven:3-jdk-8-alpine' args '-v $HOME/.m2:/root/.m2' } } steps { echo "2. 代碼編譯打包" sh 'mvn clean package -Dfile.encoding=UTF-8 -DskipTests=true' } } stage('Docker Build') { agent any steps { echo "3. 構(gòu)建Docker鏡像" echo "鏡像地址:${DOCKER_IMAGE}" //登錄Docker倉庫 sh "sudo docker login -u ${DOCKER_REGISTER_CREDS_USR} -p ${DOCKER_REGISTER_CREDS_PSW} ${DOCKER_REGISTRY}" script { def profile = "dev" if (env.gitlabTargetBranch == "develop") { image_tag = "dev." + env.GIT_TAG } 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è)置,以區(qū)分不同環(huán)境進行鏡像構(gòu)建 sh "docker build --build-arg profile=${profile} -t ${DOCKER_IMAGE}:${image_tag} ." sh "sudo docker push ${DOCKER_IMAGE}:${image_tag}" sh "docker rmi ${DOCKER_IMAGE}:${image_tag}" } } } stage('Helm Deploy') { agent { docker { image 'lwolf/helm-kubectl-docker' args '-u root:root' } } steps { echo "4. 部署到K8s" sh "mkdir -p /root/.kube" script { def kube_config = env.KUBE_CONFIG_LOCAL def ingress_host = env.INGRESS_HOST_DEV if (env.gitlabTargetBranch == "pre-release") { ingress_host = env.INGRESS_HOST_TEST } else if (env.gitlabTargetBranch == "master"){ ingress_host = env.INGRESS_HOST_PROD kube_config = env.KUBE_CONFIG_PROD } sh "echo ${kube_config} | base64 -d > /root/.kube/config" //根據(jù)不同環(huán)境將服務(wù)部署到不同的namespace下,這里使用分支名稱 sh "helm upgrade -i --namespace=${env.gitlabTargetBranch} --set replicaCount=${params.replica_count} --set image.repository=${DOCKER_IMAGE} --set image.tag=${image_tag} --set nameOverride=${GIT_REPO} --set ingress.hosts[0].host=${ingress_host} --set ingress.hosts[0].paths={${params.ingress_path}} ${GIT_REPO} ./helm/" } } } }}
        Jenkinsfile定義了整個自動化構(gòu)建部署的流程:
        1. Code Analyze,可以使用 SonarQube 之類的靜態(tài)代碼分析工具完成代碼檢查,這里先忽略
        2. Maven Build,啟動一個 Maven 的 Docker 容器來完成項目的 maven 構(gòu)建打包,掛載 maven 本地倉庫目錄到宿主機,避免每次都需要重新下載依賴包
        3. Docker Build,構(gòu)建 Docker 鏡像,并推送到鏡像倉庫,不同環(huán)境的鏡像通過tag區(qū)分,開發(fā)環(huán)境使用 dev.commitId 的形式,如 dev.88f5822,測試環(huán)境使用 test.commitId,生產(chǎn)環(huán)境可以將 webhook 事件設(shè)置為 tag push event,直接使用 tag名稱
        4. Helm Deploy,使用helm完成新項目的部署,或已有項目的升級,不同環(huán)境使用不同的參數(shù)配置,如訪問域名,K8s 集群的訪問憑證kube_config等

        Jenkins 配置

        Jenkins 任務(wù)配置

        在 Jenkins 中創(chuàng)建一個 pipeline 的任務(wù),如圖

        配置構(gòu)建觸發(fā)器,將目標分支設(shè)置為 develop 分支,生成一個 token,如圖

        記下這里的“GitLab webhook URL”及token值,在Gitlab配置中使用。

        配置流水線,選擇“Pipeline script from SCM”從項目源碼中獲取pipeline腳本文件,配置項目Git地址,拉取源碼憑證等,如圖

        保存即完成了項目開發(fā)環(huán)境的Jenkins配置。測試環(huán)境只需將對應(yīng)的分支修改為pre-release 即可

        Jenkins 憑據(jù)配置

        在 Jenkinsfile 文件中,我們使用到了兩個訪問憑證——Docker Registry憑證與本地K8s的kube憑證,
        DOCKER_REGISTER_CREDS = credentials('aliyun-docker-repo-creds') //docker registry憑證KUBE_CONFIG_LOCAL = credentials('local-k8s-kube-config')  //開發(fā)測試環(huán)境的kube憑證
        這兩個憑證需要在 Jenkins 中創(chuàng)建。
        添加 Docker Registry 登錄憑證,在 Jenkins 憑據(jù)頁面,添加一個用戶名密碼類型的憑據(jù),如圖


        添加 K8s 集群的訪問憑證,在 master 節(jié)點上將 /root/.kube/config 文件內(nèi)容進行 base64 編碼,

        base64 /root/.kube/config > kube-config-base64.txtcat kube-config-base64.txt

        使用編碼后的內(nèi)容在 Jenkins 中創(chuàng)建一個 Secret text 類型的憑據(jù),如圖

        在 Secret 文本框中輸入 base64 編碼后的內(nèi)容。

        Gitlab 配置

        在 Gitlab 項目的 Settings - Integrations 頁面配置一個 webhook,在 URL 與 Secret Token 中填入前面 Jenkins 觸發(fā)器部分的“GitLab webhook URL”及token值,選中“Push events”作為觸發(fā)事件,如圖

        開發(fā)、測試環(huán)境選擇“Push events”則在開發(fā)人員push代碼,或merge代碼到develop,pre-release分支時,就會觸發(fā)開發(fā)或測試環(huán)境的Jenkins pipeline任務(wù)完成自動化構(gòu)建;生產(chǎn)環(huán)境選擇“Tag push events”,在往master分支push tag時觸發(fā)自動化構(gòu)建。如圖為pipeline構(gòu)建視圖

        總結(jié)

        本文介紹使用 Gitlab+Jenkins Pipeline+Docker+Kubernetes+Helm 來實現(xiàn) Spring Boot項目的自動化部署,只要稍加修改即可應(yīng)用于其它基于Spring Boot的項目(具體修改的地方在源碼的 Readme 文件中說明)。

        - END -

        ?推薦閱讀?

        Kubernetes 企業(yè)容器云平臺運維實戰(zhàn)?
        面試官:你都監(jiān)控 Redis 哪些指標?
        Linux運維工程師的 6 類好習(xí)慣和 23 個教訓(xùn)
        一名運維小哥對運維規(guī)則的10個總結(jié),收藏起來
        終于明白了 DevOps 與 SRE 的區(qū)別!
        Kubernetes上生產(chǎn)環(huán)境后,99%都會遇到這2個故障
        如何用 Kubernetes 實現(xiàn) CI/CD 發(fā)布流程?| 漫畫
        K8s kubectl 常用命令總結(jié)(建議收藏)
        Kubernetes 的這些核心資源原理,你一定要了解
        我在創(chuàng)業(yè)公司的 “云原生” 之旅
        基于Nginx實現(xiàn)灰度發(fā)布與AB測試
        編寫 Dockerfile 最佳實踐
        12年資深運維老司機的成長感悟
        搭建一套完整的企業(yè)級高可用 K8s 集群(kubeadm方式)



        點亮,服務(wù)器三年不宕機

        瀏覽 86
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 影音先锋你懂得 | 啊~cao死你个小sao货绿帽 | 韩国精品 A片 | 青草国产 | TS人妖另类精品视频系列 |