Gitlab+Jenkins+k8s+Helm 的自動化部署實踐
來源:https://segmentfault.com/a/1190000022637144
本文從實踐角度介紹如何結(jié)合我們常用的 Gitlab 與 Jenkins,通過 K8s 來實現(xiàn)項目的自動化部署,示例將包括基于 SpringBoot 的服務(wù)端項目與基于 Vue.js 的 Web 項目。
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)的形式組織管理
已使用 Gitlab 做源碼管理,源碼按不同的環(huán)境建立了 develop(對應(yīng)開發(fā)環(huán)境),pre-release(對應(yīng)測試環(huán)境),master(對應(yīng)生產(chǎn)環(huán)境)分支 已搭建了 Jenkins 服務(wù) 已有 Docker Registry 服務(wù),用于 Docker 鏡像存儲(基于 Docker Registry 或Harbor 自建,或使用云服務(wù),本文使用阿里云容器鏡像服務(wù)) 已搭建了 K8s 集群
預(yù)期效果:
分環(huán)境部署應(yīng)用,開發(fā)環(huán)境、測試環(huán)境、生產(chǎn)環(huán)境分開來,部署在同一集群的不同namespace,或不同集群中(比如開發(fā)測試部署在本地集群的不同 namespace中,生產(chǎn)環(huán)境部署在云端集群)
配置盡可能通用化,只需要通過修改少量配置文件的少量配置屬性,就能完成新項目的自動化部署配置
開發(fā)測試環(huán)境在push代碼時自動觸發(fā)構(gòu)建與部署,生產(chǎn)環(huán)境在 master 分支上添加版本 tag 并且 push tag 后觸發(fā)自動部署
整體交互流程如下圖

項目配置文件

包括:
Dockerfile 文件,用于構(gòu)建 Docker 鏡像的文件(參考 Docker筆記(十一):
Dockerfile 詳解與最佳實踐)
Helm 相關(guān)配置文件,Helm 是 Kubernetes 的包管理工具,可以將應(yīng)用部署相關(guān)的Deployment,Service,Ingress 等打包進行發(fā)布與管理(Helm 的具體介紹我們后面再補充)
Jenkinsfile 文件,Jenkins 的 pipeline 定義文件,定義了各個階段需執(zhí)行的任務(wù)
Dockerfile
FROM frolvlad/alpine-java:jdk8-slim#在build鏡像時可以通過 --build-args profile=xxx 進行修改ARG profileENV SPRING_PROFILES_ACTIVE=${profile}#項目的端口EXPOSE 8000WORKDIR /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/.cacheCOPY ./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包,一般不存在依賴,我這里將其刪除了
apiVersion: v2name: your-chart-namedescription: A Helm chart for Kubernetestype: applicationversion: 1.0.0appVersion: 1.16.0
在 values.yaml 中定義模板文件中需要用到的變量,如
#部署Pod的副本數(shù),即運行多少個容器replicaCount: 1#容器鏡像配置image:repository: registry.cn-hangzhou.aliyuncs.com/demo/demopullPolicy: Always# Overrides the image tag whose default is the chart version.tag: "dev"#鏡像倉庫訪問憑證imagePullSecrets:name: aliyun-registry-secret#覆蓋啟動容器名稱nameOverride: ""fullnameOverride: ""#容器的端口暴露及環(huán)境變量配置container:port: 8000env: []#ServiceAccount,默認不創(chuàng)建serviceAccount:# Specifies whether a service account should be createdcreate: false# Annotations to add to the service accountannotations: {}name: ""podAnnotations: {}podSecurityContext: {}# fsGroup: 2000securityContext: {}# capabilities:# drop:# - ALL# readOnlyRootFilesystem: true# runAsNonRoot: true# runAsUser: 1000#使用NodePort的service,默認為ClusterIpservice:type: NodePortport: 8000#外部訪問Ingress配置,需要配置hosts部分ingress:enabled: trueannotations: {}# kubernetes.io/ingress.class: nginx# kubernetes.io/tls-acme: "true"hosts:host: demo.compaths: ["/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ù)用。
# 登錄Docker Registry生成/root/.docker/config.json文件sudo docker login --username=your-username registry.cn-shenzhen.aliyuncs.com# 創(chuàng)建 namespace develop(我這里是根據(jù)項目的環(huán)境分支名稱建立namespace)kubectl 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
image_tag = "default" //定一個全局變量,存儲Docker鏡像的tag(版本)pipeline {agent anyenvironment {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 anysteps {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 anysteps {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_TAGprofile = "test"} else if (env.gitlabTargetBranch == "master"){// master分支則直接使用Tagimage_tag = env.GIT_TAGprofile = "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_LOCALdef ingress_host = env.INGRESS_HOST_DEVif (env.gitlabTargetBranch == "pre-release") {ingress_host = env.INGRESS_HOST_TEST} else if (env.gitlabTargetBranch == "master"){ingress_host = env.INGRESS_HOST_PRODkube_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/"}}}}}
Code Analyze,可以使用 SonarQube 之類的靜態(tài)代碼分析工具完成代碼檢查,這里先忽略 Maven Build,啟動一個 Maven 的 Docker 容器來完成項目的 maven 構(gòu)建打包,掛載 maven 本地倉庫目錄到宿主機,避免每次都需要重新下載依賴包 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名稱 Helm Deploy,使用helm完成新項目的部署,或已有項目的升級,不同環(huán)境使用不同的參數(shù)配置,如訪問域名,K8s 集群的訪問憑證kube_config等
Jenkins 配置
Jenkins 任務(wù)配置

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

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

保存即完成了項目開發(fā)環(huán)境的Jenkins配置。測試環(huán)境只需將對應(yīng)的分支修改為pre-release 即可
Jenkins 憑據(jù)配置
DOCKER_REGISTER_CREDS = credentials('aliyun-docker-repo-creds') //docker registry憑證KUBE_CONFIG_LOCAL = credentials('local-k8s-kube-config') //開發(fā)測試環(huán)境的kube憑證


添加 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ā)事件,如圖


總結(jié)
- 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ù)器三年不宕機


