GitLab CI build Docker 相关

使用自建 GitLab,通过 CI build Docker 的过程

弯弯绕很多,值得记录

附一份官方中文文档 极狐 https://docs.gitlab.cn/jh/ci/

安装 GitLab Runner

在项目的 Setting -> CI/CD -> Runners 中查看 Runner 情况

其中 Specific runners 为该项目专用 Runners,以外还支持 Shared runners 共享的 Runners

Show Runner installation instructions 可查看安装指引

gitlab-runner 可直接装在 linux 环境下,或跑在 Docker 环境中

这里我们按照 Environment: Linux, Architecture: amd64 的指引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Download the binary for your system
sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64

# Give it permissions to execute
sudo chmod +x /usr/local/bin/gitlab-runner

# Create a GitLab CI user
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash

# Register runner
sudo gitlab-runner register

# Install and run as service
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner start

注册过程中的 url, token 皆可在 Specific runners 中查看,其它需要关注的有

  1. 输入 Runner 的 tag,给该 runner 打标签,当你想用这个 Runner 时,在.gitlab-ci.yml的 tags 字段中指明
  2. 选择 Runner 的 Executor,常用的 Executor 有 shell, ssh, docker
    • shell 即以宿主机作为 Runner 所有 jobs 的执行器,Runner 将会从远程仓库 pull 你的工程,工程的目录为:<working-directory>/builds/<short-token>/<concurrent-id>/<namespace>/<project-name>
    • ssh 即远程到另一台主机执行,这也就是为什么有 Shared runners 共享 Runners 概念
    • docker 指定一个默认镜像,或根据.gitlab-ci.yml中的指定,拉起一个 container 去作业,好处是环境隔离、干净,需要注意的是,如果作业是要 docker build,则需要 Docker-in-Docker,详情参考 https://docs.gitlab.cn/jh/ci/docker/using_docker_build.html

还需要关注的有

由于 gitlab-runner 是以非 root 权限跑的,需要添加对 docker 的权限并验证

1
2
3
4
usermod -aG docker gitlab-runner

# Test
sudo -u gitlab-runner -H docker info

GitLab 启用 Container Registry

参考 https://docs.gitlab.cn/jh/administration/packages/container_registry.html

根据搭建 GitLab 的方式,修改gitlab.yml或者gitlab.rb,以gitlab.rb为例,添加如下

1
2
registry['enable'] = true
registry_external_url 'https://registry.gitlab.example.com'

docker 登录

使用docker login <url>登录到 GitLab Container Registry

如果该 URL 未启用 HTTPS,则需要在/etc/docker/daemon.json中指定

1
2
3
{
"insecure-registries" : ["URL"]
}

登录到需要用户名密码认证的 Registry,若未使用 Credentials store,docker 会在$HOME/.docker/config.json中存储 auth 信息,并附上安全提示

如果是通过gitlab-ci.yml进行的操作,参考 https://docs.gitlab.cn/jh/user/packages/container_registry/

可以使用$CI_REGISTRY_USER, $CI_REGISTRY_PASSWORD, $CI_JOB_TOKEN等方式登录,密码是一次性的,并无安全风险

也可以使用 credentials helpers

但要注意 gpg 生成 key 时,不能有 phrase

因为 pass 在提取密码时,如果是有 phrase 的 key,需要输入 phrase,但 docker push/pull 等操作直接跳过了这一步并 exit 2

1
read(9, "exit status 2: gpg: public key d"..., 512) = 127

导致直接 denied: access forbidden

除非预先手动使用pass,并输入 phrase,让 session 记录,但这显然不适合 GitLab CI 场景

Docker 提供四种 credentials helpers

linux 上可使用 pass

  1. 安装 pass

自行安装 pass

1
dnf install pass
  1. 下载 credential-helper

使用 docker-credential-pass-xxx,下载,解压,并 mv 到合适的 bin 目录

  1. 配置

编辑$HOME/.docker/config.json

1
2
3
{
"credsStore": "pass"
}

初始化 pass 需要用到 gpg-id,需要先生成

  1. gpg2

安装 pinentry,并生成 key

1
2
3
dnf install pinentry

gpg2 --gen-key

但这里有一个坑点,参考 https://unix.stackexchange.com/questions/477445/www-data-user-cannot-generate-gpg-key

When using pinentry, you must have the proper permissions of the terminal device (e.g. /dev/tty1) in use. However, with su (or sudo), the ownership stays with the original user, not the new one. This means that pinentry will fail with a Permission denied error, even as root.

也就是说,sudo/su 后的用户可能用不了,会提示 Permission denied,甚至你 sudo 之后是 root

比如这里我们要在 gitlab-runner 的账户下 gpg2 --gen-key,就不能以 root 登录然后su gitlab-runner

要么直接以 gitlab-runner 登录,要么使用文章中给出的一个方法

1
2
3
4
5
6
7
mkdir /path/to/gpg-alt
GNUPGHOME=/path/to/gpg-alt gpg2 --gen-key

mv /path/to/gpg-alt /home/gitlab-runner/.gnupg
chown -R gitlab-runner:gitlab-runner /home/gitlab-runner/.gnupg
find /home/gitlab-runner/.gnupg -type d -exec chmod 700 {} \;
find /home/gitlab-runner/.gnupg -type f -exec chmod 600 {} \;
  1. 配置 pass
1
pass init <gpg-id>

不容易,后面的事就是写.gitlab-ci.yml了,给份最基本的参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
stages:
- build
- deploy

before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin

build_docker:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE .
- docker tag $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
- docker push $CI_REGISTRY_IMAGE:latest
only:
- tags
tags:
- builder

deploy_docker:
stage: deploy
script:
- echo SUCCESS
only:
- tags
tags:
- builder