# 🖧 Troubleshoot node NotReady và kubelet

## Mục tiêu bài học

Sau bài này, bạn phải làm được 4 việc trong phòng thi:

1. Đọc đúng `Conditions` của node để biết nên đi theo hướng nào (kubelet, tài nguyên, hay mạng).
2. SSH vào node, kiểm tra `kubelet` và `container runtime` bằng `systemctl` và `journalctl`.
3. Nhận diện 5 nguyên nhân NotReady hay gặp nhất trong đề CKA: kubelet chết, kubelet không `enable`, swap bật, runtime chết, certificate/config sai.
4. Đưa node về `Ready` và xác nhận bằng `kubectl get nodes -w`.

> [!NOTE]
> Đây là bài **thực hành**. Bạn nên mở sẵn một cluster lab 1 control plane + 1 worker (dựng ở Module 2) và SSH được vào worker. Mọi thao tác dưới đây đều làm được trên cluster đó.

## Ôn nhanh bài trước

Bài trước chốt quy trình 4 dòng: **event → describe → log → sửa**. Với node, thứ tự này giữ nguyên, chỉ đổi "log" thành log của kubelet trên node chứ không phải log container. Nhắc lại: `kubectl describe` cho bạn `Events` ở cấp object, còn `journalctl -u kubelet` cho bạn lý do thật sự nằm ở tầng systemd.

## Bản đồ quy trình

```mermaid
flowchart TD
    Start["🖥️ Node báo NotReady"] --> Get["kubectl get nodes -w"]
    Get --> Desc["kubectl describe node node01"]
    Desc --> Cond{"Condition nào đang xấu?"}
    Cond -->|"Ready = Unknown / KubeletNotReady"| Kube["ssh node01 + systemctl status kubelet"]
    Cond -->|"DiskPressure / MemoryPressure"| Res["df -h, du -sh /var/lib/containerd"]
    Cond -->|"NetworkUnavailable = True"| Cni["Kiểm tra CNI trong /etc/cni/net.d"]
    Kube --> Jrnl["journalctl -u kubelet -n 100"]
    Jrnl --> Rt["systemctl status containerd + crictl ps"]
    Rt --> Fix["Sửa swap / config / cert / flag"]
    Cni --> Fix
    Res --> Fix
    Fix --> Reload["systemctl daemon-reload"]
    Reload --> Restart["systemctl restart kubelet"]
    Restart --> Verify["kubectl get nodes -w"]
```

## Ví dụ mẫu: đưa `node01` từ NotReady về Ready

Tình huống: sau khi reboot worker, node `node01` đứng ở `NotReady`, pod trên đó bị đẩy sang node khác. Đây là màn bạn sẽ gặp.

```steps
1. Xác nhận triệu chứng: `kubectl get nodes`. Thấy `node01` ở trạng thái `NotReady`.
2. Xem lý do ở cấp API: `kubectl describe node node01 | sed -n '/Conditions:/,/Addresses:/p'`.
3. Đọc `Events` cuối output: thường là `NodeNotReady ... Kubelet stopped posting node status` hoặc `KubeletNotReady ... container runtime is down`.
4. SSH vào node: `ssh node01` rồi `sudo -i`.
5. Kiểm tra dịch vụ: `systemctl status kubelet` và `systemctl status containerd`.
6. Đọc log kubelet: `journalctl -u kubelet -n 100 --no-pager`.
7. Sửa nguyên nhân gốc (swap, config, flag, cert, runtime), sau đó `systemctl daemon-reload` nếu có sửa file systemd.
8. Khởi động lại: `systemctl enable --now kubelet`.
9. Xác nhận từ máy control plane: `kubectl get nodes -w`, chờ `Ready` (khoảng 40 giây).
```

Output mẫu của bước 2 và 3:

```bash
Conditions:
  Type             Status    LastHeartbeatTime    Reason
  ----             ------    -----------------    ------
  MemoryPressure   False     Mon, 12 Aug ...      KubeletHasSufficientMemory
  DiskPressure     False     Mon, 12 Aug ...      KubeletHasNoDiskPressure
  PIDPressure      False     Mon, 12 Aug ...      KubeletHasSufficientPID
  Ready            Unknown   Mon, 12 Aug ...      KubeletNotReady
Events:
  Warning  NodeNotReady   node/node01  Kubelet stopped posting node status.
```

Đọc đúng hai dòng này là bạn đã khoanh vùng được 80% vấn đề: `Ready = Unknown` + `KubeletNotReady` nghĩa là **kubelet không chạy hoặc không nói chuyện được với API server**, không phải pod của bạn lỗi.

| Condition | Ý nghĩa thực tế | Đi kiểm tra gì |
| --- | --- | --- |
| `Ready = Unknown` | kubelet ngừng gửi heartbeat (mặc định quá ~40s) | `systemctl status kubelet`, `journalctl -u kubelet` |
| `DiskPressure = True` | phân vùng image/root gần đầy | `df -h`, `du -sh /var/lib/containerd` |
| `MemoryPressure = True` | node thiếu RAM | `free -h`, `kubectl describe node` phần Allocated resources |
| `PIDPressure = True` | quá nhiều process | `ps aux | wc -l` |
| `NetworkUnavailable = True` | CNI chưa sẵn sàng | `ls /etc/cni/net.d`, pod CNI trong `kube-system` |

## Thực hành có hướng dẫn

Mỗi bài dưới đây bạn **tự phá** rồi **tự sửa**. Làm xong mỗi bài, ghi lại lệnh đã dùng vào một cheat sheet riêng, vì phòng thi không cho mở tab thứ hai.

> [!WARNING]
> Chỉ làm trên cluster lab. Các lệnh dưới đây dừng dịch vụ và có thể khiến pod bị evict tạm thời.

### Bài 1: kubelet bị stop (bài cơ bản nhất, hay xuất hiện trong đề)

**Mục tiêu:** nhận ra `Ready = Unknown` và khôi phục trong dưới 2 phút.

```bash
# Trên node01
sudo -i
systemctl stop kubelet
```

Quan sát từ control plane: `kubectl get nodes -w` (chờ khoảng 40 giây mới đổi trạng thái).

**Dấu hiệu cần thấy:** `describe node` ghi `Kubelet stopped posting node status`.

**Khôi phục:**

```bash
systemctl start kubelet
systemctl status kubelet --no-pager
```

<details><summary>Xem thêm: nếu start xong vẫn NotReady</summary>

Xem log ngay: `journalctl -u kubelet -n 50 --no-pager | grep -iE "error|failed"`. Đừng đoán, log luôn nói rõ.

</details>

### Bài 2: kubelet không được `enable` sau reboot

**Mục tiêu:** học phản xạ kiểm tra `enabled` chứ không chỉ `active`.

```bash
systemctl disable kubelet
systemctl stop kubelet
systemctl status kubelet --no-pager   # Active: inactive (dead)
```

**Khôi phục đúng cách:**

```bash
systemctl enable --now kubelet
```

> [!TIP]
> Trong đề CKA, lỗi "node NotReady sau reboot" thường chỉ là kubelet bị `disabled`. Một lệnh `systemctl enable --now kubelet` là xong, đừng đi sâu vào certificate.

### Bài 3: swap đang bật

**Mục tiêu:** đọc được thông báo lỗi kinh điển của kubelet.

```bash
fallocate -l 1G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
systemctl restart kubelet
journalctl -u kubelet -n 30 --no-pager
```

**Dấu hiệu cần thấy:** log có dòng tương tự `failed to run Kubelet: running with swap on is not supported, please disable swap`.

**Khôi phục:**

```bash
swapoff -a
# xóa hoặc comment dòng swap trong /etc/fstab để không bật lại sau reboot
rm -f /swapfile
systemctl restart kubelet
```

### Bài 4: container runtime chết

**Mục tiêu:** phân biệt lỗi kubelet và lỗi runtime.

```bash
systemctl stop containerd
systemctl status kubelet --no-pager
journalctl -u kubelet -n 30 --no-pager | grep -i "runtime"
```

**Dấu hiệu cần thấy:** kubelet báo không kết nối được tới CRI socket, something như `failed to get container runtime status` hoặc `connection refused` tới `/run/containerd/containerd.sock`.

**Khôi phục:**

```bash
systemctl enable --now containerd
systemctl restart kubelet
crictl ps          # phải trả về danh sách container, không lỗi
```

<details><summary>Nếu `crictl` báo không tìm thấy endpoint</summary>

Kiểm tra `/etc/crictl.yaml` hoặc thêm flag `--runtime-endpoint unix:///run/containerd/containerd.sock`. Ở cluster kubeadm, endpoint thật nằm trong `/var/lib/kubelet/kubeadm-flags.env`.

</details>

### Bài 5: config kubelet bị sửa sai

**Mục tiêu:** biết file nào là nguồn cấu hình thật, tránh sửa nhầm.

- `/var/lib/kubelet/config.yaml` là nội dung `KubeletConfiguration`.
- `/var/lib/kubelet/kubeadm-flags.env` là các flag kubeadm truyền vào (`KUBELET_KUBEADM_ARGS`).
- `/etc/systemd/system/kubelet.service.d/10-kubeadm.conf` là drop-in của systemd.

```bash
# gây lỗi: trỏ sai runtime endpoint
cp /var/lib/kubelet/kubeadm-flags.env /root/kubeadm-flags.env.bak
sed -i 's#containerd.sock#containerd-typo.sock#' /var/lib/kubelet/kubeadm-flags.env
systemctl daemon-reload
systemctl restart kubelet
journalctl -u kubelet -n 20 --no-pager
```

**Khôi phục:**

```bash
cp /root/kubeadm-flags.env.bak /var/lib/kubelet/kubeadm-flags.env
systemctl daemon-reload
systemctl restart kubelet
```

> [!IMPORTANT]
> Sửa file cấu hình mà không `daemon-reload` (với drop-in systemd) hoặc không `restart kubelet` thì thay đổi không có hiệu lực. Đây là lỗi làm mất thời gian nhiều nhất trong phòng thi.

### Bài 6 (mở rộng, làm nếu còn thời gian): DiskPressure

```bash
df -h /var/lib/containerd
fallocate -l 5G /tmp/fill && ls -lh /tmp/fill     # điều chỉnh theo dung lượng đĩa lab
kubectl describe node node01 | grep -i disk
```

**Khôi phục:** `rm -f /tmp/fill`, dọn image không dùng `crictl rmi --prune`, giới hạn log `journalctl --vacuum-size=100M`, rồi chờ kubelet tự cập nhật condition (không cần restart).

### Bảng kiểm sau khi node đã Ready

| Kiểm tra | Lệnh | Kỳ vọng |
| --- | --- | --- |
| Node Ready | `kubectl get nodes` | `Ready` |
| Kubelet chạy và enable | `systemctl is-active kubelet`, `systemctl is-enabled kubelet` | `active`, `enabled` |
| Runtime chạy | `systemctl is-active containerd` | `active` |
| CRI hoạt động | `crictl ps` | danh sách container, không lỗi |
| Pod hệ thống ổn | `kubectl get pods -n kube-system -o wide` | pod trên node đó `Running` |
| Không còn event lỗi | `kubectl get events -A --sort-by=.lastTimestamp` | không còn `NodeNotReady` mới |

## Lỗi thường gặp & cách khắc phục

| Lỗi | Triệu chứng | Cách xử lý |
| --- | --- | --- |
| Sửa nhầm `/etc/kubernetes/kubelet.conf` khi cần sửa `/var/lib/kubelet/config.yaml` | Sửa xong không có gì thay đổi | `kubelet.conf` là kubeconfig kết nối API, `config.yaml` là cấu hình hành vi |
| Quên `daemon-reload` | Đã sửa drop-in mà log vẫn như cũ | `systemctl daemon-reload` rồi `restart` |
| Certificate kubelet hết hạn | Log có `certificate has expired`, `Unable to register node` | Kiểm tra `openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates`; đảm bảo `rotateCertificates: true` và symlink `kubelet-client-current.pem` còn đúng |
| CNI chưa cài | `NetworkUnavailable = True`, node có thể vẫn `Ready` nhưng pod không có IP | `ls /etc/cni/net.d`, kiểm tra pod CNI trong `kube-system` |
| Sai giờ hệ thống | Certificate bị coi là chưa/hết hiệu lực | `timedatectl`, bật `chronyd` hoặc `systemd-timesyncd` |
| Firewall chặn 10250 | Control plane không lấy được status | Kiểm tra kết nối tới port `10250` (kubelet API) và `6443` |

> [!CAUTION]
> Đừng xóa `ca.crt` hay `kubelet.conf` để "làm lại từ đầu" trong phòng thi. Bạn sẽ mất nhiều thời gian hơn lỗi ban đầu. Ưu tiên đọc log, sửa đúng một dòng, restart.

## Góc chuyên gia (Expert Corner)

- **Đọc log theo thứ tự ưu tiên**: dòng `failed to run Kubelet` xuất hiện gần cuối log thường là nguyên nhân gốc; các dòng phía trên chỉ là hệ quả. Dùng `journalctl -u kubelet -n 200 --no-pager | grep -iE "error|fail|expired|swap"` để lọc nhanh.
- **Ngưỡng thời gian cần nhớ**: kubelet gửi status mỗi 10 giây, control plane đánh dấu NotReady sau khoảng 40 giây (`node-monitor-grace-period`). Pod có toleration mặc định cho `not-ready` khoảng 300 giây mới bị evict. Vì vậy cứ bình tĩnh sửa, đừng hoảng khi thấy pod còn nằm trên node lỗi.
- **Không có SSH?** Dùng `kubectl debug node/node01 -it --image=busybox` để vào namespace của node, hoặc kiểm tra qua `kubectl get --raw /api/v1/nodes/node01/proxy/configz` để xem cấu hình kubelet đang chạy.
- **Quy tắc 3 lệnh**: mọi bài node NotReady trong đề đều bắt đầu bằng `kubectl describe node`, `systemctl status kubelet`, `journalctl -u kubelet`. Thuộc ba lệnh này là đủ để mở khóa bài toán.
- **Kiểm tra runtime trước khi nghi kubelet**: nếu `crictl ps` treo hoặc lỗi, sửa runtime trước, vì kubelet không thể Ready khi CRI không trả lời.
- **Ghi lại mọi thay đổi**: trong CKA, nếu bạn sửa 4 file mà không nhớ file nào, việc rollback sẽ tốn thời gian gấp đôi. Backup bằng `cp file file.bak` trước khi sửa.

## Tóm tắt & bước tiếp theo

Chốt lại trong bốn dòng:

1. `Ready = Unknown`/`KubeletNotReady` là bài toán của kubelet, không phải của workload.
2. Đi theo thứ tự: `describe node` → `systemctl status kubelet` → `journalctl -u kubelet` → `crictl ps`.
3. Bốn nguyên nhân phổ biến: kubelet không chạy, không `enable`, swap bật, runtime chết.
4. Luôn kết thúc bằng `systemctl daemon-reload` (nếu sửa file) và `kubectl get nodes -w` để xác nhận.

Điền nhanh các ô trống sau để tự kiểm tra:

Node chuyển sang NotReady sau khoảng {{c1::40}} giây kể từ khi kubelet ngừng gửi heartbeat. Lệnh xem log kubelet trên node là {{c2::journalctl -u kubelet}}, còn công cụ CLI nói chuyện với container runtime qua CRI là {{c3::crictl}}. Kubelet mặc định yêu cầu swap phải {{c4::tắt}} (`swapoff -a`). Nếu condition `NetworkUnavailable` bằng True thì thường do {{c5::CNI chưa được cài hoặc pod CNI đang lỗi}}. Sau khi sửa file drop-in của systemd, phải chạy {{c6::systemctl daemon-reload}} trước khi restart kubelet.

**Bài tiếp theo:** Troubleshoot pod với `CrashLoopBackOff`, `ImagePullBackOff` và `Pending`. Khi node đã `Ready` mà pod vẫn không lên, vấn đề chuyển sang image, resource, scheduling và probe. Chúng ta sẽ dùng lại đúng quy trình này nhưng ở cấp pod.

```flashcard
[
  { "front": "Node ở trạng thái NotReady với Ready = Unknown thường do đâu?", "back": "Kubelet ngừng gửi heartbeat hoặc không kết nối được API server." },
  { "front": "Lệnh đầu tiên để xem Conditions và Events của một node?", "back": "kubectl describe node <ten-node>" },
  { "front": "Lệnh xem log của kubelet trên node?", "back": "journalctl -u kubelet -n 100 --no-pager" },
  { "front": "Mặc định mất bao lâu để node bị đánh dấu NotReady sau khi kubelet dừng?", "back": "Khoảng 40 giây (node-monitor-grace-period)." },
  { "front": "File cấu hình KubeletConfiguration trên node kubeadm nằm ở đâu?", "back": "/var/lib/kubelet/config.yaml" },
  { "front": "File chứa các flag kubeadm truyền cho kubelet?", "back": "/var/lib/kubelet/kubeadm-flags.env" },
  { "front": "Kubelet báo 'running with swap on is not supported' thì sửa thế nào?", "back": "swapoff -a, xóa dòng swap trong /etc/fstab, rồi restart kubelet." },
  { "front": "Công cụ CLI làm việc với container runtime qua CRI?", "back": "crictl (ví dụ crictl ps, crictl info)." },
  { "front": "Condition NetworkUnavailable = True thường do gì?", "back": "CNI chưa được cài hoặc pod CNI trong kube-system đang lỗi." },
  { "front": "Sau khi sửa systemd drop-in của kubelet phải làm gì trước khi restart?", "back": "Chạy systemctl daemon-reload." },
  { "front": "Lệnh kiểm tra hạn certificate kubelet client?", "back": "openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates" },
  { "front": "Làm sao đảm bảo kubelet tự chạy lại sau khi node reboot?", "back": "systemctl enable kubelet (hoặc enable --now kubelet)." },
  { "front": "Node báo DiskPressure thì kiểm tra gì đầu tiên?", "back": "df -h và dung lượng /var/lib/containerd, sau đó dọn image bằng crictl rmi --prune." },
  { "front": "Không có SSH vào node, dùng lệnh nào để vào namespace của node?", "back": "kubectl debug node/<ten-node> -it --image=busybox" },
  { "front": "Làm sao theo dõi node chuyển về Ready theo thời gian thực?", "back": "kubectl get nodes -w" }
]
```
