【DevOps工具篇】Gitlab Runner设置(使用Docker Executor作为Runner)
@TOC
推荐超级课程:
强烈建议学习本文之前,先阅读学习此篇:
在本篇文章中,我们将在GitLab runner服务内注册docker executor,以便构建、测试和部署我们的容器化项目。
准备docker-compose文件
在注册docker executor之前,我们需要在docker-compose.yml
文件中进行少量更新,将宿主机上的docker.sock
文件挂载到GitLab runner容器中。
更新后的docker-compose.yml
文件应如下所示:
version: '3.8'
services:
gitlab-server:
image: 'gitlab/gitlab-ce:latest'
container_name: gitlab-server
environment:
GITLAB_ROOT_EMAIL: "admin@example.com"
GITLAB_ROOT_PASSWORD: "Abcd@0123456789"
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://localhost:8000'
nginx['listen_port'] = 8000
ports:
- '8000:8000'
volumes:
- ./gitlab/config:/etc/gitlab
- ./gitlab/data:/var/opt/gitlab
gitlab-runner:
image: gitlab/gitlab-runner:alpine
container_name: gitlab-runner
network_mode: 'host'
# 新增挂载docker.sock从宿主机到容器的更改
volumes:
- /var/run/docker.sock:/var/run/docker.sock
为什么需要将docker.sock挂载到GitLab Runner中?
当你在机器上安装docker时,会安装两个不同的程序:
- Docker服务器
- Docker客户端
Docker服务器通过套接字(通过网络或通过文件)接收命令。 Docker客户端通过网络发送消息给Docker服务器,以创建容器、启动容器、停止容器等。
当docker客户端和服务器在同一台计算机上运行时,它们可以通过一个特殊的文件(套接字)进行连接。而且由于Docker可以高效地在宿主机和容器之间共享文件,这意味着你可以在Docker内部运行客户端。
Docker守护进程可以通过三种不同类型的套接字监听Docker Engine API请求:unix
、tcp
和fd
。默认情况下,当你安装Docker时,会在/var/run/docker.sock
创建一个unix域套接字(或IPC套接字)。
我们需要将宿主机上的docker.sock
挂载到GitLab Runner容器中。当我们想要使用docker执行任务时,GitLab Runner可以使用这个docker.sock
文件在宿主机上创建容器,运行任务,然后任务完成后终止。
如果您的docker服务器和客户端位于不同的机器上,您可以通过TCP进行通信,但由于我们的服务器和客户端都在同一台宿主机上,所以我们通过套接字文件(即docker.sock
)进行通信。
更新docker-compose.yml
后,按CTRL+C
停止正在运行的容器,然后运行以下命令:
docker compose up --build --force-recreate
注册部分
前往您的项目运行器页面,路径为:
仓库 → 设置 → CI/CD → 运行器
URL为 http://localhost:8000/root/build-with-lal/-/settings/ci_cd
URL中的build-with-lal
是我之前创建的仓库名称。
tags
,这样我们可以通过指定特定的运行器来运行不同的任务。复制步骤1中的gitlab-runner register …
命令。
使用docker登录到您的GitLab运行器容器:
docker compose exec -it gitlab-runner /bin/bash
登录到GitLab运行器容器后,运行上述步骤1中的命令。确保在gitlab-runner register …
命令的末尾添加以下标志:
gitlab-runner register
--url http://localhost:8000
--token glrt-AMG2ra8WJDPkbx-HDAzc
--docker-volumes /var/run/docker.sock:/var/run/docker.sock
--docker-network-mode 'host'
--docker-volumes /var/run/docker.sock:/var/run/docker.sock
添加--docker-volumes /var/run/docker.sock:/var/run/docker.sock
会将GitLab运行器容器中的docker套接字文件挂载到执行器容器中,这样当我们需要运行docker CLI命令时,docker CLI将通过docker.sock
文件访问宿主机上的docker引擎。
--docker-network-mode 'host'
添加--docker-network-mode 'host'
会将执行器容器置于宿主网络,这样在克隆仓库时,执行器可以通过localhost访问它。
- 第一个是在
docker-compose.yml
文件中,我们将GitLab运行器容器的网络置于我们的宿主机网络(PC网络),这样GitLab服务器可以通过localhost与GitLab运行器通信,以管理流水线。 - 第二个是在注册我们的docker执行器时,我们将容器网络置于宿主网络(PC网络)。当流水线使用docker执行器运行时,这个网络很有用,任务会拉取docker镜像,然后尝试在执行器容器内部克隆项目仓库。此时,docker执行器会尝试通过localhost访问仓库,因此执行器容器需要与GitLab服务器位于同一个网络,即localhost。
- 当询问
GitLab实例URL
时,除非您的GitLab服务器和运行器位于不同的机器上,否则按回车键保持默认。 - 输入您喜欢的运行器名称。
- 由于我们要注册一个docker执行器,当询问执行器选项时,输入
docker
。 - 选择docker执行器时,您还需要设置一个默认的docker镜像,以防在我们的项目流水线
gitlab-ci.yml
文件中遗漏。我将默认docker镜像设置为python:3.10-alpine
,您可以在gitlab-ci.yml
文件中覆盖它。
填写完上述所有详细信息后,您的docker运行器应该已经注册,运行器页面应如下所示。
使用 Shell 和 Docker 执行器测试流水线
让我们尝试添加一个包含3个不同任务的流水线
- Shell 执行器
- 使用默认docker镜像的 Docker 执行器,即
python:3.10-alpine
- 将docker镜像覆盖为
docker:24.0.5
的 Docker 执行器
build with shell executor:
stage: build
tags:
- shell
script:
- date # print current date
- cat /etc/os-release # print os version for Linux
build with docker executor:
stage: build
tags:
- docker
image: docker:24.0.5
script:
- docker info
build with docker executor default image:
stage: build
tags:
- docker
script:
- python --version
您的流水线编辑器在 构建 → 流水线编辑器 下应该看起来像这样:
tags
,可以确保为这个任务选择正确的运行器。因此,我们在第一个任务中添加了 tags: shell
,因为我们希望这个任务由我们的Shell执行器运行。而剩下的2个任务应该由docker执行器执行,所以我们添加了 tags: docker
在第二个任务中使用 image: docker:24.0.5
,您可以使用docker CLI来构建您的项目并使用docker将您的docker镜像推送到某个容器注册中心。
提交这些更改,然后切换到 构建 → 任务 下的任务。您应该看到所有任务正在运行或者已经通过。python:3.10-alpine
docker:24.0.5
来利用docker CLI。
如果您通过 docker info
命令检查所有这些详细信息,docker服务器详细信息来自我的主机机器,我在那里安装了实际的docker引擎。我们之所以在 gitlab-ci.yml
文件中使用docker镜像,是为了让docker CLI可用。您提供给docker CLI的任何指令都将通过使用我们挂载的套接字文件 /var/run/docker.sock
传递给主机机器上的docker引擎。
当任务由docker执行器执行时,您可以在机器上监控容器,并且应该会看到一些新的容器与您现有的容器并列。这些新容器来自docker执行器,并且一旦任务完成就会终止。docker.sock
文件,所以所有容器都由主机机器的docker引擎管理。
即使我们在流水线任务中尝试构建和创建容器,容器实际上也会在我们的主机docker引擎上创建,因为我们是在docker执行器及其任务中使用相同的docker.sock。
让我们通过在任务中创建一些容器来更新我们的流水线。build with shell executor:
stage: build
tags:
- shell
script:
- date # print current date
- cat /etc/os-release # print os version for Linux
build with docker executor:
stage: build
tags:
- docker
image: docker:24.0.5
# new changes for adding dummy containers
script:
- docker run -d --rm --name nested-container1-in-pipelinejob alpine sleep 20
- docker run -d --rm --name nested-container2-in-pipelinejob alpine sleep 20
build with docker executor default image: # default python image
stage: build
tags:
- docker
script:
- python --version
- sleep 10
一旦您提交了上述更改并且流水线运行,您可以通过运行以下命令在主机机器上监控您的docker容器:
docker ps
输出应该看起来像这样。
Docker套接字绑定的已知问题
关于docker套接字绑定和管理所有容器使用单个docker引擎的一些已知问题,GitLab文档在这里有所强调链接 。
- 如果流水线任务运行了
docker rm -f $(docker ps -a -q)
,它将删除GitLab服务器和运行器容器,可能还会删除其他关键容器。 - 如果您的测试创建了具有特定名称的容器,它们可能会相互冲突。
从源代码仓库共享文件和目录到容器可能不会按预期工作。卷挂载是在主机机器的上下文中完成的,而不是构建容器。 套接字绑定不是使用GitLab运行器的docker执行器的唯一方式。还有一种称为 Docker in Docker(简称 dind)的方式,我们将在下一篇文章中讨论。
使用Docker执行器构建和部署您的项目
现在您可以在您的仓库中添加一个 Dockerfile
并尝试使用docker构建您的项目。
Dockerfile
FROM python:3.10-alpine
RUN python --version
gitlab-ci.yml
以使用docker构建项目docker build .
构建我们的项目。
同样的方式,您可以使用 docker login
登录到容器注册中心,然后使用 docker push
将您的docker构建镜像推送到注册中心
正如我们之前讨论的,使用 /var/run/docker.sock
在主机docker引擎上创建子容器可能会存在一些潜在问题。为了避免在CI任务中使用 /var/run/docker.sock
,还有一种称为 Docker-In-Docker 的方式,任务的容器将作为另一个服务容器(Docker-in-Docker)的子容器创建,而不是直接在主机docker引擎上创建。