从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.gzmaven的配置(可选) 很多java程序会用maven来进行构建,maven中又存在很多依赖组件(常用的是jar包、war包、pom等,也可把Zip包等通过POM文件定义为依赖组件),这个时候就会有一个仓库的概念,这个仓库分为三种类型,即:
- central:中央仓库,是由Maven社区提供的资源仓库,它包含了大量的常用程序库组件(jar包)。默认Maven的中央仓库地址为:http://repo1.maven.org/maven2/
- local:本地仓库,是存放maven环境本地的一个文件夹,此文件夹在第一次运行Maven命令时就创建了。Maven在执行构建任务时,根据依赖关系从中心仓库、或远程仓库下载依赖组件到本地仓库,然后本地仓库的内容供项目引用。
- remote:远程仓库,例如项目需要指定外部其他公司、或开源组织的jar包,这些依赖组件通用性等原因,未纳入Maven中央仓库,这个时候就要手动指定一个私有的远程仓库来拉取依赖。
<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中是否指定了仓库地址,类似语句:
<repositories> <repository> <id>springsource-repos</id> <name>SpringSource Repository</name> <url>http://repo.spring.io/release/</url> </repository> </repositories>
修改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:v1registry.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等
- Git
- Git Parameter
- DingTalk
- build user vars plugin
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 运行完成,钉钉也已收到通知,后续更新更多内容。 标签:CI,GIT,name,devops,agent,CD,maven,jenkins,docker From: https://www.cnblogs.com/v-fan/p/17337306.html