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

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.

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:

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
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.

# 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:

systemctl start kubelet
systemctl status kubelet --no-pager
Xem thêm: nếu start xong vẫn NotReady

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

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.

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

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

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.

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:

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.

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:

systemctl enable --now containerd
systemctl restart kubelet
crictl ps          # phải trả về danh sách container, không lỗi
Nếu `crictl` báo không tìm thấy endpoint

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.

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.
# 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:

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

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.

[
  { "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" }
]