k8s 線上安裝 jenkins並結合 jenkinsfile 實現 helm 自動化部署


需求

1. 開發人員通過上傳 gitlab 新分支代碼,通過 jenkinsfile 結合jenkins 自動發現分支並自動化部署該分支對應的容器
2. 更新代碼可以實現容器平滑更新

環境

1. k8s 1.16 高可用集群環境
2. harbor 私有倉庫已搭建
3. gitlab 可以使用
4. 部署nfs server,可提供給jenkins 存儲使用

部署jenkins

# 創建新名稱空間
kubectl create  ns   myjenkins 
 
# 准備配置文件 deployment、 svc 、ingress 、證書
1. mkdir /myjenkins/jenkins
2. deployment 准備配置yaml文件,jenkins-deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  namespace: myjenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: jenkins
    spec:
      containers:
      - env:
        - name: JAVA_OPTS
          value: -Duser.timezone=Asia/Shanghai
        image: jenkins:lts
        imagePullPolicy: IfNotPresent
        name: jenkins
        ports:
        - containerPort: 8080
          name: web
          protocol: TCP
        - containerPort: 50000
          name: agent
          protocol: TCP
        resources: {}
        securityContext:
          runAsUser: 0
        volumeMounts:
        - mountPath: /var/jenkins_home
          name: jenkinshome
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      volumes:
      - name: jenkinshome
        nfs:
          path: /data/upload/myjenkins
          server: 172.24.119.30

3.jenkins agent 准備配置yaml 文件,這是jenkins 的 agent 配置,jenkins-agent.yaml 
apiVersion: v1
kind: Service
metadata:
  labels:
    app: jenkins
  name: jenkins-agent
  namespace: myjenkins
spec:
  ports:
  - name: agent
    port: 50000
    protocol: TCP
    targetPort: 50000
  selector:
    app: jenkins
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}

4. jenkins svc 配置yaml 文件 jenkins-svc.yaml 
apiVersion: v1
kind: Service
metadata:
  labels:
    app: jenkins
  name: jenkins
  namespace: myjenkins
spec:
  ports:
  - name: web
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: jenkins
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}

5. jenkins ingress 配置yaml 文件 jenkins-ingress.yaml 
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: jenkins
  namespace: myjenkins
spec:
  rules:
  - host: myjenkins.tagtic.cn
    http:
      paths:
      - backend:
          serviceName: jenkins
          servicePort: 8080
        path: /
  tls:
  - hosts:
    - myjenkins.tagtic.cn
    secretName: all-tagtic.cn
status:
  loadBalancer: {}

# 創建以上准備好的yaml 文件
kubectl create -f jenkins-deployment.yaml  
kubectl create -f jenkins-agent.yaml 
kubectl create -f jenkins-svc.yaml
kubectl create -f jenkins-ingress.yaml 

#創建證書,已准備好服務器證書
kubectl create secret tls tls-secret --cert=1979891tagtic.cn.pem    --key=1979891tagtic.cn.key  -n myjenkins

#登陸jenkins 
通過執行  kubectl logs -n myjenkins  jenkins-7f89966ff9-622xm  獲取jenkins 登陸密碼

#訪問jenkins,瀏覽器輸入
https://myjenkins.tagtic.cn/

配置jenkins

1.按照推薦安裝插件
FAQ
部署jenkins服務器出現Please wait while Jenkins is getting ready to work ...一直進不去該怎么辦?
需要你進入jenkins的工作目錄,打開-----hudson.model.UpdateCenter.xml將 url 中的 
https://updates.jenkins.io/update-center.json
更改為https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
是國內的清華大學的鏡像地址。

2.安裝完插件修改 admin 權限密碼

3.jenkins 安裝插件
Gitlab Gitlab API Kubernetes

4.點擊系統管理--系統配置
  4.1 修改用法、jenkins url 、系統管理員郵件地址(報警郵箱地址) 
   用法: 盡可能的使用這個節點
   Jenkins URL:https://myjenkins.tagtic.cn/
   系統管理員郵件地址: yunweimonitor@infinities.com


  4.2.全局屬性,增加環境變量鍵值對,這個環境變量在后續的 jenkinsfile 中會用到
       鍵: URL_SUFFIX   值:xy.a9vg.com


  4.3 Gitlab  需要填寫 Connection name、Gitlab host URL、Credentials,寫完 Test Connections
    Connection name:  duoniu glab 
    Gitlab host URL:  https://glab.tag.cn
    Credentials:Gitlab API token,這個憑據的tocken 需要在gitlab 的個人賬戶中生成,下面有詳細生成方式。

  4.4 添加Gitlab API token 憑據
   點擊 系統管理-->安全-->Manage Credentials-->添加憑據-憑據類型為 GitLab API token、API token 是以下說明在 gitlab 中生成的、ID 為自定義名稱 glab_token
   Gitlab API Token 獲取方式:
   登陸個人gitlab 賬號-->點擊個人頭像--> 選擇 settings-->選擇 Access Tokens --> 輸入 Name 和 Expries at --> 勾選api Access your API、read_user Read user information、read_registry Read Registry --> 點擊“Create personal access token”,生成access token,記錄下來。
     


  4.5 新增加 Global Pipeline Libraries,會加載 groovy 腳本。
     Library Name:  mykubernetes-standard
     Default version: master
   Retrieval method 
     選擇  Modern SCM
     Git 項目倉庫:  https://glab.tag.cn/test/kubernetes-standard.git  
     憑據需要添加個人登錄gitlab 的賬號和密碼,和上面添加的 Gitlab API Token 類型是不一樣的。
 

  4.5.1 新增加個人登陸gitlab 的憑據和 后續使用harbor 的憑據,這兩個憑據類型為 Username with password ,
        個人登錄gitlab 需要填寫登錄gitlab 的用戶名、密碼、ID自定義為 glab_pass
        harbor 需要填寫登錄harbor 的用戶名、密碼、ID 自定義為 harbor
FAQ:
1. Warning: CredentialId "glab_pass" could not be found.
id 名稱為: glab_pass
因為  kubernetes-stand 有用到這個名稱,或者修改 helm.groovy

FAQ:
ERROR: Could not find credentials entry with ID 'harbor'
添加harbor 的憑據

5. 新增加 郵件通知,填寫完成可以寫測試郵件看是否能發送成功
添加 SMTP服務器:smtp.exmail.qq.com
     SMTP服務器:@infinities.com

6. 點擊 cloud 下面的配置雲

https://myjenkins.tagtic.cn/configureClouds/

配置前提是k8s 集群已配置寬泛的 rbac 策略
kubectl create clusterrolebinding permissive-binding \
  --clusterrole=cluster-admin \
  --user=admin \
  --user=kubelet \
  --group=system:serviceaccounts

1.第一步添加 kubernetes 名稱:kubernetes
2.第二步連接 Kubernetes 地址:https://kubernetes.default.svc.cluster.local
3.第三步Kubernetes 命名空間: myjenkins (jenkins 的名稱空間)
4.第四步Jenkins 地址:http://jenkins:8080
5.第五步Jenkins 通道:jenkins-agent.myjenkins:50000
6.第六步填寫 jenkins pod 的 label: 鍵:jenkins 值:slave
7.第七步連接 Kubernetes API 的最大連接數 32



gitlab 鈎子觸發 jenkins 報錯 403

FAQ:
1. 配置 jenkins 安全策略
系統管理-->安全-->全局安全配置-->授權策略-->勾選匿名用戶具有可讀權限

2. 系統管理--> 系統配置  Gitlab  Enable authentication for '/project' end-point  取消前面勾選

第一種情況 gitlab 代碼結構下有 Jenkinsfile

與gitlab 代碼同級的包含有 Dockerfile 和 Jenkinsfile,這個項目包含有4個分支

#Jenkinsfile 文件內容
變量含義:
bn  --> 獲取當前分支名稱
bn_replace --> 將分支名稱中的 . 更換為 -
suffix --> 為 jenkins 配置的全局環境變量

def bn = "${env.BRANCH_NAME}";
def bn_replace = bn.replace(".", "-");
def suffix = "${env.URL_SUFFIX}";
stage('Deliver for development') {
    if (bn.startsWith('build-')){
        helm{
            scmUrl="https://glab.tag/test/laravel-k8s-test.git"
            project="test-helm2-${bn_replace}"
            email="test@donews.com"
            namespace="default"
            branch="${bn}"
            registry="harbor"
            helm="donews/myapp"
            helmArgs=""" --set service.port=80,ingress.hosts={myhelm-${bn_replace}.${suffix}} \
            """
        }
        
    }
}
stage('Deliver for testing') {
    if (bn == 'dev'){
        helm{
            scmUrl="https://glab.tag/test/laravel-k8s-test.git"
            project="test-helmdev"
            email="test@donews.com"
            namespace="default"
            branch="dev"
            registry="harbor"
            helm="donews/myapp"
            helmArgs=""" --set service.port=80,ingress.hosts={myhelmdev-xy.a99.com}""" 
        }
        
    }
}
stage('Deploy for production') {
    if (bn == 'master') {
        helm{
            scmUrl="https://glab.tag/test/laravel-k8s-test.git"
            project="test-helmmaster"
            email="test@donews.com"
            namespace="default"
            branch="master"
            registry="harbor"
            helm="donews/myapp"
            helmArgs=""" --set service.port=80,ingress.hosts={myhelmmaster-xy.a99.com}"""  
        }
        
    }
}

groovy 使用

jekins 系統配置中  Global Pipeline Libraries git 項目https://glab.tag.cn/test/kubernetes-standard.git ,在項目下新建立 vars 目錄,vars 目錄下 helm.groovy 內容

import hudson.model.Result
import hudson.model.Run
import jenkins.model.CauseOfInterruption.UserInterruption

def abortPreviousBuilds() {
    Run previousBuild = currentBuild.rawBuild.getPreviousBuildInProgress()

    while (previousBuild != null) {
        if (previousBuild.isInProgress()) {
            def executor = previousBuild.getExecutor()
            if (executor != null) {
                echo ">> Aborting older build #${previousBuild.number}"
                executor.interrupt(Result.ABORTED, new UserInterruption(
                    "Aborted by newer build #${currentBuild.number}"
                ))
            }
        }

        previousBuild = previousBuild.getPreviousBuildInProgress()
    }
}

def call(body) {
    // evaluate the body block, and collect configuration into the object
    abortPreviousBuilds()
    def pipelineParams= [:]
    def reg_prefix = ""

    def label = "worker-${pipelineParams.project}"
    body.resolveStrategy = Closure.DELEGATE_FIRST
    body.delegate = pipelineParams
    body()
    if (!pipelineParams.branch){
        pipelineParams.branch = "master"
    }
    if (!pipelineParams.deployment){
        pipelineParams.deployment = pipelineParams.project
    }

    pipelineParams.registry = "harbor"
    reg_prefix = "k8s-harbor01.gdfsxxds.rjyun/xy/"
    pullSecret = "harbor"

    if (!! pipelineParams.oversea){
        http_proxy = "--build-arg HTTP_PROXY=localhost:3001"
    } else {
        http_proxy = ""
    }


    podTemplate(label: label, containers: [
      containerTemplate(name: 'docker', image: 'docker:18', command: 'cat', ttyEnabled: true),
      containerTemplate(name: 'kubectl', image: 'lachlanevenson/k8s-kubectl:v1.16.0', command: 'cat', ttyEnabled: true),
      containerTemplate(name: 'helm', image: 'k8s-harbor01.gdfsxxds.rjyun/xy/helm:2.15.2', command: 'cat', ttyEnabled: true),
    ],
    volumes: [
      hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
    ]) {
      node(label) {
        def myRepo = checkout([$class: 'GitSCM', branches: [[name: "*/${pipelineParams.branch}"]], doGenerateSubmoduleConfigurations: false, extensions:  [[$class: 'CloneOption', noTags: false, reference: '', shallow: true, timeout: 1000]]+[[$class: 'CheckoutOption', timeout: 1000]], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'glab_pass', url: pipelineParams.scmUrl]]]) 
        def gitCommit = myRepo.GIT_COMMIT
        def gitBranch = myRepo.GIT_BRANCH
        def shortGitCommit = "${gitCommit[0..10]}"
        def project = pipelineParams.scm
        
        stage('Create docker images') {
            gitlabCommitStatus {
                container('docker') {
                    try{
                        withCredentials([[$class: 'UsernamePasswordMultiBinding',
                          credentialsId: pipelineParams.registry,
                          usernameVariable: 'DOCKER_HUB_USER',
                          passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
                            retry(3) {
                              sh """
                                docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD} ${reg_prefix}
                                docker build  -t ${reg_prefix}${pipelineParams.project}:${shortGitCommit} .
                                docker push ${reg_prefix}${pipelineParams.project}:${shortGitCommit}
                                docker tag ${reg_prefix}${pipelineParams.project}:${shortGitCommit} ${reg_prefix}${pipelineParams.project}:latest
                                docker push ${reg_prefix}${pipelineParams.project}:latest
                                """
                            }
                        }
                    }
                    catch(Exception e){
                        println(e.toString());
                        currentBuild.result = 'FAILURE'
                        step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: pipelineParams.email, sendToIndividuals: true])
                        throw e
                    }
                }       
            }
        }

        if (pipelineParams.helm) {
            stage('Run helm') {
                if (!!pipelineParams.helmArgs){
                    args = pipelineParams.helmArgs
                } else {
                    args = ""
                }
                container('helm') {
                    try {
                        sh """
                        helm init --client-only --stable-repo-url=https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
                        helm repo add donews http://chart.a99.com/
                        helm repo update
                        helm upgrade -i ${pipelineParams.project} ${pipelineParams.helm} \
                        --set image.repository=${reg_prefix}${pipelineParams.project},image.tag=${shortGitCommit} \
                        ${args} \
                        --namespace ${pipelineParams.namespace}
                        """
                        } 
                    catch(Exception e) {
                        println(e.toString());
                        currentBuild.result = 'FAILURE'
                        step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: pipelineParams.email, sendToIndividuals: true])
                        throw e
                    }
                }
            }

        } else {
            stage('Run kubectl') {
              container('kubectl') {
                try {
                    sh """
                    kubectl set image deployment/${pipelineParams.deployment}-deployment ${pipelineParams.project}=${reg_prefix}${pipelineParams.project}:${shortGitCommit} -n ${pipelineParams.namespace}
                    kubectl rollout status deployment ${pipelineParams.deployment}-deployment -n ${pipelineParams.namespace}
                    """
                    } 
                catch(Exception e) {
                    println(e.toString());
                    currentBuild.result = 'FAILURE'
                    step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: pipelineParams.email, sendToIndividuals: true])
                    throw e
                }

              }
            }
        }
        currentBuild.result = 'SUCCESS'
        step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: pipelineParams.email, sendToIndividuals: true])
      }
    }
}

helm 自建chart 倉庫,之前文章已介紹

前面文章:  https://www.cnblogs.com/lixinliang/p/14304469.html

jenkins 部署多分支流水線項目

新建任務--> 起一個任務名稱 duofenzhi  -->選擇多分支流水線

選擇配置 -->分支源-->Git



等 1分鍾,jenkins 將會自動拉取gitlab 代碼進行編譯構建

gitlab 添加自動觸發,必須是 project :

在gitlab -->項目下-->settings-->Integrations-->增加 Webhooks
https://myjenkins.tagtic.cn/project/duofenzhi

查看 jenkins 已構建完成

查看k8s,容器已正常啟動

項目部署完成。

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

第二種情況 gitlab 代碼結構下沒有 Jenkinsfile

#結構
gitlab 代碼下只有Dockerfile ,沒有Jenkinsfile,不能使用Jenkins 多分支構建,只能使用流水線

#新建立流水線
新建任務-->新建名稱test -->流水線
GitLab Connection 選擇 duoniu glab

選擇構建觸發器,這是和gitlab 打通的渠道

# 流水線
新增加 Pipeline script

helm{
    scmUrl="https://glab.tag/test/mall_h5.git"
    project="test"
    email="test@do.com"
    namespace="default"
    branch="dev"
    helm="donews/myapp"
    helmArgs=""" --set service.port=80,ingress.hosts={mytest-xy.aaa.com}"""  
}

注:
這個helm 定義將會自動加載上文的 helm.groovy 腳本,使用helm 部署容器,這和 jenkinsfile 是不同的,因為Jenkinsfile 不用配置 jenkins 的流水線腳本

查看 jenkins 已構建完成

查看k8s,容器已正常啟動

項目部署完成。

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

第三種情況 gitlab 代碼結構下也沒有 Jenkinsfile,但是已經有 deployment svc ingress 這些配置

#新建流水線
新增加 Pipeline script

def label = "worker-${UUID.randomUUID().toString()}"

podTemplate(label: label, containers: [
  containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true),
  containerTemplate(name: 'kubectl', image: 'lachlanevenson/k8s-kubectl:v1.16.0', command: 'cat', ttyEnabled: true),
],
volumes: [
  hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
  node(label) {
    def myRepo = checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions:  [[$class: 'CloneOption', noTags: false, reference: '', shallow: true, timeout: 12000]]+[[$class: 'CheckoutOption', timeout: 7000]], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'glab_pass', url: 'https://glaxxx.cn/test/tgbusmall_api.git']]]) 
    def gitCommit = myRepo.GIT_COMMIT
    def gitBranch = myRepo.GIT_BRANCH
    def shortGitCommit = "${gitCommit[0..10]}"
    
    stage('Create docker images') {
        gitlabCommitStatus {
            container('docker') {
                withCredentials([[$class: 'UsernamePasswordMultiBinding',
                  credentialsId: 'dockerreg',
                  usernameVariable: 'DOCKER_HUB_USER',
                  passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
                  sh """
				    cp dockerfile/dockerfile-release/Dockerfile .
                    docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD} k8s-harbor01.gdfsxxds.rjyun
					docker build  -t k8s-harbor01.gdfsxxds.rjyun/xy/tgbusmall-api:${shortGitCommit} . 
                    docker push k8s-harbor01.gdfsxxds.rjyun/xy/tgbusmall-api:${shortGitCommit}
                    """
                }
            }       
        }
      
    }
    stage('Run kubectl') {
      container('kubectl') {
        sh """
        kubectl set image deployment/tgbusmall-api-deployment tgbusmall-api=k8s-harbor01.gdfsxxds.rjyun/xy/tgbusmall-api:${shortGitCommit} -n default
        kubectl rollout status deployment tgbusmall-api-deployment -n default
        """
      }
    }
  }
}

查看 jenkins 已構建完成

查看k8s,容器已正常更新

項目部署完成。

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

項目總結

第一種方式jenkins 多分支流水線配置簡單,只用在gitlab 代碼下定義好 Jenkinsfile, 適合多分支代碼測試,便捷開發和測試人員,通過 groovy 可以自動化部署。
第二種方式 jenkins 流水線配置不用定義Jenkinsfile ,只用配置好 pipeline 內容即可,適合分支少項目,通過 groovy 也可以自動化部署。
第三種方式前提是已經有部署好的 deployment、svc 和ingress ,只需要每次進行鏡像替換即可,不推薦使用,因為每部署一個新的項目必須先手動准備好這些必配文件,不使用 groovy 自動部署。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM