许多组织使用Docker统一其跨机器的构建和测试环境,并提供一种用于部署应用程序的有效机制。从Pipeline 2.5及更高版本开始,Pipeline内置支持从内与Docker进行交互 Jenkinsfile。
虽然本节将介绍从a到Docker的使用基础知识 Jenkinsfile,但不会涵盖Docker的基础知识,可以在Docker入门指南中进行阅读 。
定制执行环境
Pipeline旨在轻松地将 Docker 映像用作单个Stage 或整个Pipeline 的执行环境 。这意味着用户可以定义其管道所需的工具,而无需手动配置代理。几乎任何可以 打包在Docker容器中的工具。只需对进行较小的修改即可轻松使用Jenkinsfile。
Jenkinsfile(声明性管道)
pipeline { agent { docker { image 'node:7-alpine' } } stages { stage('Test') { steps { sh 'node --version' } } }}
切换脚本管道 (高级)
当管道执行时,Jenkins将自动启动指定的容器并在其中执行定义的步骤:
[Pipeline] stage[Pipeline] { (Test)[Pipeline] sh[guided-tour] Running shell script+ node --versionv7.4.0[Pipeline] }[Pipeline] // stage[Pipeline] }
缓存容器数据
许多构建工具会下载外部依赖项并将其本地缓存以供将来重用。由于容器最初是使用“干净”文件系统创建的,因此这可能会导致管道运行速度变慢,因为它们可能无法利用后续管道运行之间的磁盘缓存。
管道支持添加传递给Docker的自定义参数,从而允许用户指定 要安装的自定义 Docker卷,可用于 在两次管道运行之间在代理上缓存数据 。以下示例将~/.m2利用maven容器在两次管道运行之间进行 缓存,从而避免为管道的后续运行重新下载依赖项。
Jenkinsfile(声明性管道)
pipeline { agent { docker { image 'maven:3-alpine' args '-v $HOME/.m2:/root/.m2' } } stages { stage('Build') { steps { sh 'mvn -B' } } }}
切换脚本管道 (高级)
使用多个容器
代码库依赖于多种不同的技术变得越来越普遍。例如,存储库可能同时具有基于Java的后端API实现和基于JavaScript的前端实现。结合使用Docker和Pipeline可以通过将指令与不同阶段结合Jenkinsfile使用 多种技术agent {}。
Jenkinsfile(声明性管道)
pipeline { agent none stages { stage('Back-end') { agent { docker { image 'maven:3-alpine' } } steps { sh 'mvn --version' } } stage('Front-end') { agent { docker { image 'node:7-alpine' } } steps { sh 'node --version' } } }}
切换脚本管道 (高级)
使用Dockerfile
对于需要更多自定义执行环境的项目,Pipeline还支持从Dockerfile源存储库中构建和运行容器。与以前使用“现成”容器的方法相比,使用该agent { dockerfile true }语法Dockerfile将从Docker Hub而不是从 Docker Hub生成一个新映像。
重用上面的示例,并使用一个更自定义的示例Dockerfile:
Docker文件
FROM node:7-alpineRUN apk add -U subversion
通过将其提交到源存储库的根目录,Jenkinsfile可以将其更改为基于此构建容器Dockerfile,然后使用该容器运行定义的步骤:
Jenkinsfile(声明性管道)
pipeline { agent { dockerfile true } stages { stage('Test') { steps { sh 'node --version' sh 'svn --version' } } }}
该agent { dockerfile true }语法支持许多其他选项,这些选项将在“ 管道语法”部分中详细介绍 。
在Jenkins Pipeline中使用Dockerfile
指定Docker标签
默认情况下,管道假定任何已配置的 代理都能够运行基于Docker的管道。对于具有macOS,Windows或其他代理程序且无法运行Docker守护程序的Jenkins环境,此默认设置可能会出现问题。管道在“ 管理Jenkins”页面和“ 文件夹” 级别提供了一个全局选项,用于指定要使用哪些代理(按 Label)来运行基于Docker的管道。
脚本管道的高级用法
运行“ sidecar”容器
在管道中使用Docker是运行构建或一组测试可能依赖的服务的有效方法。与sidecar模式类似 ,Docker Pipeline可以“在后台”运行一个容器,而在另一个容器中执行工作。利用这种“边车”方法,管道可以为每个管道运行配备一个“干净”的容器。
考虑一个假设的集成测试套件,该套件依赖于要运行的本地MySQL数据库。使用withRun在Docker Pipeline插件对Scripted Pipeline的支持中实现的方法, Jenkinsfile可以将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,另一个通过Docker 容器链接提供执行环境。
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 。通过使用容器的ID,管道可以通过将自定义Docker参数传递给inside()方法来创建链接 。
该id属性对于在管道退出之前检查正在运行的Docker容器中的日志也很有用:
sh "docker logs ${c.id}"
建筑容器
为了创建Docker映像,Docker Pipeline 插件还提供了build()一种Dockerfile在Pipeline运行期间从存储库中的创建新映像的方法 。
使用该语法的一个主要好处docker.build("my-image-name")是,脚本化管道可以将返回值用于后续的Docker Pipeline调用,例如:
node { checkout scm def customImage = docker.build("my-image:${env.BUILD_ID}") customImage.inside { sh 'make test' }}
返回值还可以用于通过以下方法将Docker映像发布到 Docker Hub或自定义注册表,push()例如:
node { checkout scm def customImage = docker.build("my-image:${env.BUILD_ID}") customImage.push()}
映像“标签”的一种常见用法是为latest最新,经过验证的Docker映像版本指定标签。该push()方法接受一个可选tag参数,允许管道customImage使用不同的标签推送,例如:
node { checkout scm def customImage = docker.build("my-image:${env.BUILD_ID}") customImage.push() customImage.push('latest')}
该build()方法Dockerfile默认在当前目录中构建。可以通过提供包含路径Dockerfile作为方法的第二个参数的目录路径来覆盖它build(),例如:
node { checkout scm def testImage = docker.build("test-image", "./dockerfiles/test") testImage.inside { sh 'make test' }}
test-image根据位于的Dockerfile 构建./dockerfiles/test/Dockerfile。
通过将其他参数 添加到方法的第二个参数中,可以将其他参数传递给 docker buildbuild()。以这种方式传递参数时,该字符串中的最后一个值必须是docker文件的路径,并且应以文件夹结尾作为构建上下文)
本示例Dockerfile通过传递-f 标志来覆盖默认值:
node { checkout scm def dockerfile = 'Dockerfile.test' def customImage = docker.build("my-image:${env.BUILD_ID}", "-f ${dockerfile} ./dockerfiles") }
my-image:${env.BUILD_ID}根据位于的Dockerfile 构建./dockerfiles/Dockerfile.test。
使用远程Docker服务器
默认情况下,Docker Pipeline插件将与本地Docker守护进程通信,该守护进程通常通过访问/var/run/docker.sock。
要选择非默认的Docker服务器,例如 Docker Swarm,withServer()应使用该方法。
通过使用以下方法将URI以及可选的Jenkins中预先配置的Docker服务器证书身份验证的凭据ID传递给方法:
node { checkout scm docker.withServer('tcp://swarm.example.com:2376', 'swarm-certs') { docker.image('mysql:5').withRun('-p 3306:3306') { /* do things */ } }}
inside()并build()不会与码头工人群服务器正常工作,开箱即用
为了inside()正常工作,Docker服务器和Jenkins代理必须使用相同的文件系统,以便可以安装工作空间。
当前,Jenkins插件和Docker CLI都不会自动检测服务器正在远程运行的情况。典型的症状是嵌套sh命令的错误,例如
cannot create /…@tmp/durable-…/pid: Directory nonexistent
当詹金斯(Jenkins)检测到代理本身在Docker容器中运行时,它将自动将--volumes-from参数传递给 inside容器,以确保它可以与代理共享工作区。
此外,某些版本的Docker Swarm不支持自定义注册表。
使用自定义注册表
默认情况下,Docker Pipeline集成假定Docker Hub为默认的Docker Registry 。
为了使用自定义Docker注册表,脚本化管道的用户可以使用withRegistry()方法包装步骤,并传入自定义注册表URL,例如:
node { checkout scm docker.withRegistry('https://registry.example.com') { docker.image('my-custom-image').inside { sh 'make test' } }}
对于需要身份验证的Docker注册表,请从Jenkins主页添加“用户名/密码”凭据项,并将凭据ID作为第二个参数使用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() }}