Vòng Đời Của Một Pod: Phase, Condition và restartPolicy
Ở Bài 17 ta thấy một pod sinh ra qua chuỗi Event Scheduled → Pulled → Created → Started. Đó là cái nhìn từ bên ngoài, theo thời gian. Bài này mở đầu phần Pods chuyên sâu, nhìn vào bên trong một pod: nó có những trạng thái nào, ai quyết định trạng thái đó, và vì sao một pod crash liên tục lại vẫn báo Running. Đây là từ vựng bạn đọc mỗi ngày trong kubectl get pods, nên đọc kỹ một lần sẽ tiết kiệm rất nhiều lần đoán mò về sau.
Điểm cần nắm trước: trạng thái một pod không phải một con số duy nhất, mà là ba lớp chồng lên nhau, mỗi lớp một độ chi tiết.
┌─ Pod phase ──────── tóm tắt thô: Pending / Running / Succeeded / Failed
│ ▲ được SUY RA từ ↓
├─ Container state ── chi tiết từng container: Waiting / Running / Terminated
│ (+ reason, exitCode)
└─ Pod conditions ─── checklist True/False: PodScheduled, Initialized,
ContainersReady, Ready
Hiểu sai thường gặp là coi phase như nguồn sự thật. Thực ra phase là lớp tóm tắt thô nhất, được suy ra từ container state cộng với restartPolicy. Muốn biết chuyện gì thật sự xảy ra, ta đọc hai lớp dưới.
Lớp 1 — Pod phase
phase là một trong năm giá trị, trả lời câu hỏi thô "pod đang ở giai đoạn nào của vòng đời":
- Pending — pod đã được chấp nhận nhưng chưa container nào chạy (đang chờ schedule, kéo image, hay dựng mạng).
- Running — pod đã gán node và ít nhất một container đang chạy hoặc đang khởi động/restart.
- Succeeded — mọi container đã chạy xong và thoát với mã 0, không restart nữa.
- Failed — mọi container đã dừng và ít nhất một cái thoát với mã khác 0 (hoặc bị hệ thống giết).
- Unknown — không lấy được trạng thái pod, thường do mất liên lạc với node.
Succeeded và Failed là hai trạng thái kết thúc, nghĩa là pod đã chạy hết đời. Nhưng một pod có tới được chúng hay không lại phụ thuộc restartPolicy, nên ta quay lại đó sau.
Lớp 2 — Container state
Mỗi container trong pod có trạng thái riêng, chi tiết hơn phase nhiều, gồm ba khả năng:
- Waiting — chưa chạy: đang kéo image, hoặc đang chờ giữa các lần restart. Có
reason(ví dụCrashLoopBackOff,ImagePullBackOff). - Running — đang chạy, có
startedAt. - Terminated — đã dừng, có
reason(Completed,Error,OOMKilled...),exitCode, và thời điểm bắt đầu/kết thúc.
Khi container restart, trạng thái trước đó được giữ ở lastState, rất hữu ích để biết lần chết gần nhất vì sao, kể cả khi container hiện đang chạy lại.
Lớp 3 — Pod conditions
conditions là một danh sách các mục True/False, như một checklist tiến độ. Lấy một pod đang chạy bình thường ra xem:
kubectl get pod life-running -o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'
PodReadyToStartContainers=True
Initialized=True
Ready=True
ContainersReady=True
PodScheduled=True
Đọc theo thứ tự một pod đi qua khi khởi động:
- PodScheduled — đã được scheduler gán node (Bài 17).
- PodReadyToStartContainers — sandbox và network namespace của pod đã sẵn (CNI đã cấp mạng).
- Initialized — mọi init container đã hoàn tất (pod này không có init container nên
Truengay; init container là chủ đề Bài 19). - ContainersReady — mọi container trong pod đã Ready.
- Ready — pod sẵn sàng nhận lưu lượng; đây là condition mà controller endpoint nhìn vào để quyết định có thêm pod vào EndpointSlice của Service hay không.
Phân biệt Ready với Running rất quan trọng: một container có thể đang Running nhưng chưa Ready (ví dụ app khởi động chậm, chưa qua readiness probe). Khi đó pod chạy nhưng Service chưa gửi lưu lượng tới. Cơ chế probe điều khiển Ready là chủ đề Bài 20; ở đây chỉ cần nhớ Ready là cổng vào Service.
restartPolicy — thứ quyết định phase kết thúc
restartPolicy (đặt ở spec, áp cho mọi container trong pod) quyết định kubelet có khởi động lại container khi nó thoát hay không. Ba giá trị:
| restartPolicy | container thoát mã 0 | container thoát mã ≠ 0 |
|---|---|---|
| Always (mặc định) | restart | restart |
| OnFailure | không restart → pod Succeeded |
restart |
| Never | không restart → pod Succeeded |
không restart → pod Failed |
Mấu chốt: chỉ khi container không được restart thì pod mới tới được trạng thái kết thúc (Succeeded/Failed). Với Always, container luôn được dựng lại, nên pod không bao giờ tự nhiên đạt Succeeded hay Failed, nó cứ ở Running. Đó là lý do Deployment (luôn dùng restartPolicy: Always) hợp cho dịch vụ chạy mãi, còn Job (dùng OnFailure/Never) hợp cho tác vụ chạy một lần, chủ đề Bài 27.
Bốn pod thật, bốn kết cục
Lý thuyết trên gói gọn vào bốn pod. Tạo cùng lúc: một dịch vụ chạy mãi, một tác vụ thoát 0, một tác vụ thoát 1, và một container crash liên tục.
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP
life-running 1/1 Running 0 85s 10.200.0.8
life-success 0/1 Completed 0 85s 10.200.1.9
life-failed 0/1 Error 0 85s 10.200.0.9
life-crash 0/1 CrashLoopBackOff 4 (67s ago) 2m44s 10.200.1.10
Cột STATUS ở đây không phải phase; nó là một bản tóm tắt thân thiện mà kubectl ghép từ container state. Đào vào từng pod mới thấy rõ ba lớp.
life-running (restartPolicy: Always, chạy mãi) — phase=Running, container running, mọi condition True. Đây là trạng thái khỏe mạnh bình thường.
life-success (restartPolicy: Never, exit 0):
kubectl get pod life-success -o jsonpath='phase={.status.phase}{"\n"}{.status.containerStatuses[0].state.terminated.reason} exit={.status.containerStatuses[0].state.terminated.exitCode}{"\n"}'
phase=Succeeded
Completed exit=0
Container thoát sạch, Never không restart, nên pod đạt trạng thái kết thúc Succeeded. kubectl hiển thị Completed.
life-failed (restartPolicy: Never, exit 1):
kubectl get pod life-failed -o jsonpath='phase={.status.phase}{"\n"}{.status.containerStatuses[0].state.terminated.reason} exit={.status.containerStatuses[0].state.terminated.exitCode}{"\n"}'
kubectl get pod life-failed -o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'
phase=Failed
Error exit=1
PodReadyToStartContainers=False
Initialized=True
Ready=False
ContainersReady=False
PodScheduled=True
Thoát mã 1, Never không restart, pod Failed. Để ý conditions: PodScheduled và Initialized vẫn True (pod đã được gán và khởi động), nhưng Ready/ContainersReady thành False, pod không còn phục vụ được nữa.
life-crash (restartPolicy: Always, chạy 2 giây rồi exit 1):
kubectl get pod life-crash -o jsonpath='phase={.status.phase}{"\n"}restartCount={.status.containerStatuses[0].restartCount}{"\n"}lastTerminated={.status.containerStatuses[0].lastState.terminated.reason} exit={.status.containerStatuses[0].lastState.terminated.exitCode}{"\n"}'
phase=Running
restartCount=4
lastTerminated=Error exit=1
Container chết đi chết lại, nhưng phase vẫn là Running, vì Always luôn restart nên pod không bao giờ chạm trạng thái kết thúc. Thông tin thật nằm ở hai chỗ khác: restartCount tăng dần, và lastState.terminated cho biết lần chết gần nhất (Error, exit 1). Đó là lý do đừng tin mỗi phase.
CrashLoopBackOff không phải một phase
Khi container cứ chết rồi restart, kubelet không restart ngay lập tức mà chờ một khoảng tăng dần theo backoff lũy thừa: 10 giây, 20, 40... gấp đôi mỗi lần, chặn trên ở 5 phút. Trong lúc chờ, container ở trạng thái Waiting với reason: CrashLoopBackOff. Bắt đúng khoảnh khắc đó:
kubectl get pod life-crash -o jsonpath='{.status.containerStatuses[0].state.waiting.reason}'
CrashLoopBackOff
CrashLoopBackOff vì vậy không phải một phase, cũng không phải một lỗi tự thân. Nó là container state = Waiting với lý do "đang đợi giữa các lần restart", cho biết container đã chết nhiều lần và kubelet đang giãn nhịp restart. Nguyên nhân thật phải tìm ở lastState.terminated (mã thoát) và kubectl logs --previous (log của lần chạy trước khi chết). Lẫn lộn CrashLoopBackOff với một phase là một trong những hiểu nhầm phổ biến nhất khi mới gỡ lỗi pod.
🧹 Dọn dẹp
Xóa bốn pod minh hoạ:
kubectl delete pod life-running life-success life-failed life-crash
Tất cả đều là object trong cluster, xóa là sạch, không đụng gì tới node. Manifest ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 18-pod-lifecycle.
Tổng kết
Trạng thái một pod là ba lớp: phase tóm tắt thô, container state chi tiết, condition như checklist; và phase được suy ra từ container state cộng restartPolicy, không phải set thẳng. Nắm ba điều này thì đọc kubectl get pods không còn là đọc một nhãn, mà là đọc một câu chuyện: pod crash-loop vẫn báo Running vì Always không cho nó kết thúc; CrashLoopBackOff là trạng thái chờ chứ không phải lỗi; Ready khác Running và mới là cổng vào Service.
Hai mảnh ta cố ý gác lại là init container (ảnh hưởng condition Initialized) và probe (điều khiển condition Ready), nội dung hai bài kế. Bài 19 bàn về init container và sidecar container: cách chạy việc chuẩn bị trước container chính, và cách chạy một container phụ song song suốt đời pod.