在 Kubernetes 上安裝 Gitlab CI Runner?

www.qikqiak.com
圖標

上節課我們使用 Helm 快速的將 Gitlab 安裝到了我們的 Kubernetes 集群中,這節課來和大家介紹如何使用 Gitlab CI 來做持續集成,首先先給大家介紹一些關於 Gitlab CI 的一些基本概念,以及如何在 Kubernetes 上安裝 Gitlab CI Runner。

簡介

從 Gitlab 8.0 開始,Gitlab CI 就已經集成在 Gitlab 中,我們只要在項目中添加一個.gitlab-ci.yml文件,然後添加一個Runner,即可進行持續集成。在介紹 Gitlab CI 之前,我們先看看一些 Gitlab CI 的一些相關概念。

Pipeline

一次 Pipeline 其實相當於一次構建任務,裡面可以包含很多個流程,如安裝依賴、運行測試、編譯、部署測試伺服器、部署生產伺服器等流程。任何提交或者 Merge Request 的合併都可以觸發 Pipeline 構建,如下圖所示:

+------------------+ +----------------+
| | trigger | |
| Commit / MR +---------->+ Pipeline |
| | | |
+------------------+ +----------------+

Stages

Stages 表示一個構建階段,也就是上面提到的一個流程。我們可以在一次 Pipeline 中定義多個 Stages,這些 Stages 會有以下特點:

  • 所有 Stages 會按照順序運行,即當一個 Stage 完成後,下一個 Stage 才會開始
  • 只有當所有 Stages 完成後,該構建任務 (Pipeline) 才會成功
  • 如果任何一個 Stage 失敗,那麼後面的 Stages 不會執行,該構建任務 (Pipeline) 失敗

Stages 和 Pipeline 的關係如下所示:

+--------------------------------------------------------+
| |
| Pipeline |
| |
| +-----------+ +------------+ +------------+ |
| | Stage 1 |---->| Stage 2 |----->| Stage 3 | |
| +-----------+ +------------+ +------------+ |
| |
+--------------------------------------------------------+

Jobs

Jobs 表示構建工作,表示某個 Stage 裡面執行的工作。我們可以在 Stages 裡面定義多個 Jobs,這些 Jobs 會有以下特點:

  • 相同 Stage 中的 Jobs 會並行執行
  • 相同 Stage 中的 Jobs 都執行成功時,該 Stage 才會成功
  • 如果任何一個 Job 失敗,那麼該 Stage 失敗,即該構建任務 (Pipeline) 失敗

Jobs 和 Stage 的關係如下所示:

+------------------------------------------+
| |
| Stage 1 |
| |
| +---------+ +---------+ +---------+ |
| | Job 1 | | Job 2 | | Job 3 | |
| +---------+ +---------+ +---------+ |
| |
+------------------------------------------+

Gitlab Runner

如果理解了上面的基本概念之後,可能我們就會發現一個問題,我們的構建任務在什麼地方來執行呢,以前用 Jenkins 在 Master 和 Slave 節點都可以用來運行構建任務,而來執行我們的 Gitlab CI 構建任務的就是 Gitlab Runner。

我們知道大多數情況下構建任務都是會佔用大量的系統資源的,如果直接讓 Gitlab 本身來運行構建任務的話,顯然 Gitlab 的性能會大幅度下降的。GitLab CI 最大的作用是管理各個項目的構建狀態,因此,運行構建任務這種浪費資源的事情交給一個獨立的 Gitlab Runner 來做就會好很多,更重要的是 Gitlab Runner 可以安裝到不同的機器上,甚至是我們本機,這樣完全就不會影響到 Gitlab 本身了。

安裝

安裝 Gitlab Runner 非常簡單,我們可以完全安裝官方文檔:docs.gitlab.com/runner/即可,比如可以直接使用二進位、Docker 等來安裝。同樣的,我們這裡還是將 Gitlab Runner 安裝到 Kubernetes 集群中來,讓我們的集群來統一管理 Gitlab 相關的服務。

1.驗證 Kubernetes 集群

執行下面的命令驗證 Kubernetes 集群:

$ kubectl cluster-info
Kubernetes master is running at https://10.151.30.11:6443
KubeDNS is running at https://10.151.30.11:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use kubectl cluster-info dump.

cluster-info這個命令會顯示當前鏈接的集群狀態和可用的集群服務列表。

2.獲取 Gitlab CI Register Token

前面的章節中我們已經成功安裝了 Gitlab,在瀏覽器中打開git.qikqiak.com頁面,然後登錄後進入到管理頁面http://git.qikqiak.com/admin,然後點擊導航欄中的Runner,可以看到該頁面中有兩個總要的參數,一個是 URL,另外一個就是 Register Token,下面的步驟中需要用到這兩個參數值。

注意:不要隨便泄露 Token

3.編寫 Gitlab CI Runner 資源清單文件

同樣我們將 Runner 相關的資源對象都安裝到kube-ops這個 namespace 下面,首先,我們通過 ConfigMap 資源來傳遞 Runner 鏡像所需的環境變數(runner-cm.yaml):

apiVersion: v1
data:
REGISTER_NON_INTERACTIVE: "true"
REGISTER_LOCKED: "false"
METRICS_SERVER: "0.0.0.0:9100"
CI_SERVER_URL: "http://git.qikqiak.com/ci"
RUNNER_REQUEST_CONCURRENCY: "4"
RUNNER_EXECUTOR: "kubernetes"
KUBERNETES_NAMESPACE: "kube-ops"
KUBERNETES_PRIVILEGED: "true"
KUBERNETES_CPU_LIMIT: "1"
KUBERNETES_MEMORY_LIMIT: "1Gi"
KUBERNETES_SERVICE_CPU_LIMIT: "1"
KUBERNETES_SERVICE_MEMORY_LIMIT: "1Gi"
KUBERNETES_HELPER_CPU_LIMIT: "500m"
KUBERNETES_HELPER_MEMORY_LIMIT: "100Mi"
KUBERNETES_PULL_POLICY: "if-not-present"
KUBERNETES_TERMINATIONGRACEPERIODSECONDS: "10"
KUBERNETES_POLL_INTERVAL: "5"
KUBERNETES_POLL_TIMEOUT: "360"
kind: ConfigMap
metadata:
labels:
app: gitlab-ci-runner
name: gitlab-ci-runner-cm
namespace: kube-ops

要注意CI_SERVER_URL對應的值需要指向我們的 Gitlab 實例的 URL,並加上/cigit.qikqiak.com/ci )。此外還添加了一些構建容器運行的資源限制,我們可以自己根據需要進行更改即可。

注意:在向 ConfigMap 添加新選項後,需要刪除 GitLab CI Runner Pod。因為我們是使用 envFrom來注入上面的這些環境變數而不是直接使用env的(envFrom 通過將環境變數放置到ConfigMapsSecrets來幫助減小清單文件。

另外如果要添加其他選項的話,我們可以在 Pod 中運行gitlab-ci-multi-runner register --help命令來查看所有可使用的選項,只需為要配置的標誌添加 env 變數即可,如下所示:

gitlab-runner@gitlab-ci-runner-0:/$ gitlab-ci-multi-runner --help
[...]
--kubernetes-cpu-limit value The CPU allocation given to build containers (default: "1") [$KUBERNETES_CPU_LIMIT]
--kubernetes-memory-limit value The amount of memory allocated to build containers (default: "4Gi") [$KUBERNETES_MEMORY_LIMIT]
--kubernetes-service-cpu-limit value The CPU allocation given to build service containers (default: "1") [$KUBERNETES_SERVICE_CPU_LIMIT]
--kubernetes-service-memory-limit value The amount of memory allocated to build service containers (default: "1Gi") [$KUBERNETES_SERVICE_MEMORY_LIMIT]
--kubernetes-helper-cpu-limit value The CPU allocation given to build helper containers (default: "500m") [$KUBERNETES_HELPER_CPU_LIMIT]
--kubernetes-helper-memory-limit value The amount of memory allocated to build helper containers (default: "3Gi") [$KUBERNETES_HELPER_MEMORY_LIMIT]
--kubernetes-cpu-request value The CPU allocation requested for build containers [$KUBERNETES_CPU_REQUEST]
[...]

除了上面的一些環境變數相關的配置外,還需要一個用於註冊、運行和取消註冊 Gitlab CI Runner 的小腳本。只有當 Pod 正常通過 Kubernetes(TERM信號)終止時,才會觸發轉輪取消註冊。 如果強制終止 Pod(SIGKILL信號),Runner 將不會註銷自身。必須手動完成對這種被殺死的 Runner 的清理,配置清單文件如下:(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: kube-ops

我們可以看到需要一個 GITLAB_CI_TOKEN,然後我們用 Gitlab CI runner token 來創建一個 Kubernetes secret 對象。將 token 進行 base64 編碼:

$ echo rcVZF-mdHt9qCyyrCDgS | base64 -w0
cmNWWkYtbWRIdDlxQ3l5ckNEZ1MK

base64 命令在大部分 Linux 發行版中都是可用的。

然後使用上面的 token 創建一個 Secret 對象:(gitlab-ci-token-secret.yaml)

apiVersion: v1
kind: Secret
metadata:
name: gitlab-ci-token
namespace: kube-ops
labels:
app: gitlab-ci-runner
data:
GITLAB_CI_TOKEN: cmNWWkYtbWRIdDlxQ3l5ckNEZ1MK

然後接下來我們就可以來編寫一個用於真正運行 Runner 的控制器對象,我們這裡使用 Statefulset。首先,在開始運行的時候,嘗試取消註冊所有的同名 Runner,當節點丟失時(即NodeLost事件),這尤其有用。然後再嘗試重新註冊自己並開始運行。在正常停止 Pod 的時候,Runner 將會運行unregister命令來嘗試取消自己,所以 Gitlab 就不能再使用這個 Runner 了,這個是通過 Kubernetes Pod 生命周期中的hooks來完成的。

另外我們通過使用envFrom來指定SecretsConfigMaps來用作環境變數,對應的資源清單文件如下:(runner-statefulset.yaml)

apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: gitlab-ci-runner
namespace: kube-ops
labels:
app: gitlab-ci-runner
spec:
updateStrategy:
type: RollingUpdate
replicas: 2
serviceName: gitlab-ci-runner
template:
metadata:
labels:
app: gitlab-ci-runner
spec:
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:latest
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

可以看到上面我們使用了一個名為 gitlab-ci 的 serviceAccount,新建一個 rbac 資源清單文件:(runner-rbac.yaml)

apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-ci
namespace: kube-ops
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: gitlab-ci
namespace: kube-ops
rules:
- apiGroups: [""]
resources: ["*"]
verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: gitlab-ci
namespace: kube-ops
subjects:
- kind: ServiceAccount
name: gitlab-ci
namespace: kube-ops
roleRef:
kind: Role
name: gitlab-ci
apiGroup: rbac.authorization.k8s.io

4.創建 Runner 資源對象

資源清單文件準備好後,我們直接創建上面的資源對象:

$ ls
gitlab-ci-token-secret.yaml runner-cm.yaml runner-rbac.yaml runner-scripts-cm.yaml runner-statefulset.yaml
$ kubectl create -f .
secret "gitlab-ci-token" created
configmap "gitlab-ci-runner-cm" 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

創建完成後,可以通過查看 Pod 狀態判斷 Runner 是否運行成功:

$ kubectl get pods -n kube-ops
NAME READY STATUS RESTARTS AGE
gitlab-7bff969fbc-k5zl4 1/1 Running 0 4d
gitlab-ci-runner-0 1/1 Running 0 3m
gitlab-ci-runner-1 1/1 Running 0 3m
......

可以看到已經成功運行了兩個(具體取決於StatefulSet清單中的副本數) Runner 實例,然後切換到 Gitlab Admin 頁面下面的 Runner 頁面:

當然我們也可以根據需要更改 Runner 的一些配置,比如添加 tag 標籤等。

參考鏈接: edenmal.moe/post/2017/G

推薦

最後打個廣告,給大家推薦一個本人精心打造的一個精品課程,現在限時優惠中:從 Docker 到 Kubernetes 進階

微信搜索k8s技術圈關注我們的微信公眾帳號,在微信公眾帳號中回復 加群 即可加入到我們的 kubernetes 討論群裡面共同學習。


推薦閱讀:
相关文章