经过前一篇文章的介绍以后,大家对于声明式的 jenkinsfile 有了一定程度的了解。现在可以开始动手写一个专属于自己服务的 jenkinsfile 文件。

注:解放双手,从写 jenkinsfile 开始。写不了吃亏,写不了上当。

1. 编写业务相关的 Jenkinsfile

思考一下编写一个 Jenkinsfile 需要几个部分,然后逐个实践一下,属于自己服务的 jenkinsfile 就完成了。

注:以下是笔者总结的几个基础部分,如有不同的建议欢迎评论。

在这里插入图片描述

接下来将会就每一个部分详细的介绍和说明。逐个介绍完成之后,将会提供一个完整的 jenkinsfile 的 demo。

2. 入参及环境变量的配置

2.1 配置说明

此处主要示范 入参以及 环境变量如何配置,所以选择最简单的 agent 的方式进行示例。

注:某些 jenkins 集群,配置 agent 为 any 无法执行,所以此处选择了 label 的方式。

pipeline {
    agent {
        label 'node' // 此处需要自己选择合适的 node
    }
    parameters {
        string(name: 'branch', defaultValue: 'master', description: 'the branch')
    }
    
    environment {
        BASE_USER_CREDENTIALS = credentials('harbor') //配置正确的 harbor 用户
    }

    stages {
        stage('input') {
            steps {
                echo "${env.branch}"
            }
        }
        
        stage('environment') {
            steps {
                echo "${BASE_USER_CREDENTIALS_USR}"
                echo "${env.BUILD_NUMBER}" //内置环境变量
            }
        }
        
    }
}

注:在 steps 块中执行多行 sh 时,有两种方式,""" (三个双引号)和'''(三个单引号),区别在于双引号里面的引用的变量会被计算展开。

2.2 配置后的界面展示

  1. 配置了 parameters 后在触发编译时,需要填写该参数的值。

在这里插入图片描述

  1. 使用 credentials 获取环境变量

    在这里插入图片描述

3. 定义一个 kubernetes 的 agent

问:为什么使用 kubernetes 配置?

答:从可操作性的角度上分析,kubernetes 的方式更方便。每次构建的时候都是基于配置生成的一个崭新的环境,构建结束后环境会自动销毁。即使多次构建也不会互相干扰。(ps 虽然写起来有一些复杂

Kubernetes 的容器集,是独立于 jenkins slave 的机器,为了让定义的 kubernetes agent 能够正常运行,需要包含以下三个部分

  • 配置完成的 jnlp 容器 (jenkins 的 slave)
  • 支持编译特定语言的容器(比如,go 、 c++)
  • 能够将二进制打包为 docker 镜像的容器(比如, docker in docker 、kaniko)

以下分别介绍公司内部可以用的 docker in docker 和 kaniko。

3.1 基于 docker in docker 构建

在这里插入图片描述

如上图,可以在 Container 中直接运行一个 Docker Daemon,然后 Container 中的 Docker CLI 工具操作容器。

注:这种方式下,容器中的 Docker Daemon 完全独立于外部,具有良好的隔离特性。看起来,Container 类似一个 VM,但是 DinD 的作者自己也不是很推荐。

​ 主要还是安全问题。DinD 需要以特权模式启动,这种嵌套会带来潜在的安全风险。

以下是一份 docker in docker 容器集的配置

metadata:
  labels:
    label-name: jenkins-demo
spec:
  imagePullSecrets:
    - regcred
  containers:
  - name: jnlp
    resources:
      limits:
        cpu: 0.45
        memory: 450Mi
      requests:
        cpu: 0.45
        memory: 450Mi
  - name: streaming
    image: hub.xxxx.co/ubuntu:golang-1.16 //配置正确的 hub 地址
    imagePullPolicy: Always
    command:
    - cat
    tty: true
    resources:
      limits:
        cpu: 1
        memory: 1Gi
      requests:
        cpu: 1
        memory: 1Gi
  - name: docker
    image: hub.xxx.co/compiling:1.1.11-cache //配置正确的 hub 地址
    imagePullPolicy: IfNotPresent
    command:
    - cat
    tty: true
    resources:
      limits:
        cpu: 1
        memory: 1Gi
      requests:
        cpu: 1
        memory: 1Gi
    securityContext:
      privileged: true
    env:
      - name: DOCKER_HOST
        value: tcp://localhost:2375

  - name: dind
    image: docker:18.05-dind
    resources:
      limits:
        cpu: 0.5
        memory: 550Mi
      requests:
        cpu: 0.5
        memory: 500Mi
    securityContext:
      privileged: true
    args:
      - "--mtu=1450"
    volumeMounts:
      - name: dind-storage
        mountPath: /var/lib/docker

  volumes:
    - name: dind-storage
      emptyDir: {}

3.2 基于 kaniko 构建镜像

Google 发布了“ Kaniko ”,一种用于在未授权容器或 Kubernetes 集群中构建容器镜像的开源工具。虽然 Kaniko 也是根据用户给定的 Dockerfile 构建镜像,但是并不依赖于 Docker 守护进程,而是在用户空间中完全执行每个命令,并对更改的文件系统做快照。

以下是一份 kaniko 的容器集的配置

metadata:
  labels:
    label-name: jenkins-demo
spec:
  imagePullSecrets:
    - regcred
  containers:
  - name: jnlp
    resources:
      limits:
        cpu: 0.45
        memory: 450Mi
      requests:
        cpu: 0.45
        memory: 450Mi
  - name: streaming
    image: hub.xxxx.co/ubuntu:golang-1.16 //配置正确的 hub 地址
    imagePullPolicy: Always
    command:
    - cat
    tty: true
    resources:
      limits:
        cpu: 1
        memory: 1Gi
      requests:
        cpu: 1
        memory: 1Gi
    securityContext:
      privileged: true
    env:
      - name: DOCKER_HOST
        value: tcp://localhost:2375
  - name: kaniko
    image: gcr.io/kaniko-project/executor:v1.7.0-debug
    command:
      - cat
    tty: true
    resources:
      limits:
        cpu: 2
        memory: 2000Mi
      requests:
        cpu: 2
        memory: 2000Mi
    volumeMounts:
      - name: jenkins-docker-cfg
        mountPath: /kaniko/.docker
  volumes:
    - name: jenkins-docker-cfg
      secret:
        secretName: regcred
        items:
          - key: .dockerconfigjson
            path: config.json

4. 拉取代码 & 执行编译

4.1 拉取代码

git 命令,带有四个参数

  • branch:执行拉取的分支
  • credentialsID:访问该仓库的认证
  • url: 拉取仓库的git地址
  • changelog:是否打印信息

有了执行环境之后,接下来的一步就是「拉取代码 & 执行编译」,以下是一个拉取代码部分 jenkinsfile 的示例。

    environment {
        BITBUCKET = 'bitbucket-ssh-access'
    }

    stages {
        stage('pull code') {
            steps {
                container('streaming') {
                    buildName "#${env.BUILD_NUMBER}"
                    git branch: "${env.branch}", credentialsId: "${env.BITBUCKET}", url: 'ssh://git@git.xxxx.co/service.git' //配置正确的 git 地址
                }
            }
        }
        
    }

4.2 执行编译

此处编译的示例使用的 golang ,其他语言的后面系列文章会继续完善。在执行编译前需要将 go 的环境变量再次抛出。以下是执行编译部分 jenkinsfile 的示例

    stage('excuting script') {
            steps {
                container('streaming') {
                    sh """
                    export PATH="$PATH:/usr/lib/go-1.16/bin"
                    export GOPROXY=https://mirrors.aliyun.com/goproxy/
                    make build	// 代码库内已经完成了 makefile 文件的编写
                    """
                }
            }
        }

5. 构建 & 推送 docker 镜像

有了步骤 4 中编译的二进制结果,剩下的就是将二进制结果制作成镜像同时推送到 docker hub。至此你将收获一个完整的自动化构建 jenkinsfile 的完整版本。

注:不论是 docker in docker 还是 kaniko 都使用挂载 emptyDir 的方式,来实现容器间数据的共享。

以下是执行构建、推送 docker 镜像的部分 jenkinsfile 示例。

 environment {
        // credentials for other service, you can find it at:
        // Manager Jenkins -> Manger Credentials
        BASE_USER_CREDENTIALS = credentials('harbor1')
        DOCKER_USER_CREDENTIALS = credentials('harbor2')
    }
  stage ('Build Image') {
        steps {
          container('docker') {
            sh """#!/bin/bash
              set -xeu
              docker -v
              //配置正确的 hub 地址
              docker login hub.xxxx.co -u \'${env.DOCKER_USER_CREDENTIALS_USR}\' -p \'${env.DOCKER_USER_CREDENTIALS_PSW}\'
              make image_dev
              //配置正确的 hub 地址
              docker login hub.xxxx.co -u \'${env.BASE_USER_CREDENTIALS_USR}\' -p \'${env.BASE_USER_CREDENTIALS_PSW}\'
              make publish_dev
            """
          }
        }
      }

6. 总结

以上最小可以使用的 jenkinsfile 的每一个部分的详细介绍。最后将它们组合在一起,展示一个完整的🌰:

pipeline {
    agent {
        kubernetes {
            yaml '''
metadata:
  labels:
    label-name: jenkins-demo
spec:
  imagePullSecrets:
    - regcred
  containers:
  - name: jnlp
    resources:
      limits:
        cpu: 0.45
        memory: 450Mi
      requests:
        cpu: 0.45
        memory: 450Mi
  - name: streaming
    image: hub.xxxx.co/ubuntu:golang-1.16 //配置正确的 hub 地址
    imagePullPolicy: Always
    command:
    - cat
    tty: true
    resources:
      limits:
        cpu: 1
        memory: 1Gi
      requests:
        cpu: 1
        memory: 1Gi
  - name: docker
    image: hub.xxxx.co/compiling:1.1.11-cache //配置正确的 hub 地址
    imagePullPolicy: IfNotPresent
    command:
    - cat
    tty: true
    resources:
      limits:
        cpu: 1
        memory: 1Gi
      requests:
        cpu: 1
        memory: 1Gi
    securityContext:
      privileged: true
    env:
      - name: DOCKER_HOST
        value: tcp://localhost:2375

  - name: dind
    image: docker:18.05-dind
    resources:
      limits:
        cpu: 0.5
        memory: 550Mi
      requests:
        cpu: 0.5
        memory: 500Mi
    securityContext:
      privileged: true
    args:
      - "--mtu=1450"
    volumeMounts:
      - name: dind-storage
        mountPath: /var/lib/docker

  volumes:
    - name: dind-storage
      emptyDir: {}
'''
        }
    }

    environment {
        // credentials for other service, you can find it at:
        // Manager Jenkins -> Manger Credentials
        BASE_USER_CREDENTIALS = credentials('harbor1') //配置正确的 hub 地址
        DOCKER_USER_CREDENTIALS = credentials('harbor2') //配置正确的 hub 地址
       	BITBUCKET = 'bitbucket-ssh-access'
    }
    stages {
        stage('pulling code') {
            steps{
                container('streaming') {
                    buildName "#${env.BUILD_NUMBER}"
                    git branch: "${env.branch}", credentialsId: "${env.BITBUCKET}", url: 'ssh://git@git.xxxxx.co/service.git' //配置正确的 hub 地址
                }
            }
        }

        stage('excuting script') {
            steps {
                container('streaming') {
                    sh """
                    export PATH="$PATH:/usr/lib/go-1.16/bin"
                    export GOPROXY=https://mirrors.aliyun.com/goproxy/
                    make build
                    """
                }
            }
        }

      stage ('Build Image') {
        steps {
          container('docker') {
            sh """#!/bin/bash
              set -xeu
              docker -v
              //配置正确的 hub 地址
              docker login hub.xxxx.co -u \'${env.DOCKER_USER_CREDENTIALS_USR}\' -p \'${env.DOCKER_USER_CREDENTIALS_PSW}\'
              make image_dev
              //配置正确的 hub 地址
              docker login hub.xxxx.co -u \'${env.BASE_USER_CREDENTIALS_USR}\' -p \'${env.BASE_USER_CREDENTIALS_PSW}\'
              make publish_dev
            """
          }
        }
      }
    }
}

7. 碎碎念

马上就是元旦了,希望大家都能万事胜意。

  • 要在最快乐的年纪活的精彩且迷人。

  • 愿听我碎碎念念的人,都可以陪我岁岁年年。

  • 你的人生要自己去经历,所以是甜是苦,都不要辜负。

8. 参考资料

Logo

开源、云原生的融合云平台

更多推荐