# Chẩn đoán CrashLoopBackOff, OOMKilled và lỗi DNS trên cụm Kubernetes của khách bằng kubectl

> Khi pod của khách cứ chết rồi sống lại, gõ lệnh kubectl theo đúng thứ tự sẽ giúp bạn tìm ra nguyên nhân thay vì đoán mò cả buổi.

Bản gốc: https://fdetimes.net/vi/bach-khoa/thuc-hanh-kubectl-debug-pod-tren-cum-cua-khach/

Pod của khách đã restart 14 lần. Bạn gõ `kubectl logs` và nhận về màn hình trống. Đây là lúc nhiều kỹ sư bắt đầu đoán mò, trong khi câu trả lời thường đã nằm sẵn trong hai lệnh mà họ chưa chạy.

Bạn sẽ tự dựng một quy trình chẩn đoán năm bước bằng kubectl cho ba loại sự cố hay gặp khi deploy vào cụm của khách: CrashLoopBackOff, OOMKilled và lỗi mạng hay DNS. Mỗi bước đi kèm một lệnh, một việc cần kiểm tra sau khi chạy và một lỗi người ta hay mắc.

Lý do kỹ năng này quan trọng với FDE khá đơn giản. Thử hình dung bạn đứng trước một cụm không phải của mình, trong tay chỉ có một terminal, và một đội vận hành đang chờ xem bạn có phân biệt được lỗi của ứng dụng với lỗi của hạ tầng hay không.

## Cần chuẩn bị gì?

Bạn cần `kubectl` đã trỏ vào một cụm, tốt nhất là cụm thử của riêng bạn trước khi động vào cụm thật, cùng quyền đọc pod, log và event trong namespace đang xét. Các ví dụ dưới đây dùng `` và `` làm chỗ giữ tên, bạn thay bằng giá trị thật. Một số tên Service trong bước mạng chỉ là ví dụ minh họa.

## Bước 1: describe trước, đoán sau

Lệnh đầu tiên luôn là:

```bash
kubectl describe pod  -n 
```

Tài liệu Debug Pods của Kubernetes khuyên bắt đầu từ trạng thái các container: tất cả có đang `Running` không, gần đây có lần restart nào không. Sau khi chạy lệnh, bạn kiểm tra ba chỗ: dòng State, dòng Last State và bộ đếm Restart Count.

Lúc này bạn cần tách được hai trường hợp hay bị nhầm. Pod kẹt ở `Pending` nghĩa là nó chưa được lập lịch lên node nào, nên container chưa từng chạy và đọc log cũng chẳng có gì. CrashLoopBackOff thì khác: pod đã lên node, container đã chạy, chỉ là nó cứ chết đi.

Nếu đúng là CrashLoopBackOff, bạn kéo xuống phần Events. Hướng dẫn của Devtron khuyên nên xem trong đó có probe nào (liveness, readiness, startup) đang fail hay không. Đó là lý do nên đọc Events trước khi mở code: nếu thủ phạm là một probe, câu trả lời có thể nằm ở cấu hình probe chứ không nằm trong ứng dụng.

## Bước 2: vì sao log trống, và cách lấy log của lần chạy đã chết

Màn hình trống ở đoạn mở đầu có một lời giải thích đơn giản. Khi pod đang crash lặp, log của lần chạy hiện tại thường chưa có gì. Thông tin cần tìm nằm ở container vừa chết, và cờ `--previous` sẽ in log của lần chạy trước đó nếu nó còn tồn tại:

```bash
kubectl logs  --previous -n 
```

**Điểm mấu chốt:** Log trống không có nghĩa là không có lỗi, mà là bạn đang đọc nhầm lần chạy.

Sau khi chạy, bạn tìm những dòng cuối cùng trước khi tiến trình thoát: một stack trace, một biến môi trường bị thiếu, một kết nối database bị từ chối.

Cũng nên hiểu nhịp restart để không kết luận vội. Theo Site24x7, kubelet thường đợi 10 giây trước lần restart đầu, sau đó 20 giây, 40 giây và cứ thế tăng lên, tối đa 5 phút. Nếu thời gian chờ tiếp tục gấp đôi, chỉ sau khoảng năm lần crash (10, 20, 40, 80, 160 giây), lần kế tiếp sẽ chạm trần 300 giây.

Nhịp chờ này giải thích một tình huống rất hay gặp. Bạn sửa config xong và thấy pod “chưa lên ngay” thì chưa chắc bản sửa đã sai, có thể pod chỉ đang chờ hết khoảng backoff.

## Bước 3: exit code 137 nghĩa là gì?

Quay lại kết quả `describe` và tìm khối Last State của container. Đoạn dưới đây là bản minh họa rút gọn, chỉ giữ ba dòng cần đọc:

```text
Last State:     Terminated
Reason:       OOMKilled
Exit Code:    137
```

Nếu thấy đúng hai dòng `Reason: OOMKilled` và `Exit Code: 137`, ứng dụng của bạn không tự crash mà đã bị giết. Tài liệu Kubernetes giải thích rằng container nào dùng nhiều bộ nhớ hơn limit thì có thể bị kill, và nếu được phép restart, kubelet sẽ khởi động lại nó.

Vòng chết rồi sống lại này chính là thứ khiến OOMKilled trông giống hệt CrashLoopBackOff. Để xem dữ liệu đầy đủ hơn, bạn xuất pod ra YAML:

```bash
kubectl get pod  -n  -o yaml
```

Trong ví dụ chính thức, trường `lastState.terminated` có `exitCode: 137` và `reason: OOMKilled`.

CAST AI phân tích con số 137 thành 128 + 9, tức container nhận tín hiệu SIGKILL (signal 9). OOM killer của kernel gửi tín hiệu này khi container vượt giới hạn bộ nhớ của cgroup. Nhưng hãy đọc kỹ: bản thân 137 chỉ cho biết container bị SIGKILL, còn dòng Reason mới cho biết lý do là bộ nhớ, nên đừng kết luận OOM khi chỉ nhìn thấy exit code.

Lỗi hay gặp nhất ở bước này là ngồi tìm bug trong code. Khi Reason đã ghi OOMKilled, thứ cần mang đi thảo luận với khách là con số memory limit so với mức ứng dụng thực sự dùng.

## Bước 4: khi image không có shell để exec vào

Nhiều khách chạy image tối giản, chẳng hạn distroless, nên `kubectl exec` không có shell hay công cụ nào để dùng. Exec cũng vô ích khi container đã crash. Tài liệu Kubernetes chỉ ra ephemeral container dành đúng cho những tình huống này: bạn gắn tạm một container có đủ công cụ debug vào pod đang gặp sự cố, thông qua lệnh `kubectl debug`.

Khung lệnh dưới đây là bản rút gọn, chỉ để bạn biết điểm bắt đầu; phần cờ chọn image và container đích cần lấy từ tài liệu Ephemeral Containers cho đúng phiên bản cụm:

```bash
# khung rút gọn, chưa đủ cờ để chạy
kubectl debug  -n  ...
```

Trước khi dùng trên cụm của khách, bạn nên hỏi trước xem chính sách bảo mật của họ có cho phép thêm ephemeral container hay không.

## Bước 5: lỗi nằm ở mạng hay ở ứng dụng?

Khi ứng dụng báo không kết nối được tới một service khác, bạn cần chứng minh DNS hoạt động trước khi sửa bất cứ thứ gì. Tài liệu Debugging DNS Resolution hướng dẫn chạy một pod `dnsutils` (manifest mẫu có trong chính tài liệu đó), rồi chạy:

```bash
kubectl exec -i -t dnsutils -- nslookup kubernetes.default
```

Nếu lệnh trả về một địa chỉ, DNS của cụm đang hoạt động. Nếu không, bạn kiểm tra CoreDNS trong namespace `kube-system`:

```bash
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns
```

Bạn cần xác nhận các pod này đang Running và log của chúng không đầy lỗi.

Nếu DNS chung vẫn chạy nhưng một Service cụ thể không resolve được, tài liệu Debug Services gợi ý thử lần lượt từ tên ngắn sang tên có namespace, rồi đến tên FQDN đầy đủ. Ví dụ dưới đây giả định Service tên `hostnames` nằm trong namespace `default` và cụm dùng domain mặc định:

```bash
kubectl exec -i -t dnsutils -- nslookup hostnames.default
kubectl exec -i -t dnsutils -- nslookup hostnames.default.svc.cluster.local
```

Nếu tên đã resolve được mà traffic vẫn không tới, bạn rà các NetworkPolicy ingress áp lên pod đích. Tài liệu Debug Services nói rõ những rule ingress có thể ảnh hưởng tới traffic vào pod cần được xem lại, nên đừng bỏ qua bước này chỉ vì DNS đã ổn.

## Vì sao mười phút đầu ở site khách quan trọng?

Thử hình dung buổi sáng đầu tiên sau khi bạn deploy một agent lên cụm của một ngân hàng. Pod restart liên tục và đội vận hành hỏi có phải do sản phẩm của bạn không.

Nếu sau mười phút bạn chỉ ra được `Reason: OOMKilled` cùng mức memory limit họ đặt, hoặc chứng minh `nslookup` thất bại ngay cả với `kubernetes.default`, cuộc nói chuyện sẽ chuyển từ đổ lỗi sang cùng nhau sửa.

Khi đọc JD của các vị trí FDE, bạn có thể để ý những yêu cầu liên quan tới việc debug deployment trên hạ tầng của khách. Trong CV, đừng chỉ ghi “biết Kubernetes”. Hãy viết một dòng cụ thể, kiểu: đã chẩn đoán sự cố OOMKilled trên cụm của khách, xác định nguyên nhân là memory limit và đề xuất giá trị mới.

Lần tới khi một pod chết, đừng mở code trước. Hãy chạy `describe`, rồi `logs --previous`, và để cụm tự cho bạn biết lỗi nằm ở đâu.

**Thử ngay tuần này:**

- Dựng một cụm thử, deploy một pod có memory limit thấp hơn mức ứng dụng dùng, rồi tìm cho ra Reason OOMKilled và Exit Code 137 trong kubectl describe pod.
- Chạy pod dnsutils và gõ nslookup kubernetes.default, sau đó thử resolve một Service theo ba dạng tên: ngắn, có namespace và FQDN.
- Viết runbook một trang gồm năm bước trong bài, để sẵn cho lần on-call kế tiếp ở site khách.

## Nguồn

- [Debug Pods | Kubernetes](https://kubernetes.io/docs/tasks/debug/debug-application/debug-pods/)

- [kubectl logs | Kubernetes](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_logs/)

- [The ultimate guide to Kubernetes CrashLoopBackOff](https://www.site24x7.com/learn/troubleshooting-kubernetes-crashloopbackoff.html)

- [Troubleshooting Pod CrashLoopBackOff Errors in K8s](https://devtron.ai/blog/troubleshoot_crashloopbackoff_pod)

- [Assign Memory Resources to Containers and Pods | Kubernetes](https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/)

- [OOMKilled and Exit Code 137: Why Kubernetes Kills Your Pods and How to Stop It](https://cast.ai/blog/oomkilled-exit-code-137/)

- [Ephemeral Containers | Kubernetes](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/)

- [Debugging DNS Resolution | Kubernetes](https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/)

- [Debug Services | Kubernetes](https://kubernetes.io/docs/tasks/debug/debug-application/debug-service/)
