GitLab Runner Docker Executor

GitLab Runner

执行器(Executor )的比较

Shell 是最易于配置的执行器。构建中所需的依赖得你手工装在 Runner 所在机器上。

更好的方式是使用 Docker,它让你拥有干净的构建环境,以及简易的依赖管理——所有的编译项目所需的依赖都可以放进 Docker 镜像中。Docker 执行器很容易就能创建带有依赖服务的编译环境,比如 MySQL。

通常不建议使用 Docker-SSH,这是 Docker 执行器的特殊版本。该执行器可以连接到 Docker 容器,其中运行 SSH 守护进程。如果你的 Docker 镜像要复制一个完整的工作系统,这会很有用:使用进程管理系统(init),暴露 SSH 进程,并含有系列安装好的服务。这类镜像是肥镜像,Docker 社区通常也不建议使用。

以下原文:https://docs.gitlab.com/runner/executors/docker.html

Docker 执行器

GitLab Runner 能在用户提供的镜像上,使用 Docker 来执行编译。这通过 Docker 执行器完成。

Docker 执行器与 GitLab CI 一起使用时,它连到 Docker Engine,并在独立且隔离的容器中运行每个构建。它会使用在 .gitlab-ci.yml中预设好的镜像,并按照 config.toml 所定来执行。

这样可以拥有一个简单可重复的构建环境,这也可以在你的工作站上运行。附加的好处是可以测试我们将在以后从 Shell 中探索的所有命令,而不必在专用的 CI 服务器上进行测试。

工作流程

Docker 执行器将构建分为多个步骤:

  1. Prepare:创建并启动服务。
  2. Pre-build:克隆,恢复 Cache 并从以前的阶段下载工件(Artifacts)。这是在特殊的 Docker 镜像上运行的。
  3. Build:用户构建。这是在用户提供的 Docker 镜像上运行的。
  4. Post-build:创建缓存,将工件上传到 GitLab。这是在特殊的 Docker 镜像上运行的。

此特殊的 Docker 镜像基于 Alpine Linux,并包含运行 prepare 步骤所需的所有工具:Git 二进制文件和用于支持 Cache 和工件(Artifacts)的 Runner 二进制文件。 您可以在官方的 Runner 仓库中找到此特殊镜像的定义。

image 关键字

image 关键字是本地 Docker Engine 中存在的 Docker 镜像的名称(docker image 可列出所有docker 镜像),或是可在 Docker Hub 中找到的任何镜像。有关镜像和 Docker Hub 的更多信息,请阅读 Docker Fundamentals文档。

简言之,使用image引用 docker 镜像来创建容器,构建将在此容器上运行。

如果不指定命名空间,Docker 默认命名空间为包含所有官方镜像library。这就是为什么你会看到很多次.gitlab-ci.ymlconfig.toml中省略的library部分。例如,可以定义像image: ruby:2.1这样的镜像,它是image: library/ruby:2.1的快捷方式。

然后,每个 Docker 镜像都有标签(tag),表示镜像版本。这些在镜像名称后面用冒号(:)定义。 例如,对于Ruby,可以在 https://hub.docker.com/_/ruby/ 上看到支持的标签。如果没有指定标签(如image:ruby),则表示latest

services 关键词

services关键字定义了在构建期间运行的另一个 Docker 镜像,并链接到image关键字所定义的 Docker 镜像。这允许你在构建时访问服务镜像。

服务镜像可以运行任何应用程序,但最常见的使用场景是运行数据库容器,例如mysql。用已有的镜像来运行额外容器会更加容易快速,而不是每次构建项目时都安装mysql

你可以在 CI 服务示例的相关文档中看到一些广泛使用的服务示例。

服务如何链接到构建

要更好地了解容器链接的工作原理,请阅读 Linking containers together

总而言之,如果你将mysql作为服务添加到应用程序中,则该镜像将用于创建一个链接到构建容器的容器。根据工作流程,这是运行实际构建之前执行的第一步。

MySQL 的服务容器可使用主机名mysql访问。因此,为了访问你的数据库服务,您必须连接到名为mysql的主机而不是套接字(socket)或localhost

.gitlab-ci.yml定义imageservices

你可以简单地定义将用于所有作业的镜像,和要在构建时使用的服务列表。

image: ruby:2.2

services:
  - postgres:9.3

before_script:
  - bundle install

test:
  script:
  - bundle exec rake spec

也可以为每个作业定义不同的镜像和服务:

before_script:
  - bundle install

test:2.1:
  image: ruby:2.1
  services:
  - postgres:9.3
  script:
  - bundle exec rake spec

test:2.2:
  image: ruby:2.2
  services:
  - postgres:9.4
  script:
  - bundle exec rake spec

Define image and services in config.toml

查找[runners.docker]部分:

[runners.docker]
  image = "ruby:2.1"
  services = ["mysql:latest", "postgres:latest"]

这里定义的镜像和服务将添加到 Runner 运行的所有构建中,因此即使在.gitlab-ci.yml内没有定义imageconfig.toml中定义的将被使用。

从私有 Docker Registry 中定义镜像

从 GitLab Runner 0.6.0 开始,你可以定义位于私有 Registry 的镜像,这可能需要身份验证。

.gitlab-ci.yml的镜像定义中显式声明即可。

image: my.registry.tld:5000/namepace/image:tag

上例中,GitLab Runner 到my.registry.tld:5000寻找namespace/image:tag镜像。

如果存储库是私有的,则需要提供 GitLab Runner 的身份验证凭据 。更多内容参见:使用私有 Docker Registry

访问服务

假设需要一个 WordPress 实例,来测试它和你应用的 API 集成。

你可以在.gitlab-ci.yml中使用如tutum/wordpress作为服务镜像:

services:
  - tutum/wordpress:latest

当构建运行时,tutum/wordpress将首先启动,你可以用主机名为tutum__wordpresstutum-wordpress的构建容器来访问它。

GitLab Runner 为你可以使用的服务创建两个主机名别名。别名取自镜像名称,遵循以下规则:

  1. :后的一切都被舍弃
  2. 第一个别名,斜杠(/)替换为双下划线(__
  3. 第二个别名,斜杠(/)替换为单破折号(-

使用私有镜像服务将剥离给出的任何端口,并应用上述规则。服务registry.gitlab-wp.com:4999/tutum/wordpress将生成主机名registry.gitlab__wp.com__tutum__wordpressregistry.gitlab-wp.com-tutum-wordpress

配置服务

许多服务接受环境变量,允许你根据环境来轻松更改数据库名或设置帐户名。

GitLab Runner 0.5.0 及以上版本将所有 YAML 定义的变量传递给创建的服务容器。

对于所有可能的配置变量,请检查其对应Docker集线器页面中提供的每个映像的文档。

所有变量将被传递到所有服务容器。没有某服务专用的变量。

安全变量(Secure Variables)只传递给构建容器。

服务中的构建目录

从 1.5 版本开始,GitLab Runner 将一个/build目录装载到所有的共享服务中。

参见这个 issue:https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/issues/1520

PostgreSQL 服务示例

请参阅具体文档使用 PostgreSQL 作为服务

MySQL 服务示例

请参阅具体文档使用 MySQL 作为服务

服务健康检查

服务启动后,GitLab Runner 等待一段时间的服务响应。当前,Docker 执行程序会尝试打开与服务容器中第一个暴露服务的 TCP 连接。

你可以看到它 在这个 Dockerfile 中是如何实现的。

构建和缓存存储

Docker 执行器默认将所有构建存储在/builds/<namespace>/<project-name>中,所有缓存在/cache(在容器内)中。

您可以在config.toml文件的[[runners]]部分下,定义builds_dircache_dir选项,来覆盖/builds/cache目录。这将修改数据存储在容器内的位置。

如果你修改/cache存储路径,你还需要更改config.toml[runners.docker]部分的volumes = ["/my/cache/"],确保把上述目录标记为 persistent。

要了解更多信息,请阅读下一部分持久存储。

持久存储

Docker执行器可以在运行容器时提供持久存储。
volumes =下定义的所有目录将在构建之间持久化。

volumes指令支持2种类型的存储:

  1. <path> - 动态存储<path>在该项目的同一并发作业的后续运行中持续存在。数据附加到自定义缓存容器:runner-<short-token>-project-<id>-concurrent-<job-id>-cache-<unique-id>
  2. <host-path>:<path>[:<mode>] - 主机绑定存储<path>绑定到主机系统上的<host-path>。可选的<mode>可以指定该存储是只读或读写(默认)。

构建的持久存储

如果你将/builds作为主机绑定的存储,你的构建将被存储在:/builds/<short-token>/<concurrent-id>/<namespace>/<project-name>,其中:

  • <short-token>是 Runner Token 的缩写版(前 8 个字母)
  • <concurrent-id>是个唯一数字,用于标识项目上下文中特定 Runner 的本地作业 ID

特权(privileged)模式

Docker 执行器支持许多选项来微调构建容器。其中之一是privileged模式

使用具有特权模式的 docker-in-docker

配置的privileged标志被传递给构建容器和所有服务,从而允许轻松使用 docker-in-docker 方法。

首先,将您的 Runner(config.toml)配置为以privileged模式运行:

[[runners]]
  executor = "docker"
  [runners.docker]
    privileged = true

然后,使你的构建脚本(.gitlab-ci.yml)使用 docker-in-docker 容器:

image: docker:git
services:
  - docker:dind

build:
  script:
    - docker build -t my-image .
    - docker push my-image

ENTRYPOINT

Docker 执行器不会覆盖 Docker 镜像的ENTRYPOINT

这意味着,如果你的镜像定义了ENTRYPOINT,并且不允许使用CMD来运行脚本,那么该镜像将不能与 Docker 执行器一起使用。

使用ENTRYPOINT可以创建在自定义环境或安全模式下运行构建脚本的特殊 Docker 镜像。

您可能会想到创建一个使用不执行构建脚本的ENTRYPOINT的 Docker 镜像,但执行一组预定义的命令,例如从你的目录里构建 Docker 镜像。在这种情况下,您可以以特权模式运行构建容器,并使 Runner 的构建环境安全。

请考虑以下例子:

  1. 创建一个新的 Dockerfile:
    FROM docker:dind
    ADD / /entrypoint.sh
    ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]
    
  2. 创建一个用作ENTRYPOINT的 bash 脚本(entrypoint.sh):
    #!/bin/sh
    
    dind docker daemon
        --host=unix:///var/run/docker.sock \
        --host=tcp://0.0.0.0:2375 \
        --storage-driver=vf &
    
    docker build -t "$BUILD_IMAGE" .
    docker push "$BUILD_IMAGE"
    
  3. 推送镜像到 Docker Registry。

  4. privileged模式下运行 Docker 执行器。在config.toml中定义:

    [[runners]]
      executor = "docker"
      [runners.docker]
        privileged = true
    
  5. 在你的项目中使用以下.gitlab-ci.yml
    variables:
      BUILD_IMAGE: my.image
    build:
      image: my/docker-build:image
      script:
      - Dummy Script
    

这只是一个例子。通过这种方法,是有无限可能的。

镜像拉取策略如何工作

当使用dockerdocker-sshdocker+machinedocker-ssh+machine执行器时,可以设置pull_policy参数来定义 Runner 在拉取 Docker 镜像时的工作方式(imageservices关键字)。

注:
如果没有为pull_policy参数设置任何值,则 Runner 将默认使用always拉取策略。

现在看看这些策略如何工作。

使用拉取策略never

never策略完全禁止镜像拉取。如果 Runner 的pull_policy参数设为never,那用户只能使用 Runner 运行的 docker 主机上已经手工拉取的镜像。

如果在本地找不到指定的镜像,则 Runner 将会使构建失败,错误类似于:

Pulling docker image local_image:latest ...
ERROR: Build failed: Error: image local_image:latest not found

何时使用此拉取策略?

如果您希望或需要完全控制 Runner 用户使用哪些镜像,则应使用此拉取策略。对于专用于只能使用特定镜像(在任何 registry 上都不公开)的项目的私有 Runner 来说,这是一个不错的选择。

何时不使用此拉取策略?

大部分 auto-scaled 的 Docker 执行器使用场景下,该拉取策略无法正常工作。由于自动伸缩的工作原理,只有在为特定云服务提供商使用预定义的云实例镜像时,never拉取策略才可用。镜像需要包含预装的 Docker Engine 和已使用镜像的本地副本。

使用拉取策略if-not-present

使用if-not-present拉取策略时,Runner 首先检查本地是否有该镜像。如果有则使用本地版本的镜像。否则 Runner 将尝试拉取镜像。

何时使用此拉取策略?

如果想使用远程 Registry 拉取的镜像,但当使用重型和很少更新的镜像时,又希望减少分析镜像层差异费时,这种拉取策略是一个不错的选择。在这种情况下,您需要一段时间才能从本地 Docker Engine 存储区手动删除镜像以强制更新镜像。

如果您需要只在本地构建和本地可用的镜像,那么这也是一个不错的选择,但另一方面,还需要允许从远程 Registry 中拉取镜像。

何时不使用此拉取策略?

如果你的构建使用经常更新并需要在最新版本中使用的镜像,则不应使用此拉取策略。在此情况下,策略所减少的网络负载,与非常频繁地删除本地镜像副本的必要性相比,可能不太值得。(In such situation, the network load reduction created by this policy may be less worthy than the necessity of the very frequent deletion of local copies of images.)

如果 Runner 可以被不同用户使用,而这些用户又不能访问彼此使用的私有镜像,那么此拉取策略也不应该被使用。特别不要为共享 Runner 使用这个拉取策略。

要了解为什么if-not-present拉取策略在与私有镜像一起使用时会产生安全问题,请阅读安全注意事项文档

使用拉取策略always

always拉取策略确保镜像始终被拉取。当使用always时,即使本地副本可用,Runner 也会尝试拉取镜像。如果未找到镜像,则构建将失败,并显示类似以下错误:

Pulling docker image registry.tld/my/image:latest ...
ERROR: Build failed: Error: image registry.tld/my/image:latest not found

注:
对于v1.8之前的版本,当使用always拉取策略时,可能会返回(fall back)到镜像的本地副本并警告:

Pulling docker image registry.tld/my/image:latest ...
WARNING: Cannot pull the latest version of image registry.tld/my/image:latest : Error: image registry.tld/my/image:latest not found
WARNING: Locally found image will be used instead.

这在v1.8版本中已更改。To understand why we changed this and how incorrect usage of may be revealed please look into issue #1905

何时使用此拉取策略?

如果您的 Runner 公开可用,并将其配置为 GitLab 实例中的共享 Runner,则应使用此拉策略。当 Runner 将与私有镜像一起使用时,它是唯一可以被视为安全的拉取策略。

如果要强制用户始终使用最新的镜像,这也是一个不错的选择。

此外,这将是 Runner 自动伸缩(auto-scaled) 配置的最佳解决方案。

何时不使用此拉取策略?

如果你需要使用本地存储的镜像,则此拉取策略将无法正常工作。在这种情况下,Runner 将跳过镜像的本地副本,并尝试将其从远程 Registry 中拉出。如果镜像是本地构建的,并且不存在于任何公共 Registry 中(尤其是在默认的 Docker Registry 中),则构建将会失败:

Pulling docker image local_image:latest ...
ERROR: Build failed: Error: image local_image:latest not found

Docker vs Docker-SSH

注意
docker-ssh 执行器已被弃用,并且不会添加任何新功能。

我们提供一种特殊类型的 Docker 执行器的支持,即 Docker-SSH。Docker-SSH 使用与 Docker 执行器相同的逻辑,但不是直接执行脚本,它使用 SSH 客户端连接到构建容器。

然后,Docker-ssh 使用其内部 IP 连接到容器内部运行的 SSH 服务器。

Leave a Reply

Your email address will not be published.