📜 Đọc log control plane và journalctl

Khi sự cố đã vượt khỏi một pod hay một Service, bạn phải lên tầng control plane. Ở đó có một điểm dễ mất điểm thi: không phải thành phần nào cũng chạy như pod. Control plane chạy dạng static pod, còn kubelet và container runtime chạy dạng systemd service. Nhầm chỗ này là gõ sai lệnh và mất vài phút quý giá. Bài này luyện đúng phản xạ đó.

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

Sau bài này, bạn phải làm được:

  • Xác định đúng nguồn log cho từng thành phần: kube-apiserver, kube-controller-manager, kube-scheduler, etcd, kubelet, containerd.
  • Dùng thành thạo kubectl logs với --since, --tail, --timestamps và journalctl với -u, -b, -p, -g, -f, --no-pager.
  • Đọc được vài dòng log điển hình và suy ra thành phần gốc rễ, không dừng ở thành phần báo lỗi đầu tiên.
  • Biết đường vòng khi kube-apiserver đã chết, lúc mà kubectl không còn dùng được.

Ôn lại nhanh

Bài trước kết thúc ở chỗ: pod chạy ổn, Service có endpoint, vậy mà kết nối vẫn hỏng. Lúc đó bạn khoanh vùng trong namespace. Còn khi kubectl chập chờn, nhiều node cùng NotReady, hoặc mọi thao tác ghi đều timeout, thì vấn đề đã nằm ở control plane. Bước tiếp theo là mở log của đúng thành phần.

Bản đồ: log nằm ở đâu

Thành phần Chạy dưới dạng Cách đọc log
kube-apiserver static pod kubectl -n kube-system logs kube-apiserver-<node>
kube-controller-manager static pod kubectl -n kube-system logs kube-controller-manager-<node>
kube-scheduler static pod kubectl -n kube-system logs kube-scheduler-<node>
etcd static pod (hoặc external) kubectl -n kube-system logs etcd-<node>
kubelet systemd service journalctl -u kubelet
containerd systemd service journalctl -u containerd

Hai điểm cần nhớ:

  1. Tên static pod luôn có hậu tố là tên node, ví dụ kube-apiserver-cp. Đừng gõ thiếu.
  2. ==kubelet là systemd service, không phải pod==, nên kubectl logs không bao giờ đọc được log kubelet. Manifest static pod nằm trong /etc/kubernetes/manifests/.
flowchart TD
    Sym["🚨 Có sự cố"] --> Q{"API server còn trả lời?"}
    Q -->|"Có"| K["kubectl logs -n kube-system"]
    Q -->|"Không"| C["SSH vào node<br/>crictl ps / crictl logs"]
    K --> API["🧠 kube-apiserver"]
    K --> ETCD["💾 etcd"]
    K --> CM["🎛️ controller-manager và scheduler"]
    C --> KL["⚙️ kubelet: journalctl -u kubelet"]
    KL --> RT["📦 containerd: journalctl -u containerd"]

Ví dụ làm mẫu

Đề bài kiểu thi: "kubectl apply thỉnh thoảng trả về timeout, kubectl get nodes vẫn chạy, các node đều Ready. Xác định thành phần lỗi."

1. Kiểm tra sức khỏe từng thành phần: `kubectl get --raw='/readyz?verbose'` để xem check nào đang fail.
2. Liệt kê static pod control plane: `kubectl -n kube-system get pods -o wide | grep -E 'apiserver|etcd|controller-manager|scheduler'`.
3. Đọc log apiserver 10 phút gần nhất: `kubectl -n kube-system logs kube-apiserver-<node> --since=10m --tail=200 | grep -iE 'error|timeout|etcd'`.
4. Nếu `kubectl` không phản hồi: SSH vào node control plane, dùng `sudo crictl ps -a | grep apiserver` rồi `sudo crictl logs --tail 100 <container-id>`.
5. Đọc kubelet trên node nghi vấn: `sudo journalctl -u kubelet --since "15 min ago" --no-pager | tail -80`.
6. Đọc etcd: `kubectl -n kube-system logs etcd-<node> --tail=100`.
7. Kết luận thành phần gốc rễ, rồi kiểm chứng bằng một lệnh khác (ví dụ `etcdctl endpoint health`).

Giả sử bước 3 trả về:

E0102 10:14:03.512871   1 storage.go:134] etcdserver: request timed out
E0102 10:14:03.612004   1 trace.go:236] Trace[1123]: "GuaranteedUpdate etcd3" error:context deadline exceeded
W0102 10:14:08.101223   1 reflector.go:535] failed to list *v1.Pod: etcdserver: request timed out

Đọc đúng cách: apiserver không phải thủ phạm, nó chỉ là nạn nhân đang báo etcd chậm. Sang log etcd:

{"level":"warn","ts":"...","msg":"failed to send out heartbeat on time; took too long","timeout":"1.5s"}
{"level":"warn","ts":"...","msg":"leader failed to send out heartbeat on time"}

Kết luận: etcd bị chậm, thường do disk I/O hoặc CPU bão hòa trên node control plane. Đây là kiểu lập luận mà đề CKA hay hỏi gián tiếp qua log.

Bảng tra nhanh: dòng log và ý nghĩa

Dòng log Gợi ý nguyên nhân
etcdserver: request timed out etcd chậm, disk I/O kém, quá tải
lost leader / leader changed etcd mất leader, mạng giữa các member có vấn đề
x509: certificate has expired Certificate hết hạn hoặc giờ hệ thống lệch
Unable to authenticate the request Token hoặc cert không hợp lệ, hay gặp khi cert hết hạn
connection refused tới 127.0.0.1:2379 etcd không chạy hoặc apiserver trỏ sai endpoint
PLEG is not healthy Runtime hoặc kubelet bị treo
leaderelection lost ở controller-manager Mất kết nối ổn định tới apiserver
failed to send out heartbeat on time ở etcd Disk chậm, CPU bão hòa

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

Làm trên cluster lab của bạn, mỗi bài 3 đến 5 phút. Ghi lại lệnh đã dùng, vì trong thi bạn sẽ cần gõ lại từ trí nhớ.

Bài 1: Lập bản đồ log của control plane

kubectl -n kube-system get pods -o wide | grep -E 'kube-apiserver|kube-controller-manager|kube-scheduler|etcd'
kubectl -n kube-system logs kube-apiserver-<node> --tail=20 --since=5m
kubectl -n kube-system logs etcd-<node> --tail=20

Câu hỏi tự kiểm tra: tên pod có hậu tố gì, và hậu tố đó lấy từ đâu?

Xem đáp án

Hậu tố là tên node, ví dụ kube-apiserver-cp. Lý do: static pod do kubelet trên từng node tạo ra, nên tên pod gắn với node sở hữu.

Bài 2: Giới hạn và lọc log apiserver

kubectl -n kube-system logs kube-apiserver-<node> \
  --since=15m --tail=200 --timestamps | grep -iE 'error|forbidden|unauthor|timeout'

Sau đó thử đọc log controller-manager và scheduler, tìm dòng leaderelection.

Xem đáp án

--since=15m giới hạn theo thời gian, --tail=200 giới hạn số dòng, --timestamps thêm mốc thời gian để bạn đối chiếu với journalctl của kubelet. Nếu grep không ra dòng nào, nghĩa là 15 phút vừa rồi không có lỗi auth hay timeout ở apiserver: chuyển sang nguồn khác thay vì ngồi đọc tiếp.

Bài 3: journalctl cho kubelet

sudo systemctl status kubelet --no-pager -l
sudo journalctl -u kubelet --since "15 min ago" --no-pager | tail -80
sudo journalctl -u kubelet -b -p warning --no-pager | tail -50
sudo journalctl -u kubelet -g 'PLEG|failed to (pull|run)' --no-pager | tail -30

Câu hỏi tự kiểm tra: -b và -p warning khác nhau thế nào?

Xem đáp án

-b giới hạn trong lần boot hiện tại (hữu ích khi journal không persistent, chỉ giữ log của boot đang chạy). -p warning lọc theo mức ưu tiên từ warning trở lên (warning, err, crit, alert, emerg). -g là grep tích hợp sẵn của journalctl, dùng khi bạn biết chính xác từ khóa.

Bài 4: Đường vòng khi apiserver đã chết

sudo crictl ps -a | grep -E 'kube-apiserver|etcd'
sudo crictl logs --tail 100 <container-id>
sudo ls /var/log/pods | head
sudo ls -l /var/log/containers | grep apiserver
sudo tail -50 /var/log/containers/kube-apiserver-*.log

Bài này mô phỏng tình huống kubectl báo The connection to the server ... was refused. Ghi nhớ: log container vẫn nằm trên đĩa node, dưới /var/log/pods/<namespace>_<pod>_<uid>/<container>/0.log, và /var/log/containers/ chỉ là symlink trỏ tới đó.

Xem đáp án

Thứ tự đúng: crictl ps -a để tìm container đang chạy hoặc vừa crash, crictl logs để đọc log, và nếu container không còn nữa thì đọc file trong /var/log/pods/ hoặc /var/log/containers/. Nhớ dùng sudo vì các thư mục này thuộc root.

Bài 5: etcd, đọc log và kiểm chứng

kubectl -n kube-system get pods -l component=etcd
kubectl -n kube-system logs etcd-<node> --tail=50 | grep -iE 'warn|error|leader|slow'
kubectl -n kube-system exec etcd-<node> -- etcdctl \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  endpoint health --cluster

Bài học ở đây: log cho biết chuyện gì, còn endpoint health cho biết hệ thống còn sống không. Trong thi, một lệnh kiểm chứng nhanh đáng giá hơn mười phút đọc log.

Bài drill: chọn nguồn log đầu tiên

Với mỗi triệu chứng, viết ra lệnh đầu tiên bạn sẽ chạy.

  1. kubectl get nodes trả về The connection to the server ... was refused.
  2. Một node chuyển NotReady, các node khác bình thường.
  3. kubectl apply chậm và thỉnh thoảng timeout, node vẫn Ready.
  4. apiserver log có x509: certificate has expired.
  5. CronJob không tạo Job nào.
Xem đáp án
  1. SSH vào node control plane: sudo crictl ps -a | grep apiserver rồi crictl logs, hoặc journalctl -u kubelet để xem vì sao static pod không lên.
  2. Trên chính node đó: sudo journalctl -u kubelet --since "15 min ago" --no-pager | tail -80. Đồng thời kiểm tra systemctl status kubelet.
  3. Log etcd trước tiên: kubectl -n kube-system logs etcd-<node> --tail=100, vì apiserver timeout gần như luôn quy về etcd hoặc disk.
  4. sudo kubeadm certs check-expiration để xác nhận, rồi xem log kubelet quanh thời điểm lỗi. Cũng kiểm tra giờ hệ thống bằng date -u.
  5. Log kube-controller-manager: kubectl -n kube-system logs kube-controller-manager-<node> --since=15m | grep -i cronjob.

Cloze để tự kiểm tra

kubelet chạy dưới dạng systemd service nên log đọc bằng {{c1::journalctl -u kubelet}}.
kube-apiserver, kube-scheduler, kube-controller-manager và etcd chạy dưới dạng {{c2::static pod}}, manifest nằm trong /etc/kubernetes/manifests.
Khi apiserver không phản hồi, phải SSH vào node và dùng {{c3::crictl}} để đọc log container.
Để chỉ xem log trong 10 phút gần nhất với kubectl logs, dùng flag {{c4::--since=10m}}.
Dòng log "etcdserver: request timed out" cho thấy gốc lỗi nằm ở {{c5::etcd}}, thường do disk I/O chậm.
Flag {{c6::-f}} của journalctl dùng để theo dõi log theo thời gian thực.

Lỗi thường gặp và cách xử lý

1. Gõ kubectl logs kubelet-.... Kubelet không phải pod. Luôn dùng journalctl -u kubelet. Đây là lỗi tốn thời gian nhất trong nhóm này.

2. Quên hậu tố tên node. kubectl logs -n kube-system kube-apiserver sẽ báo not found. Luôn kubectl get pods -n kube-system -o wide trước để lấy tên đầy đủ.

3. Đọc log khi apiserver đã chết. kubectl cần apiserver để trả lời, kể cả khi bạn chỉ muốn xem log. Lúc đó phải dùng crictl hoặc file trong /var/log/pods/.

4. Đọc log không giới hạn thời gian. Log apiserver và kubelet rất dài. Luôn kèm --since và --tail. Mặc định của tôi trong thi là --since=10m --tail=100.

5. Dừng ở thành phần báo lỗi đầu tiên. Apiserver báo lỗi etcd không có nghĩa apiserver hỏng. Đọc thêm một tầng nữa trước khi kết luận.

6. Nhầm -p. Với kubectl logs, -p là previous container (log của lần chạy trước). Với journalctl, -p là priority. Cùng một chữ, hai nghĩa hoàn toàn khác.

7. Quên sudo. /var/log/pods và journalctl của unit hệ thống thường cần quyền root.

8. Không đối chiếu mốc thời gian. Log mặc định theo UTC. Khi so log apiserver với log kubelet, dùng date -u và --timestamps để khớp sự kiện.

[!WARNING] Đừng bật --v=4 hoặc sửa manifest static pod trên cluster đang làm bài nếu không cần. Lưu manifest trước khi sửa, và nhớ khôi phục. Một manifest apiserver hỏng sẽ làm bạn mất luôn đường vào cluster.

Góc chuyên gia

  • Quy tắc 2 phút: mỗi nguồn log chỉ dành tối đa 2 phút. Không thấy gì thì chuyển nguồn, đừng cố đọc cho bằng hết.
  • Đọc từ dưới lên: lỗi mới nhất nằm cuối file. tail trước, rồi mới mở rộng --since nếu cần context.
  • Kiểm chứng bằng lệnh thứ hai: log nói etcd chậm thì chạy endpoint health; log nói cert hết hạn thì chạy kubeadm certs check-expiration. Một kết luận có hai nguồn là kết luận bạn có thể tin.
  • Alias tiết kiệm giây: alias kl='kubectl -n kube-system logs' và alias jk='sudo journalctl -u kubelet --no-pager'. Đừng đặt alias trùng tên lệnh có sẵn.
  • readyz là điểm khởi đầu rẻ nhất: kubectl get --raw='/readyz?verbose' cho bạn danh sách check và check nào đang fail, nhanh hơn nhiều so với đoán.
  • etcd là nghi phạm số một khi apiserver timeout mà node vẫn Ready. Kiểm tra disk trên node control plane trước khi đổ lỗi cho mạng.
  • Log bị rotate: kubelet giới hạn kích thước log container, nên sự kiện quá cũ có thể đã biến mất. Đừng mất thời gian tìm log của 2 giờ trước.

[!TIP] Một mẹo nhỏ khi đọc log apiserver: thêm | grep -v 'TLS handshake' để loại nhiễu, rồi | tail -40 để xem phần mới nhất. Bạn sẽ thấy lỗi thật nhanh hơn nhiều.

Tóm tắt và bước tiếp theo

Bốn dòng tôi tự nhắc trước khi mở log control plane:

  1. Control plane là static pod, kubectl logs -n kube-system với tên đầy đủ có hậu tố node.
  2. kubelet và containerd là systemd service, đọc bằng journalctl -u <unit>.
  3. Luôn giới hạn thời gian và số dòng: --since, --tail, --no-pager.
  4. Apiserver timeout gần như luôn dẫn về etcd hoặc disk, hãy đọc thêm một tầng.

Bạn vừa có đủ bộ công cụ để lần theo log của cả ba tầng: apiserver, kubelet và etcd. Bài tiếp theo là Checkpoint: Troubleshooting cluster và workload, nơi bạn sẽ gặp các đề tổng hợp trộn pod, node, Service và control plane trong cùng một tình huống, đúng kiểu đề thi thật.

[
  { "front": "kubelet chạy dưới dạng gì và đọc log bằng lệnh nào?", "back": "Systemd service, đọc bằng `journalctl -u kubelet`." },
  { "front": "kube-apiserver, kube-scheduler, kube-controller-manager và etcd chạy dưới dạng gì trên cluster kubeadm?", "back": "Static pod, manifest nằm trong `/etc/kubernetes/manifests/`." },
  { "front": "Lệnh xem log kube-apiserver qua kubectl?", "back": "`kubectl -n kube-system logs kube-apiserver-<tên-node>`." },
  { "front": "Khi kube-apiserver đã chết, đọc log của nó bằng cách nào?", "back": "SSH vào node, dùng `crictl ps -a` tìm container rồi `crictl logs <id>`, hoặc đọc `/var/log/pods/`." },
  { "front": "Flag nào của `kubectl logs` giới hạn log theo thời gian?", "back": "`--since=10m` (nhận cả `--since=1h` hoặc mốc thời gian RFC3339)." },
  { "front": "Flag nào của `journalctl` theo dõi log theo thời gian thực?", "back": "`-f` (follow)." },
  { "front": "`journalctl -u kubelet -b -p err` nghĩa là gì?", "back": "Log kubelet trong lần boot hiện tại, chỉ mức lỗi (err) trở lên." },
  { "front": "Dòng `etcdserver: request timed out` trong log apiserver gợi ý gốc lỗi ở đâu?", "back": "Ở etcd: chậm, quá tải hoặc disk I/O kém, không phải apiserver." },
  { "front": "`x509: certificate has expired` nghĩa là gì?", "back": "Certificate đã hết hạn (hoặc giờ hệ thống lệch), cần kiểm tra bằng `kubeadm certs check-expiration`." },
  { "front": "Log container của static pod được lưu ở đâu trên node?", "back": "`/var/log/pods/<namespace>_<pod>_<uid>/<container>/0.log`, kèm symlink trong `/var/log/containers/`." },
  { "front": "Lệnh xem log etcd trong cluster kubeadm?", "back": "`kubectl -n kube-system logs etcd-<tên-node>`." },
  { "front": "Flag nào tránh journalctl mở pager khi pipe?", "back": "`--no-pager`." },
  { "front": "`leaderelection lost` ở kube-controller-manager cho biết gì?", "back": "Mất leader với apiserver, thường do apiserver không ổn định hoặc mạng giữa các control plane node." },
  { "front": "`journalctl -u kubelet -g 'failed'` dùng để làm gì?", "back": "Lọc log kubelet theo pattern `failed` bằng grep tích hợp của journalctl." },
  { "front": "`kubectl get --raw='/readyz?verbose'` cho bạn biết gì?", "back": "Danh sách các health check của control plane và check nào đang fail." }
]