本文是 GitLab CI/CD 系列的第二篇,主要介绍 GitLab CI Runner 在 Docker 和 Kubernetes 环境下的部署方式。
0. TL;DR
文档在这儿。
Changelog:
- 2018-10-08 21:05 补上缺失的 k8s
Secret
定义文件。
1. GitLab Runner 的运行环境及执行环境选择
GitLab Runner 用 Go 语言写成,最后打包成单文件进行分发,所以可以在很多平台下快速运行,包括 Windows / GNU Linux / MacOS 等,同时也提供 Docker 镜像,方便在 Docker / Kubernetes 环境中部署。
但除了 Runner 运行外,Runner 还需要一个环境来运行 jobs. 这个环境称之为执行环境(executor)。GitLab Runner 支持多种执行环境,包括 SSH,Docker,VirtualBox 等。
不同执行环境对 GitLab CI/CD 不同功能的支持情况,可以看官方文档中的兼容性表格。
由于我司主要用 Docker 或 Kubernetes 来托管服务,并且在进行测试时需要 service 的支持,所以自然只剩下了两种选择,Docker 和 Kubernetes. 在下文中会讲解 GitLab 在 Docker 和 Kubernetes 中的部署方式。
尽管运行环境和执行环境可以相互独立,但为了方便起见,我更推荐在同一个环境中运行 runner daemon 和 jobs.
2. GitLab Runner 部署
简单来讲,GitLab Runner 的部署方式分为两步:运行、注册。
2.1 注册 runner
在一个 runner daemon 进程中,我们可以同时注册并运行多个 runner,来并行完成多个场景下的不同任务。
注册的步骤在不同平台下大同小异:运行 gitlab-runner register
命令,会输出一个交互界面,在里面依次输入 GitLab 实例地址、CI Token、Runner 描述、标签列表(用于区分不同类型的 Runner,使不同阶段的 job 在不同的 Runner 中运行)、执行环境类型,如果选择基于 Docker 的执行环境,则需要再输入一个缺省的 job image.
注册之后,Runner 会将配置写入 /etc/gitlab-runner/config.toml
文件中,如果文件内容不丢失,gitlab-runner
程序会自动读取配置内容并运行,无需重复注册。
当然,如果你需要一些更高级的配置,则可以直接修改 config.toml
. 具体的配置可以见官方文档。在配置文件更改后,runner daemon 会自动重新加载配置,无需重启。
此外,gitlab-runner register
非常鬼畜的一点在于,config.toml
中的部分配置可以通过环境变量注入,且所有配置都可以通过注册命令的参数传入。也就是说,如果你希望不接触 config.toml
,只使用一条命令来配置并注册单个 runner,gitlab-runner register
命令完全可以满足你的要求。🌚
2.2 在 Docker 环境中部署
一句话:
1 2 3 4 5
| docker run -d --name gitlab-runner --restart always \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /data/gitlab-runner/conf:/etc/gitlab-runner \ --net=host \ gitlab/gitlab-runner:latest
|
注意:我们在这里将 /var/run/docker.sock
挂载进了 gitlab-runner 容器,这也就意味着我们将 Docker 环境的控制权交给了 runner daemon,这样 runner daemon 可以在收到任务指令时,使用当前 Docker 环境作为执行环境,在里面运行容器以执行任务。
当然,这样挂载是一种相当危险的做法。如果你比较担心安全问题,可以考虑做一个 docker compose,并在里面运行一个 dind (Docker in Docker)来作为 Runner 的执行环境。
2.3 在 Kubernetes 环境中部署
在 k8s 环境中的部署要稍微复杂一些(因为配置文件太长😂),大体需要配置以下五部分:
- 一个或者两个
Namespace
(取决于你是否要把 job pod 和 daemon 放在同一个 namespace 里)
- 一个
ConfigMap
,用于注入 runner 配置
- 一个
Deployment
,用于运行 runner daemon
- 一个
ServiceAccount
,用于给 daemon 使用,来启动 pod,为运行任务提供环境(当然,用 default
)也不是不可以
- 一个
Role
+ RoleBinding
,为上面的 ServiceAccount
赋予 pods
和 pods/exec
权限
runner 的配置注入有两种方式:
- 先在 GitLab 中注册好 runner,然后在
ConfigMap
中写好配置文件,并在 Deployment
中作为一个 Volume 挂载到配置目录下。这是官方文档中推荐的做法;
- 在
ConfigMap
中定义环境变量,并在 pod template 的 envFrom
属性中定义一个 configMapRef
来注入环境变量,并在 pod 启动时当场注册一个 runner;在 pod 被停止前再调用命令将这个 runner 注销掉(推荐,否则 GitLab 后台会看到很多离线的 runner)。
由于编辑器的高亮规则通常不会处理一个文件里出现两种不同语言的情况,在 ConfigMap
里写 toml 会很挣扎,所以我使用的是第二种注入方式。
完整的配置文件:
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
| apiVersion: v1 kind: Namespace metadata: name: cicd
--- apiVersion: v1 kind: ServiceAccount metadata: name: executor namespace: cicd imagePullSecrets: - name: dockersecret
--- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: cicd name: executor-role rules: - apiGroups: [""] resources: ["pods", "pods/exec"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
--- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: namespace: cicd name: executor-rolebinding subjects: - kind: ServiceAccount name: executor namespace: cicd roleRef: kind: Role name: executor apiGroup: rbac.authorization.k8s.io
--- apiVersion: v1 kind: ConfigMap metadata: namespace: cicd labels: app: gitlab-deployer name: gitlab-runner-cm data: REGISTER_NON_INTERACTIVE: "true" REGISTER_LOCKED: "false" CI_SERVER_URL: "https://gitlab.com/ci" METRICS_SERVER: "0.0.0.0:9100" RUNNER_CONCURRENT_BUILDS: "4" RUNNER_REQUEST_CONCURRENCY: "4" RUNNER_TAG_LIST: "tag1,tag2" RUNNER_EXECUTOR: "kubernetes" KUBERNETES_NAMESPACE: "cicd" KUBERNETES_SERVICE_ACCOUNT: "executor" KUBERNETES_CPU_LIMIT: "100m" KUBERNETES_MEMORY_LIMIT: "100Mi" KUBERNETES_SERVICE_CPU_LIMIT: "100m" KUBERNETES_SERVICE_MEMORY_LIMIT: "100Mi" KUBERNETES_HELPER_CPU_LIMIT: "100m" KUBERNETES_HELPER_MEMORY_LIMIT: "100Mi" KUBERNETES_PULL_POLICY: "if-not-present" KUBERNETES_TERMINATIONGRACEPERIODSECONDS: "10" KUBERNETES_POLL_INTERVAL: "5" KUBERNETES_POLL_TIMEOUT: "360" KUBERNETES_IMAGE: "kubectl:1.8.1" --- apiVersion: v1 kind: Secret metadata: name: gitlab-ci-token namespace: cicd type: Opaque data: token: aGhoaGhoaGg=
--- apiVersion: apps/v1beta2 kind: Deployment metadata: name: runner namespace: cicd labels: app: runner spec: replicas: 1 selector: matchLabels: app: runner template: metadata: labels: app: runner spec: containers: - name: ci-builder image: gitlab/gitlab-runner:v10.6.0 command: - /bin/bash - -c - "/usr/bin/gitlab-runner unregister -n $RUNNER_NAME || true; /usr/bin/gitlab-runner register; exec /usr/bin/gitlab-runner run" imagePullPolicy: IfNotPresent envFrom: - configMapRef: name: gitlab-runner-cm env: - name: CI_SERVER_TOKEN valueFrom: secretKeyRef: name: gitlab-ci-token key: token - name: RUNNER_NAME valueFrom: fieldRef: fieldPath: metadata.name ports: - containerPort: 9100 name: http-metrics protocol: TCP resources: limits: cpu: "100m" memory: "100Mi" requests: cpu: "100m" memory: "100Mi" lifecycle: preStop: exec: command: - /bin/bash - -c - "/usr/bin/gitlab-runner unregister -n $RUNNER_NAME" restartPolicy: Always
|
这里注意两点:
- Kubernetes executor 不支持在运行 job 是使用 service alias,所以访问服务时都要通过
127.0.0.1
来访问;
- 你可能注意到,配置里出现了三种 resource limit 配置:
KUBERNETES_CPU_LIMIT
, KUBERNETES_SERVICE_CPU_LIMIT
, KUBERNETES_HELPER_CPU_LIMIT
,这里三个配置将应用于 kubernetes job pod 中的三类容器,第一类用于实际执行命令,第二类(service 容器)用于启动 job 所需的 service,第三类(helper 容器)用于任务执行之前的代码拉取,以及任务执行之后构建产物(artifact)的上传。我们可以通过配置来为三种容器赋予不同的资源限制。
至此,我们可以在 Docker 和 Kubernetes 环境下部署 GitLab Runner.