kubernetes快速部署GitLab CI Runner

前面我们介绍了如何在 kubernetes 中快速部署 GitLab 代码托管系统、今天我们来继续看看如何实现基于 GitLab 的 CICD。GitLab CI 早在 8.0版本开始的时候就已经就已经集成到GitLab中了。我们只需要在项目中添加一个 .gitlab-ci.yaml 文件,然后添加一个 Runner 即可实现 CI。所以在实现GitLab的CI之前我们还需要先把 GitLab CI Runner 部署到 Kubernetes中。

image-20210805140936330

GitLab CI 中有一些基本的概念:Pipeline、Stages、Jobs等。

Pipeline:Pipeline流水线相当于一次构建任务,Pipeline中可以包含很多个流程;例如安装依赖、运行测试、编译安装、部署测试服务器、部署生产服务器等流程。任何提交或者 Merge 的合并都可以触发 Pipeline 的构建。

Stages :而 Stages 则表示一个构建阶段,也就是我们在 Pipeline 中提到的流程;我们可以在一次 Pipeline 中定义多个 Stages 。这些 Stages 有以下特性:所有的 Stages 会按照先后顺序运行,当一个 Stage 完成以后才会继续下一个 Stage;当所有的 Stages 完成以后,该构建任务 Pipeline 才会成功;如果任何一个 Stage 失败,后面的 Stages 不会执行,则该构建任务 Pipeline 失败。

Jobs:Jobs表示构建工作,表示某个 Stage 里面执行的工作;我们可以在 Stages 里面定义多个 Jobs。相同的 Stage 中的 Jobs 会并行执行;相同的 Stage 中的 Jobs 都执行成功后,该 Stage 才会成功;如何任何一个 Job 失败,那么该 Stage 失败,换句话说就是该构建任务 Pipeline 失败。

1、部署 GitLab Runner

那么问题来了:在GitLab中我们在什么地方执行构建任务呢?大多数的构建任务会占用大量的系统资源,如果我们直接在 GitLab 中运行构建任务的话,GitLab 的性能肯定会大幅度下降。这里我们就需要讲到 GitLab Runner 了、在 GitLab 中我们需要通过 GitLab Runner 来执行 GitLab CI构建任务。GitLab CI 最大的作用是管理各个项目的构建状态;因此,运行构建任务这种浪费资源的事情交给一个独立的 GitLab Runner 来做就会好很多,更重要的是 Gitlab Runner 可以安装到不同的机器上;甚至是本机,这样完全就不会影响到 GitLab 本身了。

GitLab Runner 的安装非常简单,也有多种安装方式:比如二进制、Docker、Docker-Compose等方式。这里我们将 GitLab Runner 部署到 Kubernetes 集群中。

GitLab Runner官方文档:https://docs.gitlab.com/runner/install/

1.1、验证K8S集群

执行下面的命令验证 Kubernetes 集群:

[root@kubernetes01 ~]# kubectl cluster-info
Kubernetes master is running at https://172.16.200.11:6443
CoreDNS is running at https://172.16.200.11:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
[root@kubernetes01 ~]# 

cluster-info这个命令会显示当前链接的集群状态和可用的集群服务列表。

1.2、获取GitLab CI Register Token

我们可以在 GitLab Menu — Admin — Runners 页面看到两个重要的参数:一个是URL、另外一个是 Register Token ,我们在下面的操作步骤中会用到这两个参数值。

360截图18241008109117113

1.3、GitLab CI Runner 资源文件

我们将和GitLab相关的资源对象都部署到GitLab这个NS下面。首先我们就需要通过configmap资源来传递Runner镜像所需要的环境变量,详细的资源清单文件如下(gitlab-ci-config.yaml):

[root@kubernetes01 gitlab-ci]# cat gitlab-ci-config.yaml 
apiVersion: v1
data:
  REGISTER_NON_INTERACTIVE: "true"
  REGISTER_LOCKED: "false"
  # 根据实际情况修改你的CI_SERVER_URL
  CI_SERVER_URL: "http://gitlab.gitlab.svc.cluster.local/ci"
  METRICS_SERVER: "0.0.0.0:9100"
  RUNNER_REQUEST_CONCURRENCY: "4"
  RUNNER_EXECUTOR: "kubernetes"
  KUBERNETES_NAMESPACE: "gitlab"
  KUBERNETES_PRIVILEGED: "true"
  KUBERNETES_CPU_REQUEST: "250m"
  KUBERNETES_MEMORY_REQUEST: "256Mi"
  KUBERNETES_CPU_LIMIT: "1"
  KUBERNETES_MEMORY_LIMIT: "1Gi"
  KUBERNETES_SERVICE_CPU_REQUEST: "150m"
  KUBERNETES_SERVICE_MEMORY_REQUEST: "256Mi"
  KUBERNETES_SERVICE_CPU_LIMIT: "1"
  KUBERNETES_SERVICE_MEMORY_LIMIT: "1Gi"
  KUBERNETES_HELPER_CPU_REQUEST: "150m"
  KUBERNETES_HELPER_MEMORY_REQUEST: "100Mi"
  KUBERNETES_HELPER_CPU_LIMIT: "500m"
  KUBERNETES_HELPER_MEMORY_LIMIT: "200Mi"
  KUBERNETES_PULL_POLICY: "if-not-present"
  KUBERNETES_TERMINATIONGRACEPERIODSECONDS: "10"
  KUBERNETES_POLL_INTERVAL: "5"
  KUBERNETES_POLL_TIMEOUT: "360"
  # 根据你的实际情况修改并添加对应的hosts映射文件、不然后面会报错
  RUNNER_PRE_CLONE_SCRIPT : "echo '172.16.200.11 gitlab.z0ukun.com registry.z0ukun.com' >> /etc/hosts"
  RUNNER_PRE_CLONE_SCRIPT : "echo '172.16.200.12 gitlab.z0ukun.com registry.z0ukun.com' >> /etc/hosts"
  RUNNER_PRE_CLONE_SCRIPT : "echo '172.16.200.13 gitlab.z0ukun.com registry.z0ukun.com' >> /etc/hosts"
kind: ConfigMap
metadata:
  labels:
    app: gitlab-ci-runner
  name: gitlab-ci-runner-cm
  namespace: gitlab

注:CI_SERVER_URL 对应的值需要指向 GitLab 实例的 URL(可以是外网地址,也可以是 Kubernetes 集群内部的 Service DNS 地址,因为 Runner 也是运行在 Kubernetes 集群中的)并加上/ci(我这里是 http://gitlab.gitlab.svc.cluster.local/ci )。此外还添加了一些构建容器运行的资源限制,我们可以自己根据需要进行更改即可。如果你在向configmap中添加了新的选项和参数后,需要把在运行的 GitLab CI Runner Pod 删除。因为我们是使用 envFrom 来注入上面的这些环境变量而不是直接使用 env 的(envFrom 通过将环境变量放置到ConfigMaps或Secrets来帮助减小清单文件)。

当然更详细的参数选项我们可以在Pod中运行 kubectl exec -it gitlab-ci-runner-0 /bin/bash -n gitlab 命令来查看,只需要为要配置的标志添加 env 变量即可,例如:

[root@kubernetes01 gitlab-ci]# kubectl exec -it gitlab-ci-runner-0 /bin/bash -n gitlab
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
gitlab-runner@gitlab-ci-runner-0:/$ 
gitlab-runner@gitlab-ci-runner-0:/$ gitlab-ci-multi-runner register --help | more
Runtime platform                                    arch=amd64 os=linux pid=229 revision=8925d9a0 version=14.1.0
NAME:
   gitlab-ci-multi-runner register - register a new runner

USAGE:
   gitlab-ci-multi-runner register [command options] [arguments...]

OPTIONS:
   -c value, --config value                                                    Config file (default: "/home/gitlab-runner/.gitlab-runne
r/config.toml") [$CONFIG_FILE]
   --template-config value                                                     Path to the configuration template file [$TEMPLATE_CONFI
G_FILE]
   --tag-list value                                                            Tag list [$RUNNER_TAG_LIST]
   -n, --non-interactive                                                       Run registration unattended [$REGISTER_NON_INTERACTIVE]
   --leave-runner                                                              Don't remove runner if registration fails [$REGISTER_LEA
VE_RUNNER]
   -r value, --registration-token value                                        Runner's registration token [$REGISTRATION_TOKEN]
   --run-untagged                                                              Register to run untagged builds; defaults to 'true' when
 'tag-list' is empty [$REGISTER_RUN_UNTAGGED]
   --locked                                                                    Lock Runner for current project, defaults to 'true' [$RE
GISTER_LOCKED]
   --access-level value                                                        Set access_level of the runner to not_protected or ref_p
rotected; defaults to not_protected [$REGISTER_ACCESS_LEVEL]
   --maximum-timeout value                                                     What is the maximum timeout (in seconds) that will be se
t for job when using this Runner (default: "0") [$REGISTER_MAXIMUM_TIMEOUT]
   --paused                                                                    Set Runner to be paused, defaults to 'false' [$REGISTER_
PAUSED]
   --name value, --description value                                           Runner name (default: "gitlab-ci-runner-0") [$RUNNER_NAM
E]
   --limit value                                                               Maximum number of builds processed by this runner (defau
lt: "0") [$RUNNER_LIMIT]
   --output-limit value                                                        Maximum build trace size in kilobytes (default: "0") [$R
UNNER_OUTPUT_LIMIT]
   --request-concurrency value                                                 Maximum concurrency for job requests (default: "4") [$RU
NNER_REQUEST_CONCURRENCY]
   -u value, --url value                                                       Runner URL (default: "http://gitlab.gitlab.svc.cluster.l
ocal/ci") [$CI_SERVER_URL]
   -t value, --token value                                                     Runner token [$CI_SERVER_TOKEN]
   --tls-ca-file value                                                         File containing the certificates to verify the peer when
 using HTTPS [$CI_SERVER_TLS_CA_FILE]
   --tls-cert-file value                                                       File containing certificate for TLS client auth when usi
ng HTTPS [$CI_SERVER_TLS_CERT_FILE]
   --tls-key-file value                                                        File containing private key for TLS client auth when usi
ng HTTPS [$CI_SERVER_TLS_KEY_FILE]
   --executor value                                                            Select executor, eg. shell, docker, etc. (default: "kube
--More--

1.4、GitLab CI Runner注册脚本

除了上面的一些环境变量相关的配置外,我们还需要一个用于注册、运行和取消注册 GitLab CI Runner 的小脚本。只有当 Pod 正常通过 Kubernetes(TERM信号)终止时,才会触发转轮取消注册。 如果强制终止 Pod(SIGKILL信号),Runner 将不会注销自身。必须手动完成对这种被杀死的 Runner 的清理,资源清单文件(runner-scripts-cm.yaml)如下:

[root@kubernetes01 gitlab-ci]# cat runner-scripts-cm.yaml 
apiVersion: v1
data:
  run.sh: |
    #!/bin/bash
    unregister() {
        kill %1
        echo "Unregistering runner ${RUNNER_NAME} ..."
        /usr/bin/gitlab-ci-multi-runner unregister -t "$(/usr/bin/gitlab-ci-multi-runner list 2>&1 | tail -n1 | awk '{print $4}' | cut -d'=' -f2)" -n ${RUNNER_NAME}
        exit $?
    }
    trap 'unregister' EXIT HUP INT QUIT PIPE TERM
    echo "Registering runner ${RUNNER_NAME} ..."
    /usr/bin/gitlab-ci-multi-runner register -r ${GITLAB_CI_TOKEN}
    sed -i 's/^concurrent.*/concurrent = '"${RUNNER_REQUEST_CONCURRENCY}"'/' /home/gitlab-runner/.gitlab-runner/config.toml
    echo "Starting runner ${RUNNER_NAME} ..."
    /usr/bin/gitlab-ci-multi-runner run -n ${RUNNER_NAME} &
    wait
kind: ConfigMap
metadata:
  labels:
    app: gitlab-ci-runner
  name: gitlab-ci-runner-scripts
  namespace: gitlab

1.5、创建 Secret资源对象

从上面的资源清单文件我们可以看到、这里我们需要一个 GITLAB_CI_TOKEN,我们用 GitLab CI Runner Token 来创建一个 Kubernetes secret 对象。然后将 token 进行 base64 编码:

# 注:这里的 M_cDBYmtaxTGZWy4megE 就是我们上图中看到的 registration token
[root@kubernetes01 gitlab-ci]# echo M_cDBYmtaxTGZWy4megE | base64 -w0
TV9jREJZbXRheFRHWld5NG1lZ0UK
[root@kubernetes01 gitlab-ci]# 

然后我们使用上面的 Token 创建一个 Secret 对象,资源清单文件如下:

[root@kubernetes01 gitlab-ci]# cat gitlab-ci-token-secret.yaml 
apiVersion: v1
kind: Secret
metadata:
  name: gitlab-ci-token
  namespace: gitlab
  labels:
    app: gitlab-ci-runner
data:
  GITLAB_CI_TOKEN: TV9jREJZbXRheFRHWld5NG1lZ0UK

1.6、创建Runner StatefulSet 控制器

现在我们就可以来编写一个用于真正运行 Runner 的 StatefulSet 控制器资源对象。首先,在开始运行的时候,尝试取消注册所有的同名 Runner,当节点丢失时(即NodeLost事件)然后再尝试重新注册自己并开始运行。在正常停止 Pod 的时候,Runner 将会运行unregister命令来尝试取消自己,所以 GitLab 就不能再使用这个 Runner 了,这个是通过 Kubernetes Pod 生命周期中的hooks来完成的。另外我们通过使用envFrom来指定SecretsConfigMaps来用作环境变量,对应的资源清单文件(runner-statefulset.yaml )如下:

[root@kubernetes01 gitlab-ci]# cat runner-statefulset.yaml 
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: gitlab-ci-runner
  namespace: gitlab
  labels:
    app: gitlab-ci-runner
spec:
  updateStrategy:
    type: RollingUpdate
  replicas: 3
  serviceName: gitlab-ci-runner
  selector:
    matchLabels:
      app: gitlab-ci-runner
  template:
    metadata:
      labels:
        app: gitlab-ci-runner
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - topologyKey: "kubernetes.io/hostname"
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - gitlab-ci-runner
      volumes:
      - name: gitlab-ci-runner-scripts
        projected:
          sources:
          - configMap:
              name: gitlab-ci-runner-scripts
              items:
              - key: run.sh
                path: run.sh
                mode: 0755
      serviceAccountName: gitlab-ci
      securityContext:
        runAsNonRoot: true
        runAsUser: 999
        supplementalGroups: [999]
      containers:
      - image: gitlab/gitlab-runner:v14.1.0
        name: gitlab-ci-runner
        command:
        - /scripts/run.sh
        envFrom:
        - configMapRef:
            name: gitlab-ci-runner-cm
        - secretRef:
            name: gitlab-ci-token
        env:
        - name: RUNNER_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        ports:
        - containerPort: 9100
          name: http-metrics
          protocol: TCP
        volumeMounts:
        - name: gitlab-ci-runner-scripts
          mountPath: "/scripts"
          readOnly: true
      restartPolicy: Always

1.7、创建RBAC资源对象

我们还需要为上面的 Runner 控制器创建一个RBAC资源对象,详细的资源清单文件(runner-rbac.yaml)如下:

[root@kubernetes01 gitlab-ci]# cat runner-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gitlab-ci
  namespace: gitlab
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: gitlab-ci
  namespace: gitlab
rules:
  - apiGroups: [""]
    resources: ["*"]
    verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: gitlab-ci
  namespace: gitlab
subjects:
  - kind: ServiceAccount
    name: gitlab-ci
    namespace: gitlab
roleRef:
  kind: Role
  name: gitlab-ci
  apiGroup: rbac.authorization.k8s.io

1.8、创建 Runner 资源对象

资源清单文件都准备无误之后我们就直接创建这些资源对象:

[root@kubernetes01 gitlab-ci]# ls
gitlab-ci-config.yaml  gitlab-ci-token-secret.yaml  runner-rbac.yaml        runner-statefulset.yaml
gitlab-ci-k8s-demo     gitlab-sa.yaml               runner-scripts-cm.yaml
[root@kubernetes01 gitlab-ci]# 
[root@kubernetes01 gitlab-ci]# kubectl apply -f .
configmap/gitlab-ci-runner-cm created
secret/gitlab-ci-token created
serviceaccount/gitlab created
clusterrolebinding.rbac.authorization.k8s.io/gitlab created
serviceaccount/gitlab-ci created
role.rbac.authorization.k8s.io/gitlab-ci created
rolebinding.rbac.authorization.k8s.io/gitlab-ci created
configmap/gitlab-ci-runner-scripts created
statefulset.apps/gitlab-ci-runner created
[root@kubernetes01 gitlab-ci]# 

1.9、验证 Runners

创建完成后,可以通过查看 Pod 状态判断 Runner 是否运行成功:

[root@kubernetes01 gitlab-ci]# kubectl get pod -n gitlab
NAME                                 READY   STATUS    RESTARTS   AGE
gitlab-ci-runner-0                   1/1     Running   0          3m47s
gitlab-ci-runner-1                   1/1     Running   0          3m45s
gitlab-ci-runner-2                   1/1     Running   0          3m41s
gitlab-f5d7f6c48-54pgk               1/1     Running   0          3d12h
gitlab-postgresql-678f8fc4c6-pcxzp   1/1     Running   0          3d12h
gitlab-redis-5899459775-s5xb9        1/1     Running   0          4d6h
[root@kubernetes01 gitlab-ci]#

从上面的信息我们可以看到已经成功运行了三个(具体取决于StatefulSet清单中的副本数,这里我配置了3个副本) Runner 实例,然后我们可以刷新 GitLab Menu — Admin — Runners 页面:

image-20210803223755085

好了、到这里 GitLab CI Runner 就已经不是成功了;当然我们也可以根据需要更改 Runner 的一些配置,比如添加 tag 标签等。

推荐文章