Jenkinsfile说明
当我们在使用jenkins进行CI/CD的时候,简单的内容我们可以通过jenkins页面来实现配置。但是如果有复杂的需求还是需要通过jenkinsfile来实现
jenkinsfile简单介绍
Jenkinsfile使用两种语法进行编写,分别是声明式和脚本式。下面我们会用声明式的写法来提供一个jenkinsfile,并进行说明
Jenkinsfile模板
pipeline { agent { kubernetes { yaml ''' apiVersion: v1 kind: Pod metadata: labels: jenkinsci: jenkins-golang spec: containers: - name: golang image: golang:1.18.3-bullseye tty: true - name: kaniko image: registry.cn-hangzhou.aliyuncs.com/cefso/kaniko-project.executor:debug command: ["/busybox/cat"] tty: true - name: kubelet image: registry.cn-hangzhou.aliyuncs.com/cefso/kubectl:v1.23.1 command: ["cat"] tty: true ''' } } environment { PROJECT_NAME = "${JOB_NAME.split('/')[0]}" VSERSION = "${BRANCH_NAME.replace('_', '-')}-${BUILD_ID}" } stages { stage('git clone') { steps { checkout scm } } stage('format & test') { steps { container('golang') { sh 'go fmt $(go list ./... | grep -v /vendor/)' sh 'go vet $(go list ./... | grep -v /vendor/)' sh 'go test -race $(go list ./... | grep -v /vendor/)' } } } stage('build') { steps { container('golang') { sh 'mkdir -p mybinaries' sh 'go build -o mybinaries/mybin-${VSERSION} ./...' } } } stage('archiveArtifacts') { steps { archiveArtifacts artifacts: 'mybinaries/*', followSymlinks: false } } stage('build images') { steps { container('kaniko') { sh 'mkdir -p /kaniko/.docker' sh 'export IFS=\'\'' withCredentials([usernamePassword(credentialsId: 'aliyunRegistry', usernameVariable: 'CI_REGISTRY_USER', passwordVariable: 'CI_REGISTRY_PASSWORD')]) { sh ''' echo 'diable bash debug' set +x echo "{\\"auths\\":{\\"registry.cn-hangzhou.aliyuncs.com\\":{\\"auth\\":\\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d \'\\n\')\\"}}}" > /kaniko/.docker/config.json set -x echo 'enable bash debug' ''' } sh ''' /kaniko/executor \ --context "${WORKSPACE}" \ --build-arg "version=${VSERSION}" \ --dockerfile "${WORKSPACE}/Dockerfile" \ --destination "registry.cn-hangzhou.aliyuncs.com/cefso/${PROJECT_NAME}:${VSERSION}" \ --force ''' } } } stage('deploy') { steps { container('kubelet') { withKubeConfig(credentialsId: 'jenkins-robot-secret', serverUrl: 'https://172.16.0.30:6443') { sh 'kubectl get pods -A' } } } } } }
jenkinsfile详解
agent部分和pod模板
jenkinsfile内容
kubernetes { yaml ''' apiVersion: v1 kind: Pod metadata: labels: jenkinsci: jenkins-golang spec: containers: - name: golang image: golang:1.18.3-bullseye tty: true - name: kaniko image: registry.cn-hangzhou.aliyuncs.com/cefso/kaniko-project.executor:debug command: ["/busybox/cat"] tty: true - name: kubelet image: registry.cn-hangzhou.aliyuncs.com/cefso/kubectl:v1.23.1 command: ["cat"] tty: true ''' } }
我们这里使用了jenkins的kubernetes插件,并且jenkins是部署在k8s集群中的,所以无需额外配置即可将我们的构建放入到kubernetes集群中以pod的形式运行。如果jenkins没有安装到k8s集群中则需要手动配置才能以pod的形式进行构建。
kubernetes插件查考文档
https://plugins.jenkins.io/kubernetes/
配置的位置如下
系统管理-节点管理
configure clouds
在这里配置连接到k8s集群即可
这里我们通过yaml文件的形式定义了我们构建时使用的pod信息,pod包含3个容器
apiVersion: v1 kind: Pod metadata: labels: jenkinsci: jenkins-golang spec: containers: # 容器1,名称golang,镜像为golang:1.18.3-bullseye - name: golang image: golang:1.18.3-bullseye tty: true # 容器1,名称kaniko,镜像为registry.cn-hangzhou.aliyuncs.com/cefso/kaniko-project.executor:debug - name: kaniko image: registry.cn-hangzhou.aliyuncs.com/cefso/kaniko-project.executor:debug command: ["/busybox/cat"] tty: true # 容器1,名称kubelet,镜像为registry.cn-hangzhou.aliyuncs.com/cefso/kubectl:v1.23.1 - name: kubelet image: registry.cn-hangzhou.aliyuncs.com/cefso/kubectl:v1.23.1 command: ["cat"] tty: true
其中
golang的容器是用来进行代码的构建等操作的,如果后续需要构建java应用就可以调整为maven的镜像来使用
kaniko容器用来构建我们的docker镜像(这部分可以通用)
kubelet容器是用来做应用的部署的(这部分也可以通用)
这里需要注意我们的每个容器都有不同的名字,后续我们可以通过容器的名字来指定我们当前的pipeline步骤运行在那个容器中
环境变量配置
我们将后续常用的一些环境变量进行配置,方便后续的使用
environment { PROJECT_NAME = "${JOB_NAME.split('/')[0]}" VSERSION = "${BRANCH_NAME.replace('_', '-')}-${BUILD_ID}" }
克隆代码
这里我们使用了jenkinsfile的一个语法,这里的checkout scm是在多分支流水线中最常用的一个语法。jenkins会根据我们的现在正在构建的分支来克隆相关代码。
stage('git clone') { steps { // 克隆当前分支代码(即我们构建的是那个分支就克隆那个分支的代码) checkout scm } }
格式化代码和测试
这里是golang代码常用的一个步骤,对代码进行格式化和测试
stage('format & test') { steps { container('golang') { sh 'go fmt $(go list ./... | grep -v /vendor/)' sh 'go vet $(go list ./... | grep -v /vendor/)' sh 'go test -race $(go list ./... | grep -v /vendor/)' } } }
这里我们需要注意一段代码
container('golang') { // *** }
还记的我们上面说我们可以使用指定的容器来进行pipeline的操作,这里我们就通过container
的语法来指定了当前操作的步骤是在golang
的容器中运行
构建
这部分没什么好说的,正常的构建代码
stage('build') { steps { container('golang') { sh 'mkdir -p mybinaries' sh 'go build -o mybinaries/mybin-${VSERSION} ./...' } } }
产物
产物是经常被忽略的部分。但是通过产物功能,我们可以很方便的在页面上下载好构建的成品,无论是进行测试,还是部署问题排查都会很方便。尤其是当我们在pod中进行构建,构建完成后pod就会被销毁,此时产物就显得更加重要
stage('archiveArtifacts') { steps { archiveArtifacts artifacts: 'mybinaries/*', followSymlinks: false } }
镜像构建和推送
stage('build images') { steps { container('kaniko') { sh 'mkdir -p /kaniko/.docker' sh 'export IFS=\'\'' withCredentials([usernamePassword(credentialsId: 'aliyunRegistry', usernameVariable: 'CI_REGISTRY_USER', passwordVariable: 'CI_REGISTRY_PASSWORD')]) { sh ''' echo 'diable bash debug' set +x echo "{\\"auths\\":{\\"registry.cn-hangzhou.aliyuncs.com\\":{\\"auth\\":\\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d \'\\n\')\\"}}}" > /kaniko/.docker/config.json set -x echo 'enable bash debug' ''' } sh ''' /kaniko/executor \ --context "${WORKSPACE}" \ --build-arg "version=${VSERSION}" \ --dockerfile "${WORKSPACE}/Dockerfile" \ --destination "registry.cn-hangzhou.aliyuncs.com/cefso/${PROJECT_NAME}:${VSERSION}" \ --force ''' } } }
Kaniko
https://github.com/GoogleContainerTools/kaniko
kaniko是谷歌开源的一款用来构建容器镜像的工具。与docker不同,Kaniko 并不依赖于Docker daemon进程,完全是在用户空间根据Dockerfile的内容逐行执行命令来构建镜像,这就使得在一些无法获取 docker daemon
进程的环境下也能够构建镜像,比如在标准的Kubernetes Cluster上。
Kaniko 以容器镜像的方式来运行的,同时需要三个参数: Dockerfile,上下文,以及远端镜像仓库的地址。
Kaniko会先提取基础镜像(Dockerfile FROM 之后的镜像)的文件系统,然后根据Dockerfile中所描述的,一条条执行命令,每一条命令执行完以后会在用户空间下面创建一个snapshot,并与存储与内存中的上一个状态进行比对,如果有变化,就将新的修改生成一个镜像层添加在基础镜像上,并且将相关的修改信息写入镜像元数据中。等所有命令执行完,kaniko会将最终镜像推送到指定的远端镜像仓库。
需要说明几点:
args 部分
这部分就是上面所讲的,kaniko运行时需要三个参数: Dockerfile(--dockerfile),上下文(--context),远端镜像仓库(--destination)
secret 部分
推送至指定远端镜像仓库需要credential的支持,所以需要将credential以secret的方式挂载到/kaniko/.docker/这个目录下,文件名称为config.json,内容如下:
{ "auths": { "https://index.docker.io/v1/": { "auth": "AbcdEdfgEdggds=" } } }
其中auth的值为: echo"docker_registry_username:docker_registry_password"|base64
部署
这里我们又多了一个语法withKubeConfig
,通过这个语法我们可以实现在容器中使用kubectl命令时的鉴权。该语法由Kubernetes CLI插件提供
stage('deploy') { steps { container('kubelet') { withKubeConfig(credentialsId: 'jenkins-robot-secret', serverUrl: 'https://172.16.0.30:6443') { sh 'kubectl set image deployment/${PROJECT_NAME} ${PROJECT_NAME}=registry.cn-hangzhou.aliyuncs.com/cefso/${PROJECT_NAME}:${VSERSION}' } } } }
Kubernetes CLI 参考链接
https://plugins.jenkins.io/kubernetes-cli/
创建jenkins访问集群凭证
创建serviceaccount
kubectl -n default create serviceaccount jenkins-robot
创建clusterrolebinding,我们将权限绑定到cluster-admin上
kubectl -n default create clusterrolebinding jenkins-robot-binding --clusterrole=cluster-admin --serviceaccount=default:jenkins-robot
1.22以上版本集群并不会自动为serviceaccount创建token,所以我们需要手动创建token
kubectl apply -f - <<EOF apiVersion: v1 kind: Secret metadata: name: jenkins-robot-secret annotations: kubernetes.io/service-account.name: jenkins-robot type: kubernetes.io/service-account-token EOF
获取我们创建的token
kubectl -n default get secrets jenkins-robot-secret -o go-template --template '{{index .data "token"}}' | base64 -d
将token配置到jenkins上
类型选择Secret text,将我们上面获取的token填写到Secret中,后续通过ID调用即可
// 这里credentialsId填写我们上面的ID,serverUrl填写集群api server地址即可 withKubeConfig(credentialsId: 'jenkins-robot-secret', serverUrl: 'https://172.16.0.30:6443') { // *** }