devops-5:从0开始构建一条完成的CI CD流水线
阅读原文时间:2023年07月08日阅读:1

从0开始构建一条完成的CI CD流水线

前文中已经讲述了静态、动态增加agent节点,以动态的k8s cloud为例,下面就以Maven构建Java程序为例,开始构建出一条完整的CI CD流水线。

实现功能目标:

 1.分别可以根据分支和tag从源码仓库clone代码

 2.拿到源码后开始编译

 3.构建image,并push到镜像仓库

 4.部署到对应k8s集群

 5.部署成功后,钉钉告警

以上是此pipeline实现的功能,后续计划:

 1.通过webhooks实现源码仓库push代码后,自动出发pipeline运行

 2.增加SonarQube代码质量检测步骤

 3.配合argoCD实现自动CD

后续文章会陆续更新,敬请期待。

镜像准备

以k8s cloud当做agent的话,肯定需要一个基础镜像,镜像中需要有git、java和maven这些必要的工具环境,当然,可以使用jenkins提供的tools功能来配置工具导入到环境中,例如:

tools {  
    maven 'apache-maven-3.8.6'  
}

这种方式是比较方便的,但是每到一个新宿主机都要去主动下载一遍,还需要在jenkins中配置下载地址和方式,迁移时也比较麻烦,所以这里就采用一劳永逸的方法,把这些环境都提前打包到agent的镜像中,方便以后使用。

这里选用的基础镜像是jenkins官方的agent镜像:

docker pull jenkins/agent:latest

这个官方镜像内已经包含git、java环境,以及后边需要和jenkins master建立连接的agent.jar包,所以我们只需再将maven包打包进去即可。

maven工具包的准备

官方下载maven包:

wget https://dlcdn.apache.org/maven/maven-3/3.8.6/binaries/apache-maven-3.8.6-bin.tar.gz

maven的配置(可选)

很多java程序会用maven来进行构建,maven中又存在很多依赖组件(常用的是jar包、war包、pom等,也可把Zip包等通过POM文件定义为依赖组件),这个时候就会有一个仓库的概念,这个仓库分为三种类型,即:

修改Maven中央仓库地址

一般Maven的中央仓库由于网络问题会访问不到,这个时候可以修改地址为国内的Maven仓库地址或者公司私有的仓库地址,例如阿里的:http://maven.aliyun.com/nexus/content/groups/public

修改步骤是要修改apache-maven-3.8.6/conf/settings.xml文件中以下字段:

修改为:

<mirror>  
  <id>nexus-aliyun</id>  
  <mirrorOf>central</mirrorOf>  
  <name>Nexus aliyun</name>  
  <url>http://maven.aliyun.com/nexus/content/groups/public</url>  
</mirror>

若修改后未生效,可以检查代码pom.xml中是否指定了仓库地址,类似语句:

springsource-repos SpringSource Repository http://repo.spring.io/release/

修改Maven本地仓库路径

Maven本地仓库路径默认为 ${user.home}/.m2/repository

可以直接在此进行修改,也可以在构建时用参数指定:

mvn clean install -Dmaven.repo.local=/home/maven/local_repo/

也可以在构建时指定配置文件地址:

mvn clean install -s /home/maven/settings.xml

开始构建镜像

准备好的物料包及Dockerfile:

[root@node01 agent-jenkins]# ls
apache-maven-3.8.6.tar.gz Dockerfile jenkins-agent kubectl.tar.gz

这里要说下jenkins-agent这个脚本文件,这个脚本文件也是官方提供的,源码文件在这里:https://github.com/jenkinsci/docker-inbound-agent,这是专门用来agent连接jenkins master的,采用的jnlp的方式。

查看Dockerfile内容

[root@node01 agent-jenkins]# cat Dockerfile
FROM jenkins/agent:latest

USER root

ADD apache-maven-3.8.6.tar.gz /opt/
ADD kubectl.tar.gz /usr/local/bin/
ENV PATH $PATH:/opt/apache-maven-3.8.6/bin/
COPY jenkins-agent /usr/local/bin/
CMD ["/bin/sh","-c","/usr/local/bin/jenkins-agent"]

用于CD环节的工具,这里添加了kubectl命令,可根据需要添加。

构建镜像

# docker build -t registry.example.com:5000/jenkins/agent:v1 .

docker push registry.example.com:5000/jenkins/agent:v1

registry.example.com:5000 是我的私有仓库

配置k8s cloud的pod Template

前边镜像准备完毕,下边要准备一个pod yaml模板,来运行每次临时加入和运行job的agent,默认情况下,k8s cloud会有一个名称为jnlp的容器专门来和jenkins master连接,然后我们可以再启动一个容器专门来跑Pipeline的job,但这里有一点要注意,如果pod中有多个容器,我们需要在Pipeline中指定某个在哪个容器中运行,这个具体怎么指定后边再说,我们这里采用覆盖截jnlp容器的方式来实现全部的工作都由一个container来完成,最终pod Template如下:

apiVersion: "v1"
kind: "Pod"
metadata:
name: jenkins-agent
namespace: "default"
spec:
containers:

  • env:
    • name: "MAVEN_HOME"
      value: "/opt/apache-maven-3.8.6/"
      image: "registry.example.com:5000/jenkins/agent:v1"
      imagePullPolicy: "IfNotPresent"
      name: "jnlp"
      resources:
      limits:
      memory: "2G"
      cpu: "1500m"
      requests:
      memory: "1G"
      cpu: "100m"
      volumeMounts:
    • mountPath: "/root/.m2"
      name: "m2"
      readOnly: false
    • mountPath: "/home/jenkins/agent"
      name: "workspace-volume"
      readOnly: false
    • mountPath: "/usr/bin/docker"
      name: "docker-client"
      readOnly: true
    • mountPath: "/var/run/docker.sock"
      name: "docker-engine"
      readOnly: true
      volumes:
  • hostPath:
    path: "/root/.m2"
    type: "DirectoryOrCreate"
    name: "m2"
  • hostPath:
    path: "/home/jenkins"
    name: "workspace-volume"
  • hostPath:
    path: "/usr/bin/docker"
    type: File
    name: "docker-client"
  • hostPath:
    path: "/var/run/docker.sock"
    type: Socket
    name: "docker-engine"

这里有四个volume:

  • m2:这个是用作maven的本地仓库路径,使用hostpath挂载到了本地目录,当然也可以存储到某些共享存储中,目的就是让依赖包只下载一次。
  • workspace-volume:这个是将jenkins的工作目录也使用hostpath挂载。
  • docker-client:docker命令的挂载,用于build、push等命令
  • docker-engine:docker engine的挂载,用于build、push等

gitlab项目克隆

simple-java-maven-app项目地址:https://github.com/jenkins-docs/simple-java-maven-app,将此项目克隆到本地gitlab即可。

网访问github慢的话,可以git我的码云:https://gitee.com/vfancloud/simple-java-maven-app.git

Pipeline编写

创建凭证

1.代码仓库我们使用前边搭建的gitlab,需要提前将gitlab的用户凭证在Jenkins创建好,方便后边Jenkins下载代码使用:

系统管理—>凭证管理—>创建Username with password类型凭证(id需要记住,Pipeline中会使用)

2.我们的服务是部署在k8s集群中,所以还需要目标k8s的kubeconfig凭证,用来管理操控目标k8s:

系统管理—>凭证管理—>创建Secret file类型凭证

一般项目都会有多个环境,所以每个环境的kubeconfig凭证都要提前创建好。

3.镜像仓库的账号密码也要提前准备好,Username with password类型即可。

安装插件

一些常用的必须插件,要提前安装:

Pipeline

此Pipeline起一个示例效果,有些功能点可以省略或者选择使用,酌情增删即可:

pipeline {
agent {
kubernetes {
cloud 'kubernetes-internal' //指定cloud name
inheritFrom 'jenkins-agent' //指定podTemplate,新版本已经不再用label指定
namespace 'default'
}
}
environment {
GIT_CERT = credentials('vfan-gitlab') //gitlab用户凭证
HARBOR_HOST = 'registry.example.com:5000'
SERVER_NAME = 'simple-java-maven-app'
}
/* tools {
maven 'apache-maven-3.8.6' 镜像有maven环境了,可以不指定
} */
options {
buildDiscarder(logRotator(numToKeepStr: '10')) //保持历史构建的最大个数
timeout(20) //默认单位分钟,20分钟
timestamps() //Pipeline开始时间以及每个step执行开始时间
}
parameters {
choice(
name: 'GIT_REPO_URL',
choices: 'http://10.85.122.128:880/vfan/simple-java-maven-app.git',
description: 'Git Repo example environment'
)
choice(
name: 'GIT_TYPE',
choices: ['branch', 'tag'],
description: 'Git Repo example brance'
)
choice(
name: 'GIT_REPO_BRANCE',
choices: ['master', 'dev', 'test'],
description: 'Git Repo example brance'
)
gitParameter name: 'GIT_TAG',
type: 'PT_TAG',
branch: 'master',
branchFilter: '.*',
defaultValue: '',
selectedValue: 'TOP',
sortMode: 'DESCENDING_SMART',
listSize: '1',
description: 'Select you git tag.'
choice(
name: 'ENVIRONMENT',
choices: ['INT', 'DEV', 'PROD'],
description: 'Select deployment environment'
)
}
stages {
stage('git clone branch') {
when {
expression { params.GIT_TYPE == "branch" }
}
steps {
git(
branch: params.GIT_REPO_BRANCE,
credentialsId: env.GIT_CERT,
url: params.GIT_REPO_URL
)
}
post {
success {
sh '''
echo "use branch build"
git status
'''
}
}
}
stage('git clone tag') {
when {
expression { params.GIT_TYPE == "tag" }
}
steps {
checkout([$class: 'GitSCM',
branches: [[name: "${GIT_TAG}"]],
userRemoteConfigs: [[credentialsId: env.GIT_CERT, url: params.GIT_REPO_URL]]])
}
post {
success {
sh '''
echo "use tag build"
git status
'''
}
}
}
stage('Maven Build') {
steps {
sh 'mvn -B -DskipTests clean package'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
stage('Deliver') {
steps {
sh './jenkins/scripts/deliver.sh'
}
}
stage('Docker build && push') {
steps {
withCredentials([usernamePassword(credentialsId: 'harbor-auth', passwordVariable: 'HARBOR_PASSWD', usernameVariable: 'HARBOR_USER')]) {
sh '''
echo "Other operations…"
echo "Start building…"
date -d "+8 hour" +%Y%m%d_%H%M%S > /tmp/date
BUILD_TIME=`cat /tmp/date`
docker build --build-arg APP_NAME=simple-java-maven-app -t ${HARBOR_HOST}/${SERVER_NAME}:${GIT_REPO_BRANCE}_${BUILD_TIME} .
echo "Build complete."
docker login $HARBOR_HOST -u $HARBOR_USER -p $HARBOR_PASSWD
docker push ${HARBOR_HOST}/${SERVER_NAME}:${GIT_REPO_BRANCE}_${BUILD_TIME}
docker rmi ${HARBOR_HOST}/${SERVER_NAME}:${GIT_REPO_BRANCE}_${BUILD_TIME}
'''
}
}
}
stage('Deploy to k8s'){
input{
message "Should we continue deploy?"
ok "Yes, we should."

        }  
        environment {  
            // 提前创建好secret file类型的凭据  
            KUBE\_CONFIG\_INT = credentials('mycluster\_int')  
            // KUBE\_CONFIG\_DEV = credentials('mycluster\_dev')  
            // KUBE\_CONFIG\_PROD = credentials('mycluster\_prod')  
        }  
        steps{  
            sh'''  
                BUILD\_TIME=\`cat /tmp/date\`  
                case $ENVIRONMENT in  
                    "INT")  
                        kubectl set image deployment ${SERVER\_NAME} --kubeconfig=${KUBE\_CONFIG\_INT} app=${HARBOR\_HOST}/${SERVER\_NAME}:${GIT\_REPO\_BRANCE}\_${BUILD\_TIME}  
                        kubectl rollout status deployment ${SERVER\_NAME} --kubeconfig=${KUBE\_CONFIG\_INT}  
                    ;;  
                    "DEV")  
                        kubectl set image deployment ${SERVER\_NAME} --kubeconfig=${KUBE\_CONFIG\_DEV} app=${HARBOR\_HOST}/${SERVER\_NAME}:${GIT\_REPO\_BRANCE}\_${BUILD\_TIME}  
                        kubectl rollout status deployment ${SERVER\_NAME} --kubeconfig=${KUBE\_CONFIG\_DEV}  
                    ;;  
                esac  
                echo "Deployment complete."  
            '''  
        }  
    }  
}  
post {  
    success{  
        echo 'Deployment succeeded.'  
        dingtalk (  
            robot: 'myapp-dingding-robot',  
            type: 'MARKDOWN',  // 发什么类型的消息,有TEXT、LINK、MARKDOWN、和ACTION\_CARD,参考https://jenkinsci.github.io/dingtalk-plugin/guide/pipeline.html  
            at: \[\],  
            atAll: false,  
            title: 'Jenkins发版成功',  
            text: \[  
                "## 构建结果:\*\*${currentBuild.result}\*\*",  
                '---',  
                "## 构建信息",  
                '---',  
                "- 项目名称:${SERVER\_NAME}",  
                "- 构建环境:${ENVIRONMENT}",  
                "- 构建分支:${GIT\_REPO\_BRANCE}",  
                "- 构建标签:${GIT\_TAG}",  
                "- 项目地址:${GIT\_REPO\_URL}",  
                "- 构建用户:${env.BUILD\_USER}"  
                \],  
        //    messageUrl: '',  
        //    picUrl: '',  
        //    singleTitle: '',  
        //    btns: \[\],  
        //    btnLayout: '',  
        //    hideAvatar: false  
        )  
    }  
    failure{  
        echo "Deployment failed."  
        dingtalk (  
            robot: 'myapp-dingding-robot',  
            type: 'MARKDOWN',  // 发什么类型的消息,有TEXT、LINK、MARKDOWN、和ACTION\_CARD,参考https://jenkinsci.github.io/dingtalk-plugin/guide/pipeline.html  
            at: \[\],  
            atAll: false,  
            title: 'Jenkins发版失败',  
            text: \[  
                "## 构建结果:\*\*${currentBuild.result}\*\*",  
                '---',  
                "## 构建信息",  
                '---',  
                "- 项目名称:${SERVER\_NAME}",  
                "- 构建环境:${ENVIRONMENT}",  
                "- 构建分支:${GIT\_REPO\_BRANCE}",  
                "- 构建标签:${GIT\_TAG}",  
                "- 项目地址:${GIT\_REPO\_URL}",  
                "- 构建用户:${env.BUILD\_USER}"  
                \],  
        //    messageUrl: '',  
        //    picUrl: '',  
        //    singleTitle: '',  
        //    btns: \[\],  
        //    btnLayout: '',  
        //    hideAvatar: false  
        )  
    }  
}  

}

测试运行Pipeline

运行完成,钉钉也已收到通知,后续更新更多内容。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器