..

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

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

自定义执行环境

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

pipeline {
    agent {
        docker {image: 'node:7-alpine'}
    }
    stages {
        stage('test'){
            steps{
                sh 'node --version'
            }
        }
    }
}

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

[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这个文件夹中。

pipeline {
    agent {
        docker {
            image: 'maven:2-alpine'
            args: '-v $HOME/.m2:/root/.m2'
        }
    }
    stages {
        stage {
            steps {
                sh 'mvn -B'
            }
        }
    }
}

使用多种容器

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

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文件的如下:

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

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

pipeline {
    agent {dockerfile true}
    stages {
        stage('test') {
            sh 'node --version'
            sh 'svn --vesion'
        }
    }
}

容器环境的高级用法

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

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数据库,一个提供执行环境

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同样能够用来获取容器的日志

sh "docker logs ${c.id}"

编译镜像

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

node {
    checkout scm

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

    customImage.inside {
        sh 'make test'
    }
}

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

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

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

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

    customImage.push('latest')
}

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

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文件

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

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()来指定一个特殊的镜像仓库

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()
    }
}