持续集成的构建环境

功能介绍

构建环境是指用来运行构建任务的环境,它通常是一个预装了开发语言 SDK、命令行工具的环境,分为两种:

  • 默认环境:操作系统环境
  • Docker 环境:Docker 镜像或 Dockerfile

默认环境

「使用默认构建环境」即在「构建节点」的操作系统上运行。CODING 持续集成为每个构建任务分配一台 Linux 云主机(VM),即「CODING 云主机」,也可以接入「自定义构建节点」。

对应的 Jenkinsfileagent any

pipeline {
  agent any
  stages  {
    stage("检出") {...}
    stage("检查代码规范") {...}
  }
}

「CODING 云主机」为 Ubuntu 系统,预装了以下 SDK 和命令行工具:

SDK:

  • android-sdk: 26.1.1
  • build-essential
  • dotnet-core: 2.2
  • elixir: 1.8.1
  • erlang: Erlang/OTP 21
  • go: 1.14.4
  • java: 1.8.0_191
  • nodejs: 10
  • php: 8.0、7.4、7.3
  • python3/pip3: 3.9、3.8、3.7
  • python: 2.7.12
  • ruby: 2.6.0

命令行工具:

  • bundler: 1.17.2
  • cmake: 3.5.1
  • composer:1.10.8
  • coscmd:1.8.5.36
  • docker-compose: 1.26.0
  • docker:20.10.6
  • git-lfs: 2.7.2
  • git:2.28.0
  • gradle: 4.10.2
  • helm: 2.13.1
  • jq: 1.5-1-a5b5cbe
  • kubectl: 1.18.4
  • maven: 3.6.3
  • mercurial: 3.7.3
  • pigz: 2.3.1
  • rancher: 2.2.0
  • rvm: 1.29.7
  • sshpass: 1.05
  • svn: 1.9.3
  • tccli: 3.0.67.1
  • vsftpd: 3.0.3
  • yarn: 1.15.2

预装的软件版本有限且定期升级,而各个项目需要的版本不同,所以建议自定义版本或使用 Docker 环境

自定义版本

在持续集成中可自行下载安装软件的各种版本,如果官网下载较慢,建议从内地的「CODING 公共制品库」下载。如需增加软件,欢迎提交到开源项目

Go

stage('Go') {
  steps {
    // 建议设置「缓存目录」 /root/.cache/downloads
    sh 'mkdir -p /root/.cache/downloads'
    dir ('/root/.cache/downloads') {
      sh 'wget -nc "https://coding-public-generic.pkg.coding.net/public/downloads/go-linux-amd64.tar.gz?version=1.16.4" -O go-linux-amd64-1.16.4.tar.gz | true'
      // sh 'wget -nc "https://coding-public-generic.pkg.coding.net/public/downloads/go-linux-amd64.tar.gz?version=1.15.12" -O go-linux-amd64-1.15.12.tar.gz | true'
      sh 'tar -zxvf go-linux-amd64-1.16.4.tar.gz -C /root/programs'
    }
    sh 'go version'
  }
}

Helm

stage('Helm') {
  steps {
    sh 'mkdir -p /root/.cache/downloads'
    dir ('/root/.cache/downloads') {
      sh 'wget -nc "https://coding-public-generic.pkg.coding.net/public/downloads/helm-linux-amd64.tar.gz?version=v3.6.0" -O helm-linux-amd64-v3.6.0.tar.gz | true'
      sh "tar -zxvf helm-linux-amd64-v3.6.0.tar.gz -C \$HELM_BIN linux-amd64/helm --strip-components 1"
    }
    sh 'helm version'
  }
}

kubectl

stage('kubectl') {
  steps {
    sh 'mkdir -p /root/.cache/downloads'
    dir ('/root/.cache/downloads') {
      sh 'wget -nc "https://coding-public-generic.pkg.coding.net/public/downloads/kubectl-linux-amd64?version=v1.21.0" -O kubectl-linux-amd64-v1.21.0 | true'
      sh 'cp kubectl-linux-amd64-v1.21.0 /usr/local/bin/kubectl'
    }
    sh 'chmod +x /usr/local/bin/kubectl'
    sh 'kubectl version --client'
  }
}

Node.js

stage('Node.js') {
  steps {
    sh 'curl -fsSL https://deb.nodesource.com/setup_14.x | bash -'
    // sh 'curl -fsSL https://deb.nodesource.com/setup_16.x | bash -'
    sh 'apt-get install -y nodejs'
    sh 'node -v'
  }
}

PHP

pipeline {
  agent {
    docker {
      reuseNode 'true'
      registryUrl 'https://coding-public-docker.pkg.coding.net'
      image 'public/docker/php:8.0'
      // image 'public/docker/php:7.4' 以及 7.3、7.2、7.1、5.6
      args '-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker'
    }
  }
  stages {
    stage('安装依赖') {
      steps {
        // Possible values for ext-name:
        // bcmath bz2 calendar ctype curl dba dom enchant exif fileinfo filter ftp gd gettext gmp
        // hash iconv imap interbase intl json ldap mbstring mysqli oci8 odbc opcache pcntl pdo
        // pdo_dblib pdo_firebird pdo_mysql pdo_oci pdo_odbc pdo_pgsql pdo_sqlite pgsql phar posix pspell
        // readline recode reflection session shmop simplexml snmp soap sockets sodium spl standard
        // sysvmsg sysvsem sysvshm tidy tokenizer wddx xml xmlreader xmlrpc xmlwriter xsl zend_test zip
        sh 'apt-get update && apt-get install -y libbz2-dev'
        sh 'docker-php-ext-install bz2'
        sh 'php -i | grep bz2'
      }
    }
  }
}

Docker 环境

使用 Docker 构建环境有几种方式:

  • 使用 CODING 官方提供的镜像;
  • 使用存放在项目内制品库的 Docker 镜像:适用于项目层级的标准构建环境,保障项目内镜像安全,方便管理,通过项目令牌您也可以拉取其他项目的镜像;
  • 使用指定 Registry 地址(默认为 Docker Hub)的 Docker 镜像:适用于拉取储存在其他平台的镜像;
  • 使用 Dockerfile 脚本构建环境:适用于灵活搭建构建环境的需求;

CODING 官方 Docker 镜像

在【持续集成计划设置】->【流程配置】->【基础配置】->【图形化编辑器】中,选择 「使用 CODING 官方提供的 Docker 镜像」,比如 Node.js 14:

对应的 Jenkinsfile 为:

pipeline {
  agent {
    docker {
      reuseNode 'true'
      registryUrl 'https://coding-public-docker.pkg.coding.net'
      image 'public/docker/nodejs:14'
    }
  }
  stages {
    stage('Test') {
      steps {
        sh 'node --version'
      }
    }
  }
}

项目制品库 Docker 镜像

对应的 Jenkinsfile

pipeline {
  agent {
    docker {
      reuseNode 'true'
      registryUrl 'https://codes-farm-docker.pkg.coding.net'
      registryCredentialsId "${env.DOCKER_REGISTRY_CREDENTIALS_ID}"
      image 'coding-workshop/release/workshop-web-app:1.2.3'
    }
  }
}

如何推送自定义构建环境镜像到制品库并作持续集成构建环境,请参见在持续集成中使用自定义 Docker 构建环境

注意:env.CODING_ARTIFACTS_CREDENTIALS_ID 只可用于本项目的制品库,而跨项目使用制品库需按照下列步骤进行操作:

  1. 在制品库所在的项目中创建「项目令牌」,获得用户名和密码;
  2. 在持续集成所在的项目中录入「项目凭据」,选择「用户名+密码」类型,并授权给持续集成;
  3. 在持续集成中添加环境变量 DOCKER_CREDENTIALS_ID,取值选用「项目凭据」,上方的代码也对应改成 registryCredentialsId env.DOCKER_CREDENTIALS_ID

如何将「项目令牌」录入凭据请参见在持续集成中使用凭据

指定地址的 Docker 镜像

其中,「Docker 镜像」为必填项,需要填入您的镜像名称。

「Registry 地址」需填写的格式为不带路径的 URL 地址,例如 https://codes-farm-docker.pkg.coding.net 而不是 https://codes-farm-docker.pkg.coding.net/laravel-demo/laravel-docker/

若拉取私有镜像,需填写 「Registry 认证凭据 ID」,如何录入凭据请参见在持续集成中使用凭据

对应的 Jenkinsfile

pipeline {
  agent {
    docker {
      image 'node:14-alpine'
      reuseNode 'true'
    }
  }
  stages {
    stage('Test') {
      steps {
        sh 'node --version'
      }
    }
  }
}

Dockerfile

如果项目已经使用 Docker,建议把 Dockerfile 提交到代码库,用它作为持续集成构建环境。Dockerfile 示例代码:

FROM php:8.0-apache

RUN apt-get update \
  && apt-get install -y unzip

Jenkinsfile

pipeline {
  agent any
  stages {
    stage('Checkout') {
      steps {
        checkout([
          $class: 'GitSCM', 
          branches: [[name: env.GIT_BUILD_REF]], 
          userRemoteConfigs: [[url: env.GIT_REPO_URL, credentialsId: env.CREDENTIALS_ID]]
        ])
      }
    }
    stage('Use Docker') {
      agent {
        dockerfile {
          filename 'Dockerfile' // 可选,自定义 Dockerfile 文件名
          dir 'build' // 可选,Dockerfile 所在目录
          additionalBuildArgs  '--build-arg version=1.0.2' // 可选,docker build 自定义参数
        }
      }
      stages {
        stage('Test') {
          steps {
            sh 'php -v'
            sh 'unzip -v'
          }
        }
      }
    }
  }
}

每次构建都根据 Dockerfile 进行 docker build 会浪费时间,推荐进阶阅读:《最佳实践 - Jenkins Dockerfile 保存镜像用于下次构建》

每个阶段不同 Docker

pipeline {
  agent none
  stages {
    stage('Back-end') {
      agent {
        docker {
          image 'maven:3-alpine'
          reuseNode 'true'
        }
      }
      steps {
        sh 'mvn --version'
      }
    }
    stage('Front-end') {
      agent {
        docker {
          image 'node:14-alpine'
          reuseNode 'true'
        }
      }
      steps {
        sh 'node --version'
      }
    }
  }
}

多个 Docker 后台

自动化测试往往需要临时的基础设施(比如 MySQL、Redis、Elasticsearch),创建一个桥接网络,在其中启动多个 Docker 后台即可,测试完毕自动删除。

node {
  stage("检出") {
    checkout([
      $class: 'GitSCM',
      branches: [[name: GIT_BUILD_REF]],
      userRemoteConfigs: [[
        url: GIT_REPO_URL,
        credentialsId: CREDENTIALS_ID
    ]]])
  }
  stage('准备数据库') {
    sh 'docker network create bridge1'
    sh(script:'docker run --net bridge1 --name mysql -d -e "MYSQL_ROOT_PASSWORD=my-secret-pw" -e "MYSQL_DATABASE=test_db" mysql:5.7', returnStdout: true)
    sh(script:'docker run --net bridge1 --name redis -d redis:5', returnStdout: true)
  }
  docker.image('ecoding/php:8.0').inside("--net bridge1 -v \"${env.WORKSPACE}:/root/code\" -e 'APP_ENV=testing' -e 'DB_DATABASE=test_db'" +
      " -e 'DB_USERNAME=root' -e 'DB_PASSWORD=my-secret-pw' -e 'DB_HOST=mysql' -e 'REDIS_HOST=redis'" +
      " -e 'APP_KEY=base64:tbgOBtYci7i7cdx5RiFE3KZzUkRtJfbU3lbj5uPdL8U='") {
    sh 'composer install'

    stage('单元测试') {
      sh 'XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html storage/reports/tests/ --log-junit storage/test-results/junit.xml --coverage-text tests/'
      junit 'storage/test-results/junit.xml'
      codingHtmlReport(name: '测试覆盖率报告', path: 'storage/reports/tests/')
    }
  }
}

根节点工作空间

在自定义 Docker 构建环境时,可以选择是否使用根节点的工作空间。当勾选该选项时,当前 stage 的 Docker 容器会和 pipeline 在同一个 node 运行,当前 stage 在运行时可以获取 pipeline 工作空间(workspace)的根目录下保存的所有文件。

对应的 Jenkinsfile 参数为 reuseNode ,类型:Boolean,默认为 false:

pipeline {
  agent {
    docker {
      registryUrl 'https://coding-public-docker.pkg.coding.net'
      image 'public/docker/android:29'
    }
  }
  stages {
    // 代码被检出到 pipeline agent 的工作空间根目录下
    stage('检出代码') {
      steps {
        checkout([
          $class: 'GitSCM', 
          branches: [[name: env.GIT_BUILD_REF]], 
          userRemoteConfigs: [[url: env.GIT_REPO_URL, credentialsId: env.CREDENTIALS_ID]]
        ])
      }
    }
    stage('单元测试') {
      agent {
        dockerfile {
          // 默认在当前节点工作空间根目录下找名为 「Dockerfile」的文件构建环境
          filename 'Dockerfile'
          // 如果 reuseNode 为 false,则无法找到之前检出到 pipeline agent 的工作空间根目录下的 Dockerfile
          reuseNode true
        }
      }
      steps {
        sh 'npm run test:ci'
        junit '*.xml'

      }
    }
  }
}

Docker in Docker

在 Jenkins Docker 环境中执行 docker 命令时,需要挂载外部虚拟机的 docker socket,否则会报错「docker: command not found」。

pipeline {
  agent {
    docker {
      image 'ecoding/php:8.0'
      reuseNode 'true'
      // 挂载外部虚拟机的 docker socket
      args '-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker'
    }
  }
  stages {
    stage('自定义阶段') {
      steps {
        sh 'php -v'
      }
    }
    stage('构建 Docker 镜像') {
      steps {
        sh 'docker -v'
        script {
          docker.withRegistry("https://${env.CCI_CURRENT_TEAM}-docker.pkg.coding.net", "${env.CODING_ARTIFACTS_CREDENTIALS_ID}") {
            //docker.build("foo:bar").push()
          }
        }
      }
    }
  }
}

如果使用的是「自定义构建节点」,请先执行这些操作:

  1. 在自有服务器上安装 Java、Docker;
  2. 在 CODING 界面接入节点,请勿勾选「使用 Docker 运行构建节点」;

参考资料

上一篇持续集成快速入门
最近更新
感谢反馈有用
感谢反馈没用

在阅读中是否遇到以下问题?

您希望我们如何改进?