建立 K8S 基礎資源,規劃服務間的通訊路徑。
開場先來個冷知識:Kubernetes 這個字有 10 個字母,K-u-b-e-r-n-e-t-e-s,掐頭(K)去尾(s)之後,中間剩下 8 個字母——「K」+「8」+「s」,K8S 的縮寫就是這樣來的,跟 i18n(internationalization,中間也是 18 個字母被縮成 18)是同一種命名套路,這種縮寫方式叫做 numeronym。今天正好是系列的 第 8 天,咱們就來講 K8S。
Day 7 我們用 Docker Compose 把整套系統在本機跑起來了,很爽。但生產環境呢?容器半夜掛了誰重啟?流量暴增誰擴容?節點掛掉服務要搬家怎麼辦?
這就是 Kubernetes (K8S) 登場的時刻。K8S 是容器編排的霸主,幫我們管理容器的生老病死。今天先在本機的 Docker Desktop K8S 練功(對,Docker Desktop 內建一個單節點叢集,練習超方便),Day 9 來聊聊「要不要現在就上雲」這個問題。
在動手寫 K8S 的 YAML 之前,先把 Day 7 用的 Docker Compose 跟 K8S 放在一起比一比,不然直接跳資源定義會覺得莫名其妙——為什麼同一件事,K8S 要拆成好幾個檔案寫?
本質上的差異:Docker Compose 是單機的容器編排——所有服務都在同一台機器上,這台機器掛了,整套系統就跟著掛。K8S 是叢集的容器編排——服務可以分散在多台節點上,某個節點掛了,K8S 會把上面的 Pod 重新排程到其他節點,這也是 Day 8 稍後會實測的自癒能力,Compose 完全沒有這個概念。
寫法對照:Compose 把「這個服務要跑什麼 image、怎麼連網路、環境變數、要跑幾個」全部塞進同一個 services.<name> 區塊;K8S 把這些職責拆成好幾種獨立資源,各司其職:
| Docker Compose 概念 | K8S 對應資源 | 差異重點 |
|---|---|---|
services.<name>(一個區塊全包) |
Deployment |
K8S 把「怎麼跑」單獨拆成一個資源,管的是 Pod 的生命週期與副本數 |
image: / build: |
spec.containers[].image |
Compose 可以直接 build: 本機 Dockerfile;K8S 只吃已經 push 上 registry 的 image,不會幫你現場 build |
ports:(host:container 直接打通) |
Service(ClusterIP/NodePort)+ containerPort |
Pod 的 IP 是動態的(重啟就換),K8S 多了 Service 這層穩定的「門牌」抽象,Compose 靠固定的容器名稱不用煩惱這件事 |
environment: |
env: 或 envFrom: configMapRef / secretRef |
概念相同(都是塞環境變數),但 K8S 多了 ConfigMap/Secret 這種獨立資源,可以被多個 Deployment 共用同一份設定 |
depends_on: condition: service_healthy |
通常沒有原生對應,得用 initContainers 手動等 |
Compose 有內建的啟動順序控制;K8S 傾向讓服務自己有重試邏輯,或額外寫一個 initContainer 輪詢依賴服務(例如用 nc -z postgres-service 5432 等資料庫先就緒) |
healthcheck: |
livenessProbe + readinessProbe |
Compose 只有一種健康檢查;K8S 拆成兩種語意——壞了要不要重啟(liveness)、好了沒可以導流量嗎(readiness),Day 14 會細講 |
.env 檔案 |
ConfigMap(非機密)/ Secret(機密) |
Compose 用一個 .env 檔案混著放所有變數;K8S 明確拆開,Secret 至少會 base64 編碼、能做存取控制(Day 13 會講這還不夠安全的地方) |
docker compose up |
kubectl apply -f |
Compose 是「執行一次,啟動整個 stack」;K8S 的 apply 是「宣告期望狀態」,叢集會持續背景 reconcile——這就是為什麼等一下砍掉 Pod,K8S 會自動補回來,Compose 沒有這種行為 |
哪些東西是共用的:不是砍掉重練,很多 Day 3~5 已經做好的東西直接搬過來就能用:
POSTGRES_HOST、JWT_SECRET 這些 key 兩邊都一樣,差別只在「怎麼餵進去」(Compose 用 .env,K8S 用 ConfigMap/Secret)http://postgres:5432),K8S 靠 Service 名稱當 DNS(http://wafer-backend-svc:8000),「用名字互相找對方」這個概念完全沒變,只是底層機制從 Compose 的內建網路換成 K8S 的 Service。搞懂這張對照表,接下來的 Namespace、Deployment、Service 就不是三個憑空冒出來的新名詞,而是把 Compose 那個「一個檔案全包」的區塊,拆成三塊各自負責的積木。
我們定義了 k8sdemo 這個 Namespace,把 Wafer BI 的所有資源與其他系統隔開,避免名稱衝突與權限越界:
apiVersion: v1
kind: Namespace
metadata:
name: k8sdemo
在 k8s/base/wafer-bi-deployment.yaml 中定義後端的 Deployment(簡化版):
apiVersion: apps/v1
kind: Deployment
metadata:
name: wafer-backend
namespace: k8sdemo
spec:
replicas: 2 # 隨時維持兩個實例,高可用
selector:
matchLabels:
app: wafer-backend
template:
metadata:
labels:
app: wafer-backend
spec:
containers:
- name: backend
image: wafer-bi-backend:ironman
ports:
- containerPort: 8000
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
resources 區塊先埋個伏筆:requests 是跟 K8S「預訂」的資源,limits 是天花板,Day 28 效能調優會深入。
還有一件事現在看不出來、但之後會很重要:改掉 image 那行再 apply 一次,K8S 不會把兩個 Pod 全砍了重開,而是一個一個換(先起新的、確認健康、再關舊的),這叫滾動更新 (Rolling Update),是 Deployment 的預設行為。換得多快、中途容許少幾個 Pod、換壞了怎麼退回去——Day 20 專門講這件事。
Pod 的 IP 是會變的(重啟就換一個),所以需要 Service 提供固定的門牌:
apiVersion: v1
kind: Service
metadata:
name: wafer-backend-svc
namespace: k8sdemo
spec:
selector:
app: wafer-backend
ports:
- protocol: TCP
port: 8000
targetPort: 8000
type: ClusterIP
這樣 API Gateway 只要呼叫 http://wafer-backend-svc:8000 就能穩定找到後端,Service 還自帶負載均衡。
紙上談兵不如實際跑一次。apply 之後兩個 Pod 順利 Running,接著做一件壞壞的事——手動刪掉其中一個 Pod:

▲ kubectl apply 部署、刪除 Pod 後 2 秒 K8S 自動補新 Pod
看最後一段:刪掉 9pn9b 之後兩秒內,K8S 就補上了新的 Pod m9nxq(AGE 2s)。這就是 Deployment 的自癒能力——它的職責是「讓現實世界永遠符合你聲明的期望狀態 (Desired State)」,你說要 2 個,它就想辦法永遠維持 2 個。
第一次親手刪 Pod 看它復活的時候,真的會忍不住多刪幾次(誤)。
Namespace、Deployment、Service 是 K8S 的三塊基石。本機練功告一段落,接下來就要面對「這套東西要跑在哪裡」的問題。
雲端選項很多,這個系列不做橫向比較——我當初挑的是 Oracle Cloud (OCI),理由很單純:它的 Always Free 方案給的免費額度在同類服務裡算大方,看起來很適合拿來跑一個練習用的叢集。明天就來記錄實際去開這座叢集的過程,以及「免費額度」這四個字在真正動手時的樣子。