服务
- Tier: 基础版, 专业版, 旗舰版
- Offering: JihuLab.com, 私有化部署
当你配置 CI/CD 时,你需要指定一个镜像,该镜像用于创建运行作业的容器。要指定此镜像,可以使用 image 关键词。
你可以使用 services 关键词指定一个附加镜像。此附加镜像用于创建另一个容器,该容器对第一个容器可用。两个容器可以互相访问,并在运行作业时进行通信。
服务镜像可以运行任何应用程序,但最常见的用例是运行一个数据库容器,例如:
- MySQL
- PostgreSQL
- Redis
- GitLab 作为提供 JSON API 的微服务示例
假设你正在开发一个使用数据库进行存储的内容管理系统。你需要一个数据库来测试应用程序中的所有功能。在这种情况下,运行一个数据库容器作为服务镜像是一个很好的用例。
使用现有镜像并将其作为附加容器运行,而不是每次构建项目时都安装 mysql。
你不仅限于数据库服务。你可以根据需要在 .gitlab-ci.yml 中添加任意数量的服务,或者手动修改 config.toml。在 DockerHub 或你的私有容器镜像仓库中找到的任何镜像都可以用作服务。
有关使用私有镜像的信息,请参见从私有容器镜像仓库访问镜像。
服务继承与 CI 容器本身相同的 DNS 服务器、搜索域和附加主机。
服务如何链接到作业
如果你将 mysql 添加为应用程序的服务,镜像将用于创建一个链接到作业容器的容器。
MySQL 的服务容器可在主机名 mysql 下访问。要访问你的数据库服务,请连接到名为 mysql 的主机,而不是套接字或 localhost。详细信息请参见访问服务。
服务的健康检查如何工作
服务旨在提供 网络可访问 的附加功能。它们可能是像 MySQL 或 Redis 这样的数据库,甚至是 docker:dind,允许你使用 Docker-in-Docker (DinD)。实际上,它可以是 CI/CD 作业继续所需的任何东西,并且通过网络访问。
为了确保这项工作,runner:
- 检查默认情况下从容器中暴露的端口。
- 启动一个特殊容器,等待这些端口可访问。
如果检查的第二阶段失败,它会打印警告:*** WARNING: Service XYZ probably didn't start properly。这个问题可能是由于以下原因导致的:
- 服务中没有打开的端口。
- 在超时之前,服务没有正确启动,端口没有响应。
在大多数情况下,它会影响作业,但在某些情况下,即使打印了该警告,作业仍可能成功。例如:
- 在警告出现后不久,服务启动,作业从一开始并未使用链接服务。在这种情况下,当作业需要访问服务时,它可能已经在那里等待连接。
- 服务容器没有提供任何网络服务,但正在处理作业的目录(所有服务都将作业目录作为卷挂载在 /builds 下)。在这种情况下,服务完成了它的工作,因为作业没有尝试连接到它,所以不会失败。
如果服务成功启动,它们会在 before_script 运行之前启动。这意味着你可以编写一个查询服务的 before_script。
即使作业失败,服务也会在作业结束时停止。
使用服务镜像提供的软件
当你指定 service 时,这提供了 网络可访问 的服务。数据库是此类服务的最简单示例。
服务功能不会将定义的 services 镜像中的任何软件添加到作业的容器中。
例如,如果你在作业中定义了以下 services,则 php、node 或 go 命令对你的脚本不可用,并且作业将失败:
yaml1job: 2 services: 3 - php:7 4 - node:latest 5 - golang:1.10 6 image: alpine:3.7 7 script: 8 - php -v 9 - node -v 10 - go version
如果你需要 php、node 和 go 可用于你的脚本,你应该:
- 选择包含所有必需工具的现有 Docker 镜像。
- 创建你自己的 Docker 镜像,其中包含所有必需的工具,并在你的作业中使用它。
在 .gitlab-ci.yml 文件中定义 services
也可以为每个作业定义不同的镜像和服务:
yaml1default: 2 before_script: 3 - bundle install 4 5test:2.6: 6 image: ruby:2.6 7 services: 8 - postgres:11.7 9 script: 10 - bundle exec rake spec 11 12test:2.7: 13 image: ruby:2.7 14 services: 15 - postgres:12.2 16 script: 17 - bundle exec rake spec
或者你可以为 image 和 services 传递一些扩展配置选项:
yaml1default: 2 image: 3 name: ruby:2.6 4 entrypoint: ["/bin/bash"] 5 services: 6 - name: my-postgres:11.7 7 alias: db,postgres,pg 8 entrypoint: ["/usr/local/bin/db-postgres"] 9 command: ["start"] 10 before_script: 11 - bundle install 12 13test: 14 script: 15 - bundle exec rake spec
访问服务
假设你需要一个 Wordpress 实例来测试与应用程序的一些 API 集成。然后你可以在你的 .gitlab-ci.yml 文件中使用例如 tutum/wordpress 镜像:
yamlservices: - tutum/wordpress:latest
如果你没有指定服务别名,当作业运行时,tutum/wordpress 将启动。你可以从构建容器中通过以下两个主机名访问它:
- tutum-wordpress
- tutum__wordpress
具有下划线的主机名不是 RFC 有效的,可能会在第三方应用程序中引起问题。
服务主机名的默认别名是根据其镜像名称创建的,规则如下:
- 删除冒号 (:) 之后的所有内容。
- 斜杠 (/) 替换为双下划线 (__) 并创建主别名。
- 斜杠 (/) 替换为单破折号 (-) 并创建次别名(需要极狐GitLab Runner v1.1.0 或更高版本)。
要覆盖默认行为,你可以指定一个或多个服务别名。
连接服务
你可以在复杂作业中使用相互依赖的服务,例如需要与自身数据库通信的外部 API。
例如,对于一个使用 API 的前端应用程序的端到端测试,并且该 API 需要一个数据库:
yaml1end-to-end-tests: 2 image: node:latest 3 services: 4 - name: selenium/standalone-firefox:${FIREFOX_VERSION} 5 alias: firefox 6 - name: registry.gitlab.com/organization/private-api:latest 7 alias: backend-api 8 - name: postgres:14.3 9 alias: db postgres db 10 variables: 11 FF_NETWORK_PER_BUILD: 1 12 POSTGRES_PASSWORD: supersecretpassword 13 BACKEND_POSTGRES_HOST: postgres 14 script: 15 - npm install 16 - npm test
要使此解决方案有效,你必须使用为每个作业创建新网络的网络模式。
将 CI/CD 变量传递给服务
你还可以直接在 .gitlab-ci.yml 文件中传递自定义 CI/CD 变量来微调你的 Docker images 和 services。有关更多信息,请阅读.gitlab-ci.yml 定义的变量。
yaml1# 以下变量会自动传递到 Postgres 容器以及 Ruby 容器,并在其中可用。 2variables: 3 HTTPS_PROXY: "https://10.1.1.1:8090" 4 HTTP_PROXY: "https://10.1.1.1:8090" 5 POSTGRES_DB: "my_custom_db" 6 POSTGRES_USER: "postgres" 7 POSTGRES_PASSWORD: "example" 8 PGDATA: "/var/lib/postgresql/data" 9 POSTGRES_INITDB_ARGS: "--encoding=UTF8 --data-checksums" 10 11default: 12 services: 13 - name: postgres:11.7 14 alias: db 15 entrypoint: ["docker-entrypoint.sh"] 16 command: ["postgres"] 17 image: 18 name: ruby:2.6 19 entrypoint: ["/bin/bash"] 20 before_script: 21 - bundle install 22 23test: 24 script: 25 - bundle exec rake spec
services 的可用设置
设置 | 是否必需 | 极狐GitLab 版本 | 描述 |
---|---|---|---|
name | 是,当与其他选项一起使用时 | 9.4 | 要使用的镜像的全名。如果完整镜像名称包括注册表主机名,请使用 alias 选项定义更短的服务访问名称。有关更多信息,请参见访问服务。 |
entrypoint | 否 | 9.4 | 作为容器入口点执行的命令或脚本。在创建容器时,它会转换为 Docker 的 --entrypoint 选项。语法类似于 Dockerfile 的 ENTRYPOINT 指令,其中每个 shell 标记都是数组中的单独字符串。 |
command | 否 | 9.4 | 应用作容器命令的命令或脚本。它会转换为在镜像名称之后传递给 Docker 的参数。语法类似于 Dockerfile 的 CMD 指令,其中每个 shell 标记都是数组中的单独字符串。 |
alias | 否 | 9.4 | 从作业的容器访问服务的附加别名。多个别名可以用空格或逗号分隔。有关更多信息,请参见访问服务。在 Kubernetes 执行器中使用别名作为容器名称是在极狐GitLab Runner 17.9 中引入的。有关更多信息,请参见使用别名作为 Kubernetes 执行器的服务容器名称。 |
variables | 否 | 14.5 | 独家传递给服务的附加环境变量。语法与作业变量相同。服务变量不能引用它们自己。 |
从同一镜像启动多个服务
在新的扩展 Docker 配置选项之前,以下配置无法正常工作:
yamlservices: - mysql:latest - mysql:latest
runner 将启动两个容器,每个容器使用 mysql:latest 镜像。然而,它们都将以 mysql 别名添加到作业的容器中,基于默认主机名命名。这将导致其中一个服务无法访问。
在新的扩展 Docker 配置选项之后,上面的示例将如下所示:
yamlservices: - 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:
dockerfileFROM super/sql:latest CMD ["/usr/bin/super-sql", "run"]
-
在 .gitlab-ci.yml 中的作业中:
yamlservices: - my-super-sql:latest
-
在新的扩展 Docker 配置选项之后,你可以在 .gitlab-ci.yml 文件中设置一个 command:
yamlservices: - name: super/sql:latest command: ["/usr/bin/super-sql", "run"]
command 的语法类似于 Dockerfile CMD。
使用别名作为 Kubernetes 执行器的服务容器名称
History
- 在极狐GitLab 和极狐GitLab Runner 17.9 中引入。
你可以使用服务别名作为 Kubernetes 执行器的服务容器名称。极狐GitLab Runner 根据以下条件命名容器:
- 当为服务设置多个别名时,服务容器将以第一个别名命名,该别名:
- 尚未被其他服务容器使用。
- 符合 Kubernetes 标签名的约束。
- 当别名无法用于命名服务容器时,极狐GitLab Runner 回退到 svc-i 模式。
以下示例说明了如何使用别名为 Kubernetes 执行器的服务容器命名。
每个服务一个别名
在以下 .gitlab-ci.yml 文件中:
yaml1job: 2 image: alpine:latest 3 script: 4 - sleep 10 5 services: 6 - name: alpine:latest 7 alias: alpine 8 - name: mysql:latest 9 alias: mysql
系统在创建的作业 Pod 中会有名为 alpine 和 mysql 的容器,除了标准的 build 和 helper 容器之外。这些别名被使用是因为它们:
- 未被其他服务容器使用。
- 符合 Kubernetes 标签名的约束。
然而,在以下 .gitlab-ci.yml 中:
yaml1job: 2 image: alpine:latest 3 script: 4 - sleep 10 5 services: 6 - name: mysql:lts 7 alias: mysql 8 - name: mysql:latest 9 alias: mysql
系统创建了两个额外的容器,名为 mysql 和 svc-0,除了 build 和 helper 容器之外。mysql 容器对应于 mysql:lts 镜像,而 svc-0 容器对应于 mysql:latest 镜像。
每个服务多个别名
在以下 .gitlab-ci.yml 文件中:
yaml1job: 2 image: alpine:latest 3 script: 4 - sleep 10 5 services: 6 - name: alpine:latest 7 alias: alpine,alpine-latest 8 - name: alpine:edge 9 alias: alpine,alpine-edge,alpine-latest
系统创建了四个额外的容器,除了 build 和 helper 容器之外:
- alpine 应对应于带有 alpine:latest 镜像的容器。
- alpine-edge 应对应于带有 alpine:edge 镜像的容器(alpine 别名已用于上一个容器)。
在此示例中,别名 alpine-latest 未被使用。
然而,在以下 .gitlab-ci.yml 中:
yaml1job: 2 image: alpine:latest 3 script: 4 - sleep 10 5 services: 6 - name: alpine:latest 7 alias: alpine,alpine-edge 8 - name: alpine:edge 9 alias: alpine,alpine-edge 10 - name: alpine:3.21 11 alias: alpine,alpine-edge
除了 build 和 helper 容器外,还会创建六个容器。
- alpine 应指带有 alpine:latest 镜像的容器。
- alpine-edge 应指带有 alpine:edge 镜像的容器(alpine 别名已用于上一个容器)。
- svc-0 应指带有 alpine:3.21 镜像的容器(alpine 和 alpine-edge 别名已用于上一个容器)。
-
i 在 svc-i 模式中不表示服务在提供列表中的位置。相反,它表示在没有可用别名时的服务位置。
-
当提供了无效别名(不满足 Kubernetes 约束)时,作业会因以下错误而失败(例如使用别名 alpine_edge)。此失败是因为别名也用于在作业 Pod 上创建本地 DNS 条目。
plaintextERROR: 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://gitlab.cn/docs/runner/shells/index.html#shell-profile-loading for more information.
将 services 与 docker run(Docker-in-Docker)并行使用
使用 docker run 启动的容器也可以连接到极狐GitLab 提供的服务。
如果启动服务的成本高昂或耗时,你可以从不同的客户端环境中运行测试,而只启动一次被测试的服务。
yaml1access-service: 2 stage: build 3 image: docker:20.10.16 4 services: 5 - docker:dind # necessary for docker run 6 - tutum/wordpress:latest 7 variables: 8 FF_NETWORK_PER_BUILD: "true" # activate container-to-container networking 9 script: | 10 docker run --rm --name curl \ 11 --volume "$(pwd)":"$(pwd)" \ 12 --workdir "$(pwd)" \ 13 --network=host \ 14 curlimages/curl:7.74.0 curl "http://tutum-wordpress"
要使此解决方案有效,你必须:
- 使用为每个作业创建新网络的网络模式。
- 不要使用 Docker 执行器和 Docker 套接字绑定。如果你必须这样做,那么在上述示例中,不要使用 host,而是使用为此作业创建的动态网络名称。
Docker 集成如何工作
以下是 Docker 在作业时间执行的步骤的高级概述。
- 创建任何服务容器:mysql、postgresql、mongodb、redis。
- 创建一个缓存容器,以存储在 config.toml 和构建镜像的 Dockerfile(如上例中的 ruby:2.6)中定义的所有卷。
- 创建一个构建容器,并将任何服务容器链接到构建容器。
- 启动构建容器,并向容器发送作业脚本。
- 运行作业脚本。
- 在 /builds/group-name/project-name/ 中检出代码。
- 运行 .gitlab-ci.yml 中定义的任何步骤。
- 检查构建脚本的退出状态。
- 删除构建容器和所有创建的服务容器。
捕获服务容器日志
History
- 在极狐GitLab Runner 15.6 中引入。
服务容器中运行的应用程序生成的日志可以被捕获以供后续检查和调试。当服务容器启动成功但由于意外行为导致作业失败时,可以查看服务容器日志。日志可以指示容器中服务的缺失或不正确配置。
CI_DEBUG_SERVICES 仅在服务容器正在积极调试时启用,因为捕获服务容器日志会对存储和性能产生影响。
要启用服务日志记录,请将 CI_DEBUG_SERVICES 变量添加到项目的 .gitlab-ci.yml 文件中:
yamlvariables: CI_DEBUG_SERVICES: "true"
接受的值为:
- 启用:TRUE、true、True
- 禁用:FALSE、false、False
任何其他值都会导致错误消息,并有效地禁用该功能。
启用后,所有服务容器的日志都会被捕获并与其他日志同时流入作业跟踪日志。每个容器的日志前面都有该容器的别名前缀,并以不同的颜色显示。
要诊断作业失败,可以在你想捕获日志的服务容器中调整日志记录级别。默认日志级别可能无法提供足够的故障排除信息。
启用 CI_DEBUG_SERVICES 可能会暴露被屏蔽的变量。当 CI_DEBUG_SERVICES 启用时,服务容器日志和 CI 作业的日志会同时流入作业的跟踪日志。这意味着服务容器日志可能会插入到作业的屏蔽日志中。这会破坏变量屏蔽机制,并导致被屏蔽的变量被揭示。
请参见屏蔽 CI/CD 变量
本地调试作业
以下命令在没有 root 权限的情况下运行。验证你是否可以使用用户帐户运行 Docker 命令。
首先创建一个名为 build_script 的文件:
shellcat <<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
在此我们使用极狐GitLab Runner 仓库作为示例,其中包含一个 Makefile,因此运行 make 会执行 Makefile 中定义的目标。你可以用特定于你的项目的命令替代 make runner-bin-host。
然后创建一个服务容器:
shelldocker run -d --name service-redis redis:latest
上一个命令使用最新的 Redis 镜像创建了一个名为 service-redis 的服务容器。服务容器在后台运行 (-d)。
最后,通过执行我们之前创建的 build_script 文件来创建一个构建容器:
shelldocker run --name build -i --link=service-redis:redis golang:latest /bin/bash < build_script
上面的命令创建了一个名为 build 的容器,从 golang:latest 镜像派生,并链接了一个服务。build_script 使用 stdin 管道到 bash 解释器中,后者在 build 容器中执行 build_script。
使用以下命令在测试完成后删除容器:
shelldocker rm -f -v build service-redis
这会强制删除 (-f) build 容器、服务容器以及所有在创建容器时创建的卷 (-v)。
使用服务容器时的安全性
Docker 特权模式适用于服务。这意味着服务镜像容器可以访问主机系统。你应该仅使用来自可信来源的容器镜像。
共享 /builds 目录
构建目录作为卷挂载在 /builds 下,并在作业和服务之间共享。服务运行后,作业会将项目检出到 /builds/$CI_PROJECT_PATH。你的服务可能需要访问项目文件或存储产物。如果需要,请等待目录存在并检出 $CI_COMMIT_SHA。在作业完成其检出过程之前进行的任何更改都会被检出过程移除。
服务必须检测作业目录何时被填充并准备好进行处理。例如,等待特定文件可用。
立即启动的服务可能会失败,因为作业数据可能尚未可用。例如,容器使用 docker build 命令与 DinD 服务建立网络连接。服务指示其 API 启动一个容器镜像构建。Docker 引擎必须能够访问你在 Dockerfile 中引用的文件。因此,你需要访问服务中的 CI_PROJECT_DIR。然而,Docker 引擎直到在作业中调用 docker build 命令时才尝试访问它。此时,/builds 目录已填充数据。立即尝试写入 CI_PROJECT_DIR 的服务可能会因 No such file or directory 错误而失败。
在服务与作业数据交互不受作业本身控制的情况下,请考虑Docker 执行器工作流。