1. 程式人生 > >Gitlab CI 整合 Kubernetes 叢集部署 Spring Boot 專案

Gitlab CI 整合 Kubernetes 叢集部署 Spring Boot 專案

在上一篇部落格中,我們成功將 Gitlab CI 部署到了 Docker 中去,成功建立了 Gitlab CI Pipline 來執行 CI/CD 任務。那麼這篇文章我們更進一步,將它整合到 K8s 叢集中去。這個才是我們最終的目標。眾所周知,k8s 是目前最火的容器編排專案,很多公司都使用它來構建和管理自己容器叢集,可以用來做機器學習訓練以及 DevOps 等一系列的事情。

在這裡,我們聚焦 CI/CD,針對於 Spring Boot 專案,藉助 Gitlab CI 完成流水線的任務配置,最終部署到 K8s 上去。本文會詳細講解如何一步步操作,完成這樣的一條流水線。

軟體的核心版本如下:

  • Kubernetes:v1.16.0-rc.2
  • 部署 Gitlab 的 Docker:19.03.2
  • Gitlab CE:gitlab-ce:latest
  • Gitlab Runner: helm chart gitlab-runner-v0.10.0-rc1
  • Helm:2.14.3

k8s 叢集資訊:

[root@master01 ~]# kubectl get nodes
NAME            STATUS   ROLES    AGE   VERSION
172.17.11.62    Ready    <none>   37d   v1.16.0-rc.2
172.17.13.120   Ready    <none>   91d   v1.16.0-rc.2
172.17.13.121   Ready    <none>   91d   v1.15.1
172.17.13.122   Ready    <none>   91d   v1.16.0-rc.2
172.17.13.123   Ready    <none>   92d   v1.16.0-rc.2

Runner 的安裝和註冊

在上一篇部落格《Docker Gitlab CI 部署 Spring Boot 專案》中我們已經實現了在 Docker 中部署這一套流水線,但是單節點的 Docker 只適合本地除錯,如果真正搭建起來用於公司的 CI/CD 工作,還是會把它放到叢集環境下,因此現在我們將流水線部署到 k8s 上。

其實部署到 k8s 上包含兩種,一個是把 Gitlab 部署上去,另一個是把 CI 這部分部署上去(也就是 Gitlab Runner)。其實 Gitlab 本身就是一個服務,部署在哪裡都可以,可以選擇 Docker 部署,也可以找一臺伺服器單獨部署,作為程式碼倉庫。最關鍵的其實是後者,CI/CD 的流程複雜且消耗資源多,部署在叢集上就能自動排程,達到資源利用最大化。那麼下面著重講 Gitlab Runner 的 k8s 部署,想了解前者的可以看官方文件,通過 helm 安裝。GitLab cloud native Helm Chart

假設 Gitlab 都已經部署成功了,那麼下面開始安裝 Gitlab Runner。具體的就是把 Runner 安裝到某個節點的 pod 的上,在處理具體的 CI 任務時,Runner 會啟動新的 pod 來執行任務,由 k8s 進行節點間的排程。

一般來說,我們會使用 Helm 來進行安裝,Helm 是類似 CentOS 裡的 yum,是一種軟體管理工具,可以幫我們快速安裝軟體到 k8s 上。我們需要在其中一個主節點上安裝好 Helm 的 client 和 server。具體可參考:

  • Helm User Guide - Helm 使用者指南
  • Kubernetes Helm 初體驗

顯示如下,證明安裝成功。

[root@master01 ~]# helm version
Client: &version.Version{SemVer:"v2.14.3", GitCommit:"0e7f3b6637f7af8fcfddb3d2941fcc7cbebb0085", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.14.3", GitCommit:"0e7f3b6637f7af8fcfddb3d2941fcc7cbebb0085", GitTreeState:"clean"}

新增 gitlab 源並更新。

[root@master01 ~]# helm repo add gitlab https://charts.gitlab.io
[root@master01 ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "gitlab" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete.
[root@master01 ~]# helm search gitlab-runner
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
gitlab/gitlab-runner    0.10.0-rc1      12.4.0-rc1      GitLab Runner

這裡看到有兩個版本,一個是 chart version 一個是 app version。 chart 是 helm 中描述相關的一組 Kubernetes 資源的檔案集合,裡面包含了一個 value.yaml 配置檔案和一系列模板(deployment.yaml、svc.yaml 等),而具體的 app 是通過需要單獨去 Docker Hub 上拉取的。這兩個欄位分別就是描述了這兩個版本號。

安裝前先建立一個 gitlab 的 namespace,併為其配置相應的許可權。

[root@master01 ~]# kubectl create namespace gitlab-runners

建立一個 rbac-runner-config.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: gitlab
  namespace: gitlab-runners
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: gitlab-runners
  name: gitlab
rules:
- apiGroups: [""] #"" indicates the core API group
  resources: ["*"]
  verbs: ["*"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: gitlab
  namespace: gitlab-runners
subjects:
- kind: ServiceAccount
  name: gitlab # Name is case sensitive
  apiGroup: ""
roleRef:
  kind: Role #this must be Role or ClusterRole
  name: gitlab # this must match the name of the Role or ClusterRole you wish to bind to
  apiGroup: rbac.authorization.k8s.io

然後執行以下。

[root@master01 ~]# kubectl create -f rbac-runner-config.yaml

接下來配置 runner 的註冊資訊。上一篇部落格中,我們是先安裝 gitlab runner 然後進入容器執行 gitlab-runner register 來進行註冊的。在 k8s 可支援這麼操作,但是同時 helm 也提供了一個配置檔案可以在安裝 runner 的時候為其註冊一個預設的 runner。我們可以去 gitlab-runner 的專案原始碼 中獲取到 values.yaml 這個配置檔案。配置檔案比較長,可以根據需要自己去配置,下面就貼下本文中需要配置的地方。

## 拉取策略, 先取本地映象
imagePullPolicy: IfNotPresent

## 配置 gitlab 的 url 和註冊令牌
## 可以在 gitlab 專案中設定 --CI/CD--Runner-- 手動設定 specific Runner 中查詢
gitlabUrl: http://172.17.193.109:7780/
runnerRegistrationToken: "qzxposxDst_Nnq5MMnPf"

## 和之前配置的 rbac name 對應
rbac:
  serviceAccountName: gitlab

## 指定關聯 runner 的標籤
tags: "maven,docker,k8s"

privileged: true

serviceAccountName: gitlab

然後通過 helm install 安裝 runner 就可以了。token 和 url 不知道如何獲取的,見我上一篇部落格。

[root@master01 ~]# helm install --name gitlab-runner gitlab/gitlab-runner -f values.yaml --namespace gitlab-runners
NAME:   gitlab-runner
LAST DEPLOYED: Fri Oct 25 10:08:40 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME              DATA  AGE
gitlab-runner-gitlab-runner  5     <invalid>

==> v1/Deployment
NAME              READY  UP-TO-DATE  AVAILABLE  AGE
gitlab-runner-gitlab-runner  0/1    0           0          <invalid>

==> v1/Secret
NAME              TYPE    DATA  AGE
gitlab-runner-gitlab-runner  Opaque  2     <invalid>
NOTES:
Your GitLab Runner should now be registered against the GitLab instance reachable at: "http://172.17.193.109:7780/"

等待一段時間後就可以在 k8s 的 dashboard 上看到啟動成功的 runner 的 pod 了。

這個時候可以進入 pod 看一下 runner 的配置檔案(/home/gitlab-runner/.gitlab-runner/config.toml)了。這個檔案就是根據之前配置的 values.yaml 自動生成的。

listen_address = "[::]:9252"
concurrent = 10
check_interval = 30
log_level = "info"

[session_server]
  session_timeout = 1800

[[runners]]
  name = "gitlab-runner-gitlab-runner-6767fdcb6-pjvbz"
  request_concurrency = 1
  url = "http://172.17.193.109:7780/"
  token = "Soxf6HEQVj4wr25zsGUS"
  executor = "kubernetes"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
  [runners.kubernetes]
    host = ""
    bearer_token_overwrite_allowed = false
    image = "ubuntu:16.04"
    namespace = "gitlab-runners"
    namespace_overwrite_allowed = ""
    privileged = true
    service_account = "gitlab"
    service_account_overwrite_allowed = ""
    pod_annotations_overwrite_allowed = ""
    [runners.kubernetes.pod_security_context]
    [runners.kubernetes.volumes]

註冊成功後就可以在 gitlab 的 web UI 上看到對應 token 的 runner 了。

專案的建立與 Dockerfile

這邊沒啥好說的,直接新建一個 spring boot 的 hello world 專案,並新增一個 dockerfile。

FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY  /target/demo-0.0.1-SNAPSHOT.jar app.jar
ENV PORT 5000
EXPOSE $PORT
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-Dserver.port=${PORT}","-jar","/app.jar"]

.gitlab-ci.yml 改造

由於使用的 executor 不同,所以. gitlab-ci.yml 和之前也有些不同。比如 k8s 貌似不支援快取,volume 掛載方式的不同,以及許可權問題等。先看完整的配置檔案。

image: docker:latest
variables:
  DOCKER_DRIVER: overlay2
  # k8s 掛載本地卷作為 maven 的快取
  MAVEN_OPTS: "-Dmaven.repo.local=/home/cache/maven"
  REGISTRY: "registry.cn-hangzhou.aliyuncs.com"
  TAG: "tmp-images/hello-spring"
  TEST_POD: "hello-spring"
  TEST_POD_CONTAINER: "spring-boot"
stages:
  - package
  - build
  - deploy
  - release
maven-package:
  image: maven:3.5-jdk-8-alpine
  tags:
    - maven
  stage: package
  script:
    - mvn clean package -Dmaven.test.skip=true
  artifacts:
    paths:
      - target/*.jar
docker-build:
  tags:
    - docker
  stage: build
  script:
    - echo "Building Dockerfile-based application..."
    - docker login [email protected] registry.cn-hangzhou.aliyuncs.com -p [your pwd]
    - docker build -t $REGISTRY/$TAG:$CI_COMMIT_SHORT_SHA .
    - docker push $REGISTRY/$TAG
  only:
    - master
k8s-deploy:
  image: bitnami/kubectl:latest
  tags:
    - k8s
  stage: deploy
  script:
    - echo "deploy to k8s cluster..."
    - kubectl version
    - kubectl set image deployment/$TEST_POD $TEST_POD_CONTAINER=$REGISTRY/$TAG:$CI_COMMIT_SHORT_SHA --namespace gitlab-runners
  only:
    - master
#   when: manual
release:
  stage: release
  script:
    - echo "release to prod env..."
  when: manual

需要特別指出的地方有 3 點:

  • 繫結本地 Docker 守護程序。

  • maven 倉庫的快取。
  • k8s 測試映象的部署。

繫結本地 Docker 守護程序

Docker 環境下我們使用了一個 docker:dind 的服務用於執行 docker build 到本地映象倉庫。在 k8s 中,發現用了這個東西會出現 runner 連不到 Gitlab 伺服器上,報了一個 host xxx is unreachable 的錯誤。所以最終採用 volume 繫結的形式把本地 docker.sock 通過 host_path 的方式掛載到 runner 中去。

# /home/gitlab-runner/.gitlab-runner/config.toml
[[runners.kubernetes.volumes.host_path]]
  name = "docker"
  mount_path = "/var/run/docker.sock"

Maven 倉庫的快取

在之前的 Docker 環境下,volume 的掛載是一件很容易的事情,我們直接可以把本地的 maven 倉庫掛載。或者通過 cache 節點進行快取的配置。但是在 k8s 環境下,我折騰了很久也沒找到 cache 節點的配置方法。生產環境下應該可以配置 aws 或者 minio 作為快取。目前最終測試出來本地可行的方案是通過掛載 nfs pvc。

首先我們需要建立一個 PersistentVolume 和 PersistentVolumeClaim。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: gitlab-runner-maven-repo
spec:
  accessModes:
  - ReadWriteMany
  capacity:
    storage: 5Gi
  mountOptions:
  - nolock
  nfs:
    path: /opt/maven-cache/
    server: 172.17.13.120
  persistentVolumeReclaimPolicy: Recycle
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: gitlab-runner-maven-repo-claim
  namespace: gitlab-runners
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  volumeName: gitlab-runner-maven-repo
status:
  accessModes:
  - ReadWriteMany
  capacity:
    storage: 5Gi

在 runner 的配置中繫結。

# /home/gitlab-runner/.gitlab-runner/config.toml
[[runners.kubernetes.volumes.pvc]]
  name = "gitlab-runner-maven-repo-claim"
  mount_path = "/home/cache/maven"

然後配置 MAVEN_OPTS 到掛載的路徑,就可以實現 maven 倉庫的快取了。

k8s 測試映象的部署

在 Docker 中,build 之後的映象直接可以通過 docker run 啟動。在 k8s 下相對比較複雜,需要通過配置 deployment.yaml 來進行啟動,如果需要外部訪問,還需要配置 services 等元件。這裡僅僅是為了演示流水線的執行過程,就預先啟動一個 pod 作為測試。每次觸發新的構建任務時,直接通過命令 kubectl set image 替換掉舊映象就可以了。

執行流水線

git push & commit 程式碼後,流水線會自動建立執行。完成後可在之前配置的 services 埠看到部署的結果。

Troubleshooting

第一次實操過程中,坑還是很多的,在這裡記錄一下。

apiVersion 發生變化產生的問題

k8s 迭代的速度較快,使用最新版但是周邊生態沒有跟上腳步同步更新的話很容易產生問題。在 1.16 中 API versions 就有了比較大的變化,所以導致 helm 的模板沒及時更新出錯了。

Error: validation failed: unable to recognize "": no matches for kind"Deployment"in version"extensions/v1beta1"

1. Helm 服務端安裝失敗

以往 helm 的服務端安裝直接呼叫 helm init 就可以了。由於 apiVersion 的問題需要通過以下命令才能裝上。完整的安裝的命令如下:

# 新增許可權控制賬戶
[root@master01 ~]# kubectl create serviceaccount --namespace kube-system tiller
[root@master01 ~]# kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller

# 通過此命令修改 apiVersion 由 extensions/v1beta1 變為 apps/v1,並使用國內映象。
[root@master01 ~]# helm init --upgrade -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.14.3 --service-account tiller --override spec.selector.matchLabels.'name'='tiller',spec.selector.matchLabels.'app'='helm' --output yaml | sed 's@apiVersion: extensions/v1beta1@apiVersion: apps/v1@' | kubectl apply -f -

[root@master01 ~]# [root@master01 ~]# helm repo remove stable
[root@master01 ~]# helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

2. Gitlab 無法直接託管 k8s 叢集安裝 Runner

同樣的,上文我們是通過手動安裝的 helm server 和 gitlab runner。而實際上 Gitlab CE 支援免費繫結 1 個 k8s 叢集,並且提供一鍵式的安裝。但是由於版本號的問題,我這裡安裝不上。大家可以用低版本的 k8s 測試一下。具體在 Gitlab WebUI 中 root 賬戶登入,管理中心 -- Kubernetes 或者專案管理介面下,運維 --Kubernetes 進行叢集的繫結。

繫結後會託管到 Gitlab,建立一個 namespace 來進行程式的安裝。

Runner 中 config.toml 配置消失

之前在配置 runner 的 volume 的時候是直接進入 pod 內部修改 config.toml 這個配置檔案的。這樣會存在一個問題,如果叢集重啟了,修改過的資料就全沒了,因為重啟後會重新起一個 pod。以往在 Docker 中,我們會 commit 成一個新的映象儲存,但這樣儲存的映象顯然沒有普遍性,並且每次還需要修改 deployment.yaml 中的映象,因此最好的方法是修改 helm 的 chart 中的模板,把所有的配置資訊都寫入 yaml 檔案中,而非直接修改 pod 本身中的配置。

因此為了能夠高度的配置我們自己的 runner,就不直接從 helm 官方的源映象安裝了。

[root@master01 ~]# helm fetch gitlab/gitlab-runner

helm fetch 可以將 chart 下載到本地,可以看到是. tgz 格式的,解壓後的資料夾內包含的就是描述資源的一些模板檔案。

gitlab-runner-v0.10.0-rc1
├── CHANGELOG.md
├── Chart.yaml
├── CONTRIBUTING.md
├── LICENSE
├── NOTICE
├── README.md
├── scripts
│   ├── changelog2releasepost
│   └── prepare-changelog-entries.rb
├── templates
│   ├── _cache.tpl
│   ├── configmap.yaml
│   ├── deployment.yaml
│   ├── _env_vars.tpl
│   ├── _helpers.tpl
│   ├── hpa.yaml
│   ├── NOTES.txt
│   ├── role-binding.yaml
│   ├── role.yaml
│   ├── secrets.yaml
│   └── service-account.yaml
└── values.yaml

之前提取的 values.yaml 和這裡是一樣的,用於設定一些可配置的變數。具體的模板在 templates 目錄下。這裡我們可以仔細看下 values.yaml、deployment.yaml 和 configmap.yaml,會發現 runner 的 config.toml 檔案中的大部分資訊都是根據這三個配置檔案中的資訊自動生成的。那麼我們只需要額外配置一下需要的 volume 就可以了。

這裡我們需要修改 vaules.yaml 和 configmap.yaml。

## configmap.yaml
if ! sh /scripts/register-the-runner; then
  exit 1
fi

# add volume config
cat >>/home/gitlab-runner/.gitlab-runner/config.toml <<EOF
  [[runners.kubernetes.volumes.pvc]]
    name = "{{.Values.maven.cache.pvcName}}"
    mount_path = "{{.Values.maven.cache.mountPath}}"
  [[runners.kubernetes.volumes.host_path]]
    name = "docker"
    mount_path = "/var/run/docker.sock"
EOF

# Start the runner
exec /entrypoint run --user=gitlab-runner \
  --working-directory=/home/gitlab-runner

在 register 和 start 之間新增配置 volume 的資訊,並在 values.yaml 配置相應的變數。

## my config
#  maven cache
maven:
  cache:
    pvcName: gitlab-runner-maven-repo-claim
    mountPath: /home/cache/maven

配置完成後,執行 helm upgrade gitlab-runner gitlab-runner-v0.10.0-rc1/

之後無論重啟 pod 還是叢集,runner 的配置資訊都能被正確載入了。

映象下載不到的問題

gcr.io 無法訪問,Docker Hub 訪問速度慢,這個大家都懂的。

  • 阿里雲映象加速器。
  • 尋找能用的映象 pull 下來本地打 tag。七牛雲映象中心
  • 尋找本地的映象伺服器

阿里雲通用映象伺服器: https://registry.cn-hangzhou.aliyuncs.com

helm chart 映象

  • 阿里:https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
  • Azure:http://mirror.azure.cn/kubernetes/charts

總結

本文主要描述了在 k8s 上部署流水線的整個過程,由於對 k8s 不太熟悉,遇到了不少的坑,國內相關的部落格也較少,所以就記錄一下整個配置的過程。對於整個流程不熟悉的讀者可以先閱讀上一篇部落格《Docker Gitlab CI 部署 Spring Boot 專案》。下一步的工作就是要將流水線對接到實際的專案中去了。

未完待續...

參考資料

  • GitLab Runner Helm Chart
  • GitLab CI/CD Pipeline Configuration Reference
  • Helm User Guide - Helm 使用者指南
  • 使用 GitLab CI 在 Kubernetes 服務上執行 GitLab Runner 並執行 Pipeline