直至目前的 YAML 設定已經碰過 Deployment、Service、ConfigMap、Secret、HPA 與 Ingress。
若每個環境都複製整份 YAML,只改 image 或一些小設定,未來如果有要改全環境的功能時,就得每個 YAML 都改一次,很容易漏掉其中一份。
就像在開發 Function 時,有多個字串輸出功能,中間都要做一樣的去除特殊符號處理,但是每個字串輸出功能都自己寫一隻,
這樣未來如果特殊符號有異動,那麼不就全部都要個別改一次?
K8s YAML 這裡也是一樣的意思。
而 Kustomize 可以做出所有不同環境的共用設定檔,並針對不同環境去透過 overlay 的設定檔來做到差異化,就像是程式語言把重複的 Function 抽出成共用 Function 供其他方法去呼叫。
如果用前面共用 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。

這次 Demo Kustomize 的整個目錄結構如下:
app/
base/
deployment.yaml
service.yaml
kustomization.yaml
overlays/
dev/
kustomization.yaml
configmap.yaml
base 放固定、共用的 Deployment、ClusterIP Service
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」。接著把各個檔案補完整,就比較容易看出每一層到底在處理什麼。
先以之前的 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 內。
先將 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 暫時跑 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 的,例如 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 來落實。這些責任越早談清楚,後面的部署與維護就越容易執行。