Kubernetes 把叢集變成宣告狀態
我拆 Kubernetes 的控制迴圈、核心物件和可直接複製的叢集模板,讓你少走一堆 YAML 冤枉路。

我拆 Kubernetes 的控制迴圈、核心物件和可直接複製的叢集模板,讓你少走一堆 YAML 冤枉路。
我用 Kubernetes 有一陣子了,越用越火大。第一次把真的服務接上去,我以為是 manifest 寫錯;後來怪 ingress,再怪 network policy,最後才發現都不是。它根本不是在幫我「理解」應用,它只是在很認真地照我下的指令做事。我要三份,它就給我三份;我把 node 弄掛,它就重排;我忘了 resource limit,它也不會良心發現幫我擋一下。這就是 Kubernetes 最煩的地方:它不是部署按鈕,它是一個會一直修正現實的控制系統。你不懂它的 loop,就只會一直跟自己裝的機器打架。
後來我去看 Kubernetes 的 Wikipedia 頁面,不是當百科看,是當設計筆記看,才突然通了。這頁很密,但錨點夠準:Google 在 2014 年 6 月 6 日公開它,創辦人是 Joe Beda、Brendan Burns、Craig McLuckie,現在由 CNCF 維護。這段歷史不是八卦,因為它直接解釋了 Kubernetes 的脾氣:宣告式、reconciliation、一直把叢集拉回你指定的狀態。
Kubernetes 不是部署工具,它是修正迴圈
訂閱 AI 趨勢週報
每週精選模型發布、工具應用與深度分析,直送信箱。不定期,不騷擾。
不會寄垃圾信,隨時可取消。
Kubernetes defines a set of building blocks (“primitives”) that collectively provide mechanisms that deploy, maintain, and scale applications based on CPU, memory or custom metrics.
翻譯一下就是:Kubernetes 不太在乎你想不想被理解,它只在乎「目前的狀態」跟「你宣告的狀態」有沒有對上。你殺掉一個 pod,它會補回來;你改掉副本數,它會調整;你把機器弄壞,它會想辦法把工作搬走。這不是包裝容器而已,這是一直在修正偏差。

我以前把 orchestration 想成 Docker 的豪華外掛,這個理解很害人。因為你一旦把它當外掛,就會期待它去猜你的意圖。它不會。它只讀物件、比對狀態、執行動作。這就是為什麼它看起來很死板,但其實是很一致。它不是在討好你,它是在維持模型。
我自己剛上手時最常犯的錯,就是把 manifest 當 startup script 寫。結果每次出事都在怪 YAML 太難寫。後來我才懂,Kubernetes 要的是宣告,不是命令。你要先回答三件事:要存在什麼、要幾份、狀態偏掉時要怎麼拉回來。這三題答得出來,你才算真的在用 Kubernetes。
實操上,我現在都先用這個順序想:先定義 desired state,再決定 controller,最後才補 runtime 細節。不要反過來。你如果先想容器怎麼起,再回頭補叢集要什麼,最後通常會長成一坨只能靠人肉維運的 YAML。
控制平面不是一台機器,是一組會互相盯著的角色
Kubernetes 最容易被講爛的一句話就是「控制平面」。很多人把它講得像一個黑盒子,彷彿整個叢集就是一顆腦袋。不是。它是幾個職責很清楚的元件湊在一起:etcd、API server、scheduler、controllers。你只要把這四個角色分開看,很多故障會突然變得很白話。
etcd 是真相來源。它存 cluster state。Wikipedia 有提到它在分割時偏向 consistency 而不是 availability,這個取捨我反而很喜歡。因為如果狀態庫開始亂講話,後面全部都會變成戲劇。很多團隊平常把 etcd 當背景音,等 cluster 開始怪怪的才突然想起來它的重要性。
API server 是前門。所有請求都走 API,透過 JSON over HTTP 驗證、寫入,最後進 etcd。你用 kubectl、client library,甚至很多 operator,都是在跟它說話。這也是 Kubernetes 之所以像一個可編程系統的原因:沒有旁門左道,全部都經過同一個 API。
Scheduler 是配對的人。它決定未排程的 pod 要放哪裡,依據的是資源、限制、約束,而不是什麼神秘的「最佳機器」。這很重要,因為很多 Pending 的 pod 其實不是壞掉,只是你給的條件太刁,根本沒有 node 同時滿足。
Controllers 是修正引擎。ReplicaSet 會把你要的副本數維持住,少了就補,多了就收。這不是額外加裝的功能,這就是整套系統的心臟。
實操寫法很簡單:debug 時先分類,不要亂猜。是狀態沒存到、請求沒進來、排程失敗,還是 reconcile 沒發生?我現在都用一個很土但很有效的檢查法:物件存在但 pod 不存在,先看 scheduling;pod 存在但活不久,先看 controller 跟 app;連物件都不穩,先看 API 或定義本身。
- etcd 存狀態。
- API server 收請求、驗證、寫入。
- Scheduler 負責放置。
- Controllers 負責修正漂移。
Pod 才是你真正交付的單位,不是 container
Kubernetes 很愛講 pod,因為它逼你別再用單一 container 的腦袋看世界。Pod 才是排程的基本單位。也就是說,scheduler 排的是 pod,controllers 管的是 pod,Service 通常也是對著 pod 這群人。你如果還在想「一個 container 就是一個 app」,很容易卡在網路、生命週期、sidecar 這些地方。

我自己踩過的坑是,把 sidecar 跟 app container 當成 Docker Compose 那種獨立服務在想。表面上好像可以,直到我想讓它們一起死、一起起來,才發現 Kubernetes 根本不是那個模型。它把關係講得很明白:同一個 pod,共享生命週期。這很煩,但也很乾淨。
Wikipedia 也提到 namespaces、labels、selectors。這些不是裝飾品。Namespace 是範圍;labels 跟 selectors 是讓 controller 和 service 找到正確 pod 的方式,不是靠硬編碼名稱。這件事小看不得,因為一旦 workload 變多,你就會感謝自己沒有把名字寫死。
白話一點說,Kubernetes 偏好的是「描述式身分」,不是「名字式身分」。pod 可以消失再生出來,但 label selector 才是系統持續黏著同一個工作負載的方式。這也是為什麼 Deployment 換掉 pod,Service 幾乎不會在意。它原本就不該在意單一 pod,它在意的是那一群。
實操上,我現在的 label 會分三類:app identity、environment、ownership。像是 app=payments、env=prod、team=platform。然後 selector 只吃這些 label,不吃名字。這樣 rollout、查問題、切流量都會少很多蠢事。
- 用 pod 表示共享生命週期。
- 用 labels 做分群。
- 用 selectors 做穩定指向。
- 用 namespaces 做範圍與清理邊界。
Deployment 之所以好用,是因為它很無聊而且很堅持
Wikipedia 把 ReplicaSet、ReplicationController、Deployment 都列出來,這一段我建議你直接抓核心:Deployment 是描述 rollout 的方式,ReplicaSet 是維持副本數的機械手臂。重點不是「生出一個 pod」,而是「在變動中維持這個工作負載」。
我看過不少團隊手改 production 裡的 pod,然後一臉驚訝地問為什麼改動不見了。拜託,pod 本來就不是拿來長住的。你要的是持久變更,就該改 controller 或 Deployment spec。Kubernetes 只認這個版本,其他臨時補丁它根本不當一回事。
這背後其實是一個很不討喜但很實用的觀念:每個 instance 都應該是可拋棄的。這對從傳統 VM、SSH、手動修機器走來的人很不友善,因為以前你修的是「那台機器」,現在你修的是「一份宣告」。但 Kubernetes 就是吃這套,它要的是 repeatability,不是英雄主義。
實操寫法我會很早就定好 rollout 策略:更新怎麼做、失敗怎麼回、要不要 scale down。不要等服務長大才補,因為到那時候你只會得到一堆 YAML 跟一堆「先手動 patch 一下」的爛習慣。我也幹過,最後只會更貴。
我現在的規則很簡單:如果這東西要撐 restart、要撐 scale change、要撐 node 掛掉,就丟進 controller。反過來,如果只是一次性任務,就別硬裝成長期服務。Kubernetes 有不同物件不是裝飾,是因為它真的分工不同。
Service、Volume、Config 才是 app 真的開始像 app 的地方
Wikipedia 會把 Services、Volumes、ConfigMaps、Secrets 一起講,因為這裡才開始碰到真實世界。光有 pod 不夠。真的上線的 app 還需要穩定入口、持久儲存、以及不用每次改設定就重建 image 的能力。
Service 提供穩定的網路身份,解掉「pod 重啟 IP 就變」這種老問題。你應該把 client 指向 service,而不是 pod。這個概念很抽象,直到你某天因為硬編碼 pod IP 而掉一下午,才會真的記住。
Volume 處理應該活得比 pod 久的資料。像 database、需要持久化的 cache,或任何不能在重啟時消失的狀態,都得靠它。你如果忽略這件事,最後通常會得到資料遺失跟很尷尬的 retrospective。
ConfigMap 跟 Secret 則是把設定跟 image 分開。這件事看起來普通,但它直接救你脫離「每個環境都重 build 一次」的地獄。非敏感設定放 ConfigMap,敏感值放 Secret,然後再掛進去。
實操寫法是:不要把環境值烤死在 image 裡。我看過太多人這樣做,因為初期很快,後面卻會把環境差異、憑證、端點全揉成一團。比較健康的做法是 image 保持笨,runtime 保持聰明。image 放程式,cluster 放環境。
如果你要查官方說法,我會直接看 Kubernetes 官方文件、Service 概念頁、以及 Secrets 頁面。不是叫你背,是別亂猜。
API 才是產品核心,其他東西都只是它的附屬品
Wikipedia 提到 Kubernetes 可擴充,而且內部元件與外部擴充都依賴 Kubernetes API。這句其實很直白:API 才是中心。你一旦接受這件事,整個生態系就合理了。像 kubectl、API objects、custom resources、controllers、operators,全部都是圍著這個 API contract 在轉。
我以前以為 operator 是平台團隊才會玩的高階技巧。後來才發現,它本質上就是包了一層 domain 的 controller。只要你能把自己的領域定義成 desired state,就能教 Kubernetes 幫你 reconcile。這很方便,但也很殘酷,因為你寫錯了,它也會很誠實地幫你做錯。
換句話說,這套平台真正厲害的地方不是 container,而是你可以在同一個 control plane 上,建立自己的 automation language。這也是為什麼很多 Kubernetes 周邊工具看起來都很像,因為它們都在講同一種 API 句法。
實操上,如果你在做內部平台工具,我會優先考慮 custom resource 加 controller,而不是寫一堆 shell script 去 loop kubectl。Script 可以做一次性管理,拿來當控制策略就太脆了。如果行為需要持續,就做 controller;如果行為需要宣告,就做 resource。
我也會順手看 Cluster API。如果你管的是 cluster 本身,那它就是把同一套宣告式思維往上一層延伸。這種東西一開始看起來很繞,但一旦通了,你會發現它跟 workload 的邏輯其實是一樣的。
你選哪種 Kubernetes,會直接改變你怎麼活
Wikipedia 有提到 open-source distribution、commercial distribution、managed distribution。這不是註腳,這是現實。GKE、EKS、AKS 都是同一套核心概念外面包不同的操作責任。這差很多。
我看過團隊以為「Kubernetes」就是一個固定東西,結果從自架 cluster 換到 managed service 之後,才發現升級、網路、權限、節點管理的假設全都要重來。這很正常。核心物件沒變,但你要付出的營運成本會換地方。
實操寫法是:先決定你到底想不想養 cluster。如果團隊不想天天顧 control plane 升級,就用 managed。你如果真的需要高度客製,而且有人力承擔,那再自己管多一點。不要假裝這兩者差不多,完全不是。
我還會在叢集出事前就定好支援政策。Wikipedia 提到 Kubernetes 1.19 之後支援窗口從 N-2 變 N-3,這種政策看起來很無聊,但它其實就是升級節奏。你不先排,最後就是它來排你。
可抄的模板
# Kubernetes operating template
## 1) 我在宣告什麼
- App name:
- Namespace:
- Environment:
- Owner/team:
## 2) Desired state
- Replicas:
- Container image:
- Resource requests:
- Resource limits:
- Health checks:
- Rollout strategy:
## 3) Stable identity
- Labels:
- app:
- env:
- team:
- Service name:
- Selector:
## 4) Configuration
- ConfigMaps:
- non-sensitive env values
- feature flags
- endpoints
- Secrets:
- passwords
- tokens
- certificates
## 5) Storage
- Volume needed? yes/no
- PersistentVolumeClaim name:
- Mount path:
- Retention policy:
## 6) Scheduling rules
- Node affinity:
- Tolerations:
- Anti-affinity:
- Priority class:
## 7) Failure behavior
- What should restart automatically?
- What should scale horizontally?
- What should be recreated if a node dies?
- What should never be auto-replaced?
## 8) Controller choice
- Deployment for long-running stateless app
- StatefulSet for stable identity and storage
- DaemonSet for one pod per node
- Job for run-to-completion work
## 9) Day-2 checks
- kubectl get pods -n
- kubectl describe pod
- kubectl get events -n
- kubectl get svc -n
- kubectl get deploy -n
## 10) Copy-ready starter manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-app
namespace: example
labels:
app: example-app
env: prod
team: platform
spec:
replicas: 3
selector:
matchLabels:
app: example-app
template:
metadata:
labels:
app: example-app
env: prod
team: platform
spec:
containers:
- name: app
image: ghcr.io/your-org/example-app:1.0.0
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: example-app-config
- secretRef:
name: example-app-secrets
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /livez
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
name: example-app
namespace: example
spec:
selector:
app: example-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP這份模板我刻意寫得很無聊,因為叢集狀態本來就該無聊。它給你的東西很基本:宣告 identity、穩定 selector、明確 config、真的 resource limits、健康檢查,還有一個不靠 pod 名稱的 Service。這些夠你在真實團隊裡起步了。
如果我今天要把它丟進新 repo,我會先改三件事:換 image、換 namespace、把 resource requests 收到符合 app 的真實需求。然後只有在真的需要持久化時才補 storage。我寧願先上一個乾淨的 Deployment,也不要假裝自己有 state,結果硬做一個假的 StatefulSet。
原始觀點主要來自 Kubernetes Wikipedia 頁面,我加上的是自己的實務理解,外加幾個常用官方文件與生態系連結。模板是我根據這些概念重新整理的原創版本,能直接拿去改。