方案说明

◆ 由于 CI/CD 的非持续运行而运行期间又需要较高系统资源的特性,符合 ACK Serverless 按资源使用时间计费的场景,降低 CI/CD 成本的同时又能提高并行多任务的执行效率。
◆ 方案的系统结构与流程如下面来自参考文档[1]的截图,当没有任务时仅 gitlab-runner 作为一个 pod 始终运行,以随时接受来自 gitlab 的任务分配。当有任务需要执行时,针对每个任务创建一个 kubernetes executor 类型的 pod 执行任务,且任务执行结束后,这些 kubernetes executor 类型的 pod 会自动销毁。

步骤

准备 ACK Serverless 集群环境

● 在阿里云工作台页面创建 ACK Serverless 类型的 kubernetes 集群。
● 根据”集群信息”中的”连接信息”,配置本地 kubectl,使可以在本地 kubectl 上对集群做操作。

准备配置文件

◇ 从 GitHub 上 clone 由参考文档[1]提供的配置文件样例到本地,使用其中”eci-gitlab-runner”文件夹里的配置文件。
◇ 以下配置的内容最终将集中到 config-map.yml 并经由集群的 deployment 被 mount 到 config.toml 文件,以方便地应用到所部署的 gitlab-runner pod 中。
◇ 未来由 gitlab-runner 拉起的 kubernetes executor 类型 pod 的配置也在这个 config.toml 文件里的,部分配置也会被 mount 成 pod 中的文件。
◇ 以下配置内容和命令中的所有由”${}”包裹的变量都需要替换成实际值。

secret

◆ 通过 secret 存放 kubernetes 集群、docker registry、git ssh 的鉴权信息。
◆ 修改样例 secret.yaml 配置,其中的 ca.crt、tls.crt、tls.key 依次填写集群连接信息里的 certificate-authority-data、client-certificate-data、client-key-data。
◆ 执行”kubectl apply -f secret.yaml”使生效。secret.yaml 完整内容如下:

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Secret
metadata:
name: gitlab-runner-secret
type: kubernetes.io/tls
data:
ca.crt: ${ca.crt}
tls.crt: ${tls.crt}
tls.key: ${tls.key}

◆ 如果 gitlab 的 pipeline 任务需要推送 docker 镜像到镜像仓库,则在集群创建名为”registry-auth-secret”的 secret,执行如下命令:

1
kubectl create secret docker-registry registry-auth-secret --docker-server=${registry-server-hostname} --docker-username=${username} --docker-password=${password}

◆ 如果 gitlab 的 pipeline 任务需要 clone 其他 git 仓库,则在集群创建名为”gitlab-credentials”的 secret,执行如下命令:

1
kubectl create secret generic gitlab-credentials --from-file=id_rsa=${pathto_id_rsa} --from-file=id_rsa.pub=${pathto_id_rsa.pub} --from-file=known_hosts=${pathto_known_hosts}

◆ secret 创建成功后可在”阿里云工作台页面集群详情->配置管理->保密字典”位置看到。

配置 cache

● gitlab 的 cache 用于让 executor 缓存文件与文件夹以减少重复的下载、安装等工作。
● executor 容器的 cache 存储到的默认路径是”/cache”,样例已经在 config-map.yaml 中将 pvc 配置到了此路径。

● 准备好 NAS(Network Attached Storage) 盘,将其地址与缓存文件夹根目录(此处定义为/gitlab-runner-cache)填写到 nas-pv.yaml。nas-pv.yaml 完整内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: PersistentVolume
metadata:
name: gitlab-runner-cache-pv
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 10Gi
mountOptions:
- nolock,noresvport,noacl,hard
- vers=3
- rsize=1048576
- wsize=1048576
- proto=tcp
- timeo=600
- retrans=2
nfs:
path: /gitlab-runner-cache
server: ${nas-server}

● 相应的 nas-pvc.yaml 直接使用样例的即可。nas-pvc.yaml 完整内容如下:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitlab-runner-cache-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
volumeName: gitlab-runner-cache-pv

● 分别执行”kubectl apply -f nas-pv.yaml”和”kubectl apply -f nas-pvc.yaml”使生效。
◆ pv 与 pvc 创建成功后可分别在”阿里云工作台页面集群详情->存储->存储卷|存储声明”位置看到。

配置 image-cache

○ imagecache-crd(Custom Resource Definition,CRD)是用于实现镜像缓存的 kubernetes 控制器,样例在 config-map.yaml 和 gitlab-runner-deployment.yaml 中通过 k8s.aliyun.com/eci-image-cache 这个自定义注解标识了对 imagecache 的启用。

○ 通过”kubectl get crd”命令查看 imagecache-crd 是否已安装。如果未安装则通过”kubectl apply -f imagecache-crd.yaml”安装。直接使用样例的 imagecache-crd.yaml 即可,完整内容如下:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: imagecaches.eci.alibabacloud.com
spec:
group: eci.alibabacloud.com
version: v1
names:
kind: ImageCache
plural: imagecaches
shortNames:
- ic
categories:
- all
scope: Cluster
subresources:
status: {}
validation:
openAPIV3Schema:
required:
- spec
properties:
spec:
type: object
required:
- images
properties:
imagePullSecrets:
type: array
items:
type: string
images:
minItems: 1
type: array
items:
type: string
imageCacheSize:
type: integer
additionalPrinterColumns:
- name: Age
type: date
JSONPath: .metadata.creationTimestamp
- name: CacheId
type: string
JSONPath: .status.imageCacheId
- name: Phase
type: string
JSONPath: .status.phase
- name: Progress
type: string
JSONPath: .status.progress

○ 根据实际需要将待缓存的 docker 镜像名填写到 imagecache.yaml 中,我使用的 imagecache.yaml 完整内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: eci.alibabacloud.com/v1
kind: ImageCache
metadata:
name: gitlab-runner
spec:
images:
- gitlab/gitlab-runner-helper:x86_64-latest
- gitlab/gitlab-runner:latest
- node:20-alpine
- node:20
- mcr.microsoft.com/playwright:v1.44.0-jammy
- gcr.io/kaniko-project/executor:v1.14.0-debug

○ 执行”kubectl apply -f imagecache.yaml”使生效。
○ imagecache 创建成功后可在”阿里云工作台页面集群详情->工作负载->自定义资源->资源对象浏览器”位置看到。

配置 ConfigMap

◇ gitlab-runner 的配置文件在容器虚拟机的默认位置是”/etc/gitlab-runner/config.toml”,样例已经在 gitlab-runner-deployment.yaml 中将 configMap 的 mountPath 配置到了/etc/gitlab-runner。
◇ 参照gitlab 文档得到 authentication token(注意不能是 registration token)填写到 config-map.yaml。
◇ 将私有 gitlab 网站与 kubernetes 集群的连接信息填写到 config-map.yaml 文件样例。其中 concurrent 控制支持同时执行任务的数量,由于 Serverless 特性可以认为不受限制。然后 executor 的 cpu 与内存配置通过 cpu_limit、cpu_request、memory_limit、memory_request 控制。
◇ config.toml 中有关 [runners.kubernetes] 配置项的说明也在gitlab 文档
◇ config-map.yaml 完整内容如下。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
apiVersion: v1
kind: ConfigMap
metadata:
name: gitlab-runner-config
data:
config.toml: |
concurrent = 10
check_interval = 0
[[runners]]
name = "runner-on-k8s"
url = ${gitlab-url}
token = ${authentication token}
executor = "kubernetes"
output_limit = 51200
[runners.kubernetes]
host = ${k8s-connect-url}
cert_file = "/etc/gitlab-runner/tls.crt"
key_file = "/etc/gitlab-runner/tls.key"
ca_file = "/etc/gitlab-runner/ca.crt"
namespace = "default"
pull_policy = "if-not-present"
cpu_limit = "2"
cpu_request = "2"
memory_limit = "4Gi"
memory_request = "4Gi"
helper_cpu_limit = "0.5"
helper_cpu_request = "0.5"
helper_memory_limit = "1Gi"
helper_memory_request = "1Gi"
helper_image = "gitlab/gitlab-runner-helper:x86_64-latest"
[runners.kubernetes.pod_annotations]
"k8s.aliyun.com/eci-image-cache" = "true"
[runners.kubernetes.volumes]
[[runners.kubernetes.volumes.pvc]]
name = "gitlab-runner-cache-pvc"
mount_path = "/cache"
readonly = false
[[runners.kubernetes.volumes.secret]]
name = "registry-auth-secret"
mount_path = "/kaniko/.docker"
read_only = true
[runners.kubernetes.volumes.secret.items]
".dockerconfigjson" = "config.json"
[[runners.kubernetes.volumes.secret]]
name = "gitlab-credentials"
mount_path = "/root/gitlab-credentials"
read_only = true

◇ 执行”kubectl apply -f config-map.yaml”使生效。
◇ ConfigMap 创建成功后可在”阿里云工作台页面集群详情->配置管理->配置项”位置看到。

使用 deployment 部署 gitlab-runner 集群

◆ 用于部署集群的 deployment 直接使用样例的 gitlab-runner-deployment.yaml 即可,完整内容如下:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitlab-runner
spec:
selector:
matchLabels:
app: gitlab-runner
template:
metadata:
labels:
app: gitlab-runner
annotations:
k8s.aliyun.com/eci-image-cache: "true"
spec:
containers:
- image: gitlab/gitlab-runner:latest
imagePullPolicy: IfNotPresent
name: gitlab-runner
volumeMounts:
- mountPath: /etc/gitlab-runner
name: config
volumes:
- name: config
projected:
defaultMode: 420
sources:
- secret:
items:
- key: ca.crt
path: ca.crt
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
name: gitlab-runner-secret
- configMap:
items:
- key: config.toml
path: config.toml
name: gitlab-runner-config

◆ 执行”kubectl apply -f gitlab-runner-deployment.yaml”完成 gitlab-runner 的部署。
◆ 部署成功后可在”阿里云工作台页面集群详情->工作负载->容器组”位置看到 gitlab-runner 的 pod。接下来在私有 gitlab 网站配置好 runner,当有 pipeline 任务执行时也可以看到相应 executor 的 pod。

配置 gitlab pipeline 中的相关问题与处理

artifacts 与 cache

♂ artifacts 与 cache 的区别在 gitlab 文档(https://docs.gitlab.com/ee/ci/caching/#how-cache-is-different-from-artifacts)里有说明。
♂ 当前 gitlab-runner 的方案中,考虑到 artifacts 存储位置是 gitlab 所在远程主机,而 cache 存储位置在每个 executor 的本地磁盘(通过将 NAS 挂载到每个 executor 上实现了 cache 内容的共享),特别当需缓存内容比较大的时候,使用 cache 优于 artifacts。
♂ cache 没有 artifacts 那样的自动过期删除机制,对于打包结果这种每次存储路径不同的 cache,可在 after_script 阶段增加脚本以针对 cache key 清理不再使用的 cache 内容。示例:

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
28
29
30
31
32
.dist-cache: &dist-cache
key: $CI_PIPELINE_ID
paths:
- ./dist

stages:
- build
- deploy

build-job:
stage: build
script:
- echo 'build';
cache:
- <<: *dist-cache
policy: pull-push
allow_failure: false

deploy-job:
stage: deploy
image:
name: gcr.io/kaniko-project/executor:v1.14.0-debug
entrypoint: ['']
script:
- echo 'build and push docker image based on cache';
after_script:
- rm -rf /cache/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/$CI_PIPELINE_ID*;
cache:
- <<: *dist-cache
policy: pull
dependencies: [build-job]

build docker 镜像

♀ 对于 docker 镜像的构建与推送,采用配置更简单的 kaniko 方式,根据参考文档[3],已经在之前 ConfigMap 中配置 docker 仓库 secret 的 mountPath 到”/kaniko/.docker/config.json”。
♀ kaniko 镜像是个特别精简的 linux,不适合在其中配置打包环境。于是将打包与 docker 操作拆分到不同 job,打包结果通过上步 cache 的方式共享到 kaniko 镜像对应的容器中。
♀ 使用 kaniko 的 deploy-job 的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.echo-variables: &echo-variables
- echo "CI_PIPELINE_ID:" $CI_PIPELINE_ID;

deploy-job:
stage: deploy
image:
name: gcr.io/kaniko-project/executor:v1.14.0-debug
entrypoint: ['']
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "./Dockerfile"
--destination "${registry_dist}"
before_script:
- *echo-variables
after_script:
- rm -rf /cache/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/$CI_PIPELINE_ID*;
cache:
- <<: *dist-cache
policy: pull
dependencies: [build-job]

git ssh 鉴权

▲ git ssh 信息的 mountPath 直接指定到”/root/.ssh”的时候遇到了如下问题:
(1)gitlab-runner 的 config.toml 配置中[runners.kubernetes.volumes.secret]不支持指定 mout 文件的 mode,使用 id_rsa 文件做 ssh 鉴权会报错:”Permissions 0644 for ‘/root/.ssh/id_rsa’ are too open. This private key will be ignored”。
(2)即使将配置中[runners.kubernetes.volumes.secret]的 read_only 设置为 false,对 id_rsa 执行 chmod 操作也报错”chmod: changing permissions of ‘/root/.ssh/id_rsa’: Read-only file system”。
▲ 于是在 ConfigMap 中将 mountPath 指定到了”/root/gitlab-credentials”,之后在 pipeline 里再将文件从”/root/gitlab-credentials”拷贝到”/root/.ssh”并设置文件 mode。
▲ 如下示例定义一个初始化 git 鉴权文件的锚点供需要时引用。

1
2
3
4
5
6
.prepare-git: &prepare-git
- set +e;cat /root/.ssh/known_hosts;if [[ $? -ne 0 ]]; then
mkdir /root/.ssh;cat /root/gitlab-credentials/id_rsa > /root/.ssh/id_rsa;chmod 400 /root/.ssh/id_rsa;
cp /root/gitlab-credentials/id_rsa.pub /root/gitlab-credentials/known_hosts /root/.ssh/;
fi;set -e;
- ssh -T git@${git_domain}

参考文档

  1. 从零入门 Serverless | 教你 7 步快速构建 GitLab 持续集成环境
  2. gitlab 官方 doc
  3. kubernetes executor 上支持 docker 命令