38

Jenkins 在 kubernetes 中的 DevOps 流水线最佳实践

 3 years ago
source link: https://mp.weixin.qq.com/s/JJf1JiKhW3GPchpgM_J6Ig
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

kubernetes目前是最为流行的应用运行环境,应用以容器的形态运行在平台之上,可以实现一些动态的策略,保证服务的SLA。因此DevOps也必须适应这种新形态应用的部署的方式。而对DevOps来讲,其实容器化的出现以及容器平台的出现,其实让DevOps更为简单了。在使用Jenkins kubernetes插件的时候,你就会对流水线的认知更加深刻。总结起来具有以下优势:

  • 容器化平台的动态创建slave的方式节约了系统的资源,构建结束后可自行销毁。

  • 容器化配置同一流水线的不同的步骤运行环境,且在一定程度上实现隔离

  • kubernetes Pod的多容器共享volume的机制,让各个流水线共享操作的中间产物。

  • 模块化的配置流水线的方式,可以对流水线不同版本的工具进行统一的配置管理。

传统的kubernetes的操作方式是在一个环境下配置所有的工具,导致对Jenkins master/slave的环境配置要求较大。而到了容器的平台之上,不同的环境的操作可以天然的隔离到不同的容器之中,而且可以像搭积木一样实现流水线分工,还能通过存储共享的方案实现互相之间中间产物的传递,可以说真正的将流水线的设计 体现 的淋漓尽致。

7RjMNbE.png!mobile

本文就主要围绕Jenkins在kubernetes中的使用方案进行探讨,给大家呈现一个kubernetes下的最佳实践。

准备工作

  • kubernetes环境,可以使用各种商业/免费版本,自行选择。

    我本人使用的是rancher的k3s,因为整体的安装体积小,便于测试。任何一个可用的kubernetes发行版都能满足我们的需求,我们运行应用使用kubernetes最基本的功能即可。

  • helm安装Jenkins到k8s之上(也可以独立安装,Jenkins kubernetes插件不要求Jenkins master必须运行在kubernetes之上)

    请参考我的历史文章: 使用helm在kubernetes环境中安装jenkins

    此外需要安装相应的kubernetes插件,请参见历史文章:

    使用Jenkins kubernetes插件在kubernetes中运行Jenkins流水线

  • 镜像仓库,可以使用Harbor或者Nexus作为镜像仓库。

    镜像仓库可以使用开源的方案,本文不用过多阐述。

  • helm仓库,可以存储已经打包好的helm应用包。

    可以使用harbor存储helm包,也可以使用chartmuseum进行helm包的存储。当然也可以自己搭建web应用服务器进行存储。在kubernetes平台上建议使用helm进行应用部署。方便管理和配置。

    历史文章: 快速搭建Helm 私有仓库Chartmuseum

    历史文章: kubernetes包管理工具 - Helm 3入门到实战(一)

  • Gitlab环境,用于存储代码,用于流水线配置使用。

    官方提供了Gitlab的helm安装部署方式,可以参考。

    https://docs.gitlab.com/charts/

  • Maven仓库,用于存储java的制品,管理jar包依赖,内网存储可以提高构建的速度。

流水线配置

这里把一整段的jenkins流水线配置贴出来供大家参考,另外对其中的一些信息做相应的介绍:

podTemplate(cloud: 'kubernetes',
    imagePullSecrets: ['regcred'],
    containers: [
    containerTemplate(name: 'jnlp', image: '192.168.100.3:5000/jenkins/jnlp-slave:cicd', ttyEnabled: true),
    containerTemplate(name: 'maven', image: '192.168.100.3:5000/mavenbuild:cicd', ttyEnabled: true),
    containerTemplate(name: 'docker', image: '192.168.100.3:5000/dind:19.03.6', privileged: true, ttyEnabled: true),
    containerTemplate(name: 'helm', image: '192.168.100.3:5000/helmcli:v3.1.2', privileged: true, ttyEnabled: true, command: 'cat')
    ],
    volumes: [
       persistentVolumeClaim(claimName: 'kubeconfigs', mountPath: '/root/kubeconfigs/', readOnly: true)
    ]
){
    node(POD_LABEL) {
        container('maven'){
            stage('Pull source code'){
                sh "rm -rf ${config.project_name}-pipeline"
                git credentialsId: 'test', branch: 'master' , url: 'http://192.168.100.3/gitlab/demo.git'
            }
            stage('Compile code') {
                sh 'mvn clean package'
            }
        }

        container('docker'){
            stage('Docker image build & Push') {
                withCredentials([usernamePassword(credentialsId: 'nexus-docker', passwordVariable: 'PASSWORD', usernameVariable: 'USERNAME')]) {
                    sh 'echo "$PASSWORD" | docker login dockerrepo:5000 -u "$USERNAME" --password-stdin'
                    sh "docker build -t dockerrepo:5000/cicd/app1:v1.${BUILD_NUMBER} \
                    -f Dockerfile --force-rm --no-cache ."
                    sh "docker push dockerrepo:5000/cicd/app1:v1.${BUILD_NUMBER}"
               }
            }
        }
        
        container('helm'){
          stage('deploy to kubesphere') {
              
              withCredentials([file(credentialsId: 'k3s-kubeconfig', variable: 'KUBECONFIG')]) {
                
                def action
                result = sh returnStatus: true, script: "helm status myproject -n default --kubeconfig $KUBECONFIG"
                if(result == 0){
                  action = "upgrade"
                }else{
                  action = "install"
                }
              
      
                sh "helm ${action} myproject \
                  http://192.168.100.4:9000/helm-repo/springboot-0.1.0.tgz \
                  --set image.repository=dockerrepo:5000/cicd/app1:v1.${BUILD_NUMBER} \
                  --set nameOverride=myproject \
                  --set fullnameOverride=myproject \
                  --set ingress.enabled=yes \
                  --set ingress.host=test.cicd.com \
                  --set ingress.path=/test \
                  -n default \
                  --kubeconfig $KUBECONFIG"
              }
          }
        }
    }
}

说明:

  1. 在containerTemplate中根据功能的不同配置了多个不同的环境,例如docker,maven等。我们需要自己配置自己的运行环境的容器。例如,我自己打包的maven镜像中已经包含了连接到nexus制品库的信息。

    示例,我的maven镜像的Dockerfile,其中包含maven的配置以及证书等信息。仅供参考。

    [aiops@3 mavenimage]$ ls
    cert.pem  Dockerfile  entrypoint.sh  README.txt  settings.xml
    [aiops@3 mavenimage]$ cat entrypoint.sh
    #!/bin/bash
    /usr/local/bin/mvn-entrypoint.sh && cat
    [aiops@3 mavenimage]$ cat Dockerfile
    FROM maven:3.6.2-jdk-8
    COPY settings.xml /usr/share/maven/ref/
    COPY cert.pem /usr/share/maven/ref/
    COPY entrypoint.sh /usr/local/bin/
    RUN chmod 777 /usr/local/bin/entrypoint.sh
    RUN keytool -importcert -file /usr/share/maven/ref/cert.pem -alias nexus -storepass changeit -keystore $JAVA_HOME/jre/lib/security/cacerts -trustcacerts -noprompt
    RUN git config --global http.sslverify false
    ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
    [aiops@3 mavenimage]$

    示例:我的docker in docker 镜像的内容, 可以将证书,credential等配置提前预置好。仅供参考

    [aiops@3 dind]$ ls
    CA  ca.crt  config.json  daemon.json  Dockerfile
    [aiops@3 dind]$ cat Dockerfile
    FROM 192.168.100.3:5000/docker:19.03.6-dind
    RUN mkdir -p /etc/docker/certs.d/192.168.100.3:5000/ && mkdir -p /root/.docker/
    COPY ca.crt /etc/docker/certs.d/192.168.100.3:5000
    COPY config.json /root/.docker/
    COPY daemon.json /etc/docker/

    示例:helm镜像

    [aiops@3 helm]$ cat Dockerfile
    FROM 192.168.100.3:5000/alpine:3.10
    WORKDIR ~
    RUN apk add curl
    RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kubectl
    RUN chmod +x ./kubectl
    RUN mv ./kubectl /usr/local/bin/kubectl
    RUN wget https://get.helm.sh/helm-v3.1.2-linux-amd64.tar.gz
    RUN tar -zxvf helm-v3.1.2-linux-amd64.tar.gz
    RUN mv ./linux-amd64/helm /usr/local/bin/helm

    jnlp的镜像就是官方提供的镜像,如果自己需要添加自定义的工具可以自行构建Dockerfile

    以上四个容器,就作为流水线中的不同的部分对共有的jenkins workspace进行相应的操作。

  1. 使用Jenkins 的credential的机制,存储敏感数据,避免明文配置和运行。这里我们用到的是用户名密码以及SecretFile的方式。存储的是docker仓库的用户名密码以及kubeconfig文件。建议不要使用明文进行配置。

    withCredentials([usernamePassword(credentialsId: 'nexus-docker', passwordVariable: 'PASSWORD', usernameVariable: 'USERNAME')])
    withCredentials([file(credentialsId: 'k3s-kubeconfig', variable: 'KUBECONFIG')])
  2. 使用helm部署的方式部署应用,可以根据自己的需要打包成helm包,然后上传到helm仓库中,在jenkinsfile中指定相应的helm包即可。同时在声明式语法中也可以使用脚本的语言,例如例子中使用脚本判断是否有helm release已经部署,来决定是否使用helm install命令,否则使用upgrade命令。

    def action
    result = sh returnStatus: true, script: "helm status myproject -n default --kubeconfig $KUBECONFIG"
    if(result == 0){
    action = "upgrade"
    }else{
    action = "install"
    }

总结

本文提供了Jenkins在kubernetes环境中的使用方案,该方案是利用Jenkins kubernetes插件的方式运行流水线,并可以自己动态构建所需要的流水线运行环境。helm对应用进行封装,并暴露出相应的配置用于Jenkins中helm命令的调用。使用withcredential的方式使用Jenkins凭据,减少敏感信息的明文存储。并可以使用groovy脚本配合声明式配置来进行更加复杂的操作。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK