DaemonSet, Job, CronJob
Mục tiêu bài học
Sau 25 phút này, bạn phải làm được 4 việc sau mà không cần tra tài liệu:
- Nhìn yêu cầu đề bài và chọn đúng loại workload: DaemonSet, Job hay CronJob.
- Viết được YAML DaemonSet chạy trên mọi node, kể cả control plane.
- Cấu hình Job với
completions,parallelism,backoffLimitvà kiểm tra nó hoàn thành. - Viết CronJob với
schedule,concurrencyPolicy, history limits, và test nó mà không phải ngồi chờ.
Ôn nhanh bài trước
Bài trước bạn đã nắm Deployment: nó quản lý ReplicaSet và đảm bảo luôn có N pod chạy mãi, có rolling update và rollback. Nhưng Deployment không trả lời được 3 dạng yêu cầu quen thuộc trong đề CKA:
- "chạy đúng một pod trên mỗi node" → DaemonSet
- "chạy cho đến khi hoàn thành rồi dừng" → Job
- "chạy lặp lại theo lịch mỗi X phút" → CronJob
| Tiêu chí | Deployment | DaemonSet | Job | CronJob |
|---|---|---|---|---|
| Số pod | theo replicas |
1 pod mỗi node | theo completions |
tạo Job theo lịch |
| Pod có kết thúc không | Không | Không | Có | Có |
| Đơn vị điều khiển | ReplicaSet | node | số lần hoàn thành | schedule |
| Dùng cho | app stateless | agent, log, CNI | batch, migration, backup một lần | backup, report định kỳ |
| Lệnh imperative | kubectl create deployment |
không có | kubectl create job |
kubectl create cronjob |
flowchart TD
Start["Đọc yêu cầu đề"] --> Q1{"Pod phải chạy<br/>trên mọi node?"}
Q1 -->|Có| DS["🖥️ DaemonSet"]
Q1 -->|Không| Q2{"Pod phải chạy<br/>đến khi xong?"}
Q2 -->|Không| DEP["♻️ Deployment"]
Q2 -->|Có| Q3{"Lặp lại<br/>theo lịch?"}
Q3 -->|Có| CJ["⏰ CronJob"]
Q3 -->|Không| JOB["✅ Job"]
[!NOTE] Ba từ khóa quyết định trong đề: "every node"/"each node" → DaemonSet, "completion"/"process"/"runs N times" → Job, "every minute"/"schedule"/"daily" → CronJob.
Ví dụ mẫu
Ví dụ 1: DaemonSet chạy trên mọi node, kể cả control plane
Đề kiểu: "Create a DaemonSet named log-agent using image busybox:1.36 that runs on all nodes including the control plane. Command: sh -c "while true; do echo log; sleep 30; done"."
Điểm mấu chốt: không có kubectl create daemonset. Mẹo nhanh nhất là sinh YAML từ Deployment rồi sửa.
1. Sinh YAML nháp: `kubectl create deployment log-agent --image=busybox:1.36 --dry-run=client -o yaml > ds.yaml`
2. Sửa file: đổi `kind: Deployment` thành `kind: DaemonSet`, xoá `spec.replicas`, xoá `spec.strategy`, xoá `status: {}`.
3. Thêm `tolerations` cho taint của control plane vào `spec.template.spec`.
4. Apply: `kubectl apply -f ds.yaml`
5. Kiểm tra: `kubectl get ds log-agent` với cột DESIRED phải bằng số node, rồi `kubectl get pods -o wide -l app=log-agent`
Kết quả YAML hoàn chỉnh:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-agent
spec:
selector:
matchLabels:
app: log-agent
template:
metadata:
labels:
app: log-agent
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: log-agent
image: busybox:1.36
command: ["sh", "-c", "while true; do echo log; sleep 30; done"]
Điều quan trọng nhất của bài này: DaemonSet tự động thêm toleration cho các taint hệ thống (node.kubernetes.io/not-ready, unreachable, disk-pressure, memory-pressure, pid-pressure, và unschedulable), nhưng ==nó không tự tolerate taint node-role.kubernetes.io/control-plane==. Đề nào yêu cầu "including control plane" thì bạn phải tự thêm toleration như trên.
Ví dụ 2: Job có nhiều lần hoàn thành
Đề kiểu: "Create a Job named report that completes 4 times, running 2 pods in parallel, image busybox:1.36, restartPolicy Never, backoffLimit 3."
1. Sinh YAML nháp: `kubectl create job report --image=busybox:1.36 --dry-run=client -o yaml -- sh -c "echo processing; sleep 5" > job.yaml`
2. Thêm `completions: 4`, `parallelism: 2`, `backoffLimit: 3` vào `spec`.
3. Apply: `kubectl apply -f job.yaml`
4. Theo dõi: `kubectl get job report -w` cho tới khi COMPLETIONS hiện `4/4`
apiVersion: batch/v1
kind: Job
metadata:
name: report
spec:
completions: 4
parallelism: 2
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: report
image: busybox:1.36
command: ["sh", "-c", "echo processing; sleep 5"]
Lưu ý: kubectl create job đã tự set restartPolicy: Never cho bạn, không cần sửa. Nhưng nếu bạn tự viết YAML từ đầu thì restartPolicy bắt buộc là Never hoặc OnFailure, đặt Always sẽ bị API server từ chối.
Ví dụ 3: CronJob chạy mỗi 2 phút
1. Sinh YAML nháp: `kubectl create cronjob backup --image=busybox:1.36 --schedule="*/2 * * * *" --dry-run=client -o yaml -- sh -c "date; echo backup done" > cj.yaml`
2. Thêm `concurrencyPolicy: Forbid` và `successfulJobsHistoryLimit: 2`.
3. Apply: `kubectl apply -f cj.yaml`
4. Test ngay không cần chờ: `kubectl create job --from=cronjob/backup manual-run-1`
5. Kiểm tra: `kubectl get cj`, `kubectl get jobs`, `kubectl get pods`
apiVersion: batch/v1
kind: CronJob
metadata:
name: backup
spec:
schedule: "*/2 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 1
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: busybox:1.36
command: ["sh", "-c", "date; echo backup done"]
Schedule dùng 5 field: phút giờ ngày-trong-tháng tháng ngày-trong-tuần. Mặc định tính theo UTC. Nếu đề cho giờ Việt Nam và cluster là bản mới, dùng thêm timeZone: "Asia/Ho_Chi_Minh".
Luyện tập có hướng dẫn
Làm từng bài trong lab, tự bấm giờ khoảng 2-3 phút mỗi bài. Chỉ mở đáp án sau khi đã thử.
Bài 1. Tạo DaemonSet tên node-monitor, image busybox:1.36, command ["sh", "-c", "while true; do date; sleep 20; done"], chạy trên tất cả node gồm cả control plane. Kiểm tra cột DESIRED.
Đáp án gợi ý
kubectl create deployment node-monitor --image=busybox:1.36 --dry-run=client -o yaml > ds.yaml
# sửa: kind: DaemonSet, xoá replicas/strategy, thêm tolerations
kubectl apply -f ds.yaml
kubectl get ds node-monitor
kubectl get pods -o wide -l app=node-monitor
DESIRED phải bằng tổng số node trong kubectl get nodes. Nếu thiếu control plane thì bạn quên toleration node-role.kubernetes.io/control-plane.
Bài 2. Đổi image của node-monitor sang busybox:1.37 và theo dõi rollout.
Đáp án gợi ý
kubectl set image ds/node-monitor node-monitor=busybox:1.37
kubectl rollout status ds/node-monitor
kubectl get ds node-monitor -o wide
DaemonSet cũng hỗ trợ rollout status và rollout undo. Nếu updateStrategy là OnDelete, đổi image sẽ không tự thay pod, bạn phải tự xoá từng pod.
Bài 3. Tạo Job countdown image busybox:1.36, command in ra số rồi sleep, completions: 5, parallelism: 2, backoffLimit: 4, restartPolicy: Never. Chờ đến khi xong và đọc log của một pod.
Đáp án gợi ý
kubectl create job countdown --image=busybox:1.36 --dry-run=client -o yaml \
-- sh -c "echo running; sleep 5" > job.yaml
# thêm completions: 5, parallelism: 2, backoffLimit: 4
kubectl apply -f job.yaml
kubectl get job countdown -w
kubectl logs job/countdown
kubectl get pods -l job-name=countdown
Sau khi đạt 5/5, Job giữ nguyên các pod đã Completed. Xoá bằng kubectl delete job countdown.
Bài 4. Tạo CronJob hello chạy mỗi phút một lần, image busybox:1.36, command echo hello from cronjob, concurrencyPolicy: Replace. Test ngay bằng cách trigger thủ công thay vì chờ.
Đáp án gợi ý
kubectl create cronjob hello --image=busybox:1.36 --schedule="*/1 * * * *" \
--dry-run=client -o yaml -- sh -c "echo hello from cronjob" > cj.yaml
# thêm concurrencyPolicy: Replace
kubectl apply -f cj.yaml
kubectl create job --from=cronjob/hello hello-manual-1
kubectl get jobs
kubectl logs job/hello-manual-1
--from=cronjob/... là mẹo ăn điểm: bạn kiểm chứng jobTemplate chạy đúng mà không phải chờ đến phút tiếp theo.
Bài 5. Một CronJob có schedule: "*/1 * * * *" nhưng 5 phút trôi qua vẫn không thấy Job mới nào. Nêu 4 nguyên nhân cần kiểm tra, theo thứ tự.
Đáp án gợi ý
kubectl get cj <name> -o yaml
kubectl describe cj <name>
suspend: truetrong spec.concurrencyPolicy: Forbidvà Job của lần chạy trước vẫn đang chạy, nên lần mới bị bỏ qua.- Schedule sai định dạng (6 field kiểu có seconds, hoặc gõ
* /1có dấu cách). startingDeadlineSecondsquá nhỏ, controller bỏ qua vì trễ hạn; hoặc controller-manager trên control plane không chạy.
Lỗi thường gặp và cách xử lý
| Triệu chứng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
kubectl get ds có DESIRED nhỏ hơn số node |
Thiếu toleration cho taint của control plane hoặc taint riêng của node | Thêm tolerations với operator: Exists, kiểm tra kubectl describe node phần Taints |
| Apply DaemonSet báo lỗi field lạ | Còn sót spec.replicas hoặc spec.strategy do copy từ Deployment |
Xoá hai field đó khỏi YAML |
Job báo valid values: OnFailure, Never |
restartPolicy: Always do tự viết YAML hoặc sửa từ Pod template |
Đổi sang Never hoặc OnFailure |
| Job không bao giờ đạt completions, pod Failed liên tục | Command trả exit code khác 0, vượt backoffLimit (mặc định 6) |
Sửa command, hoặc tăng backoffLimit, xem kubectl logs và kubectl describe pod |
kubectl apply sửa Job báo field is immutable |
spec.template của Job không sửa được sau khi tạo |
kubectl delete job <name> rồi apply lại |
| Xoá pod của DaemonSet nhưng pod quay lại | Đúng thiết kế, DaemonSet luôn đảm bảo 1 pod mỗi node | Không cần xử lý, đó là hành vi mong đợi |
| CronJob không tạo Job | suspend: true, Forbid đang chặn, hoặc schedule sai |
Xem Bài 5 ở trên |
| Giờ chạy lệch 7 tiếng | CronJob mặc định dùng UTC | Thêm timeZone: "Asia/Ho_Chi_Minh" (cần cluster 1.27+) hoặc tự quy đổi giờ |
[!WARNING] Đừng set
ttlSecondsAfterFinishedbừa trong lúc luyện thi. Nếu đề yêu cầu kiểm tra log hoặc trạng thái Job sau khi hoàn thành, Job bị tự xoá sẽ khiến bạn mất điểm oan.
Góc kinh nghiệm thi
- Không có lệnh imperative cho DaemonSet. Đây là bẫy tiết kiệm thời gian lớn nhất:
kubectl create deployment ... --dry-run=client -o yamlrồi sửakindluôn nhanh hơn gõ YAML tay. Sửa bằngvimvài dòng là xong. - Có lệnh cho Job và CronJob, dùng luôn:
kubectl create job,kubectl create cronjob. Nhớ đặt--dry-run=client -o yaml > file.yamlđể có base rồi mới thêmcompletions,parallelism,concurrencyPolicy. - Luôn kiểm tra sau khi apply bằng đúng cột đề quan tâm:
kubectl get ds(DESIRED),kubectl get job(COMPLETIONS),kubectl get cj(SCHEDULE, SUSPEND, ACTIVE). Giám khảo chấm trạng thái object, không chấm file YAML của bạn. - Test CronJob bằng
--from=cronjob/<name>thay vì chờ. Mỗi phút chờ là một phút mất điểm ở bài khác. - Rule of thumb đọc đề: thấy "on every node" hoặc "agent" thì nghĩ DaemonSet; thấy "process", "compute", "runs to completion", "successful" thì nghĩ Job; thấy "every N minutes", "daily", "schedule" thì nghĩ CronJob.
- Schedule hay gõ sai:
*/5 * * * *là mỗi 5 phút.0 */2 * * *là mỗi 2 giờ. Luôn đặt trong dấu ngoặc kép để shell không nuốt dấu*. - Nhớ
kubectl explainkhi quên tên field:kubectl explain cronjob.spec.concurrencyPolicy,kubectl explain daemonset.spec.template.spec.tolerations.
Tóm tắt và bước tiếp theo
Bốn điều cần mang ra khỏi bài này:
- DaemonSet = 1 pod mỗi node, không có
replicas, không có lệnh imperative, và không tự tolerate taint của control plane. - Job = chạy đến khi đủ
completions, điều tiết bằngparallelism, giới hạn retry bằngbackoffLimit(mặc định 6),restartPolicychỉ đượcNeverhoặcOnFailure. - CronJob = schedule 5 field theo UTC,
concurrencyPolicymặc địnhAllow, giữ 3 Job thành công và 1 Job thất bại gần nhất. - Job là immutable: muốn đổi pod template thì xoá rồi tạo lại.
Tự kiểm tra nhanh bằng các câu điền khuyết sau:
1. DaemonSet đảm bảo có đúng {{c1::một pod}} trên mỗi node, nên nó không có trường replicas.
2. Job chỉ chấp nhận restartPolicy là {{c2::Never hoặc OnFailure}}, đặt Always sẽ bị API server từ chối.
3. CronJob mặc định tính giờ theo múi {{c3::UTC}}, nên schedule giờ Việt Nam cần thêm timeZone.
4. Đặt concurrencyPolicy: {{c4::Forbid}} để CronJob bỏ qua lần chạy mới khi Job trước còn chạy.
5. Không có lệnh imperative nào tạo được {{c5::DaemonSet}}, phải viết YAML hoặc sửa từ Deployment.
6. updateStrategy: {{c6::OnDelete}} khiến DaemonSet chỉ thay pod khi bạn tự xoá pod.
Bộ flashcard để ôn lại sau:
[
{ "front": "DaemonSet đảm bảo điều gì?", "back": "Đúng một pod chạy trên mỗi node (hoặc mỗi node khớp selector)." },
{ "front": "DaemonSet có trường replicas không?", "back": "Không. Số pod do số node quyết định." },
{ "front": "DaemonSet tự tolerate những taint hệ thống nào?", "back": "not-ready, unreachable, disk-pressure, memory-pressure, pid-pressure và unschedulable." },
{ "front": "DaemonSet có tự tolerate taint node-role.kubernetes.io/control-plane không?", "back": "Không. Muốn chạy trên control plane phải tự thêm toleration với operator Exists." },
{ "front": "Có lệnh kubectl create daemonset không?", "back": "Không có. Phải viết YAML hoặc sửa từ Deployment dry-run." },
{ "front": "updateStrategy nào khiến DaemonSet chỉ thay pod khi bạn xoá pod?", "back": "OnDelete (mặc định là RollingUpdate)." },
{ "front": "Job chấp nhận restartPolicy nào?", "back": "Chỉ Never hoặc OnFailure." },
{ "front": "Giá trị mặc định của completions và parallelism trong Job?", "back": "Cả hai đều mặc định là 1." },
{ "front": "backoffLimit mặc định của Job là bao nhiêu?", "back": "6." },
{ "front": "Sửa spec.template của Job đã tồn tại thì sao?", "back": "Báo lỗi field is immutable, phải xoá Job rồi tạo lại." },
{ "front": "CronJob dùng định dạng schedule mấy field và múi giờ nào?", "back": "5 field (phút giờ ngày tháng thứ), mặc định UTC." },
{ "front": "Ba giá trị của concurrencyPolicy trong CronJob?", "back": "Allow (mặc định), Forbid, Replace." },
{ "front": "Giá trị mặc định của successfulJobsHistoryLimit và failedJobsHistoryLimit?", "back": "3 và 1." },
{ "front": "Làm sao test CronJob ngay mà không chờ lịch?", "back": "kubectl create job <ten> --from=cronjob/<ten-cronjob>." },
{ "front": "Field nào tạm dừng một CronJob?", "back": "spec.suspend: true." }
]
Bài tiếp theo chuyển sang Resource requests, limits và QoS class: cách khai báo tài nguyên cho các pod bạn vừa tạo, cách scheduler dùng requests để xếp pod, và vì sao pod của bạn bị Pending hoặc bị OOMKilled khi thiếu limits.