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 自动部署。
原文地址:https://www.cnblogs.com/lixinliang/p/14324616.html