實戰Kubernetes Gitlab CI

語言: CN / TW / HK

一 背景

在目前微服務大行其道的背景下,Gitlab CI整合kubernetes已經是不可或缺的基本操作,我們前幾節系統的實戰了前後端專案以及物理/K8s混合環境部署,這節課我們來學習Gitlab CI如何將應用釋出進K8s,我們都知道在之前的將gitlab-runner部署在伺服器上面是存在一定的風險,如果執行pipeline的伺服器宕機,釋出任務就沒辦法繼續了,更可怕的時候如果common-runner傳送故障,多個釋出任務就都有問題,在微服務架構中,不可變的基礎設施,容器的自包含環境使得我們釋出變得更加簡單快捷,不用在考慮擔心runner的環境如何根據不同的專案區分,且動態的Job觸發,我們會隨時拉起一個Pod來執行我們的Job,執行完成後又進行銷燬,這樣不僅能實現動態執行節省資源,而且可以不用考慮多專案多工併發構建的問題,這節課就讓我們來盡情享受K8s+Gitlab CI為我們帶來暢快淋漓的釋出體驗。

二 架構解析

本文以構建一個Java軟體專案並將其部署到阿里雲容器服務Kubernetes叢集中為例,說明如何使用GitLab CI在阿里雲Kubernetes服務上執行GitLab Runner、配置Kubernetes型別的executor,並執行Pipeline。

2.1 Gitlab CI流程圖

2.2 流程詳解

如上圖為一個簡單的Gitlab CI部署進K8s流程圖,同之前我們講到的CI整合一致,只需要專案中存在.gitlab-ci.yml檔案即可,與之前的差異為在整合kubernetes的時候,我們講我們的gitlab-runner執行在我們K8s內,其為一個POD形式執行,其控制著後續的pipeline中各stage的執行,我們可以看到,當一個pipeline有多個stage,每個stage都是有一個單獨的Pod去執行,這個Pod使用的映象在我們CI的.gitlab-ci.yml 的每個stage的image中定義,如所示為一個部署Java專案的流程

  • 開發者或專案維護者merge request到特定分支,改分支存在CI檔案,開始進行CI
  • CI任務由已經註冊在gitlab-server執行在K8s叢集內的giitlab-runner進行下發
  • 第一個為package,對java專案利用maven映象進行打包,生成war包製品到快取目錄中;
  • 利用docker映象對根據專案中的Dockerfile對快取中的製品進行映象構建,構建完成後登入映象倉庫進行映象推送;
  • 在此我們利用將部署檔案託管在專案內,也體現了gitops的思想,將之前構建推送成功的映象地址資訊在deployment.yaml檔案進行修改,之後apply進k8s中,java專案構建的映象就執行在K8s叢集內了,完成整個的釋出。

在流程中有一些注意事項:

  • 我們可以在stage中新增自己業務需求的內容,例如單元測試,程式碼掃描等。
  • 在部署檔案中,我們可以將整個專案製作成helm的chart,替換其中的映象,利用helm來進行整個應用的部署。
  • 應用在部署中單個stage利用的是不同的image,在各個stage中傳遞已經生成的製品例如war/jar包,需要使用到外部儲存來快取製品。

三 優點

通過上面的Gitlab CI流程我們能夠看到將gitlab runner執行在K8s叢集中,每個Job啟動單獨的POD執行操作,此種方式完全利用起了K8s的一些優點

  • 高可用:當某個節點出現故障時,Kubernetes 會自動建立一個新的 GitLab-Runner 容器,並掛載同樣的 Runner 配置,使服務達到高可用。
  • 彈性伸縮:觸發式任務,合理使用資源,每次執行指令碼任務時,Gitlab-Runner 會自動建立一個或多個新的臨時 Runner來執行Job。
  • 資源最大化利用:動態建立Pod執行Job,資源自動釋放,而且 Kubernetes 會根據每個節點資源的使用情況,動態分配臨時 Runner 到空閒的節點上建立,降低出現因某節點資源利用率高,還排隊等待在該節點的情況。
  • 擴充套件性好:當 Kubernetes 叢集的資源嚴重不足而導致臨時 Runner 排隊等待時,可以很容易的新增一個 Kubernetes Node 到叢集中,從而實現橫向擴充套件。

如果您的業務目前執行環境為K8s,那麼Gitlab CI完全契合您的業務場景,您只需要自定義gitlab-ci.yml中的各個自己需求的stage即可,配合gitops將配置也託管在專案內,跟隨專案一塊維護管理,實現端到端的CI工作流,使得運維工作也可通過git追溯,提高工作效能,敏捷開發上線部署。

四 實戰

我們在上面瞭解來Gitlab CI與Kubernetes的整合及其優點,下面就讓我們通過實戰來更具體的瞭解其流程。

4.1 環境準備

  • 需要有Gitlab 伺服器,可以是部署在物理伺服器上,當然也可以部署在K8s叢集內部。

  • 我需要準備好K8s叢集,可以為公有云的容器編排引擎,例如阿里的ACK,騰訊的TKE,華為的CCE等都適用這些方式。

  • 由於gitlab-runner安裝較為複雜,我們在示例中使用helm來進行安裝,helm版本為v2.14.3,如果有能力可以自己編寫資源清單部署。

4.1.1 記錄註冊資訊

登入Gitlab 伺服器記錄gitlab 的 url 和註冊令牌,在我們部署進K8s的gitlab-runnner的配置中需要填寫該資訊,執行在K8s中的Pod就利用此此資訊在gitlab伺服器進行註冊。

4.1.2 獲取gitlab-runner

由於單獨部署gitlab-runner進K8s中,自己去寫資源清單檔案難度較大,而且容易出錯,我們在此利用官方的chart映象通過helm來進行部署,僅修改其中我們關係的欄位即可,首先在登入K8s叢集進行gitlab-runner的helm repo的新增,之後將chart下載到本地,解壓檔案並修改其中的values.yml檔案。

  • 新增repo獲取charts

shelll [[email protected] common-service]# helm repo add gitlab https://charts.gitlab.io "gitlab" has been added to your repositories [[email protected] common-service]# helm repo update Hang tight while we grab the latest from your chart repositories... ...Skip local chart repository ...Successfully got an update from the "aliyun" chart repository ...Successfully got an update from the "apphub" chart repository ...Successfully got an update from the "gitlab" chart repository ...Successfully got an update from the "stable" chart repository Update Complete. [[email protected] common-service]# helm search gitlab-runner NAME CHART VERSION APP VERSION DESCRIPTION gitlab/gitlab-runner 0.14.0 12.8.0 GitLab Runner

我看已經看到了 gitlab-runner的chart,其是 helm 中描述相關的一組 Kubernetes 資源的檔案集合,裡面包含了一個 value.yaml 配置檔案和一系列模板(deployment.yaml、svc.yaml 等)。當然如果我們能力夠,可以自己去編寫這些資源清單檔案。

在此有想去的夥伴可以檢視之前K8s學習筆記,希望能對讀者學習K8s有幫助:awesome-kubernetes-notes

  • 建立角色並繫結許可權

gitlab-runner在執行的時候需要訪問我們K8s的api,我們在此為期建立ServiceAccount併為其進行RBAC授權,首先建立一個gitlab-runners的名稱空間,並建立對用的Role,將ServiceAccount於Role進行繫結。

```shell [[email protected] gitlab-runner]# cat > rbac-runner-config.yaml <<EOF apiVersion: v1 kind: ServiceAccount # 在gitlab-runners名稱空間下建立名為gitlab的serviceaccount metadata: name: gitlab namespace: gitlab-runners


kind: Role # 建立gitlab角色, 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: [""] - apiGroups: ["extensions"] resources: ["deployments"] verbs: [""]


kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: gitlab # 將sa與角色進行繫結 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

EOF

建立資源清單

[[email protected] gitlab-runner]# kubectl create -f rbac-runner-config.yaml serviceaccount/gitlab created role.rbac.authorization.k8s.io/gitlab created rolebinding.rbac.authorization.k8s.io/gitlab created ```

  • 獲取charts檔案,並進行配置

shell [[email protected] gitlab-runner]# helm fetch gitlab/gitlab-runner [[email protected] gitlab-runner]# tar xf gitlab-runner-0.14.0.tgz [[email protected] gitlab-runner]# vim gitlab-runner/values.yaml

helm為我們提供了一個配置檔案可以在安裝 runner 的時候為其註冊一個預設的 runner。我們可以去 gitlab-runner 的專案原始碼 中獲取到 values.yaml 這個配置檔案。

4.1.3 繫結docker.sock

由於在gitlab-runner中需要執行docker build 命令,需要docker服務端,我們可以來繫結K8s node節點的docker.sock來實現,編輯chart下面的template目錄中的configmap.yaml,在大約42行修改

```yaml

add volume and bind docker.sock

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 ```

4.1.4 配置快取

在我們的流程詳解中能夠看到生成的war/jar包製品需要儲存在一個地方,這個地方就需要我們掛載一塊外接的儲存裝置,在k8s中我們需要為其提供PVC,如果你有NFS儲存或者分散式ceph製成的儲存類都可以。

再此,我們機器中使用的為storageclass,演示利用儲存類來宣告PVC,在POD中進行掛載

  • 建立pvc宣告檔案gitlab-runner-pvc.yaml

yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: gitlab-runner-pvc # 建立名稱為gitlab-runner-pvc的pvc namespace: gitlab-runners spec: accessModes: - ReadWriteMany # 訪問模式 storageClassName: rbd # 儲存類名稱 resources: requests: storage: 2Gi # 儲存大小

  • 建立pvc

shell [[email protected] gitlab-runner]# kubectl apply -f gitlab-runner-pvc.yaml persistentvolumeclaim/gitlab-runner-pvc created [[email protected] gitlab-runner]# kubectl get pvc -n gitlab-runners NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE gitlab-runner-pvc Bound pvc-a620cd43-f396-4626-8599-c973c19cddd3 2Gi RWO rbd 4s

檢視已經在gitlab-runners名稱空間下已經建立好了PVC。

  • 配置values

在上面我們配置繫結宿主機docker程序的時候也已經將pvc配置進去了,我們還需要在charts的values.yaml下面新新增配置 volume 的資訊,並在 values.yaml 配置相應的變數

```

新增在values.yaml 的最後即可

maven: cache: pvcName: gitlab-runner-pvc mountPath: /home/cache/maven ```

4.1.5 安裝gitlab-runner

配置檔案比較長,可以根據需要自己去配置,下面就貼下本文中需要配置的地方。

```shell [[email protected] gitlab-runner]# egrep "^$|^#|^[[:space:]]+#" -v values.yaml imagePullPolicy: IfNotPresent gitlabUrl: http://43.xx.xx.xx/ # gitlab url runnerRegistrationToken: "nnxxxxxxxxxxxxxxxS" # gitlab token unregisterRunners: true terminationGracePeriodSeconds: 3600 concurrent: 10 checkInterval: 30 rbac: create: true clusterWideAccess: false serviceAccountName: gitlab # rbac的sa metrics: enabled: true runners: image: ubuntu:16.04 # 使用的映象 tags: "gitlab-runner" privileged: true imagePullPolicy: "if-not-present" # 如果執行具體job的runner不存在則拉取 pollTimeout: 180 outputLimit: 4096 cache: {} builds: {} services: {} helpers: {} serviceAccountName: gitlab # serviceaccount nodeSelector: {}

securityContext: fsGroup: 65533 runAsUser: 100 resources: {} affinity: {} nodeSelector: {} tolerations: [] hostAliases: [] podAnnotations: {} podLabels: {} maven: # 配置maven快取資訊 cache: pvcName: gitlab-runner-pvc # pvc資訊 mountPath: /home/cache/maven # 掛載點 ```

  • 安裝gitlab-runner

在配置完成values.yaml相關的欄位後,我們利用helm來其 進行安裝,後期如果我們有變動,直接修改value.yml ,然後進行更新即可helm upgrade gitlab-rujner gitlab-runner/

```yaml [[email protected] gitlab-runner]# helm install --name gitlab-runner -f gitlab-runner/values.yaml --namespace gitlab-runners gitlab-runner/ NAME: gitlab-runner LAST DEPLOYED: Sun Feb 23 22:45:37 2020 NAMESPACE: gitlab-runners STATUS: DEPLOYED

RESOURCES: ==> v1/ConfigMap NAME DATA AGE gitlab-runner-gitlab-runner 5 1s

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

==> v1/Pod(related) NAME READY STATUS RESTARTS AGE gitlab-runner-gitlab-runner-6745dd4cd6-r9z28 0/1 Pending 0 0s

==> v1/Secret NAME TYPE DATA AGE gitlab-runner-gitlab-runner Opaque 2 1s

NOTES:

Your GitLab Runner should now be registered against the GitLab instance reachable at: "http://43.254.54.93:88/" ```

檢視已經執行ok的相關pod資源,gitlab-runner的配置是通過configmap掛載進去,如果我們有新的配置可以修改,並刪除之前的gitlab-runner的pod,其deployment控制器會進行建立Pod掛載新的配置檔案。

  • 檢視pod執行狀態

shell [[email protected] ~]# kubectl get po -n gitlab-runners NAME READY STATUS RESTARTS AGE gitlab-runner-gitlab-runner-6745dd4cd6-r9z28 1/1 Running 0 21h

  • 檢視configmap及pod中掛載的配置

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

yaml [[email protected] ~]# kubectl get cm -n gitlab-runners gitlab-runner-gitlab-runner NAME DATA AGE gitlab-runner-gitlab-runner 5 21h

  • 登入gitlab控制檯檢視目前gitlab-runner已經註冊上了。

  • 當pod執行起來後,檢視完整配置

```yaml [[email protected] gitlab-runner]# kubectl exec -it -n gitlab-runners gitlab-runner-gitlab-runner-6c7dfd859c-62dv5 -- cat /home/gitlab-runner/.gitlab-runner/config.toml listen_address = "[::]:9252" concurrent = 10 check_interval = 30 log_level = "info"

[session_server] session_timeout = 1800

[[runners]] name = "gitlab-runner-gitlab-runner-6c7dfd859c-62dv5" output_limit = 4096 request_concurrency = 1 url = "http://43.254.54.93:88/" # 註冊的url token = "eRskGn8Q-g4FZnZib63q" # gitlab註冊的token executor = "kubernetes" # 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" # 使用的映象為ubuntu namespace = "gitlab-runners" # 使用的namespace為gitlab-runners namespace_overwrite_allowed = "" privileged = true poll_timeout = 180 service_account = "gitlab" # serviceaccont 為gitlab service_account_overwrite_allowed = "" pod_annotations_overwrite_allowed = "" [runners.kubernetes.pod_security_context] [runners.kubernetes.volumes] [[runners.kubernetes.volumes.pvc]] name = "gitlab-runner-pvc" # 掛載的pvc名稱 mount_path = "/home/cache/maven" # 掛載在pod下的快取目錄 [[runners.kubernetes.volumes.host_path]] name = "docker" mount_path = "/var/run/docker.sock" # 繫結宿主機的docker.sock ```

至此我們已經將gitlab-runner在K8s叢集中執行起來,接下來讓我們來整合CI。

4.2 整合CI

4.2.1 定義檔案

由於使用的 executor 不同,所以. gitlab-ci.yml 和之前伺服器也有些不同,不如image,預設如果每個stage中沒有寫指定某個image則使用該image

```yaml image: docker:latest variables: DOCKER_DRIVER: overlay2 # k8s 掛載本地卷作為 maven 的快取 MAVEN_OPTS: "-Dmaven.repo.local=/home/cache/maven"

stages: - package # 原始碼打包階段 - docker_build # 映象構建和打包推送階段 - deploy_k8s # 應用部署階段

before_script: - export APP_TAG="${CI_COMMIT_TAG:-${CI_COMMIT_SHA::8}}" # 定義製作好的映象tag

maven-package: #image: maven:3.5-jdk-8-alpine image: registry.cn-beijing.aliyuncs.com/codepipeline/public-blueocean-codepipeline-slave-java:0.1-63b99a20 # maven映象進行java原始碼的build tags: - gitlab-runner # 指定使用gitlab-runner來追尋 stage: package script: - mvn clean package -Dmaven.test.skip=true artifacts: # 將生成的war包上傳到pvc掛載目錄中 paths: - target/*.war docker-build: tags: - gitlab-runner stage: build script: - echo "Building Dockerfile-based application..." - docker build -t ${REGISTRY}/${QHUB_NAMESPACE}/${APP_NAME}:${APP_TAG} . # 構建映象 - docker login --username=${DOCKER_USERNAME} ${REGISTRY} -p ${DOCKER_PASSWORD} # 登入映象倉 - docker push ${REGISTRY}/${QHUB_NAMESPACE}/${APP_NAME}:${APP_TAG} # push映象 only: - master k8s-deploy: image: bitnami/kubectl:latest # 使用kubectl映象來進行最終的部署 tags: - gitlab-runner stage: deploy script: - echo "deploy to k8s cluster..." - sed -i "[email protected]$(grep -E "^[[:space:]]+image:" deployment.yaml | awk '{print $2}' |head -1)@${REGISTRY}/${QHUB_NAMESPACE}/${APP_NAME}:${APP_TAG}@g" deployment.yaml # 替換deployment中的映象 - kubectl apply -f deployment.yaml # 應用資源清單檔案 only: - master ```

可以看到CI檔案中我們定義了三個stage:

  1. 利用maven映象進行打包,生成war包製品到快取目錄中。
  2. 利用docker映象對根據專案中的Dockerfile對快取中的製品進行映象構建,構建完成後登入映象倉庫進行映象推送。
  3. 利用kubectl映象先對原有deployment檔案進行映象替換,之後進行將資源清單更新至K8s叢集中。
  4. 在為映象打tag時,可以自行設定,也可以利用commit來進行與git版本對照。

4.2.2 注意事項

  • 由於在映象構建的stage中,需要使用docker命令來進行相關操作,需要繫結本地 Docker 守護程序。
  • maven 倉庫的快取,在又需要生產製品並儲存的構建中,需要外部儲存來存放製品。
  • 在stage中引用的映象最好能事先pull到各個node節點上,不然在第一次執行CI,如果網路有波動或頻寬小,可能會因為映象下載超時導致CI失敗。

4.2.3 配置變數

由於在CI檔案中又一些敏感資訊,例如映象倉庫的登入資訊以及後期可以更改的映象名稱等,利用環境注入的方式,使得CI檔案脫敏而且更具靈活性和適用性。

Project --> Settings --> CI/CD --> Variables, 新增GitLab Runner可用的環境變數,本示例新增變數如下:

  • APP_NAME:製作完成後映象的名稱

  • DOCKER_USERNAME: 映象倉庫使用者名稱

  • DOCKER_PASSWORD 映象倉庫密碼
  • QHUB_NAMESPACE:映象倉庫的名稱空間,在該專案中我們使用的騰訊的映象倉庫,當然可以自建harbor或使用其他公有云提供的映象倉庫或者dockerhub等。
  • REGISTRY:映象倉庫的地址

4.3 執行測試

在我們配置好了CI檔案好,讓我們來執行測試,提交程式碼,或者手動執行。

4.3.1 執行pipeline

在專案CI/CD->Pipelines右上角有RunPipeline

在圖中我們可以看到在其中是可以並行執行多個pipeline,互不影響。

選擇對應的分支或者傳入變數手動執行pipeline

4.3.2 檢視執行POD

登入K8s叢集,檢視此時執行的POD

4.3.3 檢視Job

  • 打包映象

  • 映象構建

  • 部署進K8s

4.3.4 檢視構建情況

  • 檢視pipeline

  • 檢視構建應用

至此我們就完成了示例Gitlab CI。

五 注意事項

  • 在實戰中的資源清單檔案只寫來deployment,讀者可以根據自己的專案來,可以將service/configmap/hpa等資源清單檔案也放在專案中託管,當然也可以製作成helm應用,每次釋出利用helm來更新應用
  • 在Gitlab CI中注意將敏感資訊例如映象倉庫的登入方式等進行脫敏,將有變化的欄位設定為變數,為此進行傳入是的CI更擴充套件性及靈活性。
  • 在此節中部分內容需要讀者具備一定的K8s經驗,例如其中的helm及相關的資源清單檔案,如果前期瞭解後期業務微服務話後看本章節內容更為合適。
  • 注意在寫rbac與pvc的資源清單的名稱空間為你想部署業務的名稱空間,即例如你想把資源清單部署在common名稱空間下,你就需要把pvc和rbac建立在common名稱空間下,將gitlab-runner註冊在common名稱空間下。

六 應用場景

  • 業務容器微服務化,或半微服務化
  • 需要運維人員具有較強的K8s運維能力
  • 追求極致持續整合持續交付體驗gitops追崇者
  • 有明確的流程規範,上線部署規範及明確可循的標準

七 反思

通過本次敏捷無敵之Gitlab CI實戰,再次筆者感謝大家能夠了解Gitlab的此特性,如果您的公司在使用Gitlab並且有使用其他的CI工具, 困於多套系統維護的複雜性,不妨嘗試下Gitlab CI簡單整合,Gitops的端到端運維工作,在雲原生時代,將工具或中介軟體下沉到基礎設施中,使用者只需要專注於自身業務的開發,敏捷高效、文化中協作、快速試錯、快速反饋、持續改進、不斷迭代,以Git為來源打破研發與運維壁壘隔閡,實現產品更快、更頻繁、更穩定的交付。

其他