Kubernetes筆記(三):Gitlab+Jenkins Pipeline+Docker+k8s+Helm自動化部署實踐(乾貨分享!)

經過前面兩篇文章,咱們已經有了一個「嗷嗷待哺」的K8s集羣環境,也對相關的概念與組件有了一個基本瞭解(前期對概念有個印象便可,由於只有實踐了才能對其有深刻理解,所謂「紙上得來終覺淺,絕知此事要躬行」),本文從實踐角度介紹如何結合咱們經常使用的Gitlab與Jenkins,經過K8s來實現項目的自動化部署,示例將包括基於SpringBoot的服務端項目與基於Vue.js的Web項目。html

本文涉及到的工具與技術包括:java

  • Gitlab —— 經常使用的源代碼管理系統
  • Jenkins, Jenkins Pipeline —— 經常使用的自動化構建、部署工具,Pipeline以流水線的方式將構建、部署的各個步驟組織起來
  • Docker,Dockerfile —— 容器引擎,全部應用最終都要以Docker容器運行,Dockerfile是Docker鏡像定義文件
  • Kubernetes —— Google開源的容器編排管理系統
  • Helm —— Kubernetes的包管理工具,相似Linux的yum,apt,或Node的npm等包管理工具,能將Kubernetes中的應用及相關依賴服務以包(Chart)的形式組織管理

環境背景:linux

  1. 已使用Gitlab作源碼管理,源碼按不一樣的環境創建了develop(對應開發環境),pre-release(對應測試環境),master(對應生產環境)分支
  2. 已搭建了Jenkins服務
  3. 已有Docker Registry服務,用於Docker鏡像存儲(基於Docker Registry或Harbor自建,或使用雲服務,本文使用阿里雲容器鏡像服務)
  4. 已搭建了K8s集羣

預期效果:nginx

  1. 分環境部署應用,開發環境、測試環境、生產環境分開來,部署在同一集羣的不一樣namespace,或不一樣集羣中(好比開發測試部署在本地集羣的不一樣namespace中,生產環境部署在雲端集羣)
  2. 配置儘量通用化,只須要經過修改少許配置文件的少許配置屬性,就能完成新項目的自動化部署配置
  3. 開發測試環境在push代碼時自動觸發構建與部署,生產環境在master分支上添加版本tag而且push tag後觸發自動部署
  4. 總體交互流程以下圖

jenkins-cicd

項目配置文件

首先咱們須要在項目的根路徑中添加一些必要的配置文件,以下圖所示git

springboot-ci-structure

包括:web

  1. Dockerfile文件,用於構建Docker鏡像的文件(參考 Docker筆記(十一):Dockerfile詳解與最佳實踐
  2. Helm相關配置文件,Helm是Kubernetes的包管理工具,能夠將應用部署相關的Deployment,Service,Ingress等打包進行發佈與管理(Helm的具體介紹咱們後面再補充)
  3. Jenkinsfile文件,Jenkins的pipeline定義文件,定義了各個階段需執行的任務

Dockerfile

在項目根目錄中添加一個Dockerfile文件(文件名就叫Dockerfile),定義如何構建Docker鏡像,以Spring Boot項目爲例,spring

FROM frolvlad/alpine-java:jdk8-slim
#在build鏡像時能夠經過 --build-args profile=xxx 進行修改
ARG profile
ENV SPRING_PROFILES_ACTIVE=${profile}
#項目的端口
EXPOSE 8000 
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 ./target/your-project-name-1.0-SNAPSHOT.jar ./app.jar
ENTRYPOINT ["java", "-jar", "/mnt/app.jar"]

將SPRING_PROFILES_ACTIVE經過參數profile暴露出來,在構建的時候能夠經過 --build-args profile=xxx 來進行動態設定,以知足不一樣環境的鏡像構建要求。docker

SPRING_PROFILES_ACTIVE本能夠在Docker容器啓動時經過docker run -e SPRING_PROFILES_ACTIVE=xxx來設定,因這裏使用Helm進行部署不直接經過docker run運行,所以經過ARG在鏡像構建時指定shell

Helm配置文件

Helm是Kubernetes的包管理工具,將應用部署相關的Deployment,Service,Ingress等打包進行發佈與管理(能夠像Docker鏡像同樣存儲於倉庫中)。如上圖中Helm的配置文件包括:npm

helm                                    - chart包的目錄名
├── templates                           - k8s配置模版目錄
│   ├── deployment.yaml                 - Deployment配置模板,定義如何部署Pod
│   ├── _helpers.tpl                    - 如下劃線開頭的文件,helm視爲公共庫定義文件,用於定義通用的子模版、函數、變量等
│   ├── ingress.yaml                    - Ingress配置模板,定義外部如何訪問Pod提供的服務,相似於Nginx的域名路徑配置
│   ├── NOTES.txt                       - chart包的幫助信息文件,執行helm install命令成功後會輸出這個文件的內容
│   └── service.yaml                    - Service配置模板,配置訪問Pod的服務抽象,有NodePort與ClusterIp等
|── values.yaml                         - chart包的參數配置文件,各模版文件能夠引用這裏的參數
├── Chart.yaml                          - chart定義,能夠定義chart的名字,版本號等信息
├── charts                              - 依賴的子包目錄,裏面能夠包含多個依賴的chart包,通常不存在依賴,我這裏將其刪除了

咱們能夠在Chart.yaml中定義每一個項目的chart名稱(相似安裝包名),如

apiVersion: v2
name: your-chart-name
description: A Helm chart for Kubernetes

type: application
version: 1.0.0
appVersion: 1.16.0

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

#部署Pod的副本數,即運行多少個容器
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: ""
#容器的端口暴露及環境變量配置
container:
  port: 8000
  env: []
#ServiceAccount,默認不建立
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,默認爲ClusterIp
service:
  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

  #.... 省略了其它默認參數配置

這裏在默認生成的基礎上添加了container部分,能夠在這裏指定容器的端口號而不用去改模板文件(讓模板文件在各個項目通用,一般不須要作更改),同時添加env的配置,能夠在helm部署時往容器裏傳入環境變量。將Service type從默認的ClusterIp改成了NodePort。部署同類型的不一樣項目時,只須要根據項目狀況配置Chart.yaml與values.yaml兩個文件的少許配置項,templates目錄下的模板文件可直接複用。

部署時須要在K8s環境中從Docker鏡像倉庫拉取鏡像,所以須要在K8s中建立鏡像倉庫訪問憑證(imagePullSecrets)

# 登陸Docker Registry生成/root/.docker/config.json文件
sudo docker login --username=your-username registry.cn-shenzhen.aliyuncs.com
# 建立namespace develop(我這裏是根據項目的環境分支名稱創建namespace)
kubectl create namespace develop
# 在namespace develop中建立一個secret
kubectl 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項目的構建部署, 編寫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')  //開發測試環境的kube憑證
        KUBE_CONFIG_PROD = "" //credentials('prod-k8s-kube-config') //生產環境的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"    //開發環境的域名
        INGRESS_HOST_TEST = "test.your-site.com"  //測試環境的域名
        INGRESS_HOST_PROD = "prod.your-site.com"  //生產環境的域名
    }
    parameters {
        string(name: 'ingress_path', defaultValue: '/your-path', description: '服務上下文路徑')
        string(name: 'replica_count', defaultValue: '1', description: '容器副本數量')
    }

    stages {
        stage('Code Analyze') {
            agent any
            steps {
               echo "1. 代碼靜態檢查"
            }
        }
        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. 構建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 "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"
                    //根據不一樣環境將服務部署到不一樣的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定義了整個自動化構建部署的流程:

  1. Code Analyze,可使用SonarQube之類的靜態代碼分析工具完成代碼檢查,這裏先忽略
  2. Maven Build,啓動一個Maven的Docker容器來完成項目的maven構建打包,掛載maven本地倉庫目錄到宿主機,避免每次都須要從新下載依賴包
  3. Docker Build,構建Docker鏡像,並推送到鏡像倉庫,不一樣環境的鏡像經過tag區分,開發環境使用dev.commitId的形式,如dev.88f5822,測試環境使用test.commitId,生產環境能夠將webhook事件設置爲tag push event,直接使用tag名稱
  4. Helm Deploy,使用helm完成新項目的部署,或已有項目的升級,不一樣環境使用不一樣的參數配置,如訪問域名,K8s集羣的訪問憑證kube_config等

Jenkins配置

Jenkins任務配置

在Jenkins中建立一個pipeline的任務,如圖

jenkins-pipeline-pro

配置構建觸發器,將目標分支設置爲develop分支,生成一個token,如圖

jenkins-pipeline-config1

記下這裏的「GitLab webhook URL」及token值,在Gitlab配置中使用。

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

jenkins-pipeline-config2.png

保存即完成了項目開發環境的Jenkins配置。測試環境只需將對應的分支修改成pre-release便可

Jenkins憑據配置

在Jenkinsfile文件中,咱們使用到了兩個訪問憑證——Docker Registry憑證與本地K8s的kube憑證,

DOCKER_REGISTER_CREDS = credentials('aliyun-docker-repo-creds') //docker registry憑證
KUBE_CONFIG_LOCAL = credentials('local-k8s-kube-config')  //開發測試環境的kube憑證

這兩個憑證須要在Jenkins中建立。

添加Docker Registry登陸憑證,在Jenkins 憑據頁面,添加一個用戶名密碼類型的憑據,如圖

jenkins-cred

jenkins-cred2

添加K8s集羣的訪問憑證,在master節點上將/root/.kube/config文件內容進行base64編碼,

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

使用編碼後的內容在Jenkins中建立一個Secret text類型的憑據,如圖

jenkins-cred3

在Secret文本框中輸入base64編碼後的內容。

Gitlab配置

在Gitlab項目的 Settings - Integrations 頁面配置一個webhook,在URL與Secret Token中填入前面Jenkins觸發器部分的「GitLab webhook URL」及token值,選中「Push events」做爲觸發事件,如圖

gitlab-webhook-config

開發、測試環境選擇「Push events」則在開發人員push代碼,或merge代碼到develop,pre-release分支時,就會觸發開發或測試環境的Jenkins pipeline任務完成自動化構建;生產環境選擇「Tag push events」,在往master分支push tag時觸發自動化構建。如圖爲pipeline構建視圖

jenkins-build

總結

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

本文涉及的全部配置文件(包括基於Spring Boot的服務端項目與基於Vue.js的Web項目)可在源碼項目中獲取(源碼地址獲取辦法:關注公衆號「半路雨歌」,首頁輸入「k8sops」便可)。

原文地址:http://blog.jboost.cn/k8s3-cd.html


做者:雨歌
歡迎關注做者微信公衆號:半路雨歌,一塊兒學習成長

微信公衆號

相關文章
相關標籤/搜索