iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Kubernetes

初見 Kubernetes - 純網站後端開發踏入 K8s 世界的經驗分享系列 第 23 篇

Day 23 - 用 Kustomize 達到通用化、差異化、可維護性高的 YAML

  • 分享至 

  • xImage
  •  

直至目前的 YAML 設定已經碰過 Deployment、Service、ConfigMap、Secret、HPA 與 Ingress。

若每個環境都複製整份 YAML,只改 image 或一些小設定,未來如果有要改全環境的功能時,就得每個 YAML 都改一次,很容易漏掉其中一份。

就像在開發 Function 時,有多個字串輸出功能,中間都要做一樣的去除特殊符號處理,但是每個字串輸出功能都自己寫一隻,
這樣未來如果特殊符號有異動,那麼不就全部都要個別改一次?
K8s YAML 這裡也是一樣的意思。

而 Kustomize 可以做出所有不同環境的共用設定檔,並針對不同環境去透過 overlay 的設定檔來做到差異化,就像是程式語言把重複的 Function 抽出成共用 Function 供其他方法去呼叫。

base、overlay 與 kustomization 各自做什麼?

如果用前面共用 Function 的想法來看,base 就是放共用內容的地方,overlay 則是各個環境要補上的差異,而 kustomization.yaml 負責把這些檔案組合起來。

假設同一個 API 要部署到 dev、test 環境,Service 都是用 80 port 轉到 API 的 8080 port,Pod 的 label 也一樣,這些就可以共用。
但是兩個環境要跑的 image 版本、應用程式設定與資源大小可能不同,那麼就可以交給各自的 overlay 處理。

名稱 用途 這次 API 的例子
base 整理能被多個環境重複使用的資源與設定 Deployment 的 Pod 結構、Service 的 selector 與 port
overlay 引用共用內容,再加入或調整這個環境的設定 dev 使用 v2、test 使用 v1,各自加入 ConfigMap
kustomization.yaml 列出要載入的資源,以及組合時要做的調整 resources、images、namespace、patches

這邊要注意,base 和 overlay 都會有自己的 kustomization.yaml。base 的那份負責整理共用資源,overlay 的那份則會引用 base,再加上環境差異。

base、overlays 是常用的目錄命名慣例,也可以自行換成其他名稱。

Kustomize 會按照 kustomization.yaml 寫好的引用關係處理,不會因為資料夾叫 dev 就知道它是開發環境。

而 kustomization.yaml 本身也不會變成一個執行中的 Pod 或 Service。它是給 Kustomize 讀取的組合設定,處理完成後,才會產生原本熟悉的 Deployment、Service 等 Kubernetes YAML。

kubectl 本身已整合 Kustomize,所以這次可以直接用 kubectl kustomize 產生結果,再用 kubectl apply -k 套用,不需要為了這些操作另外安裝獨立的 Kustomize CLI。

Kustomize base、overlay 與套用流程

這次 Demo Kustomize 的整個目錄結構如下:

app/
  base/
    deployment.yaml
    service.yaml
    kustomization.yaml
  overlays/
    dev/
      kustomization.yaml
      configmap.yaml
  • base 放固定、共用的 Deployment、ClusterIP Service
    • 若 HPA 或其他已確認為共同的設定,也可放這裡。
  • overlays/dev 設定不同環境的 image、ConfigMap 與 Ingress。
# app/overlays/dev/kustomization.yaml
resources:
  - ../../base
  - configmap.yaml
images:
  - name: registry.example.internal/demo/api-a
    newTag: v2

上面先用最小的設定示範「把 base 和 ConfigMap 組起來,再把 image 換成 v2」。接著把各個檔案補完整,就比較容易看出每一層到底在處理什麼。

base:先把能共用的部分整理出來

先以之前的 api-a 示範,將以下內容存成 app/base/deployment.yaml:

# app/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-a
spec:
  selector:
    matchLabels:
      app: api-a
  template:
    metadata:
      labels:
        app: api-a
    spec:
      containers:
        - name: api
          image: registry.example.internal/demo/api-a:v1
          ports:
            - name: http
              containerPort: 8080
          envFrom:
            - configMapRef:
                name: api-a-settings
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi

這份 Deployment 先定義 API 的共用結構,並制定應用程式設定從 api-a-settings 這個 ConfigMap 讀取。至於 ConfigMap 裡面的值,就讓各個環境自己決定。

另外,延續 Day 20,如果 Deployment 已交給 HPA 管理,這裡就不要再寫固定的 spec.replicas,避免每次重新 apply,又把 HPA 調整過的副本數蓋回去。

Service 則存成 app/base/service.yaml:

# app/base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: api-a
spec:
  type: ClusterIP
  selector:
    app: api-a
  ports:
    - name: http
      port: 80
      targetPort: http

這份 Service 透過 app: api-a 找到 Pod,再把 80 port 的流量送到 container 裡名為 http 的 port,也就是前面宣告的 8080。

最後用 base 的 kustomization.yaml 把兩份資源引用進來:

# app/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml

resources 可以放完整資源檔,也可以引用另一個包含 kustomization.yaml 的目錄。

所以,就算 base/ 裡面放了十份 YAML,只在 resources 列出兩份,其他八份也不會自動被載入。也因此未來新增其他資源,除了建立 YAML 檔案以外,也要記得把它列到 kustomization.yaml 內。

overlay:使用同一份 base,並組合不同環境的設定

先將 dev 使用的 ConfigMap 存成 app/overlays/dev/configmap.yaml:

# app/overlays/dev/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: api-a-settings
data:
  ASPNETCORE_ENVIRONMENT: Development

接著,假設 dev 環境想用比較小的資源配置,就新增 app/overlays/dev/patch-deployment-resources.yaml:

# app/overlays/dev/patch-deployment-resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-a
spec:
  template:
    spec:
      containers:
        - name: api
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 250m
              memory: 128Mi

這份 patch 只列出想調整的資源大小,並有再複製一整份 Deployment。

metadata.name: api-a 用來對到原本的 Deployment,而 containers 裡的 name: api 用來對到原本的 container。

對 Deployment 的這種 strategic merge patch,container 清單會依 name 合併,因此 image、port 與 ConfigMap 引用都可以保留。

新增這份 patch 後,dev 的 kustomization.yaml 完整內容如下:

# app/overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: team-a
resources:
  - ../../base
  - configmap.yaml
images:
  - name: registry.example.internal/demo/api-a
    newTag: v2
patches:
  - path: patch-deployment-resources.yaml

從 app/overlays/dev/ 往上兩層,再進入 base/,就是 ../../base。Kustomize 會先讀取 base 列出的 Deployment 與 Service,再加入 dev 的 ConfigMap,接著依設定調整 Namespace、image 與資源大小。

這裡幾個欄位的作用可以一起看:

  • resources:載入完整資源或其他 Kustomization 目錄。ConfigMap 是新增的完整物件,所以放這裡。
  • namespace:替這組 namespaced 資源設定 Namespace,本例使用之前的 team-a。它不會自動建立 Namespace,所以部署前 team-a 必須已存在。
  • images:依原 image 名稱匹配,再調整 image。這裡的 name 是 registry.example.internal/demo/api-a,不是 Deployment 名稱,也不是 container 的 name: api。
  • patches:修改已載入的資源。這份 patch 要改 base 的 Deployment,所以列在這裡。

images 的 newTag 會讓本例 image 從 v1 變成 v2;如果還要更換 registry 或 repository,也可以設定 newName。

另外,查資料時可能會看到舊範例用 bases: 引用共用目錄。這個欄位已被標示為 deprecated,現在可以像本次的範例統一放在 resources:,詳見 官方 bases 說明。

再加一個 test 環境,凸顯共用的效果

假設 test 暫時跑 v1,想使用 base 裡原本的資源配置,就不用再寫一份資源 patch,只要新增自己的 ConfigMap 和 kustomization.yaml。

# app/overlays/test/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: api-a-settings
data:
  ASPNETCORE_ENVIRONMENT: Staging
# app/overlays/test/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: team-a-test
resources:
  - ../../base
  - configmap.yaml
images:
  - name: registry.example.internal/demo/api-a
    newTag: v1

test 是部署環境的目錄名稱,Staging 是這個例子給 ASP.NET Core 使用的環境名稱,兩者可以依實際需求設定,沒有一定要同名。

此範例讓 test 環境使用另一個 Namespace team-a-test,方便看出差異。

現在整個目錄就會是:

app/
  base/
    deployment.yaml
    service.yaml
    kustomization.yaml
  overlays/
    dev/
      kustomization.yaml
      configmap.yaml
      patch-deployment-resources.yaml
    test/
      kustomization.yaml
      configmap.yaml

兩個環境組出來的差異如下:

設定 dev test
Namespace team-a team-a-test
image tag v2 v1
ASP.NET Core 環境 Development Staging
CPU request/limit 50m/250m 100m/500m
Memory request/limit 64Mi/128Mi 128Mi/256Mi
Service port → container port 80 → 8080,沿用 base 80 → 8080,沿用 base

這樣未來如果所有環境都要調整 Service 的共用設定,只要改 base 的 Service,兩個 overlay 在下一次組合時就都會拿到更新。
但如果只是 dev 要先升到新版,就只改 dev 的 images,test 仍然維持自己的版本。

base 改了,也要重新產生與檢查每個 overlay 的結果,再套用到各自的環境,叢集裡的資源才會更新。

先看組合結果,再進行套用

先跑 kubectl kustomize app/overlays/dev ,跑這個可以讓 Terminal 顯示所有 Kustomize YAML 組成的最終完整 YAML 結果會長什麼樣子,理論上有跟先前個別寫單一完整 YAML 的內容要 相同(在設定內容都一致的情況下)。

逐項比對 Deployment image、Service selector/port、HPA、ConfigMap 與 Ingress 後,再決定是否 kubectl apply -k app/overlays/dev 來做實際套用。

而若要執行整批刪除,則可以執行 kubectl delete -k,它的作用是刪除這組配置描述的資源,而不是「退回前一版」。

前面的範例可以分別執行以下兩行,先看 dev、test 的組合結果。指令中的 app/ 是相對於 Terminal 當下位置,所以這裡要在 app/ 的上一層執行:

kubectl kustomize app/overlays/dev
kubectl kustomize app/overlays/test

這兩行只會在本機產生 YAML,不會把資源送進叢集。本例每個 overlay 都會產生三個物件:Deployment、Service、ConfigMap,並符合前面的差異表。

我會特別確認 dev 的 Deployment 仍只有原本的 api container,image 已變成 v2,資源大小已變成 patch 設定的值,而 port、label 與 api-a-settings 引用都還在。test 則應該保留 v1 與 base 的資源大小。

等 image 已換成可用版本、Namespace 已存在,且要部署的完整資源已整理好後,再明確指定本機 Demo 的 context 進行套用:

kubectl --context k3d-ironman apply -k app/overlays/dev
kubectl --context k3d-ironman -n team-a get deployments,pods,services,configmaps
kubectl --context k3d-ironman -n team-a rollout status deployment/api-a

-k 後面放的是環境目錄,由 kubectl 先完成 Kustomize 組合再套用。

YAML 整理好後,維護權責也要一起確認

在實際的企業導入專案中,可能會有多個團隊或單位是各自維護自己的 YAML 的,例如 Deployment A 相關的服務(包含 ConfigMap 等)是由 A 單位這個程式開發單位維護,而像是 Ingress、VirtualService 等影響整個叢集服務的,可能就由 Infra 單位維護。

但有個很明顯的問題,我的經驗是一定要請企業內部如果能在導入、部署 K8s 之前,越快處理好相關的維運 SOP 與相關權責劃分、規劃越完整越好,因為 K8s 的整個維運,其實跟企業維運職責息息相關。

例如 Namespace 如何劃分;新增了後端 API 站台 Pod,那麼誰敢去調整 VirtualService 或 Ingress;RBAC 角色權責該如何切分;甚至 Namespace 下所屬的角色要如何定義,這些其實都是企業內的開發 - 測試 - 部署生命週期息息相關,
當有模糊地帶時,好一點可能在 YAML 規劃時就會發現並且討論處理,
嚴重一點很可能會到部署了,測試營運時才發現,再嚴重一點就上演踢皮球的劇情,這些其實都是不樂見的。

實際整理時,我也會確認每個環境的設定是不是集中在操作者找得到的地方。共用的結構可以放 base,但只在 dev 成立的網址或設定,就不要讓 test 默默繼承到,再期待大家都記得補 patch。

如果依團隊拆成各自維護的目錄或 repository,除了確認各自能產生完整 YAML,也要討論第一次部署時哪些資源必須先存在。例如維運團隊要建立某個 Namespace 裡的 RoleBinding,但 Namespace 卻由應用團隊建立,那就要先把建立順序講好。

而 Kustomize 負責的是配置組合;誰能修改檔案、誰能核准變更、誰能實際 apply,仍要搭配 repository 權限、審核流程與 Kubernetes RBAC 來落實。這些責任越早談清楚,後面的部署與維護就越容易執行。

參考資料


上一篇
Day 22 - Canary 金絲雀部署與 Blue-Green 藍綠部署
下一篇
Day 24 - Helm
系列文
初見 Kubernetes - 純網站後端開發踏入 K8s 世界的經驗分享 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言