前言
除了前面介紹的 ReplicaSet 和 Deployment 之外,Kubernetes 還提供了幾種針對特定場景設計的控制器:DaemonSet 用於在每個節點運行守護程序、Job/CronJob 處理批次任務、StatefulSet 管理有狀態應用。這篇文章會逐一介紹這些控制器的設計理念與實戰應用。
DaemonSet:每個節點一個副本
DaemonSet 確保叢集中的全部(或部分)Node 上都恰好運行一個 Pod 副本。當新 Node 加入叢集時自動建立 Pod,Node 移除時 Pod 也會被回收。
典型使用場景
- 日誌收集:在每個節點上運行 Fluentd、Filebeat 或 Fluent-bit
- 監控代理:部署 Prometheus Node Exporter、Datadog Agent
- 叢集儲存:在每個節點運行 Ceph 或 GlusterFS 守護程序
- 網路外掛:CNI 外掛如 Calico、Flannel 本身也是以 DaemonSet 部署的
DaemonSet 關鍵欄位
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: my-daemonset
spec:
selector:
matchLabels:
app: monitoring
template:
metadata:
labels:
app: monitoring
spec:
containers:
- name: node-exporter
image: prom/node-exporter:v0.18.0
updateStrategy:
type: RollingUpdate # 或 OnDelete(手動刪除 Pod 才更新)
rollingUpdate:
maxUnavailable: 1 # 滾動更新時最多同時有幾個不可用
與 Deployment 不同的是,DaemonSet 的更新策略沒有 maxSurge(因為每個 Node 就只能有一個副本),只有 maxUnavailable 參數。預設採用 RollingUpdate 策略,也可以設為 OnDelete,讓管理員手動控制更新節奏。
實戰:部署 Node Exporter
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
labels:
app: prometheus
component: node-exporter
spec:
selector:
matchLabels:
app: prometheus
component: node-exporter
template:
metadata:
labels:
app: prometheus
component: node-exporter
spec:
hostNetwork: true # 使用宿主機網路
hostPID: true # 存取宿主機的程序資訊
containers:
- name: node-exporter
image: prom/node-exporter:v0.18.0
ports:
- containerPort: 9100
hostPort: 9100
livenessProbe:
tcpSocket:
port: 9100
initialDelaySeconds: 3
readinessProbe:
httpGet:
path: '/metrics'
port: 9100
initialDelaySeconds: 5
這裡使用了 hostNetwork 和 hostPID 是因為 Node Exporter 需要存取宿主機的網路和程序資訊才能正確收集指標。更新映像檔版本後,DaemonSet 會自動進行滾動更新。
Job:一次性批次任務
Job 負責執行「跑完就結束」的批次任務,保證指定數量的 Pod 成功完成。與 Deployment 等控制器不同,Job 管理的 Pod 在任務完成後不會被重新啟動。
Job 關鍵欄位
| 欄位 | 說明 | 預設值 |
|---|---|---|
completions |
需要成功完成的總次數 | 1 |
parallelism |
最大並行數 | 1 |
backoffLimit |
標記為 Failed 前的重試次數 | 6 |
activeDeadlineSeconds |
任務最大存活時間(秒) | 無限制 |
ttlSecondsAfterFinished |
完成後保留多久才自動清理 | 不清理 |
restartPolicy |
重啟策略(Job 只能用 Never 或 OnFailure) | - |
範例:串行與並行任務
串行執行:completions=2, parallelism=1(預設),表示執行完第一個才會啟動第二個。
apiVersion: batch/v1
kind: Job
metadata:
name: job-serial
spec:
completions: 2
parallelism: 1
backoffLimit: 3
activeDeadlineSeconds: 300
ttlSecondsAfterFinished: 3600
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: alpine:3.11
command: ["/bin/sh", "-c", "echo Processing...; sleep 60"]
並行執行:completions=12, parallelism=2,表示同時最多運行 2 個 Pod,總共需要成功 12 次。
apiVersion: batch/v1
kind: Job
metadata:
name: job-parallel
spec:
completions: 12
parallelism: 2 # 同時運行 2 個 Pod
backoffLimit: 3
activeDeadlineSeconds: 1200
ttlSecondsAfterFinished: 3600
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: alpine:3.11
command: ["/bin/sh", "-c", "echo Processing...; sleep 60"]
CronJob:週期性排程任務
CronJob 在 Job 的基礎上加入了時間排程功能,語法與 Linux 的 crontab 完全一致。CronJob 與 Job 的關係類似於 Deployment 與 ReplicaSet 的關係。
CronJob 關鍵欄位
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cronjob
spec:
schedule: "*/2 * * * *" # 每 2 分鐘執行一次(分 時 日 月 週)
concurrencyPolicy: Forbid # Allow/Forbid/Replace
failedJobsHistoryLimit: 1 # 保留失敗紀錄數
successfulJobsHistoryLimit: 3 # 保留成功紀錄數
startingDeadlineSeconds: 300 # 錯過排程的容許時間
jobTemplate:
spec:
completions: 1
parallelism: 1
ttlSecondsAfterFinished: 600
backoffLimit: 3
activeDeadlineSeconds: 60
template:
spec:
restartPolicy: OnFailure
containers:
- name: task
image: alpine
command: ["/bin/sh", "-c", "date; echo Hello from CronJob; sleep 10"]
concurrencyPolicy 的三個選項值得注意:
- Allow:允許並行,前一個還沒跑完就啟動下一個
- Forbid:禁止並行,前一個未完成則跳過本次排程
- Replace:取消前一個正在運行的 Job,啟動新的
StatefulSet:有狀態應用管理
StatefulSet 是專為有狀態應用設計的控制器,它保證每個 Pod 都有穩定且唯一的身份標識。這是 Deployment 無法提供的能力。
StatefulSet 的四大保證
- 穩定的持久化儲存:Pod 重新調度後仍能存取相同的 PVC
- 穩定的網路標識:Pod 名稱固定為
${STATEFULSET_NAME}-${ORDINAL}(如 web-0、web-1、web-2) - 有序部署:從序號 0 開始依序建立,前一個就緒後才建立下一個
- 有序縮容:從最大序號開始逆序刪除
StatefulSet 的強制依賴:Headless Service
StatefulSet 必須搭配 Headless Service(clusterIP: None)使用,這樣每個 Pod 都會獲得一個穩定的 DNS 記錄:${POD_NAME}.${SERVICE_NAME}.${NAMESPACE}.svc.cluster.local。
實戰範例
# Headless Service
apiVersion: v1
kind: Service
metadata:
name: demoapp-sts
spec:
clusterIP: None
ports:
- port: 80
selector:
app: demoapp
controller: sts-demo
---
# StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sts-demo
spec:
serviceName: demoapp-sts # 綁定 Headless Service
replicas: 2
selector:
matchLabels:
app: demoapp
controller: sts-demo
template:
metadata:
labels:
app: demoapp
controller: sts-demo
spec:
containers:
- name: demoapp
image: ikubernetes/demoapp:v1.0
ports:
- containerPort: 80
volumeMounts:
- name: appdata
mountPath: /app/data
volumeClaimTemplates: # 每個 Pod 自動建立獨立的 PVC
- metadata:
name: appdata
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "longhorn"
resources:
requests:
storage: 2Gi
觀察建立過程,會發現 Pod 是嚴格依序建立的:先完成 sts-demo-0 才會建立 sts-demo-1。
DNS 解析驗證
透過 nslookup 可以驗證每個 Pod 都有唯一的 DNS 記錄:
# 正向解析 - 列出所有 Pod IP
nslookup -query=A demodb.default.svc.cluster.local
# 反向解析 - 確認 IP 對應到特定 Pod
nslookup -query=PTR <Pod-IP>
# 結果:demodb-1.demodb.default.svc.cluster.local
擴容、縮容與更新
StatefulSet 的擴縮容和更新都遵循有序原則:
- 擴容:從最大序號 +1 開始,依序建立
- 縮容:從最大序號開始,逆序刪除
- 更新:逆序更新,先更新最大序號的 Pod
# 使用 patch 修改副本數
kubectl patch sts/demodb -p '{"spec":{"replicas":5}}' # 擴容
kubectl patch sts/demodb -p '{"spec":{"replicas":2}}' # 縮容
值得注意的是,StatefulSet 的更新策略支援 partition 參數,可以實現分區更新。例如設定 partition: 2 表示只更新序號 >= 2 的 Pod,這在灰度發佈有狀態應用時非常實用。
控制器選擇指南
| 場景 | 推薦控制器 | 原因 |
|---|---|---|
| Web 應用、API 服務 | Deployment | 無狀態,需要滾動更新與快速擴縮容 |
| 日誌/監控代理 | DaemonSet | 每個節點需要恰好一個副本 |
| 資料庫、訊息佇列 | StatefulSet | 需要穩定的網路標識和持久化儲存 |
| 資料處理、ETL | Job | 一次性任務,執行完即結束 |
| 定時備份、排程報表 | CronJob | 需要定期執行的任務 |
總結
K8s 提供了豐富的控制器來應對不同的工作負載場景。DaemonSet 解決了「每節點一個」的需求,Job 和 CronJob 處理批次與排程任務,而 StatefulSet 則為有狀態應用提供了穩定的身份與儲存保證。在實務中,選擇正確的控制器類型比調整控制器參數更重要,先搞清楚你的應用是無狀態還是有狀態、是長期運行還是一次性任務,再決定使用哪種控制器。

發佈留言