iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
Kubernetes

不是背 YAML!30 天從零打造 Kubernetes 微服務:從本機實戰一路到 CKA系列 第 20 篇

Day 20|不是所有 Pod 都要永遠活著:Job、CronJob 與 DaemonSet

  • 分享至 

  • xImage
  •  

前面我們部署 FastAPI、Redis、PostgreSQL 時,通常希望它們一直保持運作。以 FastAPI 為例,只要服務還在線上,它的 Process 理論上就不應該自己結束。

這正是 Deployment 適合處理的工作。

Deployment 的核心想法是:

我希望指定數量的 Pod 持續處於可工作的狀態。

因此如果你建立:

replicas: 3

Kubernetes 就會盡量維持三個 Pod。

而且 Deployment 建立的 Pod,其 restartPolicy 是 Always。如果 FastAPI Container 一直執行完就退出,即使 Exit Code 是 0,Kubernetes 仍然會再次啟動它。假如不停「啟動 → 結束 → 啟動 → 結束」,最後甚至可能看到 CrashLoopBackOff。

但問題是,有些程式本來就應該執行完後結束。

例如:

Database Migration
資料批次處理
產生每日/月報
資料庫 Backup
寄送通知
一次性的初始化工作

假設 Migration 成功跑完:

python migrate.py

然後 Process 正常 Exit Code 0,這不是故障,而是:

工作完成了。

這就是為什麼 Kubernetes 除了 Deployment 之外,還需要 Job、CronJob、DaemonSet。

這一章最重要的觀念不是背:

Job
CronJob
DaemonSet

而是學會問:

這個 Controller 想維持的 Desired State 到底是什麼?


開始之前:確認實驗環境

這一章不需要額外安裝任何 Kubernetes Plugin。

只要先確認:

kubectl get nodes

能正常看到:

NAME           STATUS   ROLES
controlplane   Ready    control-plane
node01         Ready    <none>

就可以繼續。

我們下面都使用:

cka-lab

這個 Namespace。

先確認它存在:

kubectl get namespace cka-lab

如果出現:

NotFound

就建立它:

kubectl create namespace cka-lab

後面所有 Job、CronJob、DaemonSet 都放在這裡。


一、Job:我要的是「成功做完一次」

Job 最適合拿來執行:

做完就可以結束

的工作。

例如現在建立一個非常簡單的 Job:

kubectl create job \
  hello-job \
  -n cka-lab \
  --image=busybox:1.36 \
  -- \
  sh -c 'date; echo "Ironman Job completed"'

這裡可以拆開理解。

kubectl create job hello-job

表示建立一個叫做:

hello-job

的 Job。

接著:

--image=busybox:1.36

代表 Pod 裡面的 Container 使用 BusyBox。

最後的:

-- sh -c '...'

就是 Container 真正要執行的 Command:

date
echo "Ironman Job completed"

也就是印出目前時間與一行文字,然後程式結束。

建立後查看 Job:

kubectl get jobs -n cka-lab

看到:

NAME        STATUS     COMPLETIONS   DURATION
hello-job   Complete   1/1           3s

這裡的:

1/1

非常重要。

意思是:

需要成功完成:1 次
目前成功完成:1 次

所以這個 Job 已經完成自己的任務。


Job 底下其實還是 Pod

Job 自己不會直接執行 Container。

它實際上的關係是:

Job
 │
 ▼
Pod
 │
 ▼
Container

所以再查看:

kubectl get pods -n cka-lab

你會發現一個類似:

hello-job-xxxxx

的 Pod,而且它的狀態可能是:

Completed

https://ithelp.ithome.com.tw/upload/images/20260921/20168537MEIjUAXw1o.png

以前學 Deployment 的時候,看到 Pod 沒有 Running,可能直覺會覺得:

Pod 掛掉了嗎?

但對 Job 完全不是這樣。

Job 的 Desired State 本來就是:

工作成功完成指定次數。

因此:

Completed

反而就是我們想看到的結果。Kubernetes 官方對 Job 的定義也是如此:一般 Job 在 Pod 成功結束後,就會被視為完成。Job 的 Pod 只能使用 restartPolicy: Never 或 OnFailure。


查看 Job 執行結果

要看程式到底輸出了什麼,當然可以先查 Pod:

kubectl get pods -n cka-lab

再:

kubectl logs hello-job-xxxxx -n cka-lab

但其實有更方便的方法:

kubectl logs job/hello-job -n cka-lab

這裡直接指定:

job/hello-job

kubectl 會幫你找到這個 Job 對應的 Pod。

就可以看到:
https://ithelp.ithome.com.tw/upload/images/20260921/20168537zG7l4tYldz.png

這種 Resource-oriented 的 kubectl 用法很值得熟悉。

例如之後你也會常看到:

kubectl describe job hello-job -n cka-lab

如果 Job 沒成功,第一步通常就是:

kubectl get pods -n cka-lab
kubectl logs job/hello-job -n cka-lab
kubectl describe job hello-job -n cka-lab

再搭配:

kubectl get events -n cka-lab --sort-by=.metadata.creationTimestamp

排查到底是 Image、Command、Scheduling 還是程式本身出了問題。


Job 失敗時會怎麼樣?

假設 Container 執行:

exit 1

代表程式失敗。

Job 並不會看到第一次失敗就直接放棄,而是可以重新嘗試。這和:

工作執行完、Exit 0

完全不同。

Exit Code 0:

成功

非 0:

失敗

Job 會根據像 backoffLimit 這類設定控制失敗後可以重新嘗試多少次。

所以 Job 的核心不是:

Pod 必須一直 Running。

而是:

最後這個工作到底有沒有成功完成。


二、CronJob:定時產生 Job

Job 解決的是:

執行一次工作。

但假設今天需求變成:

每天凌晨兩點 Backup Database

或者:

每五分鐘產生一次報表

總不能每天人工:

kubectl create job ...

這時就需要 CronJob。

CronJob 可以理解成:

Job + Scheduler

它自己主要負責:

到了指定時間,建立一個 Job。

而真正執行程式的仍然是 Job 產生的 Pod。官方文件也是以 CronJob 定期建立 Job 的方式運作。


建立第一個 CronJob

建立一個檔案:

vim report-cronjob.yaml

內容如下:

apiVersion: batch/v1
kind: CronJob

metadata:
  name: report-job
  namespace: cka-lab

spec:
  schedule: "*/5 * * * *"

  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never

          containers:
            - name: report
              image: busybox:1.36
              command:
                - sh
                - -c
                - 'date; echo "Generating report"'

存檔後執行:

kubectl apply -f report-cronjob.yaml

確認:

kubectl get cronjob -n cka-lab

也可以縮寫:

kubectl get cj -n cka-lab

https://ithelp.ithome.com.tw/upload/images/20260921/20168537ZDjSkKFqcl.png


*/5 * * * * 到底是什麼?

CronJob 的:

schedule: "*/5 * * * *"

使用的是 Cron 格式。

五個欄位依序代表:

分鐘  小時  日期  月份  星期

所以:

*/5 * * * *

意思就是:

每五分鐘執行一次

注意 Kubernetes 這裡不是每五「秒」,CronJob 一般使用分鐘作為最小排程單位。

例如:

0 * * * *

代表:

每個小時的第 0 分鐘

而:

0 2 * * *

代表:

每天 02:00

CronJob 最重要的 Controller 關係

這裡很容易搞混。

CronJob 並不是直接管理 Pod。

完整關係其實是:

CronJob
   │
   │ 到時間
   ▼
 Job
   │
   │ 建立
   ▼
 Pod
   │
   │ 執行程式
   ▼
Completed

所以 Troubleshooting CronJob 時,一定要先知道:

我現在是在排查哪一層?

假設你發現:

怎麼完全沒有 Pod?

不要第一時間只盯著 Pod。

先看:

kubectl get cronjob -n cka-lab

再看:

kubectl get jobs -n cka-lab

最後才看:

kubectl get pods -n cka-lab

如果:

CronJob 存在
↓
但 Job 完全沒有建立

問題比較可能在 Cron Schedule 或 CronJob Controller 這一層。

如果:

Job 已建立
↓
Pod 失敗

問題就比較可能在 Job Template、Image 或 Container Command。

理解 Controller 關係,比死背指令重要很多。


不想真的等五分鐘怎麼辦?

這是非常實用的技巧。

假設:

schedule: "*/5 * * * *"

你剛 Apply 完,不想坐著等五分鐘測試。

可以直接從這個 CronJob 建立一次 Job:

kubectl create job \
  --from=cronjob/report-job \
  report-manual \
  -n cka-lab

這裡的意思是:

不要等 CronJob 排程。

直接拿 report-job 裡面的 jobTemplate,
現在立刻建立一個叫 report-manual 的 Job。

這個功能是 kubectl 原生支援的。

接著:

kubectl get jobs -n cka-lab

應該會看到:

report-manual

查看結果:

kubectl logs job/report-manual -n cka-lab

如果看到:

Generating report

表示 Job Template 本身可以正常工作。

這在 Troubleshooting 非常有用,因為可以把問題拆開:

Job Template 有沒有問題?

以及:

Cron Schedule 有沒有問題?

不用兩個混在一起查。


三、DaemonSet:不是問 replicas,而是問 Node

DaemonSet 和 Job、CronJob 的概念又完全不同。

Deployment 通常問:

我要幾份 Pod?

例如:

replicas: 3

就是:

我要三個 Pod。

至於這三個 Pod 到底放在哪些 Node,再交給 Scheduler 處理。

DaemonSet 則不是這種思維。

它問的是:

哪些 Node 上應該各存在一份這個 Pod?

假設 Cluster 有:

worker1
worker2
worker3

你希望每台機器都有 Log Agent:

worker1
└── log-agent

worker2
└── log-agent

worker3
└── log-agent

這就是非常典型的 DaemonSet。

Kubernetes 官方也將 DaemonSet 定義為確保指定 Node 上執行一份 Pod 的 Controller。

常見情境包括:

Log Agent
Monitoring Agent
CNI Network Plugin
Storage Agent
Security Agent

因為這些程式通常不是:

整個 Cluster 隨便跑三份就好。

而是:

每台機器都需要一份。

實際建立 DaemonSet

建立:

vim node-agent.yaml

輸入:

apiVersion: apps/v1
kind: DaemonSet

metadata:
  name: node-agent
  namespace: cka-lab

spec:
  selector:
    matchLabels:
      app: node-agent

  template:
    metadata:
      labels:
        app: node-agent

    spec:
      containers:
        - name: agent
          image: busybox:1.36
          command:
            - sh
            - -c
            - 'while true; do echo "agent alive"; sleep 30; done'

然後:

kubectl apply -f node-agent.yaml

這個 Container 跟剛剛 Job 最大的差別之一,在於 Command:

while true

也就是無限迴圈:

印 agent alive
↓
睡 30 秒
↓
再印一次
↓
再睡 30 秒
↓
一直繼續

所以它本身就是一個應該長期執行的 Agent。


觀察 DaemonSet

查看:

kubectl get daemonset -n cka-lab

可以縮寫:

kubectl get ds -n cka-lab

看到:
https://ithelp.ithome.com.tw/upload/images/20260921/20168537L6jgKsi94A.png

接著最重要的是:

kubectl get pods \
  -n cka-lab \
  -l app=node-agent \
  -o wide

-o wide 很重要,因為它會額外顯示:

NODE

例如:

https://ithelp.ithome.com.tw/upload/images/20260921/20168537XTu4qB1Bch.png

這時你就可以直接確認:

DaemonSet 到底在哪些 Node 上建立了 Pod?


為什麼 Control Plane 上可能沒有 Pod?

假設你的 Cluster 是:

cka-lab-control-plane
cka-lab-worker
cka-lab-worker2

你可能原本以為:

目前有三個 Node
=
DaemonSet 應該有三個 Pod

結果卻只有:

cka-lab-worker
cka-lab-worker2

兩個 Pod。

這很可能是因為 Control Plane Node 有 Taint。

查看:

kubectl describe node cka-lab-control-plane

找到:

Taints:

在 kubeadm 建立的 Cluster 中,Control Plane 通常存在:

node-role.kubernetes.io/control-plane:NoSchedule

https://ithelp.ithome.com.tw/upload/images/20260921/20168537iatRIOiD29.png
這是 kubeadm 用來避免一般 Workload 被排到 Control Plane 上的 Taint。

因此普通 DaemonSet 不一定能進去。

這正好把之前學過的:

Taint
+
Toleration

重新串起來。


如果連 Control Plane 也需要 Node Agent,該怎麼做?

假設你的需求真的就是:

每台機器都要收集 Log,
連 Control Plane 也不能漏掉。

這種情況不要急著把 Node Taint 刪掉。

比較合理的方法是在 DaemonSet Pod Template 裡加入對應的 Toleration:

vim node-agent.yaml
spec:
  containers:
    - name: agent
      image: busybox:1.36
      command:
        - sh
        - -c
        - 'while true; do echo "agent alive"; sleep 30; done'

  tolerations:
    - key: node-role.kubernetes.io/control-plane
      operator: Exists
      effect: NoSchedule

然後重新:

kubectl apply -f node-agent.yaml

再觀察:

kubectl get pods \
  -n cka-lab \
  -l app=node-agent \
  -o wide

如果環境符合條件,就可能變成:

NAME               NODE
node-agent-xxxxx   cka-lab-control-plane
node-agent-yyyyy   cka-lab-worker
node-agent-zzzzz   cka-lab-worker2

https://ithelp.ithome.com.tw/upload/images/20260921/201685370WYxnqr21Y.png

也就是:

controlplane
└── node-agent

node01
└── node-agent

這才真正呈現 DaemonSet 的概念。

要注意,DaemonSet 本身會自動帶有部分與 Node 狀態有關的 Toleration,例如 not-ready、unreachable 等,但 control-plane:NoSchedule 並不是因此就可以一律忽略;若 Control Plane 有這個 Taint,而你又需要 Agent 跑上去,就應該明確處理它。


最後把四種 Workload Controller 串起來

學 Kubernetes Controller,建議不要用:

Deployment 是什麼?
Job 是什麼?
CronJob 是什麼?
DaemonSet 是什麼?

這種死背方式。

直接從 Desired State 思考會清楚很多:

Controller Kubernetes 想維持的狀態
Deployment 我希望有指定數量的 Pod 持續運作
Job 我希望這個工作成功完成
CronJob 我希望在指定時間建立 Job
DaemonSet 我希望符合條件的 Node 各自有一份 Pod

例如看到一個程式:

FastAPI Web Server

它應該一直提供服務:

Deployment

看到:

python migrate.py

Migration 成功做完就結束:

Job

看到:

每天凌晨 02:00 備份資料庫

就是:

CronJob

看到:

每台 Node 都需要安裝 Log Collector

就是:

DaemonSet

這樣你不是在背 Kubernetes 名詞,而是在判斷:

這個程式正常的生命週期到底長什麼樣子?


一組指令快速複習

最後實際確認三種 Resource:

kubectl get jobs -n cka-lab
kubectl get cronjobs -n cka-lab
kubectl get ds -n cka-lab

看所有相關 Pod:

kubectl get pods -n cka-lab -o wide

Job 看 Log:

kubectl logs job/hello-job -n cka-lab

手動測 CronJob:

kubectl create job \
  --from=cronjob/report-job \
  report-manual \
  -n cka-lab

確認 DaemonSet 分布在哪些 Node:

kubectl get pods \
  -n cka-lab \
  -l app=node-agent \
  -o wide

如果做完實驗想全部清掉:

kubectl delete job hello-job report-manual -n cka-lab
kubectl delete cronjob report-job -n cka-lab
kubectl delete daemonset node-agent -n cka-lab

理解完這章之後,其實下一個問題就會自然出現:

前面 FastAPI 使用 Deployment 很合理,因為 Web Server 本來就是:

掛了重建
Pod 換一台通常也沒關係

但 PostgreSQL 不太一樣。

如果:

postgres-abc

死掉後 Kubernetes 隨便建立:

postgres-xyz

那資料在哪裡?Pod 名稱改變有沒有影響?如果未來有三台 PostgreSQL,每一台的身分與 Storage 又要怎麼維持?

這就是下一個 Controller:

StatefulSet

要解決的核心問題。


上一篇
Day 19|HPA 實戰:讓 Kubernetes 看 CPU 自動從 1 個 Pod 擴到 5 個
下一篇
Day 21|StatefulSet、StorageClass 與 PV:為什麼 Database 不只是另一個 Deployment?
系列文
不是背 YAML!30 天從零打造 Kubernetes 微服務:從本機實戰一路到 CKA 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言