iThome Cloud Summit 2022 - Lab: GitOps with GtLab and K8s

2022 年再次受邀在 iThome Cloud Summit 分享一堂 90 分鐘的 Hands-on Lab,於是再次端出了仍算是熱門的 GitOps 為題,分享如何在 GitLab + K8s 的環境中實作 GitOps。

這是一篇留在草稿區很久的文章草稿,本來應該在 2022 年 7 月把它寫出來,但一拖稿,就無限延後了,趁著春節休假期間,終於有時間將它整理為一篇「堪用」的文章。

前言

感謝 iThome 的邀請,2022 年能有機會再次擔任 iThome Cloud Summit 的講師,負責一場分享一場 Hands-on Lab。

由於 2021 ~ 2022 年,個人並沒有玩太多新鮮的雲端新工具,因此最後還是決定以 GitOps 為題來準備這場 Hands-on Lab。但畢竟我在 2020 年就已經分享過一次 GitOps,總不能直接拿老東西出來分享,因此這次特別針對內容做了調整,試著將 Kubernetes(K8s)放進 Lab 的內容中。

在 2020 年時分享的 Hands-on Lab 是以 GitOps with GitLab 為題,主要是以 GitLab 的 CI/CD Pipeline,搭配 GitLab 當時仍在 beta 階段的 Terraform 相關功能,讓學員運用 GitLab Runner 透過 CI/CD Pipeline 可以彼此 Tirgger 的方式來實現 GitOps 的概念;當時還跟 AWS 合作索取了一些試用額度,讓 Lab 學員可以用 GitOps 的方式,在 AWS 建立 Ec2 並部署 Application。

為了有別於 2020 年的 Hands-on Lab,在 2022 則規劃了一個與 K8s 有關的 GitOps 實作案例,雖然仍是以 GitLab 的 CI/CD Pipeline 為主,但這次的案例就真的是對 K8s 進行操作,讓學員可以實作 GitOps 的概念,在 K8s 上運行 Pods,完成 Application 部署。

回到 Lab 的內容,當天現場使用的簡報內容,主要是介紹一些 GitOps 的基本概念,如果是曾聽過我在 2020 年演講的朋友,對於其中的內容應該不陌生,畢竟 GitOps 的基本概念在這兩年間並沒有太大的變化,最讓大家糾結的還是如何將 GitOps 落地在自家的環境中,畢竟各家公司內的 infrastructure、組織結構、工作流程⋯⋯等差異,導致要實踐 GitOps 時,會有各自不同的難關需要克服。

我將 Hands-on Lab 的實作步驟微調之後公開在本文中,對這個主題有興趣的朋友,歡迎嘗試看看,如何在 gitlab.com 上實作一個簡單的 GitOps。如果有任何想法,也歡迎回饋給我,謝謝

Lab 事前動作

  1. gitlab.com 的帳號。
  2. 為你的 GitLab User 帳號產生一組擁有 read_registry 權限的 Personal Access Tokens。(Token 取得後,請保管好,在 Lab 事前動作 6 會用到。)
  3. 準備一座你擁有足夠權限的 Kubernetes (K8s) Cluster。
  4. 續上,取得 K8s nodes 的正確 ip,你可以透過 kubectl ,利用 CLI 指令 kubectl get nodes -o wide 得到它。(ip 取得之後,請保存好,後續會使用到。)
  5. 在 gitlab.com 建立一個 Lab 專用的 Group: demo,並在其中透過 GitLab K8s Agent 讓 GitLab 與 K8s 整合。
  6. 續上,在 GitLab K8s Agent 的 Project 中,要開放 GitLab CI/CD Tunnel 給 Gtoup: demo
  7. 續上,在 K8s 建立一個 secret,以便讓 Lab 環境可以有權限自 gitlab.com 上的 container registry 取得 images,指令會類似如下,記得 <your gitlab username><your personal access token> 要改成正確的值。
    kubectl create secret docker-registry onlylab --docker-server=registry.gitlab.com --docker-username=<your gitlab username> --docker-password=<your personal access token>
    
  8. 為 Group: demo 設置一個 Executordocker 的 GitLab Runner,讓後續在此 Group 內的 Project 都能使用此 Runner;或者你也可以使用 gitlab.com 提供的 Shared Runner,但要注意這是有 Free limit 限制的。

如果你不知道該如何處理第 3 ~ 6 項事前動作,可以參閱筆者為《和艦長一起30天玩轉GitLab》(第二版)所撰寫的操作步驟——「GitLab Auto DevOps 環境建置步驟」。在這份操作步驟中的前四項,即是與此相關的教學。

Lab 實作步驟

建立 Application Project

按著「Lab 事前動作」的操作,你應該已經擁有一個類似 demo 名稱的 Group,而在此 Group 內則會有一個名為 k8s 的 Project。

在該 Group: demo 中,我們要再新增一個空白的 Project: application。在 Project 內要再新增三個檔案,請將檔案送進 main branch。

第一個新增 index.html,當作是我們的 application code,內容可以自行輸入,建議輸入最基本的 html 內容,以下是範例。

<!DOCTYPE html>
<title>iThome - Lab</title>
<body>
  User - lab-01 app v1.0
</body>
</html>

第二個新增 Dockerfile,因為我們的 appliation code 只是一個 html,因此我們只需要將它放進 Nginx 為 base 的 container 中,就算是完成了這個 Application。

    FROM nginx:1.21
    
    # 用我們的 index.html 覆蓋 nginx default 的 index.html
    COPY index.html /usr/share/nginx/html/index.html

第三個新增 .gitlab-ci.yml,用來為這個 Project 加上基本的 CI Pipeline,透過 Pipeline 將我們的 application 打包成 container image。

docker-build:
  # Use the official docker image.
  image: docker:latest
  stage: build
  services:
    - docker:dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  # Default branch leaves tag empty (= latest tag)
  # All other branches are tagged with the escaped branch name (commit ref slug)
  script:
    - |
      if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
        tag=""
        echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
      else
        tag=":$CI_COMMIT_REF_SLUG"
        echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
      fi      
    - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
    - docker push "$CI_REGISTRY_IMAGE${tag}"
  # Run this job in a branch where a Dockerfile exists
  rules:
    - if: $CI_COMMIT_BRANCH
      exists:
        - Dockerfile

接著觀察 CI Pipeline 的執行狀況,如果 Pipeline 是 passed,那在 Packages & Registries > Container Registry,應該可以看到它幫我們建立好 Application 的 Container image。(類似下圖)

這裡有一個資訊是我們需要先記錄下來的,就是你的 container image 名稱,以及完整的 registry 路徑,例如 registry.gitlab.com/demo/application:latest

建立 Manifest project

回到 Group 主頁面,接著要建立 manifest project,這個 Project 會用來幫我們部署 Application 到 K8s,並且讓我可以根據需要控制所需的 pods 數量。

一樣請在 Group 內,再建立另一個空白的 Project,這次 project 命名為 manifest

在這個 Project 內,我們要新增以下檔案。

第一個是 kube/deployment.yaml

檔案內容如下,請將以下幾個地方取代成你自己的正確名稱

  • <your app name>,改成你專用的名稱,例如 lab-01app
  • <your application container image>,改成正確的 Container image 路徑,例如:registry.gitlab.com/demo/application:latest
apiVersion: apps/v1
kind: Deployment
metadata:
  name: <your app name>
  namespace: default
  labels:
    app: <your app name>
spec:
  replicas: 1
  selector:
    matchLabels:
      app: <your app name>
  template:
    metadata:
      labels:
        app: <your app name>
    spec:
      containers:
        - name: <your app name>
          image: <your application container image>
          ports:
            - containerPort: 80
              name: http
      imagePullSecrets:
        - name: onlylab

第二個檔案 kube/service.yaml,內容如下,一樣有幾個地方請取代成你正確的名稱。

  • <your app name>,請改成與上面相同的專用名稱,例如 lab-01app
  • <your node port>,因為 Lab 環境是將 K8s 上的 Node port 直接對外打通,因此你可以自己設定一個你想要的 Port,例如 30301
apiVersion: v1
kind: Service
metadata:
  name: <your app name>
  namespace: default
  labels:
    app: <your app name>
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      nodePort: <your node port>
  selector:
    app: <your app name>

第三個檔案 .gitlab-ci.yml,內容如下,老樣子需要取代成你正確的名稱。

  • <your app name>,請改成與上面相同的專用名稱,例如 lab-01app
deploy:
  image:
    name: bitnami/kubectl:latest
    entrypoint: [""]
  script:
  - kubectl config get-contexts
  - kubectl config use-context ithome-lab/k8s-agent:lab-agent726
  - kubectl apply -f kube/
  - kubectl rollout restart deploy <your app name>
  - kubectl get pods | grep "<your app name>"
  - kubectl get svc | grep "<your app name>"

將上述檔案 Commitmain branch 即可。

同樣的進入 Project 的 CI/CD > Pipelines 看看 Pipeline 是否正常運作。

理論上,你應該會看見 Pipeline 成功執行了上述的各個 kubectl 指令,順利啟用了一個 Pod,將你的 Application container image 部署在 K8s 上,。

接著打開瀏覽器,連上 http://<your node ip>:<your node port>,即可看到你的 Application 的內容。

為 Application project 加上 deploy 動作

接著再次修改 Application project 的 .gitlab-ci.yml,我們要加上一個新的 Job,讓 Application project 每次有更動時,都會自動去觸發 Manifest project 的 deploy 動作。

但首先,我們需要取得 Manifest project 的 project name;你可以回到 Manifest project 的入口頁面,那邊即可查到 Project ID。(例如下圖右側的 Project ID: 9

新增的內容如下,其中 <your Project_ID> 請修改成正確的 Manifest project 之 project id。

deploy:production:
  image: curlimages/curl:latest
  stage: deploy
  needs:
    - job: docker-build
  variables:
    TARGET_ENVIRONMENT: production
    MANIFEST_PROJECT_ID: <your Project_ID>
  script:
    - 'curl --fail --request POST --form token=${CI_JOB_TOKEN} --form ref=main --form "variables[IMAGE_REF]=${IMAGE_REF}" --form "variables[TARGET_ENVIRONMENT]=${TARGET_ENVIRONMENT}" "${CI_API_V4_URL}/projects/${MANIFEST_PROJECT_ID}/trigger/pipeline"'
  environment:
    name: $TARGET_ENVIRONMENT

修改完畢,送出 Commit,一樣前往 CI/CD Pipeline 查看 Pipeline 是否有正常執行。

正常來說,會如下圖一樣,Application project 的 Pipeline 會觸發 manifest project 的 deploy 動作。

嘗試修改 Application project,自動部署新版本的 Application

接著,你可以自行嘗試,修改你的 Application projec 的 index.html,加上更多文字,看看是否會自動部署至 K8s。

嘗試修改 Manifest project,自動部署更多的 Pods

你也可以嘗試修改你的 Manifest project 中的 kube/deployment.yaml,將 replicas: 改成 3,然後看看是否會自動部署至 K8s,產生更多 Pods。

spec:
  # replicas: 1
  replicas: 3

結語

感謝各位的閱讀,上面就是一個非常簡單的 GitOps 實作,我們到底做了哪些事情呢?

  1. GitLab 作為我們的 VCS 儲存 Application code。
  2. GitLab 作為我們的 VCS 儲存 Manifest code 或 Infra Code。
  3. GitLab K8s Agent 幫我們搞定讓 GitLab 及 GitLab CI 如何與 K8s 溝通與整合。
  4. GitLab CI Pipeline 幫我們將 Application code 建置為 Container image,並存放在 GitLab 內建的 Container registry。
  5. GitLab CD Pipeline 幫我們執行 Manifest / Infra 的異動,以及執行將 Container image 部署至 K8s Cluster 的動作)。
  6. 透過 GitLab 的 CI/CD Tunnel 功能分享 KUBECONFIG 讓 CD Pipeline 可以順利對 K8s 實施操作。
  7. 將 Application project 的 CI Pipeline 與 Manifest project 的 CD Pipeline 透過 Downstream 的方式串連。

透過這些 GitLab 的基本功能,我們達成了:

  1. Application code 有自己的 Application project,開發團隊繼續按著往常的軟體開發流程,像是 GitLab flow、GitHub flow,也能繼續落實必要的 CI 持續整合 + CI Pipeline,保護最低限度的程式碼品質。
  2. Application code 通過 CI Pipeline 後,會自動建置為 Container image。
  3. Manifest / Infra code 有自己的 Manifest project,維運團隊也能如同軟體開發流程,讓 IaC 也走 GitLab flow、GitHub flow,甚至也可以特別為 IaC 加上合適的 CI 持續整合 + CI Pipeline,在真正實施 Manifest / Infra 的異動之前,讓 IaC 程式碼也能通過某種最低限度的掃描與檢查。
  4. Manifest / Infra 的異動,會自動觸發 CD Pipeline,讓 Application 能自動部署至 K8s。
  5. 由於 Application code 的異動,也會觸發 Manifest project 的 CD Pipeline。因此只要有任何 Git Commit 發生,都會自動完成將最新版 Application 部署上線的動作。

以上就是這次在 iThome Cloud Summit 2022 設計的這個 Lab,基本上大家可以發現,我並沒有打算上讓學員去玩像是 Argo CD 這一類又新又潮的 GitOps 工具,也沒有讓學員有太多對 K8s 的直接操作。就如簡報內容,重點是放在提供一個 Lab 環境,用最低限度的技術與工具去展示 GitOps 的基本概念。

最後要做一個重要的聲明,請各位讀者千萬不要將這個 Lab 環境與實作方式,直接拿去使用在 Production 環境!因為 Lab 環境,只是用來展示 GitOps 的基本概念,但當你真的要運用 GitOps 在你家的環境時,其實裡面還有好多技術、工具、資安、流程的細節需要處理。

建議大家一定要去詳讀 CNCF 社群中,各種關於 GitOps 的文章與建議。當然也別忘了好好的評估到底 GitOps 是否適合運用在你的團隊與產品上,至少就筆者目前的觀察,GitOps 雖然有帶來一些好處,但並非任何團隊與產品都適合它。

如果你對於 GitOps 也有任何自己的獨到見解,歡迎與我或社群分享喔!

參考資料

轉貼本文時禁止修改,禁止商業使用,並且必須註明來自「艦長,你有事嗎?」原創作者 Cheng Wei Chen,及附上原文連結。

工商服務

更多文章