Help us learn about your current experience with the documentation. Take the survey.

服务

  • 层级:免费版、高级版、旗舰版
  • 提供方式:GitLab.com、GitLab 自托管版、GitLab 专属版

配置 CI/CD 时,您需要指定一个镜像,该镜像用于创建运行作业的容器。要指定此镜像,您使用 image 关键字。

您可以使用 services 关键字指定额外的镜像。这个额外的镜像用于创建另一个容器,该容器对第一个容器可用。两个容器可以互相访问,并在运行作业时进行通信。

服务镜像可以运行任何应用程序,但最常见的用例是运行数据库容器,例如:

假设您正在开发一个使用数据库存储的内容管理系统。您需要一个数据库来测试应用程序的所有功能。在此场景下,将数据库容器作为服务镜像运行是一个很好的用例。

使用现有镜像并将其作为额外容器运行,而不是每次构建项目时都安装 mysql

您不仅限于数据库服务。您可以向 .gitlab-ci.yml 添加任意数量的服务,或手动修改 config.toml。任何在 Docker Hub 或您的私有容器注册表中找到的镜像都可以用作服务。

有关使用私有镜像的信息,请参阅 访问私有容器注册表中的镜像

服务与 CI 容器本身共享相同的 DNS 服务器、搜索域和附加主机。

服务如何与作业关联

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

如果您将 mysql 添加为应用程序的服务,该镜像将用于创建一个与作业容器链接的容器。

MySQL 的服务容器可通过主机名 mysql 访问。要访问您的数据库服务,请连接名为 mysql 的主机,而不是套接字或 localhost。更多信息请参阅 访问服务

服务健康检查的工作原理

服务旨在提供网络可访问的附加功能。它们可能是像 MySQL 或 Redis 这样的数据库,甚至是 docker:dind(允许您使用 Docker-in-Docker (DinD))。它可以是 CI/CD 作业所需的任何通过网络访问的内容。

为确保此功能正常工作,Runner 会执行以下操作:

  1. 检查容器默认暴露哪些端口。
  2. 启动一个特殊容器,等待这些端口变为可访问状态。

如果检查的第二阶段失败,它会打印警告:*** WARNING: Service XYZ probably didn't start properly。此问题可能由以下原因导致:

  • 服务中没有打开的端口。
  • 服务未在超时前正确启动,且端口无响应。

在大多数情况下,这会影响作业,但也可能存在即使打印了警告作业仍成功的情况。例如:

  • 服务在警告发出后很快启动,且作业从头开始未使用链接服务。在这种情况下,当作业需要访问服务时,服务可能已就绪并等待连接。
  • 服务容器未提供任何网络服务,而是处理作业的目录(所有服务都将作业目录作为卷挂载在 /builds 下)。在这种情况下,服务完成其工作,且由于作业未尝试连接它,因此不会失败。

如果服务启动成功,它们会在 before_script 运行之前启动。这意味着您可以编写一个查询服务的 before_script

服务在作业结束时停止,即使作业失败也是如此。

使用服务镜像提供的软件

当您指定 service 时,这提供网络可访问的服务。数据库是此类服务最简单的示例。

服务功能不会将已定义的 services 镜像中的任何软件添加到作业的容器中。

例如,如果您在作业中定义了以下 servicesphpnodego 命令不会对您的脚本可用,并且作业会失败:

job:
  services:
    - php:7
    - node:latest
    - golang:1.10
  image: alpine:3.7
  script:
    - php -v
    - node -v
    - go version

如果您需要 phpnodego 对您的脚本可用,您应该:

  • 选择包含所有必需工具的现有 Docker 镜像。
  • 创建您自己的 Docker 镜像,包含所有必需工具,并在作业中使用它。

.gitlab-ci.yml 文件中定义 services

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

default:
  before_script:
    - bundle install

test:2.6:
  image: ruby:2.6
  services:
    - postgres:11.7
  script:
    - bundle exec rake spec

test:2.7:
  image: ruby:2.7
  services:
    - postgres:12.2
  script:
    - bundle exec rake spec

或者,您可以为 imageservices 传递一些扩展配置选项

default:
  image:
    name: ruby:2.6
    entrypoint: ["/bin/bash"]
  services:
    - name: my-postgres:11.7
      alias: db,postgres,pg
      entrypoint: ["/usr/local/bin/db-postgres"]
      command: ["start"]
  before_script:
    - bundle install

test:
  script:
    - bundle exec rake spec

访问服务

如果您需要一个 WordPress 实例来测试与您应用程序的 API 集成,您可以在 .gitlab-ci.yml 文件中使用 tutum/wordpress 镜像:

services:
  - tutum/wordpress:latest

如果您没有指定服务别名,当作业运行时,tutum/wordpress 将启动。您可以通过两个主机名从构建容器访问它:

  • tutum-wordpress
  • tutum__wordpress

带下划线的主机名不是 RFC 有效的,可能会导致第三方应用程序出现问题。

服务主机名的默认别名根据以下规则从其镜像名称创建:

  • 冒号 (:) 之后的所有内容将被移除。
  • 斜杠 (/) 替换为双下划线 (__) 创建主别名。
  • 斜杠 (/) 替换为单连字符 (-) 创建次别名。

要覆盖默认行为,您可以指定一个或多个服务别名

连接服务

您可以在复杂作业中使用相互依赖的服务,例如端到端测试,其中外部 API 需要与其自身数据库通信。

例如,对于使用 API 的前端应用程序的端到端测试,且 API 需要数据库:

end-to-end-tests:
  image: node:latest
  services:
    - name: selenium/standalone-firefox:${FIREFOX_VERSION}
      alias: firefox
    - name: registry.gitlab.com/organization/private-api:latest
      alias: backend-api
    - name: postgres:14.3
      alias: db postgres db
  variables:
    FF_NETWORK_PER_BUILD: 1
    POSTGRES_PASSWORD: supersecretpassword
    BACKEND_POSTGRES_HOST: postgres
  script:
    - npm install
    - npm test

要使此解决方案生效,您必须使用为每个作业创建新网络的网络模式

将 CI/CD 变量传递给服务

您还可以将自定义的 CI/CD 变量直接传递到 .gitlab-ci.yml 文件中,以微调您的 Docker imagesservices。更多信息,请参阅 .gitlab-ci.yml 中定义的变量

# 以下变量会自动传递到 Postgres 容器以及 Ruby 容器中,并在每个容器内可用。
variables:
  HTTPS_PROXY: "https://10.1.1.1:8090"
  HTTP_PROXY: "https://10.1.1.1:8090"
  POSTGRES_DB: "my_custom_db"
  POSTGRES_USER: "postgres"
  POSTGRES_PASSWORD: "example"
  PGDATA: "/var/lib/postgresql/data"
  POSTGRES_INITDB_ARGS: "--encoding=UTF8 --data-checksums"

default:
  services:
    - name: postgres:11.7
      alias: db
      entrypoint: ["docker-entrypoint.sh"]
      command: ["postgres"]
  image:
    name: ruby:2.6
    entrypoint: ["/bin/bash"]
  before_script:
    - bundle install

test:
  script:
    - bundle exec rake spec

services 的可用设置

设置 必需条件 GitLab 版本 描述
name 当与其他任何选项一起使用时为必需 9.4 要使用的镜像的完整名称。如果完整镜像名称包含注册表主机名,请使用 alias 选项定义更短的服务访问名称。更多信息,请参阅 访问服务
entrypoint 9.4 作为容器入口点执行的命令或脚本。在创建容器时,它被转换为 Docker 的 --entrypoint 选项。语法类似于 DockerfileENTRYPOINT 指令,其中每个 shell 令牌是数组中的单独字符串。
command 9.4 应作为容器命令使用的命令或脚本。它被转换为传递到镜像名称之后的 Docker 的参数。语法类似于 DockerfileCMD 指令,其中每个 shell 令牌是数组中的单独字符串。
alias 9.4 从作业容器访问服务的附加别名。多个别名可以用空格或逗号分隔。更多信息,请参阅 访问服务。在 Kubernetes 执行器中将别名用作容器名称是在 GitLab Runner 17.9 中引入的。更多信息,请参阅 使用 Kubernetes 执行器配置服务容器名称
variables 14.5 专门传递给服务的附加环境变量。语法与 作业变量 相同。服务变量不能引用自身。
pull_policy 15.1 指定执行作业时 Runner 如何拉取 Docker 镜像。有效值为 alwaysif-not-presentnever。默认值为 always。更多信息,请参阅 services:pull_policy

从同一镜像启动多个服务

在新的扩展 Docker 配置选项之前,以下配置无法正常工作:

services:
  - mysql:latest
  - mysql:latest

Runner 将启动两个容器,每个容器都使用 mysql:latest 镜像。然而,两者都会根据默认主机名命名规则被添加到作业容器中,别名为 mysql。这将导致其中一个服务无法访问。

在新的扩展 Docker 配置选项之后,先前的示例将如下所示:

services:
  - name: mysql:latest
    alias: mysql-1
  - name: mysql:latest
    alias: mysql-2

Runner 仍然启动两个使用 mysql:latest 镜像的容器,但现在每个容器都可以通过在 .gitlab-ci.yml 文件中配置的别名访问。

为服务设置命令

假设您有一个包含某个 SQL 数据库的 super/sql:latest 镜像。您希望将其用作作业的服务。还假设此镜像在启动容器时不会启动数据库进程。用户需要手动使用 /usr/bin/super-sql run 作为命令来启动数据库。

在新的扩展 Docker 配置选项之前,您需要:

  • 基于 super/sql:latest 镜像创建您自己的镜像。

  • 添加默认命令。

  • 在作业配置中使用该镜像。

    • my-super-sql:latest 镜像的 Dockerfile:

      FROM super/sql:latest
      CMD ["/usr/bin/super-sql", "run"]
    • .gitlab-ci.yml 中的作业:

      services:
        - my-super-sql:latest

在新的扩展 Docker 配置选项之后,您可以在 .gitlab-ci.yml 文件中设置 command

services:
  - name: super/sql:latest
    command: ["/usr/bin/super-sql", "run"]

command 的语法类似于 Dockerfile CMD

使用别名作为 Kubernetes 执行器的服务容器名称

您可以将服务别名用作 Kubernetes 执行器的服务容器名称。GitLab Runner 根据以下条件命名容器:

  • 当为服务设置多个别名时,服务容器将命名为第一个满足以下条件的别名:
  • 当别名无法用于命名服务容器时,GitLab Runner 会回退到 svc-i 模式。

以下示例说明了如何使用别名来命名 Kubernetes 执行器的服务容器。

每个服务一个别名

在以下 .gitlab-ci.yml 文件中:

job:
  image: alpine:latest
  script:
    - sleep 10
  services:
    - name: alpine:latest
      alias: alpine
    - name: mysql:latest
      alias: mysql

系统除了标准的 buildhelper 容器外,还创建了名为 alpinemysql 的容器。这些别名被使用是因为它们:

然而,在以下 .gitlab-ci.yml 中:

job:
  image: alpine:latest
  script:
    - sleep 10
  services:
    - name: mysql:lts
      alias: mysql
    - name: mysql:latest
      alias: mysql

系统除了 buildhelper 容器外,还创建了另外两个名为 mysqlsvc-0 的容器。mysql 容器对应 mysql:lts 镜像,而 svc-0 容器对应 mysql:latest 镜像。

每个服务多个别名

在以下 .gitlab-ci.yml 文件中:

job:
  image: alpine:latest
  script:
    - sleep 10
  services:
    - name: alpine:latest
      alias: alpine,alpine-latest
    - name: alpine:edge
      alias: alpine,alpine-edge,alpine-latest

系统除了 buildhelper 容器外,还创建了另外四个容器:

  • alpine 应对应 alpine:latest 镜像的容器。
  • alpine-edge 应对应 alpine:edge 镜像的容器(alpine 别名已被前一个容器使用)。

在此示例中,未使用 alpine-latest 别名。

然而,在以下 .gitlab-ci.yml 中:

job:
  image: alpine:latest
  script:
    - sleep 10
  services:
    - name: alpine:latest
      alias: alpine,alpine-edge
    - name: alpine:edge
      alias: alpine,alpine-edge
    - name: alpine:3.21
      alias: alpine,alpine-edge

除了 buildhelper 容器外,还创建了另外六个容器。

  • alpine 应对应 alpine:latest 镜像的容器。

  • alpine-edge 应对应 alpine:edge 镜像的容器(alpine 别名已被前一个容器使用)。

  • svc-0 应对应 alpine:3.21 镜像的容器(alpinealpine-edge 别名已被前两个容器使用)。

    • svc-i 模式中的 i 不表示服务在提供列表中的位置。相反,它表示当找不到可用别名时服务在列表中的位置。

    • 当提供无效别名(不符合 Kubernetes 约束)时,作业会因以下错误而失败(示例别名为 alpine_edge)。此失败发生是因为别名也被用于在作业 Pod 上创建本地 DNS 条目。

      ERROR: Job failed (system failure): prepare environment: setting up build pod: provided host alias
      alpine_edge for service alpine:edge is invalid DNS. a lowercase RFC 1123 subdomain must consist of lower
      case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g.
      'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*').
      Check https://docs.gitlab.com/runner/shells/index.html#shell-profile-loading for more information.

docker run(Docker-in-Docker)并行使用 services

使用 docker run 启动的容器也可以连接到 GitLab 提供的服务。

如果启动服务成本高昂或耗时较长,您可以从不同的客户端环境运行测试,同时仅启动一次被测试的服务。

access-service:
  stage: build
  image: docker:20.10.16
  services:
    - docker:dind                    # docker run 所需
    - tutum/wordpress:latest
  variables:
    FF_NETWORK_PER_BUILD: "true"     # 启用容器到容器网络
  script: |
    docker run --rm --name curl \
      --volume  "$(pwd)":"$(pwd)"    \
      --workdir "$(pwd)"             \
      --network=host                 \
      curlimages/curl:7.74.0 curl "http://tutum-wordpress"

要使此解决方案生效,您必须:

Docker 集成的工作原理

以下是 Docker 在作业期间执行步骤的高级概述。

  1. 创建任何服务容器:mysqlpostgresqlmongodbredis
  2. 创建一个缓存容器,用于存储 config.toml 和构建镜像(如先前示例中的 ruby:2.6)的 Dockerfile 中定义的所有卷。
  3. 创建一个构建容器并将任何服务容器链接到构建容器。
  4. 启动构建容器,并将作业脚本发送到容器中。
  5. 运行作业脚本。
  6. /builds/group-name/project-name/ 中检出代码。
  7. 运行 .gitlab-ci.yml 中定义的任何步骤。
  8. 检查构建脚本的退出状态。
  9. 移除构建容器和所有创建的服务容器。

捕获服务容器日志

服务容器中运行的应用程序生成的日志可以被捕获以供后续检查和调试。当服务容器成功启动但因意外行为导致作业失败时,查看服务容器日志。日志可以指示容器中服务配置的缺失或不正确。

CI_DEBUG_SERVICES 应仅在主动调试服务容器时启用,因为捕获服务容器日志会带来存储和性能影响。

要启用服务日志记录,将 CI_DEBUG_SERVICES 变量添加到项目的 .gitlab-ci.yml 文件中:

variables:
  CI_DEBUG_SERVICES: "true"

接受值:

  • 启用:TRUE, true, True
  • 禁用:FALSE, false, False

任何其他值都会导致错误消息并有效地禁用该功能。

启用后,所有服务容器的日志都会被捕获,并与其他日志一起并发流式传输到作业跟踪日志中。每个容器的日志都带有容器别名前缀,并以不同颜色显示。

要诊断作业失败,您可以调整您要捕获日志的服务容器的日志级别。默认日志级别可能无法提供足够的故障排除信息。

启用 CI_DEBUG_SERVICES 可能会暴露被屏蔽的变量。当 CI_DEBUG_SERVICES 启用时,服务容器日志和 CI 作业日志会并发流式传输到作业跟踪日志中。这意味着服务容器日志可能会插入到作业的被屏蔽日志中。这将破坏变量屏蔽机制,导致被屏蔽的变量被暴露。

请参阅 屏蔽 CI/CD 变量

在本地调试作业

以下命令以非 root 权限运行。验证您能否使用用户帐户运行 Docker 命令。

首先创建一个名为 build_script 的文件:

cat <<EOF > build_script
git clone https://gitlab.com/gitlab-org/gitlab-runner.git /builds/gitlab-org/gitlab-runner
cd /builds/gitlab-org/gitlab-runner
make runner-bin-host
EOF

这里我们使用包含 Makefile 的 GitLab Runner 仓库作为示例,因此运行 make 会执行 Makefile 中定义的目标。您也可以运行特定于您项目的命令,而不是 make runner-bin-host

然后创建一个服务容器:

docker run -d --name service-redis redis:latest

上述命令使用最新的 Redis 镜像创建了一个名为 service-redis 的服务容器。服务容器在后台运行 (-d)。

最后,通过执行我们之前创建的 build_script 文件创建一个构建容器:

docker run --name build -i --link=service-redis:redis golang:latest /bin/bash < build_script

上述命令创建了一个名为 build 的容器,该容器由 golang:latest 镜像生成,并链接了一个服务。build_script 通过 stdin 管道传输到 bash 解释器,后者在 build 容器中执行 build_script

测试完成后,使用以下命令移除容器:

docker rm -f -v build service-redis

这将强制 (-f) 移除 build 容器、服务容器以及所有随容器创建创建的卷 (-v)。

使用服务容器的安全性

Docker 特权模式也适用于服务。这意味着服务镜像容器可以访问主机系统。您应仅使用来自可信来源的容器镜像。

共享 /builds 目录

构建目录作为卷挂载在 /builds 下,并在作业和服务之间共享。作业在服务运行后将项目检出到 /builds/$CI_PROJECT_PATH。您的服务可能需要访问项目文件或存储工件。如果是这样,请等待目录存在且 $CI_COMMIT_SHA 已检出。作业完成检出过程之前所做的任何更改都会被检出过程移除。

服务必须检测作业目录何时已填充并准备好进行处理。例如,等待特定文件变为可用。

在启动后立即开始工作的服务可能会失败,因为作业数据可能尚不可用。例如,容器使用 docker build 命令与 DinD 服务建立网络连接。该服务指示其 API 启动容器镜像构建。Docker Engine 必须能够访问您在 Dockerfile 中引用的文件。因此,您需要在服务中访问 CI_PROJECT_DIR。然而,Docker Engine 直到作业中调用 docker build 命令时才会尝试访问它。此时,/builds 目录已填充数据。服务在启动后立即尝试写入 CI_PROJECT_DIR 可能会因 No such file or directory 错误而失败。

在服务不由作业本身控制且与作业数据交互的场景中,请考虑 Docker 执行器工作流