【翻译】在jenkins流水线使用docker

许多团队和组织使用docker来跨平台的测试,构建,发布他们的项目, docker提供了非常好高效的部署效率。Jenkins 在2.5版本以后加入了pipeline功能,pipeline支持在Jenkinsfile里面执行docker相关的操作。本文将会介绍在Jenkinsfile中执行docker的相关操作。

自定义执行环境

pipeline能够使用一个docker image指定执行环境,既可以为整个pipeline指定指定环境,也可以为单个stage指定执行环境。

1
2
3
4
5
6
7
8
9
10
11
12
pipeline {
agent {
docker {image: 'node:7-alpine'}
}
stages {
stage('test'){
steps{
sh 'node --version'
}
}
}
}

当这个pipeline执行的时候,Jenkins会自动的启动一个容器来执行指定的steps

1
2
3
4
5
6
7
8
9
[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] sh
[guided-tour] Running shell script
+ node --version
v7.4.0
[Pipeline] }
[Pipeline] // stage
[Pipeline] }

为容器缓存数据

许多的构建工具会下载一些外部的依赖并且缓存到本地,将来再次构建的时候会用到这些数据。pipeline支持传递自定义参数给docker命令,允许在docker执行的挂载本地的文件,这个能够缓存容器执行过程产生的数据.例如:maven构建过程中会缓存数据到~/.m2这个文件夹中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pipeline {
agent {
docker {
image: 'maven:2-alpine'
args: '-v $HOME/.m2:/root/.m2'
}
}
stages {
stage {
steps {
sh 'mvn -B'
}
}
}
}

使用多种容器

一个项目可能使用java写后端,使用javascript写前端,我们要运行他,就需要在不同的stage中使用相应的容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pipeline {
agent none
stages {
stage('back-end') {
agent {
docker {image: 'maven:2-alpine'}
}
steps {
sh 'mvn --vesion'
}
}
stage('front-end') {
agent {
docker {image: 'node:7-alpine'}
}
steps {
sh 'node --version'
}
}
}
}

使用Dockerfile

pipeline也支持从Dockerfile自定义一个执行环境,而不需要从Docker Hub上pull一个镜像到本地。使用agent {dockerfile true}语法允许从本地的Dockerfile文件构建一个镜像。一个本地的Dockerfile文件的如下:

1
2
FROM node:7-alpin
RUN APK add -U subvesion

Jenkinsfile从本地的Dockerfile编译出镜像,并且执行定义好的steps

1
2
3
4
5
6
7
8
9
pipeline {
agent {dockerfile true}
stages {
stage('test') {
sh 'node --version'
sh 'svn --vesion'
}
}
}

容器环境的高级用法

我们要在mysql的容器中执行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
node {
checkout scm
/*
* In order to communicate with the MySQL server, this Pipeline explicitly
* maps the port (`3306`) to a known port on the host machine.
*/
docker.image('mysql:5').withRun('-e "MYSQL_ROOT_PASSWORD=my-secret-pw" -p 3306:3306') { c ->
/* Wait until mysql service is up */
sh 'while ! mysqladmin ping -h0.0.0.0 --silent; do sleep 1; done'
/* Run some tests which require MySQL */
sh 'make check'
}
}

这个例子我们在升级一点,我来加入两个容器,一个运行mysql数据库,一个提供执行环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
node {
checkout scm
docker.image('mysql:5').withRun('-e "MYSQL_ROOT_PASSWORD=my-secret-pw"') { c ->
docker.image('mysql:5').inside("--link ${c.id}:db") {
/* Wait until mysql service is up */
sh 'while ! mysqladmin ping -hdb --silent; do sleep 1; done'
}
docker.image('centos:7').inside("--link ${c.id}:db") {
/*
* Run some tests which require MySQL, and assume that it is
* available on the host name `db`
*/
sh 'make check'
}
}
}

上面的例子中,我们使用withRun来给容器添加启动参数,同个容器的id来连接两个容器,id同样能够用来获取容器的日志

1
sh "docker logs ${c.id}"

编译镜像

为了创建镜像,’pipeline’同样提供了build()方法来创建一个新的镜像,从Dockerfile创建一个新的镜像

1
2
3
4
5
6
7
8
9
node {
checkout scm

def customImage = docker.build("my-image:${env.BUILD_ID}")

customImage.inside {
sh 'make test'
}
}

同样也给定了push方法来push你的镜像到镜像仓库。

1
2
3
4
5
node {
checkout scm
def customImage = docker.build("my-image:${env.BUILD_ID}")
customImage.push()
}

push方法也能接受一个参数,用来指定发布的镜像tag

1
2
3
4
5
6
7
node {
checkout scm
def customImage = docker.build("my-image:${env.BUILD_ID}")
customImage.push()

customImage.push('latest')
}

build方法默认是是从本地的Dockerfile文件来编译一个镜像,也可以指定一个别的包含Dockerfile文件的文件夹来编译镜像

1
2
3
4
5
6
7
8
node {
checkout scm
def testImage = docker.build("test-image", "./dockerfiles/test")

testImage.inside {
sh 'make test'
}
}

这里从/dockerfiles/test文件夹下面寻找Dockerfile文件,然后编译test-image镜像。

pipeline也提供了覆盖Dockerfile文件的方法,使用docker build命令的-f参数来指定Dockerfile文件

1
2
3
4
5
node {
checkout scm
def dockerfile = 'Dockerfile.test'
def customImage = docker.build("my-image:${env.BUILD_ID}", "-f ${dockerfile} ./dockerfiles")
}

这里从./dockerfiles目录寻找Dockerfile.test文件,根据Dockerfile.test文件定义的规则来编译镜像。

使用远程docker服务器

docker本身是cs架构,默认docker命令会连接本地的docker服务器,通过/var/run/docker.sock这个socket。我们也可以使用withServer()选择一个docker server。这里需要给定一个url和一个Credentialid

1
2
3
4
5
6
7
8
9
node {
checkout scm

docker.withServer('tcp://swarm.example.com:2376', 'swarm-certs') {
docker.image('mysql:5').withRun('-p 3306:3306') {
/* do things */
}
}
}

使用自定义的镜像仓库

docker pushdocker pull 默认使用Docker Hub仓库,我们可以使用withRegistry()来指定一个特殊的镜像仓库

1
2
3
4
5
6
7
8
9
10
11
node {
checkout scm

docker.withRegistry('https://registry.example.com', 'credentials-id') {

def customImage = docker.build("my-image:${env.BUILD_ID}")

/* Push the container to the custom Registry */
customImage.push()
}
}