Deployment 解不了的兩個問題
到目前為止學的 Deployment、Service、DNS 都假設一件事:「我要 N 個 Pod」。
但工作上有兩個情境,Deployment 用起來很彆扭:
第一個情境用 replicas 死命湊數量,Scheduler 還可能把兩個 Pod 放在同一台 Node。
第二個情境用 Deployment 根本不對 — Deployment 的 Pod 跑完會被自動拉起,陷入無限重啟。
K8s 各自有專門的 workload 對應這兩個情境:DaemonSet 與 CronJob。
DaemonSet:每台 Node 一份
「Daemon」就是 Linux 的守護程序。DaemonSet 確保每個 Node 上剛好跑一個 Pod,不多不少。
┌─ Node 1 ──┐ ┌─ Node 2 ──┐ ┌─ Node 3 ──┐
│ log-agent │ │ log-agent │ │ log-agent │
└──────────┘ └──────────┘ └──────────┘
新 Node 加入叢集 → 自動建 Pod
Node 移除 → Pod 跟著消失
完全不用手動管理副本數。
典型應用:
- 日誌收集:Fluentd / Filebeat 收集每台機器上所有容器的 stdout
- 監控 agent:Prometheus Node Exporter 收 CPU / 記憶體
- 網路 plugin:kube-proxy 本身就是 DaemonSet
$ kubectl get daemonsets -n kube-system
NAME DESIRED CURRENT READY
kube-proxy 3 3 3 # 你的叢集 3 台 Node
DESIRED = 你的 Node 數量。K8s 自動算的,你不能(也不該)改。
DaemonSet YAML
apiVersion: apps/v1
kind: DaemonSet # ← 不是 Deployment
metadata:
name: log-collector
spec:
# ⚠️ 沒有 replicas!由 Node 數量決定
selector:
matchLabels:
app: log-collector
template:
metadata:
labels:
app: log-collector
spec:
containers:
- name: collector
image: busybox:1.36
command: ["sh", "-c", "while true; do echo \"[$(date)] $(hostname)\"; sleep 30; done"]
跟 Deployment 唯一的差別:kind: DaemonSet + 沒有 replicas。
部署後驗證:
$ kubectl apply -f daemonset.yaml
$ kubectl get pods -o wide -l app=log-collector
NAME STATUS NODE
log-collector-abc12 Running k3d-cluster-server-0
log-collector-def34 Running k3d-cluster-agent-0
log-collector-ghi56 Running k3d-cluster-agent-1
每個 Node 上剛好一個 Pod,沒例外。
CronJob:K8s 版的 crontab
如果你寫過 Linux crontab,CronJob 對你就是直覺的延伸。
CronJob = Cron 排程 + Job
Job 在 K8s 裡是「跑一次就結束」的 workload。CronJob 按時程定時建立 Job。
CronJob YAML
apiVersion: batch/v1 # ← 不是 apps/v1
kind: CronJob
metadata:
name: hello-cron
spec:
schedule: "*/1 * * * *" # 每分鐘
jobTemplate:
spec:
template:
spec:
restartPolicy: Never # ← 必填,不能是 Always
containers:
- name: hello
image: busybox:1.36
command: ["sh", "-c", "echo 'Hello!'; date"]
幾個關鍵:
apiVersion: batch/v1(不是 apps/v1,跟 Deployment 不同 group)schedule 用 5 欄位 cron 語法:分 / 時 / 日 / 月 / 週restartPolicy: Never 或 OnFailure(不能 Always,否則跑完又重啟)CronJob → jobTemplate → template → containers(多了一層 Job)常用 schedule
| 語法 | 意思 |
|---|---|
*/1 * * * * | 每分鐘 |
*/5 * * * * | 每 5 分鐘 |
0 * * * * | 每小時整點 |
0 3 * * * | 每天凌晨 3 點 |
0 0 * * 0 | 每週日午夜 |
觀察 CronJob 運作
$ kubectl apply -f cronjob.yaml
$ kubectl get cronjobs
NAME SCHEDULE LAST SCHEDULE
hello-cron */1 * * * * <none> # 還沒到時間
# 等一分鐘
$ kubectl get jobs
NAME COMPLETIONS AGE
hello-cron-28503210 1/1 45s # 跑出第一個 Job
$ kubectl get pods
NAME STATUS
hello-cron-28503210-xkz9p Completed # ← 不是 Running!
$ kubectl logs hello-cron-28503210-xkz9p
Hello!
Sun Apr 27 03:00:01 UTC 2026
重點:CronJob 的 Pod 狀態是 Completed 不是 Running。新手常以為出錯,其實這就是正常的 — 任務跑完就該結束。
K8s 預設保留最近 3 個成功 Job + 1 個失敗 Job。要調整用:
spec:
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
三者比較
| 項目 | Deployment | DaemonSet | CronJob |
|---|---|---|---|
| 副本數 | 你指定 replicas | 每 Node 一個(自動) | 每次觸發一個 |
| Pod 狀態 | 長期 Running | 長期 Running | 跑完 Completed |
| 用途 | 無狀態應用 | 節點級服務 | 定時任務 |
有 replicas? | ✅ | ❌ | ❌ |
| API group | apps/v1 | apps/v1 | batch/v1 |
對照 Docker
| Docker | Kubernetes |
|---|---|
docker run 在每台機器手動跑 | DaemonSet 自動每台一份 |
宿主機 crontab + docker exec | CronJob |
Compose restart: 'no' 一次性容器 | Job(CronJob 內層) |
踩坑:Pod 是 Completed 不是 Running
CronJob 最常被誤判:
$ kubectl get pods
NAME STATUS
hello-cron-... Completed # ← 正常!
hello-cron-... Error # ← 真的有問題
Completed = 任務成功跑完,正確結果。Error / CrashLoopBackOff = 真的有問題,看 logs。
重點整理
- DaemonSet:每 Node 一個,沒有
replicas。日誌、監控 agent 用這個。 - CronJob:定時跑一次,
apiVersion: batch/v1。 - CronJob 的 Pod 跑完是
Completed不是Running。 restartPolicy: Never必填,不能 Always。- 三者不重疊:應用 / 節點 / 排程,各管各的。
下一步
到第五堂結尾,你已經能用 K8s 部署完整的微服務架構:Deployment + Service + DNS。
但從外面進來還只能用 NodePort Node IP:30080。生產環境要乾淨的網域路徑(/api / /admin),就要用 Ingress 把 Service 串起來。