Quy trình troubleshoot: event, describe, log
Mục tiêu bài học
Sau bài này, bạn sẽ:
- Đi đúng thứ tự bốn bước khi một workload có vấn đề: xem trạng thái, đọc event, đọc log, rồi mới vào trong container.
- Biết chính xác lệnh nào trả lời câu hỏi nào, để không mất 10 phút mò mẫm giữa phòng thi.
- Đọc được mục
Eventsở cuối output củakubectl describevà biết event nào là manh mối chính. - Dùng
--previous,--tail,--sincevà exit code để khoanh vùng lỗi thuộc về app hay thuộc về cluster. - Tránh các bẫy quen thuộc: event đã hết hạn, log của container đã chết, và nhầm namespace.
Ôn lại nhanh
Trước bài này bạn đã có đủ đồ nghề: kubectl get -o wide, -o yaml, jsonpath, kubectl explain, và bạn đã từng troubleshoot PVC Pending ở Module 5. Bài này không dạy thêm lệnh mới, nó dạy thứ tự dùng lệnh.
[!NOTE] Câu thần chú của mình khi đi thi: status trước, event sau, log cuối. Ai mới vào cũng nhảy thẳng vào
logs, mà 80% lỗi trong đề CKA lại nằm ở event (scheduling, mount, image, probe).
Lý thuyết
Bức tranh tổng thể: 4 tầng thông tin
Một pod lỗi để lại dấu vết ở bốn nơi khác nhau. Mỗi nơi trả lời một câu hỏi khác nhau:
| Tầng | Lệnh | Trả lời câu hỏi |
|---|---|---|
| Trạng thái | kubectl get pod -o wide |
Pod đang ở pha nào, chạy trên node nào, restart bao nhiêu lần |
| Event | kubectl describe pod, kubectl get events |
Control plane đã cố làm gì và thất bại ở đâu |
| Log | kubectl logs, kubectl logs --previous |
Ứng dụng bên trong nói gì trước khi chết |
| Bên trong | kubectl exec, kubectl debug |
Môi trường thực tế: DNS, file, biến, kết nối |
Đi từ trên xuống dưới. Đừng nhảy cóc.
flowchart TD
Start["🔍<br/>Pod có vấn đề"] --> Get["📋<br/>kubectl get pod -o wide"]
Get --> Status{"STATUS là gì"}
Status -->|Pending| Scheduling["📝<br/>describe: FailedScheduling / FailedMount"]
Status -->|CrashLoopBackOff| Crash["📝<br/>describe: Last State + Exit Code"]
Status -->|ImagePullBackOff| Image["📝<br/>describe: Failed to pull image"]
Status -->|Running nhưng sai| Logs["📜<br/>kubectl logs"]
Crash --> Prev["📜<br/>kubectl logs --previous"]
Scheduling --> FixA["🛠️<br/>Sửa request, selector, PVC"]
Image --> FixB["🛠️<br/>Sửa image, imagePullSecret"]
Prev --> FixC["🛠️<br/>Sửa app, config, probe"]
Logs --> Exec["🐚<br/>kubectl exec / debug"]
Bước 1: Đọc trạng thái
kubectl get pods -n shop -o wide
kubectl get pods -A -o wide
Bốn cột bạn phải đọc ngay: READY, STATUS, RESTARTS, AGE.
READY 0/1nghĩa là container chưa sẵn sàng nhận traffic, kể cả khiSTATUSlàRunning.RESTARTStăng đều là dấu hiệu container chết rồi được restart, không phải app chậm.STATUSchỉ là bề mặt.PendingvàCrashLoopBackOfflà hai hướng điều tra hoàn toàn khác nhau.
Mẹo nhỏ mà mình dùng rất nhiều: kubectl get pod -o wide cho thêm cột NODE. Khi chỉ một node có pod lỗi, bạn đã khoanh vùng được sang bài học sau (node NotReady).
Bước 2: Đọc event
kubectl describe là lệnh quan trọng nhất trong bài này. Với pod, phần đáng đọc nhất nằm ở cuối output, mục Events:
kubectl describe pod api-7d9f8c6b5-x2k4n -n shop
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 45s default-scheduler 0/3 nodes are available:
3 Insufficient cpu. preemption: 0/3 nodes are available: 3 No preemption victims found
Normal Scheduled 40s default-scheduler Successfully assigned shop/api-... to node-2
Normal Pulled 38s kubelet Container image "nginx:1.25" already present
Warning Failed 20s kubelet Error: container has runAsNonRoot and image will run as root
Cách đọc: đọc từ dưới lên, sự kiện mới nhất ở cuối. Type có Warning là ưu tiên số một.
Các Reason bạn gặp nhiều nhất trong đề thi:
| Reason | Tầng lỗi | Hướng sửa |
|---|---|---|
FailedScheduling |
Scheduler | Thiếu CPU/memory, nodeSelector, taint, PVC chưa bind |
FailedMount |
Kubelet, storage | Secret/ConfigMap thiếu, PVC Pending, sai accessModes |
Failed + Failed to pull image |
Kubelet, registry | Sai tag, private registry thiếu imagePullSecret |
BackOff |
Kubelet | Container crash liên tục, xem Last State |
Unhealthy |
Kubelet | Probe sai đường dẫn, cổng, hoặc app chưa kịp khởi động |
Killing |
Kubelet | Bị evict do vượt memory limit, hoặc bị delete |
Khi cần xem event của cả namespace hoặc toàn cluster:
kubectl get events -n shop --sort-by=.metadata.creationTimestamp
kubectl get events -A --sort-by=.lastTimestamp
kubectl get events -n shop --field-selector involvedObject.name=api-7d9f8c6b5-x2k4n
[!WARNING] Event có thời hạn. Mặc định kube-apiserver chỉ giữ event trong {{c1::1 giờ}} (
--event-ttl). Nếu bạn vào lab muộn hoặc vừadrainnode xong, event có thể đã biến mất. Lúc đó phải suy luận từ trạng thái hiện tại và log, đừng ngồi chờ event quay lại.
Bước 3: Đọc log
kubectl logs api-7d9f8c6b5-x2k4n -n shop
kubectl logs api-7d9f8c6b5-x2k4n -n shop --tail=50
kubectl logs api-7d9f8c6b5-x2k4n -n shop --since=10m
kubectl logs api-7d9f8c6b5-x2k4n -n shop -c sidecar
kubectl logs api-7d9f8c6b5-x2k4n -n shop --all-containers=true
Điểm ăn điểm nhất: container đã chết thì log hiện tại trống. Phải dùng --previous (viết tắt -p) để xem log của lần chạy trước, tức là lần đã crash:
kubectl logs api-7d9f8c6b5-x2k4n -n shop --previous
Nếu --previous cũng trống và bạn thấy RESTARTS 0 nhưng STATUS sai, thì lỗi chưa bao giờ tới được app: nó nằm ở image, ở volume, hoặc ở scheduler.
Với pod nhiều container, thiếu -c là lỗi rất hay gặp trong phòng thi. Lệnh sẽ báo a container name must be specified. Chạy kubectl get pod <name> -o jsonpath='{.spec.containers[*].name}' để lấy danh sách tên.
Bước 4: Exit code, manh mối cuối cùng
Trong kubectl describe, phần Last State: Terminated cho bạn Reason và Exit Code:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
| Exit code | Ý nghĩa | Thường gặp khi |
|---|---|---|
0 |
Thoát bình thường | Job chạy xong, container one-shot kết thúc |
1 |
App tự báo lỗi | Sai config, thiếu biến môi trường, crash khởi động |
137 |
128 + 9, bị SIGKILL |
OOMKilled hoặc bị kill cứng |
139 |
Segfault | Binary lỗi, hiếm gặp trong đề |
143 |
128 + 15, SIGTERM |
Bị terminate bình thường khi scale down hoặc delete |
OOMKilled không phải lỗi app. Nó là lỗi cấu hình: container vượt resources.limits.memory. Sửa limit, không sửa code.
Bước 5: Chỉ khi ba bước trên chưa đủ
kubectl exec -it api-7d9f8c6b5-x2k4n -n shop -- sh
kubectl debug -it api-7d9f8c6b5-x2k4n -n shop --image=busybox:1.36 --target=api
exec dùng khi app vẫn chạy nhưng hành vi sai: kiểm tra DNS bằng nslookup, kiểm tra file mount, kiểm tra biến môi trường. Nếu image không có shell (distroless), dùng kubectl debug với ephemeral container. Đừng dùng exec để đoán lỗi khi chưa đọc event, vì exec không cho bạn biết quá khứ.
Ví dụ có hướng dẫn
Ví dụ 1: Pod Pending do thiếu CPU
kubectl get pods -n shop
NAME READY STATUS RESTARTS AGE
api-7d9f8c6b5-x2k4n 0/1 Pending 0 4m
Bước 1 cho ta biết: chưa có node nào nhận pod. Bước 2, đọc event:
kubectl describe pod api-7d9f8c6b5-x2k4n -n shop | tail -n 10
Events:
Type Reason Age From Message
Warning FailedScheduling 4m default-scheduler 0/3 nodes are available:
3 Insufficient cpu.
Kết luận: requests.cpu lớn hơn CPU còn trống trên mọi node. Cách sửa trong phòng thi, chọn cách nhanh nhất:
kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu
kubectl describe node node-2 | grep -A 5 "Allocated resources"
Rồi giảm requests.cpu bằng kubectl edit deployment api -n shop, hoặc xóa một pod chiếm chỗ. Bước 3 (log) ở đây là vô nghĩa vì container chưa từng chạy. Biết dừng đúng lúc cũng là kỹ năng thi.
Ví dụ 2: CrashLoopBackOff do thiếu biến môi trường
kubectl get pods -n shop
NAME READY STATUS RESTARTS AGE
worker-6b7c9d4f5-h8m2p 0/1 CrashLoopBackOff 6 5m
RESTARTS 6 nghĩa là container có chạy, rồi chết. Vậy log sẽ có nội dung, nhưng phải là log của lần trước:
kubectl logs worker-6b7c9d4f5-h8m2p -n shop --previous
Error: DB_HOST is not set. Exiting.
Kiểm tra chéo bằng describe để thấy exit code và biến môi trường:
kubectl describe pod worker-6b7c9d4f5-h8m2p -n shop | grep -A 3 "Last State"
kubectl get pod worker-6b7c9d4f5-h8m2p -n shop -o jsonpath='{.spec.containers[0].env}'
Last State: Terminated
Reason: Error
Exit Code: 1
Kết luận: app thoát với code 1 vì thiếu DB_HOST. Không phải lỗi cluster. Sửa bằng kubectl edit deployment worker -n shop và thêm env từ ConfigMap, sau đó kubectl rollout status deployment/worker -n shop.
Ví dụ 3: ImagePullBackOff do sai tag
kubectl describe pod web-5f6d7b8c9-q1w2e -n shop | tail -n 8
Events:
Type Reason Age From Message
Warning Failed 2m (x4 over 3m) kubelet Failed to pull image "nginx:1.999":
rpc error: code = NotFound ... manifest unknown
Warning Failed 2m (x4 over 3m) kubelet Error: ErrImagePull
Normal BackOff 30s (x6 over 3m) kubelet Back-off pulling image "nginx:1.999"
Đọc đúng dòng quan trọng: manifest unknown chứng minh tag không tồn tại, không phải lỗi mạng. Nếu message là unauthorized hoặc authentication required, hướng sửa là imagePullSecret. Nếu là dial tcp ... i/o timeout, mới là mạng hoặc registry nội bộ.
Lệnh kiểm tra tag nhanh trước khi sửa:
kubectl set image deployment/web web=nginx:1.25 -n shop
kubectl rollout status deployment/web -n shop
Ví dụ 4: Event trực tiếp và log của pod đang chạy sai
Khi không có pod nào Pending hay Crash, nhưng app trả lỗi, đọc log theo thời gian:
kubectl logs -n shop -l app=api --tail=100 --since=15m --prefix=true
-l chọn theo label, rất tiện khi Deployment có nhiều replica và bạn không muốn copy tên pod. --prefix=true thêm tên pod vào mỗi dòng, giúp so sánh giữa các replica.
Nếu log cho thấy app không resolve được tên service, đó là lỗi DNS và thuộc bài 6.4, không phải bài này. Ghi nhận rồi đi tiếp.
Luyện tập có hướng dẫn
Làm từng bài theo đúng thứ tự 4 bước. Ghi ra giấy bạn đang ở bước nào. Đáp án ở phần gập bên dưới, chỉ mở sau khi tự trả lời.
Bài 1: Đọc trạng thái trước
kubectl get pods -n demo
NAME READY STATUS RESTARTS AGE
frontend-9c8b7d6f5-abc12 0/1 Pending 0 6m
frontend-9c8b7d6f5-def34 1/1 Running 0 6m
cache-5d4c3b2a1-ghi56 0/1 CrashLoopBackOff 12 6m
Trả lời: trong ba pod, pod nào có lỗi thuộc về scheduler, pod nào có lỗi thuộc về app? Lệnh tiếp theo bạn chạy cho từng pod là gì?
Xem gợi ý
abc12 là Pending nên là lỗi scheduler, xem kubectl describe pod frontend-9c8b7d6f5-abc12 -n demo, đọc FailedScheduling. ghi56 là CrashLoopBackOff với 12 lần restart nên là lỗi app hoặc config, xem kubectl logs cache-5d4c3b2a1-ghi56 -n demo --previous. Riêng def34 đang Running 1/1, nếu nó cũng lỗi thì đó là lỗi phía sau service, không phải pod.
Lưu ý bẫy: hai pod frontend khác trạng thái nhau nghĩa là manifest giống nhau nhưng node khác nhau, hoặc một pod bị kẹt từ trước. Đừng sửa Deployment ngay.
Bài 2: Khoanh vùng bằng event
Events:
Type Reason Age From Message
Warning FailedMount 20s kubelet MountVolume.SetUp failed for volume "cfg"
: configmap "app-config" not found
Trả lời: lỗi nằm ở đâu? Sửa bằng lệnh gì? Pod sẽ chuyển sang trạng thái gì sau khi bạn sửa?
Xem gợi ý
Lỗi ở ConfigMap chứ không phải image hay app. Kiểm tra bằng kubectl get configmap -n demo, tạo lại hoặc sửa tên trong Deployment. Sau khi ConfigMap xuất hiện, kubelet tự mount lại trong khoảng một phút, pod chuyển từ ContainerCreating sang Running. Không cần xóa pod, nhưng nếu đang vội trong phòng thi thì kubectl delete pod để nó được tạo lại ngay là cách nhanh hơn.
Bài 3: Chọn đúng lệnh log
Container api vừa crash lần thứ 5 và đang trong backoff. Bạn chạy kubectl logs api-xyz -n demo và nhận output rỗng.
Trả lời: tại sao rỗng và lệnh đúng là gì?
Xem gợi ý
Vì container hiện tại chưa chạy hoặc vừa mới được tạo lại, nên log hiện tại chưa có gì. Lệnh đúng:
kubectl logs api-xyz -n demo --previous
Nếu vẫn rỗng, kiểm tra kubectl describe pod api-xyz -n demo, phần Last State, để xem container có thực sự chạy lần nào chưa. Chưa chạy thì không có log, và lỗi nằm ở tầng image hoặc volume.
Bài 4: Tự viết quy trình
Cho một pod có STATUS là Running, READY 0/1, RESTARTS 0. Viết ra ba lệnh bạn sẽ chạy, theo đúng thứ tự, và nói mỗi lệnh để tìm gì.
Xem gợi ý
kubectl describe pod <name> -n <ns>, tìm eventUnhealthyở cuối output.READY 0/1vớiRESTARTS 0là dấu hiệu kinh điển của readiness probe sai.kubectl get pod <name> -n <ns> -o yaml, xemstatus.containerStatuses[].readyvà cấu hình probe:initialDelaySeconds,periodSeconds,httpGet.path,port.kubectl logs <name> -n <ns>, xem app có thực sự listen đúng cổng và đường dẫn mà probe gọi hay không.
Nếu probe gọi /healthz mà app chỉ phục vụ /health, đó là lỗi cấu hình probe, không phải lỗi app. Sửa probe hoặc sửa app, cả hai đều được tính điểm nếu đề yêu cầu pod ở trạng thái Ready.
Bài 5: Tốc độ
Trong 60 giây, hãy lấy ra 5 event Warning mới nhất của namespace demo và tên pod liên quan.
Xem gợi ý
kubectl get events -n demo --field-selector type=Warning \
--sort-by=.metadata.creationTimestamp -o custom-columns=OBJ:.involvedObject.name,REASON:.reason,MSG:.message
Đọc từ dưới lên để thấy sự kiện mới nhất. Trong phòng thi, mình thường gõ --sort-by=.lastTimestamp vì một số event được cập nhật thay vì tạo mới. Cả hai đều đúng, chọn cái bạn nhớ và gõ nhanh.
Góc chuyên gia
Bốn câu mình luôn tự hỏi theo thứ tự:
- Container đã từng chạy chưa? Nếu chưa, đừng đọc log, hãy đọc event.
- Control plane có báo lỗi gì không? Event
Warninglà câu trả lời. - App có nói gì trước khi chết không?
--previouslà bạn của bạn. - Có phải lỗi cấu hình tài nguyên không?
OOMKilledvàFailedSchedulingchiếm rất nhiều điểm trong đề.
Lỗi thường gặp của người mới:
- Chạy
kubectl logsvới podPendingrồi kết luận "không có log, chắc tại app". PodPendingchưa từng được tạo container. - Quên
-n, mặc định làdefault, trong khi đề dùng namespace riêng. Luôn kiểm trakubectl config view --minify -o jsonpath='{.contexts[0].context.namespace}'. - Sửa Deployment rồi không chờ rollout, dẫn tới tưởng là sửa sai. Luôn
kubectl rollout status. - Quên
-cvới pod nhiều container, mất thời gian đọc thông báo lỗi. - Xóa pod ngay khi gặp lỗi, làm mất bằng chứng trong event và
Last State. Đọc trước, xóa sau.
[!TIP] Trong phòng thi, mỗi câu troubleshoot bạn nên có một "câu chốt" viết ra giấy nháp: lỗi ở tầng nào và sửa bằng lệnh gì. Nếu chưa viết được câu chốt, bạn chưa hiểu lỗi, đừng sửa bừa.
Quy tắc 2 phút: nếu sau hai phút đọc event và log bạn vẫn chưa khoanh được vùng, hãy đổi đối tượng: từ pod sang node, sang PVC, hoặc sang Deployment. Đừng đốt 15 phút cho một câu.
Chỗ hay mất điểm oan: câu hỏi yêu cầu "pod phải ở trạng thái Running" nhưng bạn sửa app xong vẫn CrashLoopBackOff vì probe cũ vẫn sai. Sửa cả hai, rồi kiểm tra lại bằng kubectl get pod -w trước khi sang câu tiếp theo.
Khi một container đã crash, xem log của lần chạy trước bằng cách thêm cờ {{c1::--previous}}.
Exit code {{c2::137}} thường chỉ ra container bị SIGKILL, hay gặp nhất là OOMKilled.
Muốn lọc event theo tên pod, dùng {{c3::--field-selector involvedObject.name=<tên-pod>}}.
Trong output của kubectl describe pod, phần cần đọc nhất nằm ở cuối và có tên là {{c4::Events}}.
Nếu pod ở trạng thái {{c5::Pending}} thì chưa từng có container nào chạy, nên đọc log là vô nghĩa.
Tóm tắt và bước tiếp theo
Chốt lại trong bốn dòng:
kubectl get -o wideđể biết đang ở pha nào và pod nằm trên node nào.kubectl describeđể đọc event, luôn đọc từ dưới lên và ưu tiênWarning.kubectl logs --previousđể nghe app nói lần cuối trước khi chết, cộng với exit code.- Chỉ
exechoặcdebugkhi ba bước trên chưa đủ, vì nó không cho bạn biết quá khứ.
Bài này cho bạn quy trình. Bài tiếp theo, Troubleshoot node NotReady và kubelet, sẽ áp đúng quy trình đó lên một node: xem kubectl get nodes, đọc event của node, rồi journalctl -u kubelet trên node đó. Bạn sẽ thấy cùng một logic, chỉ khác đối tượng và khác nguồn log.
[
{ "front": "Lệnh đầu tiên khi một pod có vấn đề là gì?", "back": "kubectl get pod -o wide, để biết STATUS, RESTARTS và NODE trước khi đi sâu." },
{ "front": "Bạn tìm Events ở đâu trong output của kubectl describe pod?", "back": "Ở cuối output, mục Events, đọc từ dưới lên vì sự kiện mới nhất nằm cuối." },
{ "front": "Event mặc định được lưu bao lâu trong cluster?", "back": "1 giờ, cấu hình bằng --event-ttl của kube-apiserver, nên đừng chờ event cũ quay lại." },
{ "front": "Xem log của container vừa crash bằng lệnh nào?", "back": "kubectl logs <pod> --previous, viết tắt -p." },
{ "front": "Exit code 137 nghĩa là gì?", "back": "128 cộng 9, tức bị SIGKILL, thường gặp nhất là OOMKilled hoặc bị kill cứng." },
{ "front": "Exit code 143 nghĩa là gì?", "back": "128 cộng 15, tức SIGTERM, container bị terminate bình thường." },
{ "front": "Event Reason FailedScheduling cho biết điều gì?", "back": "Scheduler không tìm được node phù hợp, do thiếu tài nguyên, nodeSelector, taint hoặc PVC chưa bind." },
{ "front": "Event Reason FailedMount thường do đâu?", "back": "Thiếu ConfigMap hoặc Secret, PVC chưa bind, hoặc sai accessModes." },
{ "front": "Lệnh sắp xếp event theo thời gian tạo?", "back": "kubectl get events --sort-by=.metadata.creationTimestamp, hoặc --sort-by=.lastTimestamp." },
{ "front": "Lọc event chỉ của một pod bằng cách nào?", "back": "kubectl get events --field-selector involvedObject.name=<tên-pod>." },
{ "front": "Xem log của tất cả container trong một pod?", "back": "kubectl logs <pod> --all-containers=true, hoặc chỉ định từng container bằng -c." },
{ "front": "Cờ --tail và --since của kubectl logs dùng để làm gì?", "back": "--tail giới hạn số dòng cuối, --since lấy log từ một mốc thời gian, ví dụ 10m." },
{ "front": "Khi nào nên dùng kubectl exec thay vì đọc log?", "back": "Khi app vẫn chạy nhưng hành vi sai, cần kiểm tra DNS, file mount, biến môi trường bên trong container." },
{ "front": "Pod đang Pending thì có log để đọc không?", "back": "Không, container chưa từng được tạo, phải đọc event trong kubectl describe." },
{ "front": "Vì sao không nên xóa pod ngay khi gặp lỗi?", "back": "Vì sẽ mất bằng chứng trong Events và Last State, là nguồn thông tin chính để chẩn đoán." }
]