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。具體可參考:

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

[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 鏡像

總結

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

未完待續...

源碼

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章