使用 Docker 构建
原文:https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/ci/docker/using_docker_build.md
GitLab CI 允许你使用 Docker Engine 构建和测试基于 Docker 的项目。
这也允许您使用docker-compose
和其他 docker-enabled 的工具。
持续集成/持续部署的新趋势之一是:
- 创建应用程序镜像,
- 针对创建的镜像运行测试,
- 将镜像推送到远程 Registry,并
- 从推送的镜像部署到服务器。
当您的应用程序已经具有可用于创建和测试镜像的Dockerfile
时,它也很有用:
$ docker build -t my-image dockerfiles/
$ docker run my-docker-image /script/to/run/tests
$ docker tag my-image my-registry:5000/my-image
$ docker push my-registry:5000/my-image
这需要 GitLab Runner 的特殊配置,以在作业期间启用docker
支持。
Runner 配置
在作业中有三种方法可以使用docker build
和docker run
,每个都有自己的考虑。
使用 Shell 执行器
最简单的方法是在shell
执行模式下安装 GitLab Runner。然后 GitLab Runner 作为gitlab-runner
用户执行作业脚本。
- 安装 GitLab Runner。
-
在 GitLab Runner 安装期间,选择
shell
作为执行作业脚本或使用命令的方法:sudo gitlab-ci-multi-runner register -n \ --url https://gitlab.com/ \ --registration-token REGISTRATION_TOKEN \ --executor shell \ --description "My Runner"
- 在服务器上安装 Docker Engine。
有关如何在不同系统上安装 Docker Engine 的更多信息,请参阅 Supported installations。
-
新增
gitlab-runner
用户到docker
组:sudo usermod -aG docker gitlab-runner
- 验证
gitlab-runner
是否可以访问Docker:sudo -u gitlab-runner -H docker info
现在你可以通过将
docker info
添加到.gitlab-ci.yml
中来验证一切是否正常:before_script: - docker info build_image: script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
- 现在可以使用
docker
命令,如果需要可以安装docker-compose
。
注:
* 通过在docker
组中添加gitlab-runner
,你可以有效地授予gitlab-runner
的完整的 root 权限。有关更多信息,请阅读 On Docker security:docker
group considered harmful。
使用 docker-in-docker 执行器
第二种方法是使用专门的 Docker 镜像 docker-in-docker(dind),它安装了所有工具(docker
和docker-compose
),并以特权模式在该镜像的上下文中运行作业脚本。
为了做到这一点,请按以下步骤操作:
- 安装 GitLab Runner。
-
从命令行注册 GitLab Runner 以使用
docker
和privileged
模式:sudo gitlab-ci-multi-runner register -n \ --url https://gitlab.com/ \ --registration-token REGISTRATION_TOKEN \ --executor docker \ --description "My Docker Runner" \ --docker-image "docker:latest" \ --docker-privileged
上面的命令将注册一个新的 Runner 来使用 Docker 所提供的特殊
docker:latest
镜像。请注意,它使用privileged
模式启动构建和服务容器。如果要使用docker-in-docker模式,您始终必须在 Docker 容器中使用privileged = true
。上面的命令将创建一个类似于这个的
config.toml
条目:[[runners]] url = "https://gitlab.com/" token = TOKEN executor = "docker" [runners.docker] tls_verify = false image = "docker:latest" privileged = true disable_cache = false volumes = ["/cache"] [runners.cache] Insecure = false
- 您现在可以在构建脚本中使用
docker
(请注意包含docker:dind
服务)image: docker:latest # When using dind, it's wise to use the overlayfs driver for # improved performance. variables: DOCKER_DRIVER: overlay services: - docker:dind before_script: - docker info build: stage: build script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
Docker-in-Docker 运行良好,是推荐的配置,但并不是没有挑战:
- 启用
--docker-privileged
禁用了容器的所有安全机制,并使你的主机由于特权升级而暴露,从而导致容器突破(主机-容器屏障)。有关更多信息,请查看官方 Docker 文档 Runtime privilege and Linux capabilities。 - 当使用 docker-in-docker 时,每个作业都处于一个干净的环境中,没有过去的历史。并发作业工作正常,因为每个构建都获得自己的 Docker Engine 实例,因此不会相互冲突。但这也意味着作业可能会更慢,因为没有缓存层。
- 默认情况下,
docker:dind
使用--storage-driver vfs
,这是最慢的形式。要使用其他驱动程序,请参阅使用 overlayfs 驱动程序。
使用这种方法的示例项目可以在这里找到:https://gitlab.com/gitlab-examples/docker.
Use Docker socket binding
第三种方法是将/var/run/docker.sock
绑定装载到容器中,以便 docker 在该镜像的上下文中可用。
为了做到这点,遵循以下步骤:
- 安装 GitLab Runner.
-
从命令行注册 GitLab Runner 以使用
docker
并共享/var/run/docker.sock
:sudo gitlab-ci-multi-runner register -n \ --url https://gitlab.com/ \ --registration-token REGISTRATION_TOKEN \ --executor docker \ --description "My Docker Runner" \ --docker-image "docker:latest" \ --docker-volumes /var/run/docker.sock:/var/run/docker.sock
上面的命令将注册一个新的 Runner 来使用 Docker 提供的特殊
docker:latest
镜像。请注意,它正在使用 Runner 本身的 Docker 守护进程,docker 命令产生的任何容器都将是 Runner 的兄弟,而不是所运行程序的子进程。这可能会有不适合您的工作流程的复杂性和局限性。上面的命令将创建一个类似于这个的
config.toml
条目:[[runners]] url = "https://gitlab.com/" token = REGISTRATION_TOKEN executor = "docker" [runners.docker] tls_verify = false image = "docker:latest" privileged = false disable_cache = false volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"] [runners.cache] Insecure = false
- 您现在可以在构建脚本中使用
docker
(请注意,在 Docker 执行器中使用 Docker 时,不需要包含docker:dind
服务):image: docker:latest before_script: - docker info build: stage: build script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
虽然上述方法避免在特权模式下使用 Docker,但您应该了解以下影响:
- 共享 docker 守护进程,禁用了容器的所有安全机制,并将主机暴露给特权提升,从而导致容器突破屏障。例如,如果一个项目运行
docker rm -f $(docker ps -a -q)
,它将删除 GitLab Runner 容器。 - 并发作业可能无效;如果你的测试创建了具有特定名称的容器,它们可能会相互冲突。
- 将文件和目录从源代码库共享到容器中可能无法正常工作,因为卷装载是在主机上下文中完成的,而不是在构建容器中,例如:
docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
使用 OverlayFS 驱动程序
默认情况下,使用docker:dind
时,Docker 使用vfs
存储驱动程序,每次运行时都会拷贝文件系统。磁盘操作非常密集,如果使用不同的驱动程序(例如overlay
),则可以避免这种情况。
- 确保使用最近的内核,最好是
>= 4.2
。 - 检查
overlay
模块是否加载:sudo lsmod | grep overlay
如果没有结果,那就没有加载。加载之:
sudo modprobe overlay
如果一切顺利,您需要确保在系统重启时也加载该模块。Ubuntu 系统上是通过编辑
/etc/modules
完成的。添加以下行:overlay
- 在
.gitlab-ci.yml
顶部定义一个变量以使用该驱动:variables: DOCKER_DRIVER: overlay
使用 GitLab 容器 Registry
注:
– 此功能需要 GitLab 8.8 和 GitLab Runner 1.2。
– 从 GitLab 8.12 开始,如果你的帐户启用了两步认证,则需要传递个人访问令牌而不是密码,才能登录到 GitLab 的 Container Registry。
一旦构建了 Docker 镜像,就可以将其推送到内置的 GitLab 容器 Registry 中。例如,如果你在 runner 上使用 docker-in-docker,那.gitlab-ci.yml
可能如下:
build:
image: docker:latest
services:
- docker:dind
stage: build
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com
- docker build -t registry.example.com/group/project/image:latest .
- docker push registry.example.com/group/project/image:latest
必须使用为你创建的特殊gitlab-ci-token
用户,才能推送到连接到项目的 Registry。它的密码由$CI_JOB_TOKEN
变量提供。这允许您自动构建和部署 Docker 镜像。
您也可以利用其他变量来避免硬编码:
services:
- docker:dind
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
build:
stage: build
script:
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
在这里,$CI_REGISTRY_IMAGE
将解析为与该项目相关联的 Registry 地址,$CI_COMMIT_REF_NAME
将解析为该作业所在分支或标签的名称。还声明了我们自己的变量$IMAGE_TAG
,将两者结合起来,以节省我们在script
部分中的输入。
这是一个更详细的例子,将任务分解为 4 个流水线(pipeline)阶段,包括并行运行的两个测试。build
存储在容器 Registry 中,并在后续阶段使用,需要时则下载该镜像。对master
的更改也被标记为latest
,并使用特定于应用程序的部署脚本进行部署:
image: docker:latest
services:
- docker:dind
stages:
- build
- test
- release
- deploy
variables:
CONTAINER_TEST_IMAGE: registry.example.com/my-group/my-project/my-
image:$CI_COMMIT_REF_NAME
CONTAINER_RELEASE_IMAGE: registry.example.com/my-group/my-project/my-image:latest
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com
build:
stage: build
script:
- docker build --pull -t $CONTAINER_TEST_IMAGE .
- docker push $CONTAINER_TEST_IMAGE
test1:
stage: test
script:
- docker pull $CONTAINER_TEST_IMAGE
- docker run $CONTAINER_TEST_IMAGE /script/to/run/tests
test2:
stage: test
script:
- docker pull $CONTAINER_TEST_IMAGE
- docker run $CONTAINER_TEST_IMAGE /script/to/run/another/test
release-image:
stage: release
script:
- docker pull $CONTAINER_TEST_IMAGE
- docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE
- docker push $CONTAINER_RELEASE_IMAGE
only:
- master
deploy:
stage: deploy
script:
- ./deploy.sh
only:
- master
使用容器 Registry 的注意事项:
- 在运行命令之前必须先登录到容器 Registry。把它放在
before_script
里,会在每个作业之前运行它。 - 使用
docker build --pull
可以确保 Docker 在构建前获取 base 镜像的任何更改,以防您的缓存失效。这需要运行更长时间,但意味着不会遇到未打安全补丁的 base 镜像。 - 在每个
docker run
之前做一个明确的docker pull
,确保获取刚构建的最新镜像。如果您正在使用多个会在本地缓存镜像的 Runner,这一点尤为重要。在镜像标签中使用 git SHA 又使得这不太必要,因为每个作业都将是唯一的,并且您不应该有一个过时的镜像,但是如在依赖更改后,重新构建给定的 Commit,这仍然可能(有过时的镜像)。 - 同时发生多个作业的情况下,你不会想直接构建为
latest
。