[Kubernetes]整合Gitlab CI與ArgoCD實現kubernetes自動部署

CICD Pipelines using Gitlab CI & ArgoCD

Morpheus Huang
21 min readJul 3, 2022

最近剛好系統平台開發到一個段落,要準備demo環境,因為需要同時部署到多個不同的Kubernetes叢集,又不想部署完後,有修改又要重新手動Sync一次,就乾脆把一直想搞但沒時間搞的CICD環境弄起來好了。

本篇文章主要紀錄一下,自己開發測試的環境中,整合Gitlab CI與ArgoCD來實現Kubernetes自動部署的過程。

0x00 Prerequisite

  • 要有一座kubernetes
  • docker-compose
  • 對kustomize有基本概念

0x01 Overview

本文整體的環境架構如下所示:

CICD流程

  1. 使用者push程式碼或者merge request。
  2. Gitlab觸發Gitlab-CI執行pipeline,進行build image,然後將image push到docker hub上。
  3. 修改App Manifest中的image tag,並push回Gitlab上。
  4. ArgoCD偵測到Application Manifests有變動(即image tag更新)時,自動apply新的部署檔到Kubernetes中。

下面將針對上述的流程逐一說明相關的安裝與設定。

PS:上述流程,主要根據目前個人環境開發建立,實際上pipeline中可能還需包含像是測試、code scan、security scan等過程。

0x02 Gitlab 安裝

Gitlab安裝並不難,參考官網安裝方式即可安裝起來,這裡使用docker-compose來安裝Gitlab,主要參考官方文件Install GitLab using Docker Compose做修改。

version: '3.6'
services:
web:
image: 'gitlab/gitlab-ce:latest'
restart: always
hostname: '[your_external_domain]'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url '[your_external_domain:30080]'
gitlab_rails['gitlab_shell_ssh_port'] = 30022
ports:
- '30080:30080'
- '30022:22'
volumes:
- '$GITLAB_HOME/config:/etc/gitlab'
- '$GITLAB_HOME/logs:/var/log/gitlab'
- '$GITLAB_HOME/data:/var/opt/gitlab'
shm_size: '256m'

如果環境是MacBook Pro M1的話,可以使用這個版本的image:yrzr/gitlab-ce-arm64v8

設定檔中幾個變數設定說明如下:

  • $GITLAB_HOME環境變數,請設定有權限讓docker mount的目錄位置,或參考官方說明:Set up the volumes location
  • your_external_domain請自行修改成本身環境可用的domain,或可使用nip.io來做為測試環境使用

以我的測試環境為例,docker-compose如下:

version: '3.6'
services:
web:
image: 'gitlab/gitlab-ce:latest'
restart: always
hostname: 'gitlab-192-168-1-102.nip.io'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://gitlab-192-168-1-102.nip.io:30080'
gitlab_rails['gitlab_shell_ssh_port'] = 30022
ports:
- '30080:30080'
- '30443:30443'
- '30022:22'
volumes:
- '$GITLAB_HOME/config:/etc/gitlab'
- '$GITLAB_HOME/logs:/var/log/gitlab'
- '$GITLAB_HOME/data:/var/opt/gitlab'
shm_size: '256m'

因為測試關係,所以使用http比較單純一點,如果要使用https的話只要將external_url中的protocol改成https,並將port修改成30443即可。另外,如果使用https又是自簽憑證的話,會有一些延伸的問題要處理就是了。

docker-compose修改完成後,可以透過以下指令執行。

~$ docker-compose up -d

順利的話應該可以看到登入頁面,預設登入帳號為root,而初始密碼則是儲存在$GITLAB_HOME/config/initial_root_password中,該檔案會保留24小時,登入後應盡快修改密碼與新增額外使用者。

0x03 ArgoCD 安裝

ArgoCD安裝基本上也不難,可以直接照官方文件說明安裝即可

~$ kubectl create namespace argocd ~$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

因為argocd是跑在kubernetes上,請自行選擇服務暴露方式,個人有點懶所以直接以nodePort方式將argocd service暴露出來。

argocd ui登入帳號為admin,密碼則是儲存在secret中,可透過以下指令取得

~$ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo

0x04 Demo範例說明

為了測試整個pipeline流程,簡單用golang寫了一個helloworld,然後測試了在不同的應用部署方式下,該如何利用現有的工具來完成image tag的修改,有興趣的可以參考下面的git repository。

https://github.com/MorpheusPH/gitops-demo.git

git repository中有兩個目錄,

  • helloworld: golang簡單的測試用http server
  • argo-app-examples: 用來部署helloworld到kubernetes上的部署檔。

argo-app-examples中,又分別有幾個目錄,主要是為了模擬不同部署方式下,如何針對image or image tag做修改。

在解釋目錄功能之前,先來看一下修改image/image tag這件事,如果有用過kustomize的應該知道,kustomize可以針對manifests做一些值的replace or patch,像是label、image等,更多功能可以參考這裡。當然,除了藉由kustomize來修改image tag外,也可以用像是sed、envsubst等方法,來達到此功能,個人認為主要還是根據應用程式的部署方式有關。

因此目錄中主要是設想幾種不同的應用部署方式,例如:

  • 單純的manifests,該如何修改image tag →對應kustomize-manifests
  • 如果image tag是在argocd applicationset cr中設定,並透過helm chart部署應用,該如何修改applicationset cr中的image tag → 對應kustomize-applicationset。
  • 是不是可不要修改applicationset cr,而是藉由類似helm + kustomize post-render方式直接對manifests中的image做修改 → 對應kustomize-helm-applicationset。
  • 是否有除了使用kustomize外,有其他比較簡單直接對applicationset中image tag做修改的方法 → 對應envsubst-applicationset

kustomize基本修改image tag方法

首先,來看kustomize修改image tag的方法,其實很簡單,只要新增一個kustomization.yaml檔案,裡面照個kustomize官方文件定義即可,例如:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helloworld.yaml
images:
- name: morpheusph/hello
newTag: latest

要修改image tag,則可以值接透過kustomize edit image指令來對Kustomization file做修改,指令如下:

~$ kustomize edit set image morpheusph/hello:dev

執行完後,就會發現Kustomization file中的newTag已經被改為dev了,之後只要透過 kustomize build . 就能得到更新後的manifests。

不過,這裡需注意的是Kustomization file中的images,正常情況下只會去修改kubernetes built-in kind的 container image值,像是如果想要修改的image tag是在CR中的話,這裡需要額外使用kustomize的image transformer功能。

kustomize不只可以修改image tag,也能替換image的name,有興趣可以自行玩玩

kustomize image transformer

可參考 argo-app-examples/kustomize-applicationset/kustom

首先,新增一個針對argocd applicationset crd的images transformer設定

#argocdKind.yaml
images:
- path: spec/generators/list/elements/values/image
kind: ApplicationSet

然後,在kustomization.yaml中,新增customized transformer configurations。

....configurations:
- argocdKind.yaml
images:
- name: morpheusph/hello
newTag: latest

如此一來,當執行kustomize build . 時,kustomize就能針對crd所指定的path做image值的修改。

不過這方式有個限制就是,cr中的image格式必須是完整包含repository:tag的格式,無法單純只是修改tag值,等於說如果使用此方式,而applicationset後面所包含的應用部署方式是透過helm的話,helm values.yaml內的參數也必須是image: repository:tag的格式,不能像是如下的設定方式。

#values.yaml
image:
repository: morpheusph/hello
tag: latest

透過kustomize images transformer會造成helm image value在設計上可能因此缺乏彈性,所以想了一下是否有可以不修改applicationset的方法,這時想到可以kustomize+helm template來做到。

kustomized-helm

kustomize +helm template方法,可以想成類似helm中提供的一個進階功能--post-render,只是在這裡不是透過helm來執行post-render,而是由argocd cmp來幫忙執行這動作,argocd cmp可參考這裡,而helm提供的post-render使用方式可以參考這裡

首先,需新增一個cmd設定到argo-cm中:

這裡會看到有兩個ARGOCD_ENV開頭的變數:

  • $ARGOCD_ENV_HELM_VALUES: helm values.yaml內的參數設定
  • $ARGOCD_ENV_HELM_ARGS: 一些helm安裝時可能會用到的args,這裡其實也可以透過 --set replicaCount=2的方式,將values.yaml內的參數設定在這邊,不過這樣會可能會造成這個參數值太長,個人不喜歡。

這裡個變數值對應到下面檔案中的HELM_VALUES與HELM_ARGS,ARGOCD從2.4版開始,plugin的ENV會自動加上ARGOCD_ENV_的前綴,因此在argocd cmp中,在安裝plugin時,其內使用需加上前綴才拿得到值。

這裡因為用的是自訂的kustomized-helm plugin,因此原有argocd helm傳遞參數方法已經不適用,因此加上另一個藉由HELM_VALUES來傳遞helm所需參數的方法,並藉由argocd cmp generate command先執行 helm template與環境變數結合,將完整的manifests輸出到all.yaml,再透過 kustomize build . 來針對image做最後的replace。

這方法麻煩了點,最直接方式就是在原helm chart中新增一個kustomization.yaml,然後要更新image也是直接在這目錄中做kustomize edit。

當然,也可以使用 helm --post-render 的方法來達到目的,只是這樣可能更搞工了,要先弄一個.sh執行檔,把generate command寫在script中,然後在把環境變數丟給script,可能還要考慮這個.sh執行檔要怎丟給argocd,還有執行路徑等問題,一整個下來不見得比較簡單就是。

envsubst

最後,講一下envsubst的使用方法,envsubst在某些情境替換整份檔案環境變數很好用,所以想到是否能使用envsubst來替換applicationset中的image tag。

首先需新增一個applicationset.template,然後把image tag設為環境變數,例如:

- name: image.tag                      
value: ${CI_COMMIT_SHORT_SHA}

$CI_COMMIT_SHORT_SHA是gitlab預先定義好的變數,拿這個值當作image tag,接著只要透過envsubst執行替換動作即可。

#假設 CI_COMMIT_SHORT_SHA = 2f9e84c8
envsubst < applicationset.template > applicationset.yaml
#result
- name: image.tag
value: 2f9e84c8

這方法好處是,不用修改helm values既有的image參數設定,也不用額外的plugin,原先argocd + helm怎麼用就怎麼用,不過也不是沒缺點,如果一個helm chart或applicationset中,同時有多個image設定,那這方法可能就不適用,但應該是可以透過envsubst + kustomize 來做到。

上面四種案例,都是以application管理applicationset的方式,主要是因為個人實際會需要部署到多個叢集,不過基本上application也是適用就是了。

0x05 Gitlab與ArgoCD相關設定

有興趣測試的可以clone上面提到的git repository,不過部署檔內使用的git repository與image repository是寫死的,因此需要做些小修改,再push到裝好的gitlab上,需修改的內容如下:

1.修改git repository
#將 http://gitlab-192-168-1-102.nip.io:30080/Morpheus取代成自行建置的#gitlab repository位置。
#Linux環境可以透過以下指令於argo-app-examples目錄下執行,直接取代全部檔案
~$ grep -rl "http://gitlab-192-168-1-102.nip.io:30080/Morpheus" . | xargs sed -i 's/http:\/\/gitlab-192-168-1-102.nip.io:30080\/Morpheus/[your_gitlab_external_url]/g'
2.修改image repository
#將morpheusph/hello改成自己使用的image repository
#Linux環境可以透過以下指令取代全部
~$ grep -rl "morpheusph/hello" . | xargs sed -i 's/morpheusph\/hello/[your_image_repository]/g'

PS:gitlab project設定public會比較好測試

1.設定Repository Deploy key

因為pipeline中會需要git push,因此透過deploy key設定會比較好處理一點,可以透過以下指令建立一組ssh public/private key-pair

~$ ssh-keygen -t rsa -C "[your_email_or_comment]"
~$ [enter]
~$ [enter]
~$ [enter]

public/private key-pair儲存在~/.ssh下,將id_rsa.pub的內容複製,貼到[project] →[settings] →[Repository] →[Deploy Keys]中,並新增。

似乎也能直接在[User Settings][SSH Keys]新增,不過我就沒試了。

2.Gitlab Runner安裝與變數設定

首先,需要安裝一個gitlab runner來執行gitlab ci所需的工作,runner設定根據scope不同作用範圍不同,這裡僅簡單設定一個project runner。在[project] →[settings] →[CICD] →[Runner]頁面可以看到類似如下的畫面

知道註冊Runner所需資訊後,先在kubernetes上起一個gitlab runner,這裡用官方提供的helm來安裝gitlab runner。

建立一個vlaues.yaml,內容如上,並替換掉[your_gitlabUrl]與[your_registration_token]兩個值,然後執行以下指令。

#建立namespace
~$ create ns kube-ops
#新增helm repo
~$ helm repo add gitlab https://charts.gitlab.io
#安裝gitlab-runner,更多參數設定可參考官方文件
~$ helm install gitops-demo -n kube-ops gitlab/gitlab-runner -f value.yaml

安裝成功的話,會看到如上圖下方藍色框框位置,應該會跑出一個正在運作的runner。

3.CICD變數設定

gitlab-runner安裝完後,接著需要設定一些後續會用到的變數,在[project] →[settings] →[CICD] →[Variables],變數設定如下圖:

  • CI_REGISTRY: docker registry,如:https://index.docker.io/v1/
  • CI_REGISTRY_IMAGE: [your_image_repository]
  • CI_REGISTRY_TOKEN: docker login所需的token,token可以在dockerhub上建立
  • CI_REGISTRY_USER: docker login所需的User Name
  • GIT_SSH_PRIVATE_KEY: 還記得第一步驟產生的public/private key-pair嗎,這裡需要將id_rsa的內容複製貼到這來。

4.設定gitlab ci pipeline

相關環境配置好了後,就可以來設定pipeline了,在[project][CI/CD][Editor]中,設定要執行的pipeline,整個pipeline會包含兩個步驟:

  1. build image + push image
  2. 修改image tag,並push回repository上

相關的設定如下

  • build image + push image主要藉由kaniko來協助完成,有關kaniko的說明請詳見官方網站
  • 更新manifest repository的image tag部分,其實就是上面0x04部分的四種更新image tag的方法,大部分是由 kustomize edit set image 來完成。
  • [your_gitlab_repository_url][your_account]記得要修改成自己gitlab對應的值。
  • 另外要額外提醒的是,如果source code與manifests是在同一個repository內,當要更新image tag時的commit message記得要加上[skip ci],避免重新觸發一次pipeline…..

編輯完pipeline後commit change,沒意外會直接執行一次pipeline,可以藉由此次執行看是否有問題,如果不想執行pipeline,一樣可以在commit message中新增[skip ci]來略過。

5. ArgoCD設定

如果gitlab repository是public的話,其實ArgoCD不用設定什麼,只要能連到gitlab就可以了,如果是private的話,需先登入ArgoCD頁面,[settings] →[Repositories] →[Connect Repo],建立gitlab repository連線,確認連線正常。

在argo-app-examples中,還有一個argo-application目錄一直沒有提到,這個目錄內的yaml檔,主要用來部署前面提到的四個app用的,在目錄中直接輸入以下指令即可建立argo application。

~$ kubectl apply -f .

如果正常的話應該會看到類似底下的畫面,總共會有7個argo application。

補充說明: argo-application中的argo application是可以從UI上建立的,建立出來的內容其實就是差不多yaml中的設定。這裡只是先建立好,省去從UI個別新增Application的步驟而已,有興趣可以自行從UI上建立試試。

另外,argo application刪除時候有cascade delete與non-cascade delete兩種,如果不是透過UI或者argocd cli操作的話,可能要注意一下,相關資訊可參考官方文件

0x06 測試

如果上面配置都成功,就可以嘗試修改helloworld內的README.md,或者main.go內的hello world字串,然後commit後觸發CICD pipeline,並觀察argo最終是否有將image更新到最新版本。

0x07 結論

本文大概紀錄了一下,串接Gitlab與ArgoCD的過程,以及不同部署方式,如何更新image tag的想法,雖然解決方法不見得完美,足以適用於各種不同的情境,但不同情境,自然有相對應的解決方式,遇到了再來想就好了:)。

0x08 Reference

--

--