Troubleshoot pod: CrashLoopBackOff, ImagePullBackOff, Pending
Mục tiêu bài học
Sau bài này, bạn phải làm được:
- Đọc đúng status của pod và biết ngay lệnh tiếp theo cần chạy.
- Phân biệt ba nhóm lỗi: Pending (chưa được schedule), ImagePullBackOff (không lấy được image), CrashLoopBackOff (container chạy rồi thoát).
- Sửa manifest tương ứng và xác nhận pod lên Running trong vài phút, đủ nhanh cho một câu hỏi CKA.
[!NOTE] Nhắc lại bài trước: bài 6.2 dạy bạn đọc node NotReady bằng
kubectl describe nodevàjournalctl -u kubelet. Nguyên tắc cốt lõi vẫn giữ nguyên: luôn đọc Events trước khi đoán mò. Bài này áp dụng đúng nguyên tắc đó nhưng ở phạm vi pod.
Ba trạng thái này chiếm phần lớn câu hỏi troubleshooting trong đề CKA, nên hãy học theo phản xạ: thấy status nào thì chạy lệnh đó ngay, không suy nghĩ lâu.
| Status | Nghĩa là gì | Lệnh đầu tiên | Nhóm nguyên nhân |
|---|---|---|---|
| Pending | Chưa được scheduler gán vào node | kubectl describe pod |
Tài nguyên, nodeSelector/affinity, taint, PVC |
| ImagePullBackOff | Không pull được image | kubectl describe pod |
Sai tên/tag, registry private thiếu secret |
| CrashLoopBackOff | Container chạy rồi thoát, bị restart liên tục | kubectl logs --previous |
Command sai, app lỗi, probe sai, OOMKilled |
flowchart TD
Start["🔍<br/>Pod không Running"] --> Q1{"Status là gì?"}
Q1 -->|"Pending"| P["📦<br/>Chưa schedule"]
Q1 -->|"ImagePullBackOff"| I["🖼️<br/>Không pull được image"]
Q1 -->|"CrashLoopBackOff"| C["💥<br/>Container chạy rồi thoát"]
P --> PD["kubectl describe pod<br/>đọc Events"]
I --> ID["kubectl describe pod<br/>kiểm tra image và imagePullSecrets"]
C --> CL["kubectl logs --previous<br/>xem lastState và exit code"]
Ví dụ có hướng dẫn: từ CrashLoopBackOff đến Running
Bối cảnh: bạn apply một Deployment và pod cứ restart liên tục. Đây là manifest gốc (có lỗi cố ý):
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25
command: ["nginx"]
args: ["-g", "daemon off;", "--invalid-flag"]
Bước 1: Xác nhận triệu chứng.
kubectl get pods -o wide
Kết quả mẫu:
NAME READY STATUS RESTARTS AGE
web-7d9c8f5b6c-2xk4l 0/1 CrashLoopBackOff 5 (40s ago) 4m
RESTARTS tăng liên tục là dấu hiệu chắc chắn của CrashLoopBackOff. Con số (40s ago) là thời gian backoff hiện tại, nó tăng dần theo cấp số nhân: 10s, 20s, 40s, 80s, 160s, tối đa 300s.
Bước 2: Đọc describe, tập trung vào hai chỗ.
kubectl describe pod web-7d9c8f5b6c-2xk4l
Chỗ thứ nhất là Last State:
Last State: Terminated
Reason: Error
Exit Code: 1
Chỗ thứ hai là Events ở cuối output. Với lỗi này bạn sẽ thấy các dòng Started container nginx rồi Back-off restarting failed container, tức là container đã thực sự chạy và thoát, không phải lỗi image hay lỗi schedule.
Bước 3: Xem log của lần chạy trước.
kubectl logs web-7d9c8f5b6c-2xk4l --previous
Nếu quên --previous, bạn sẽ nhận Error from server (BadRequest): container ... is waiting to start: CrashLoopBackOff. Đây là lỗi thao tác phổ biến nhất của người mới.
Output mẫu:
nginx: invalid option: "--invalid-flag"
Bước 4: Kết luận nguyên nhân. Flag --invalid-flag không tồn tại, nginx thoát với exit code 1, kubelet restart, lặp mãi thành CrashLoopBackOff. Vấn đề nằm ở args trong manifest, không phải ở image.
Bước 5: Sửa manifest.
kubectl edit deployment web
Xoá --invalid-flag khỏi args, lưu lại. Hoặc dùng cách nhanh hơn nếu chỉ cần đổi image:
kubectl set image deployment/web nginx=nginx:1.25
Bước 6: Xác nhận.
kubectl rollout status deployment/web
kubectl get pods -l app=web
Pod mới phải ở trạng thái Running với RESTARTS bằng 0 (hoặc không tăng nữa). Trong đề thi, bước xác nhận này là bắt buộc, không được bỏ.
Luyện tập có hướng dẫn
Mỗi bài dưới đây là một manifest lỗi. Cách làm: apply manifest, chạy các lệnh chẩn đoán, viết ra nguyên nhân bằng một câu, rồi mới mở phần đáp án để đối chiếu. Đừng mở đáp án trước.
Bài tập 1: Pod Pending vì tài nguyên
apiVersion: v1
kind: Pod
metadata:
name: big-mem
spec:
containers:
- name: app
image: nginx:1.25
resources:
requests:
memory: "64Gi"
cpu: "8"
Việc cần làm:
kubectl apply -f big-mem.yamlkubectl get pod big-memvà ghi lại status.kubectl describe pod big-mem, đọc dòng cuối phần Events.- Trả lời: pod có được gán vào node chưa, và vì sao?
Xem đáp án
Status là Pending. Event điển hình:
Warning FailedScheduling 0/3 nodes are available: 3 Insufficient cpu, 3 Insufficient memory.
Pod chưa được schedule vì không node nào đáp ứng requests. Lưu ý: requests là mức cam kết, scheduler dùng nó để lọc node, không liên quan đến việc app có dùng hết hay không.
Cách sửa: giảm requests xuống mức thực tế (ví dụ cpu: "100m", memory: "128Mi"), rồi xoá và tạo lại pod. Với Pod trần, resources không sửa được tại chỗ, phải kubectl delete pod big-mem rồi apply lại.
Bài tập 2: Pod Pending vì nodeSelector
apiVersion: v1
kind: Pod
metadata:
name: ssd-only
spec:
nodeSelector:
disktype: ssd
containers:
- name: app
image: nginx:1.25
Việc cần làm:
- Apply manifest, xem status.
kubectl get nodes --show-labelsđể kiểm tra nhãndisktype.- Đọc Events và trả lời: sửa nhãn node hay sửa manifest thì nhanh hơn trong phòng thi?
Xem đáp án
Event điển hình:
Warning FailedScheduling 0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector.
Không node nào có nhãn disktype=ssd, nên pod không thể schedule.
Hai cách sửa, chọn theo đề bài:
- Đề cho phép đổi node:
kubectl label node <node-name> disktype=ssd - Đề yêu cầu pod chạy được mà không đụng vào node: xoá
nodeSelectorkhỏi manifest, delete pod rồi apply lại.
Trong phòng thi, nếu đề không nói rõ phải giữ nodeSelector, cách nhanh và an toàn nhất là bỏ selector. Nhưng nếu đề ghi "pod phải chạy trên node có nhãn X" thì phải gán nhãn cho node.
Bài tập 3: ImagePullBackOff
apiVersion: v1
kind: Pod
metadata:
name: bad-image
spec:
containers:
- name: app
image: nginx:1.25.99
Việc cần làm:
- Apply manifest, đợi khoảng 20 giây.
kubectl get pod bad-image, quan sát status thay đổi từErrImagePullsangImagePullBackOff.- Đọc Events, tìm chính xác tag nào không tồn tại.
- Sửa image mà không cần xoá pod, sau đó kiểm tra lại.
Xem đáp án
Event điển hình:
Warning Failed Failed to pull image "nginx:1.25.99": ... manifest unknown
Warning Failed Error: ErrImagePull
Normal BackOff Back-off pulling image "nginx:1.25.99"
Warning Failed Error: ImagePullBackOff
ErrImagePull là lần thử đầu tiên thất bại, ImagePullBackOff là trạng thái kubelet đang chờ backoff trước khi thử lại. Bản chất là cùng một lỗi, chỉ khác giai đoạn.
Cách sửa nhanh, field image của Pod là field được phép sửa tại chỗ:
kubectl set image pod/bad-image app=nginx:1.25
kubectl get pod bad-image -w
Nếu là Deployment thì dùng kubectl set image deployment/<name> <container>=<image>.
Bài tập 4: CrashLoopBackOff vì liveness probe
apiVersion: v1
kind: Pod
metadata:
name: probe-fail
spec:
containers:
- name: app
image: nginx:1.25
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
Việc cần làm:
- Apply manifest, đợi khoảng 30 giây rồi xem status và số lần RESTARTS.
- Đọc Events, tìm dòng có chữ
Liveness probe failed. - Chạy
kubectl logs probe-failvàkubectl logs probe-fail --previous. So sánh hai output, giải thích vì sao log thường không có lỗi gì. - Sửa manifest và xác nhận pod ổn định.
Xem đáp án
Event điển hình:
Warning Unhealthy Liveness probe failed: Get "http://10.244.0.5:8080/healthz": dial tcp ...: connect: connection refused
Normal Killing Container app failed liveness probe, will be restarted
Container chạy bình thường và phục vụ ở cổng 80, nhưng probe lại gọi cổng 8080 và path /healthz. Probe thất bại 3 lần liên tiếp nên kubelet giết container và khởi động lại, tạo ra vòng lặp.
Log có thể hoàn toàn sạch vì bản thân nginx không hề lỗi. Đây là lý do phải đọc describe pod chứ không chỉ đọc logs.
Cách sửa: đổi port: 8080 thành port: 80 và path: /healthz thành path: /. Sau khi sửa, kubectl delete pod probe-fail rồi apply lại, vì livenessProbe không sửa được tại chỗ trên Pod đang chạy.
Bài học: khi thấy CrashLoopBackOff mà log sạch, hãy nghĩ ngay đến probe.
[!TIP] Bài tập 1 và 2 đều là Pending nhưng nguyên nhân khác nhau hoàn toàn. Điểm chung duy nhất là cả hai đều nằm trong phần Events của
describe pod. Đừng đoán theo status, hãy đọc Events.
Lỗi thường gặp và cách xử lý
Đây là những chỗ tôi từng mất điểm hoặc mất thời gian, và tôi thấy người mới lặp lại rất nhiều:
- Chỉ nhìn status mà không đọc Events. Status chỉ nói nhóm lỗi, Events nói nguyên nhân cụ thể. Luôn cuộn xuống cuối output của
describe pod. - Quên
--previouskhi pod đang restart.kubectl logsthường trả về lỗi BadRequest vì container chưa sẵn sàng. Thêm-plà ra ngay. - Sửa trực tiếp trên Pod. Rất nhiều field không mutable:
resources,command,nodeSelector,livenessProbe. Sửa bằngkubectl edit podsẽ bị từ chối, phải xoá và tạo lại. Riêngimagethì sửa được. - Nhầm
CreateContainerConfigErrorvới CrashLoopBackOff. Nếu Pod tham chiếu ConfigMap hoặc Secret không tồn tại quaenvFromhoặcvalueFrom, status làCreateContainerConfigError, container chưa hề chạy nên không có log. Sửa bằng cách tạo ConfigMap/Secret còn thiếu hoặc sửa tên trong manifest. - Bỏ qua exit code. Exit code 1 là app lỗi, 127 là không tìm thấy lệnh, 137 là bị SIGKILL gần như chắc chắn do OOMKilled (vượt memory limit), 143 là SIGTERM. Đọc
Last Statetrong describe là ra. - Quên
-n. Pod không hiện ra vì bạn đang ởdefaultnamespace. Kiểm tra bằngkubectl get pods -A | grep <tên>. - Chờ backoff thay vì reset nó. Khi backoff đã lên 5 phút, sửa manifest cũng không giúp pod chạy ngay. Xoá pod để workload tạo pod mới với backoff bằng 0.
- Nghĩ CrashLoopBackOff là lỗi image. Pod có status CrashLoopBackOff nghĩa là image đã pull thành công và container đã chạy. Nếu image sai, status đã là ImagePullBackOff.
- Tin vào
status.phase. Pod CrashLoopBackOff vẫn cóstatus.phaselàRunning, nên lọc--field-selector status.phase!=Runningsẽ bỏ sót chúng.
Expert Corner: mẹo phòng thi
Vài thói quen tôi mang từ phòng lab vào phòng thi và thấy hiệu quả rõ rệt:
- Thứ tự lệnh cố định:
kubectl get pod→kubectl describe pod→kubectl logs [-p]. Ba lệnh này giải quyết khoảng 90% câu hỏi pod trong đề. - Alias tiết kiệm thời gian: đặt
alias k=kubectlvàexport do="--dry-run=client -o yaml"ngay khi vào đề. - Lấy exit code bằng jsonpath khi describe quá dài:
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
- Xem event toàn namespace theo thời gian khi Events của pod đã bị rotate:
kubectl get events -n <ns> --sort-by=.metadata.creationTimestamp | tail -20
- Trần thời gian cho mỗi câu là 5 phút. Nếu quá 5 phút mà chưa ra nguyên nhân, đánh dấu và đi tiếp. Một câu Pending khó không đáng để mất ba câu khác.
- Khi sửa Deployment, luôn kết thúc bằng
kubectl rollout statusđể chắc chắn replica mới đã Available. Đề CKA hay chấm điểm trên trạng thái cuối cùng, không phải trên việc bạn đã gõ lệnh gì. - Đọc đề trước khi sửa: đề có yêu cầu giữ nguyên node hay giữ nguyên resource không? Sửa đúng chỗ nhưng sai ràng buộc vẫn là 0 điểm.
- Nhớ lại nhanh mối liên hệ với Module 5: Pending cũng có thể do PVC chưa bind. Nếu Events ghi
pod has unbound immediate PersistentVolumeClaims, nguyên nhân nằm ở storage, không phải ở pod.
Tóm tắt và bước tiếp theo
Chốt lại trong bốn dòng, đúng như cách tôi tự nhắc mình trước giờ thi:
- Pending: chưa schedule, đọc Events để biết là thiếu tài nguyên, sai selector, hay thiếu PVC.
- ImagePullBackOff: sai tên/tag image hoặc thiếu
imagePullSecrets, sửa được tại chỗ bằngkubectl set image. - CrashLoopBackOff: container chạy rồi thoát, đọc
Last State, exit code vàlogs --previous. - Luôn kết thúc bằng bước xác nhận pod đã
RunningvàRESTARTSkhông tăng.
Điền nhanh để tự kiểm tra:
Pod ở trạng thái Pending nghĩa là nó chưa được {{c1::scheduler}} gán vào node nào.
Lệnh xem log của lần chạy trước của container: kubectl logs <pod> {{c2::--previous}}.
Exit code 137 thường là dấu hiệu container bị {{c3::OOMKilled}} do vượt memory limit.
Với registry private, pod cần {{c4::imagePullSecrets}} để pull được image.
CrashLoopBackOff có thời gian backoff tối đa là {{c5::300 giây}}, tức 5 phút.
Ôn lại bằng bộ thẻ dưới đây, mỗi thẻ một dữ kiện, cố gắng trả lời trước khi lật:
[
{ "front": "Pod ở trạng thái Pending nghĩa là gì?", "back": "Pod đã được API server nhận nhưng chưa được scheduler gán vào node nào." },
{ "front": "Lệnh đầu tiên nên chạy khi pod có status lạ?", "back": "kubectl describe pod <tên>, đọc kỹ phần Events ở cuối output." },
{ "front": "Xem log của lần chạy trước của container bị restart?", "back": "kubectl logs <pod> --previous (viết tắt -p)." },
{ "front": "Exit code 137 cho biết điều gì?", "back": "Container bị SIGKILL, gần như chắc chắn do OOMKilled hoặc vượt memory limit." },
{ "front": "ErrImagePull khác ImagePullBackOff ở điểm nào?", "back": "ErrImagePull là lần pull thất bại đầu tiên, ImagePullBackOff là giai đoạn kubelet chờ backoff để thử lại." },
{ "front": "Nguyên nhân phổ biến nhất của CrashLoopBackOff?", "back": "Container khởi động rồi thoát mã lỗi, do command/args sai, app lỗi, probe sai hoặc OOMKilled." },
{ "front": "Thời gian backoff tối đa của CrashLoopBackOff?", "back": "300 giây (5 phút), tăng dần từ 10 giây theo cấp số nhân." },
{ "front": "Event 'Insufficient cpu' khi pod Pending thì sửa gì?", "back": "Giảm resources.requests trong manifest hoặc giải phóng tài nguyên trên node." },
{ "front": "Event \"didn't match Pod's node affinity/selector\" nghĩa là gì?", "back": "nodeSelector hoặc affinity không khớp nhãn của node nào, sửa manifest hoặc gán nhãn cho node." },
{ "front": "Đổi image của một Pod đang lỗi mà không xoá pod?", "back": "kubectl set image pod/<tên> <container>=<image>, vì field image là mutable." },
{ "front": "Status CreateContainerConfigError nghĩa là gì?", "back": "Pod tham chiếu ConfigMap hoặc Secret không tồn tại, container chưa hề chạy nên không có log." },
{ "front": "Image có tag :latest mặc định dùng imagePullPolicy nào?", "back": "Always, nên luôn pull lại mỗi lần khởi động container." },
{ "front": "Làm sao reset backoff mà không phải chờ?", "back": "Xoá pod, workload sẽ tạo pod mới với backoff bắt đầu lại từ đầu." },
{ "front": "CrashLoopBackOff mà log hoàn toàn sạch thì nghi ngờ gì đầu tiên?", "back": "Liveness probe sai cấu hình, kiểm tra dòng 'Liveness probe failed' trong Events." }
]
Bài tiếp theo: bài 6.4 sẽ mở rộng sang lỗi kết nối: pod đã Running nhưng Service không trả lời, DNS trong cluster không phân giải được, và cách kiểm tra endpoints. Khi đó bạn sẽ dùng lại đúng quy trình get → describe → log vừa luyện, chỉ thêm một bước kiểm tra mạng.