Services

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

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

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

例如,每次构建项目时,使用现有镜像并将其作为附加容器运行比安装 mysql 更容易、更快。

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

服务继承与 CI 容器本身相同的 DNS 服务器、搜索域和其他主机。

Service 如何与作业联系起来

要更好地了解容器链接的工作原理,请阅读将容器链接在一起

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

MySQL 的服务容器可在主机名mysql 下访问。 要访问您的数据库服务,请连接到名为 mysql 的主机而不是套接字或 localhost。在访问服务中阅读更多内容。

服务的健康检查如何工作

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

为了确保有效,runner:

  1. 检查容器默认暴露了哪些端口。
  2. 启动一个特殊的容器,等待这些端口被访问。

如果检查的第二阶段失败,它会打印警告:*** WARNING:Service XYZ 可能没有正确启动。 出现此问题的原因可能是:

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

在大多数情况下,它会影响作业,但在某些情况下,即使打印了该警告,作业仍会成功。例如:

  • 服务在发出警告后不久启动,并且作业从一开始就没有使用链接的服务。在那种情况下,当作业需要访问服务时,它可能已经在那里等待连接。
  • 服务容器不提供任何网络服务,但它正在对作业的目录做一些事情(所有服务都将作业目录挂载为 /builds 下的卷)。在这种情况下,服务会完成它的作业,并且因为该作业不会尝试连接到它,所以它不会失败。

如果服务成功启动,它们会在 before_script 运行之前启动,您可以编写查询服务的 before_script

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

哪些服务不适合

如前所述,此功能旨在提供网络可访问服务。数据库是此类服务的最简单示例。

服务功能的设计目的不是,也不会将定义的 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
      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 有效的,可能会导致第三方应用程序出现问题。

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

  • 冒号 (:) 之后的所有内容都被删除。
  • 斜线 (/) 替换为双下划线 (__) 并创建主要别名。
  • 斜线 (/) 替换为单个破折号 (-) 并创建辅助别名(需要 GitLab Runner v1.1.0 或更高版本)。

连接服务

您可以将相互依赖的服务用于复杂的作业,例如外部 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
    - postgres:14.3
  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

# The following variables are automatically passed down to the Postgres container
# as well as the Ruby container and available within each.
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"

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 的可用设置

设置 是否必需 引入的版本 描述
name yes,与任何其他选项一起使用时 9.4 要使用的镜像的全名。如果完整的镜像名称包含镜像库主机名,请使用 alias 选项来定义较短的服务访问名称。有关更多信息,请参阅访问服务
entrypoint no 9.4 作为容器 entrypoint 执行的命令或脚本。在创建容器时,它被转换为 Docker 的 --entrypoint 选项。语法类似于 DockerfileENTRYPOINT 指令,其中每个 shell 令牌是数组中的一个单独字符串。
command no 9.4 应用作容器命令的命令或脚本。它被转换为在镜像名称之后传递给 Docker 的参数。语法类似于 DockerfileCMD 指令,其中每个 shell 令牌是数组中的一个单独字符串。
alias no 9.4 可用于从作业的容器访问服务的其他别名。阅读访问服务了解更多信息。
variables no 14.5 专门传递给服务的其他环境变量。语法与作业变量相同。服务变量不能引用自身。

从同一个镜像启动多个服务

在新的扩展 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 image's Dockerfile
    
    FROM super/sql:latest
    CMD ["/usr/bin/super-sql", "run"]
    
    # .gitlab-ci.yml
    
    services:
      - my-super-sql:latest
    

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

# .gitlab-ci.yml

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

command 的语法类似于 Dockerfile 的CMD

servicesdocker run(Docker-in-Docker)并排使用

以 docker run 开头的容器也可以连接到 GitLab 提供的服务。

当启动服务很昂贵或很耗时时,您可以使用此技术从不同的客户端环境运行测试,而只启动一次被测试的服务。

access-service:
  stage: build
  image: docker:20.10.16
  services:
    - docker:dind                    # necessary for docker run
    - tutum/wordpress:latest
  variables:
    FF_NETWORK_PER_BUILD: "true"     # activate container-to-container networking
  script: |
    docker run --rm --name curl \
      --volume  "$(pwd)":"$(pwd)"    \
      --workdir "$(pwd)"             \
      --network=host                 \
      curlimages/curl:7.74.0 curl "http://tutum-wordpress"

要使此解决方案起作用,您必须:

  • 使用为每个作业创建新网络的网络模式。
  • 不能将 Docker 执行器与 Docker 套接字绑定一起使用。如果必须,那么在上面的示例中,使用为该作业创建的动态网络名称,而不是 host

Docker 集成工作原理

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

  1. 创建任意服务容器:mysqlpostgresqlmongodbredis
  2. 创建一个缓存容器来存储构建镜像的 config.tomlDockerfile 中定义的所有卷(如上例中的 ruby:2.6)。
  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"

可接受的值为:

  • 已启用:TRUEtrueTrue
  • 已禁用:FALSEfalseFalse

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

启用后,所有服务容器的日志将被捕获并与其他日志同时流传输到作业跟踪日志中。来自每个容器的日志将以容器的别名作为前缀,并以不同的颜色显示。

note您可能需要调整要为其捕获日志的服务容器中的日志记录级别,因为默认日志记录级别可能无法提供足够的详细信息来诊断作业失败。
caution启用 CI_DEBUG_SERVICES 可能会导致隐藏的变量被泄露。当启用 CI_DEBUG_SERVICES 时,服务容器日志和 CI 作业的日志同时流传输到作业的跟踪日志,这使得服务容器日志可以插入到作业的隐藏日志中,将阻碍变量隐藏机制并导致被隐藏的变量被揭示。

查看隐藏 CI/CD 变量

本地 debug 作业

以下命令在没有 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
EOF

这里我们以包含 Makefile 的 GitLab Runner 仓库为例,因此运行 make 会执行 Makefile 中定义的命令。 您可以运行特定于您的项目的命令,而不是 make

然后创建一些服务容器:

docker run -d --name service-mysql mysql:latest
docker run -d --name service-postgres postgres:latest

这将创建两个服务容器,名为service-mysqlservice-postgres,分别使用最新的 MySQL 和 PostgreSQL 镜像。它们都在后台运行(-d)。

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

docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.6 /bin/bash < build_script

上面的命令创建了一个名为 build 的容器,该容器从 ruby:2.6 镜像生成,并且有两个服务链接到它。build_script 使用 stdin 流水线传输到 bash 解释器,然后在 build 容器中执行 build_script

当您完成测试并且不再需要容器时,您可以使用以下命令删除它们:

docker rm -f -v build service-mysql service-postgres

这会强制 (-f) 删除 build 容器、两个服务容器以及在创建容器时创建的所有卷 (-v)。

使用服务容器时的安全性

Docker 特权模式适用于服务,服务镜像容器可以访问主机系统。您应该只使用来自受信任来源的容器镜像。

共享 /builds 目录

服务可以访问构建中的文件,因为所有服务都将作业目录安装为 /builds 下的卷。