Pipeline 中使用 Docker 构建示例

在流水线中使用 Docker

许多组织使用 Docker 来统一跨机器的构建和测试环境,并为部署应用程序提供有效的机制。从 Pipeline 2.5 及更高版本开始,流水线内置了与 Jenkinsfile 中的 Docker 进行交互的支持。

Docker 的基本原理, 可以参考 Docker Getting Started Guide

自定义执行环境

设计流水线的目的是更方便地使用 Docker 镜像作为单个 Stage 或整个流水线的执行环境。这意味着用户可以定义流水线需要的工具,而无需手动配置代理。实际上,只需对 Jenkinsfile 进行少量编辑,任何 packaged in a Docker container 的工具,都可轻松使用。

// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent {
        docker { image 'node:16.13.1-alpine' }
    }
    stages {
        stage('Test') {
            steps {
                sh 'node --version'
            }
        }
    }
}

// Jenkinsfile (Scripted Pipeline)
node {
    /* Requires the Docker Pipeline plugin to be installed */
    docker.image('node:16.13.1-alpine').inside {
        stage('Test') {
            sh 'node --version'
        }
    }
}

当 Pipeline 执行时,Jenkins 会自动启动指定的容器并执行其中定义的步骤

工作区同步

简短:如果保持工作空间与其他阶段同步很重要,请使用 reuseNode true. 否则,dockerized stage 可以在任何其他代理或同一代理上运行,但可以在临时工作区中运行。

默认情况下,对于容器化阶段,Jenkins 会:

  • 选择任何代理,
  • 创建新的空工作区,
  • 将管道代码克隆到其中,
  • 将此新工作区安装到容器中。

如果您有多个 Jenkins 代理,您的容器化阶段可以在其中任何一个上启动。

当 reuseNode 设置为 true:不会创建新的工作空间,并且当前代理的当前工作空间将被挂载到容器中,并且容器将在同一节点启动,因此将同步整个数据。

// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    stages {
        stage('Build') {
            agent {
                docker {
                    image 'gradle:6.7-jdk11'
                    // Run the container on the node specified at the
                    // top-level of the Pipeline, in the same workspace,
                    // rather than on a new node entirely:
                    reuseNode true
                }
            }
            steps {
                sh 'gradle --version'
            }
        }
    }
}

缓存容器数据

许多构建工具将下载外部依赖项并将它们缓存在本地以供将来重用。由于容器最初是使用 “干净的” 文件系统创建的,这可能会导致流水线速度变慢,因为它们可能无法利用后续流水线运行之间的磁盘缓存。

Pipeline 支持添加传递给 Docker 的自定义参数,允许用户指定要挂载的自定义 Docker Volumes,可用于在 Pipeline 运行之间缓存代理上的数据。以下示例将使用 maven container 在 Pipeline 运行之间进行 ~/.m2 缓存,从而避免为 Pipeline 的后续运行重新下载依赖项。

// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent {
        docker {
            image 'maven:3.8.1-adoptopenjdk-11'
            args '-v $HOME/.m2:/root/.m2'
        }
    }
    stages {
        stage('Build') {
            steps {
                sh 'mvn -B'
            }
        }
    }
}

// Jenkinsfile (Scripted Pipeline)
node {
    /* Requires the Docker Pipeline plugin to be installed */
    docker.image('maven:3-alpine').inside('-v $HOME/.m2:/root/.m2') {
        stage('Build') {
            sh 'mvn -B'
        }
    }
}

使用多个容器

代码库依赖多种不同的技术变得越来越普遍。例如:存储库可能同时具有基于 Java 的后端 API 实现和基于 JavaScript 的前端实现。结合 Docker 和 Pipeline 允许 Jenkinsfile 通过将 agent {} 指令与不同阶段结合使用多种类型的技术。

// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent none
    stages {
        stage('Back-end') {
            agent {
                docker { image 'maven:3.8.1-adoptopenjdk-11' }
            }
            steps {
                sh 'mvn --version'
            }
        }
        stage('Front-end') {
            agent {
                docker { image 'node:16.13.1-alpine' }
            }
            steps {
                sh 'node --version'
            }
        }
    }
}

// Jenkinsfile (Scripted Pipeline)
node {
    /* Requires the Docker Pipeline plugin to be installed */

    stage('Back-end') {
        docker.image('maven:3-alpine').inside {
            sh 'mvn --version'
        }
    }

    stage('Front-end') {
        docker.image('node:7-alpine').inside {
            sh 'node --version'
        }
    }
}

使用 Dockerfile

对于需要更多自定义执行环境的项目,Pipeline 还支持从 Dockerfile 源存储库中构建和运行容器。与之前使用 “现成” 容器的方法相比,使用该 agent { dockerfile true } 语法将从 a 构建一个新镜像,Dockerfile 而不是从 Docker Hub 拉取一个镜像。

重新使用上面的示例,更自定义 Dockerfile:

# Dockerfile
FROM node:16.13.1-alpine

RUN apk add -U subversion

通过将其提交到源存储库的根目录,Jenkinsfile 可以将其更改为基于此 Dockerfile 构建一个容器,然后使用该容器运行定义的步骤:

// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent { dockerfile true }
    stages {
        stage('Test') {
            steps {
                sh 'node --version'
                sh 'svn --version'
            }
        }
    }
}

指定 Docker 标签

默认情况下,Pipeline 假定任何已配置的代理都能够运行基于 Docker 的 Pipelines。对于具有 macOS、Windows 或其他代理且无法运行 Docker 守护程序的 Jenkins 环境,此默认设置可能有问题。Pipeline 在 Manage Jenkins 页面和文件夹级别提供了一个全局选项,用于指定哪些代理(通过 Label)用于运行基于 Docker 的管道。

mac OS 用户的路径设置

默认情况下,该 /usr/local/bin 目录不包含在 macOS PATH 中用于 Docker 映像。/usr/local/bin 如果需要从 Jenkins 中调用可执行文件,则 PATH 需要将其扩展为包含 /usr/local/bin。在文件 “/usr/local/Cellar/jenkins-lts/XXX/homebrew.mxcl.jenkins-lts.plist” 中添加一个路径节点,如下所示:

// Contents of homebrew.mxcl.jenkins-lts.plist
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key
<string><!-- insert revised path here --></string>
</dict>

修改 PATH string 后的目录应该是一个冒号分隔的目录列表,格式与 PATH 环境变量相同,应该包括:

  • /usr/local/bin
  • /usr/bin
  • /bin
  • /usr/sbin
  • /sbin
  • /Applications/Docker.app/Contents/Resources/bin/
  • /Users/XXX/Library/Group\ Containers/group.com.docker/Applications/Docker.app/Contents/Resources/bin(其中 XXX 替换为您的用户名)

现在使用 “brew services restart jenkins-lts” 重新启动 jenkins

脚本管道的高级用法

运行 “sidecar” 容器

在 Pipeline 中使用 Docker 可能是运行构建或一组测试可能依赖的服务的有效方式。与 sidecar 模式类似,Docker Pipeline 可以 “在后台” 运行一个容器,同时在另一个容器中执行工作。利用这种 sidecar 方法,Pipeline 可以为每个 Pipeline 运行提供一个 “干净的” 容器。

考虑一个假设的集成测试套件,它依赖于运行的本地 MySQL 数据库。使用在 Docker Pipeline 插件对 Scripted Pipeline 的支持中 withRun 实现的方法,可以将 MySQL 作为 sidecar 运行 Jenkinsfile:

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 的 “sidecar”,另一个提供执行环境,通过使用 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。inside() 方法使用容器的 ID,管道可以通过将自定义 Docker 参数传递给方法来创建链接。

该 id 属性还可用于在管道退出之前检查正在运行的 Docker 容器中的日志:

sh "docker logs ${c.id}"

构建容器

为了创建 Docker 镜像,Docker Pipeline 插件还提供了一种在 Pipeline 运行期间从存储库中 Dockerfile 创建新镜像 build() 的方法。

使用 docker.build("my-image-name") 该语法的一个主要好处是脚本化管道可以将返回值用于后续 Docker 管道调用,例如:

node {
    checkout scm

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

    customImage.inside {
        sh 'make test'
    }
}

返回值也可用于通过 push() 该方法将 Docker 镜像发布到 Docker Hubcustom Registry,例如:

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

镜像 “标签” 的一种常见用法是为 Docker 镜像的最新验证版本指定 latest 标签。该 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'
    }
}
  • 从在 ./dockerfiles/test/Dockerfile 中发现的 Dockerfile 中构建 test-image

可以通过将其他参数添加到 build() 方法的第二个参数来将其他参数传递给 docker build。以这种方式传递参数时,该字符串中的最后一个值必须是 docker 文件的路径,并且应该以用作构建上下文的文件夹结尾

此示例通过传递 -f 标志覆盖 Dockerfile 默认值:

node {
    checkout scm
    def dockerfile = 'Dockerfile.test'
    def customImage = docker.build("my-image:${env.BUILD_ID}",
                                   "-f ${dockerfile} ./dockerfiles") 
}
  • my-image:${env.BUILD_ID} 从位于 ./dockerfiles/Dockerfile.test 的 Dockerfile 构建。

使用远程 Docker 服务器

默认情况下,Docker Pipeline 插件将与本地 Docker 守护进程通信,通常通过 /var/run/docker.sock 访问。

要选择非默认 Docker 服务器,例如:使用 Docker Swarm,应使用该 withServer() 方法。

通过将 URI 和可选的 Jenkins 中预先配置的 Docker Server Certificate Authentication 的凭据 ID 传递给具有以下功能的方法:

node {
    checkout scm

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

使用自定义注册表

默认情况下,Docker Pipeline 集成了 Docker Hub 的默认 Docker Registry 。

为了使用自定义的 Docker Registry,Scripted Pipeline 的用户可以使用该 withRegistry() 方法包装步骤,传入自定义的 Registry URL,例如:

node {
    checkout scm

    docker.withRegistry('https://registry.example.com') {

        docker.image('my-custom-image').inside {
            sh 'make test'
        }
    }
}

对于需要身份验证的 Docker 注册表,从 Jenkins 主页添加 “Username/Password” 凭据项,并使用凭据 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()
    }
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值