哈囉,大家好,歡迎來到 K8s 的最後一天!
這幾天我們一直在用 Deployment
來操作 K8s 的工作負載。像我們之前的 ota-backend
,它其實是一個典型的無狀態 (Stateless) 應用。什麼意思呢?就是這個服務不管跑幾個 Pod,每個 Pod 都是一模一樣的。它們沒有「誰是誰」的差別,也不需要額外記住什麼狀態。只要有請求進來,任何一個 Pod 都能處理,掛掉一個也沒差,因為新的 Pod 起來就跟舊的一模一樣。
不過,並不是所有服務都能這麼「隨便」。想像一下,我們要部署的不是 ota-backend
,而是一個資料庫叢集(像 MariaDB 的主從架構)。這時候情況就完全不同了。
在資料庫叢集裡,db-master
這個 Pod 是獨一無二的,它專門負責寫入資料。旁邊的 db-slave-1
和 db-slave-2
,則各自從 master 複製資料。這三個 Pod 的身份絕對不能搞錯,更不可能隨便替換。假如 master 突然被 Deployment 當成「可替換的副本」給重建掉,那整個資料流向就會亂套。再加上,每一個資料庫 Pod 都需要專屬的儲存空間,它寫過的資料不能跟別人混在一起,否則資料一致性會出大問題。
這種情況下,我們就需要 StatefulSet
來幫忙。
StatefulSet
是專門為這種「有狀態」的應用設計的。它的存在目的,就是保證每個 Pod 都有一個穩定且獨一無二的身份,不會被隨意替換。
在 Deployment 底下,Pod 名稱通常是隨機產生的,像 ota-backend-6b8f...
。掉了一個,就直接補上一個新的,名稱可能完全不同。對無狀態服務來說,這沒什麼影響。
但在 StatefulSet 裡,Pod 的命名方式就不一樣了。它會固定為 db-0
、db-1
、db-2
這樣的格式。就算 db-1
壞掉,重建後還是叫 db-1
,不會變成其他名字。這種穩定性,讓我們可以很安心地把資料卷或角色綁定到特定的 Pod 身上。
還有一點,StatefulSet 啟動和關閉 Pod 的方式非常謹慎。它不會一次性把所有副本丟出來,而是照順序慢慢建:先把 db-0
建好並確認能正常工作,再來才會啟動 db-1
,最後才是 db-2
。縮容時也一樣,從最後一個開始慢慢收掉。對於依賴順序啟動的系統,這一點非常重要。
再來就是儲存問題。在 StatefulSet 裡,每個 Pod 都會擁有自己專屬的磁碟卷,像 data-db-0
、data-db-1
。就算 Pod 被調度到另一台節點,它還是會掛回屬於自己的那份資料,不會被覆蓋或搞混。這就是 StatefulSet 給我們的三大承諾:穩定的身份、順序的啟動、持久的儲存。
不過 StatefulSet 並不是單打獨鬥,它要能順利運作,還需要兩個重要夥伴:Headless Service 和 volumeClaimTemplates。
clusterIP: None
,它就不再做負載平衡,而是給出像 db-0.mysql-svc.svc.cluster.local
這樣的記錄。這樣應用程式就能直接找到 db-0
或 db-1
,而不是隨機打到誰。db-0
就會有 data-db-0
,db-1
就會有 data-db-1
。這確保了每個 Pod 的資料是獨立且穩定的,不會互相干擾。下面我們來看一個簡單的範例。這份 YAML 建立了一個有三個副本的 Nginx StatefulSet,每個副本都有自己的儲存卷。套用這份檔案後,你會看到:
web-0
、web-1
、web-2
。www-web-0
、www-web-1
。這就是 StatefulSet 最直觀的效果。
# statefulset.yaml
# 建立一個 Headless Service,讓 StatefulSet 中的 Pod 能夠有固定且可預測的網路名稱
# Pod 之間就可以透過 DNS 名稱互相辨識,例如 web-0.nginx-svc、web-1.nginx-svc
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: project-space
spec:
clusterIP: None # 設為 None 就是 Headless Service
selector:
app: nginx-stateful
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: project-space
spec:
serviceName: "nginx-svc" # StatefulSet 必須綁定一個 Headless Service
replicas: 3 # 建立三個 Pod:web-0, web-1, web-2
selector:
matchLabels:
app: nginx-stateful
template:
metadata:
labels:
app: nginx-stateful
spec:
containers:
- name: nginx
image: nginx:1.16.1
ports:
- containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html # 把持久化儲存掛載到 nginx 預設網站目錄
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ] # 每個 PVC 只能被單一 Pod 掛載
resources:
requests:
storage: 1Gi # 每個 Pod 會自動生成一個 1Gi 的 PVC(www-web-0, www-web-1...)
今天,我們補上了 Kubernetes 核心工作負載的最後一塊拼圖。Deployment 和 StatefulSet 其實是兩種截然不同的哲學:
搭配 Headless Service 和 volumeClaimTemplates,StatefulSet 才能真正發揮它的威力。
到這裡,我們已經能用 Kubernetes 部署大部分的應用程式了。接下來,我們會往 DevOps 的方向前進,開始探索 CI/CD,看看怎麼把程式碼提交一路自動部署到 K8s 上。明天見!