Kiến Trúc Logging

K
Kai··4 min read

Part XIII chuyển sang quan sát: làm sao biết bên trong cụm đang xảy ra gì. Bắt đầu với thứ dùng nhiều nhất — log. kubectl logs quen thuộc tới mức ta quên hỏi nó lấy log từ đâu. Bài này lần theo một dòng log thật từ kubectl xuống tận file trên đĩa node, rồi tách các loại log của cụm.

kubectl logs lấy từ đâu

Container ghi stdout/stderr, container runtime hứng và ghi ra file trên node, kubelet đọc file đó trả về cho kubectl logs. Lần theo: tạo một pod cứ vài giây in một dòng, ghim worker-0:

kubectl -n log-demo run talker --image=busybox:1.36 -- \
  sh -c 'i=0; while true; do echo "dong log so $i tu talker"; i=$((i+1)); sleep 2; done'
kubectl -n log-demo logs talker --tail=2
dong log so 1 tu talker
dong log so 2 tu talker

kubectl logs đọc qua kubelet. Còn file thật nằm ở /var/log/pods/<ns>_<pod>_<uid>/<container>/:

ssh worker-0 'sudo ls /var/log/pods/log-demo_talker_<uid>/talker/'
ssh worker-0 'sudo tail -2 /var/log/pods/log-demo_talker_<uid>/talker/0.log'
0.log
2026-05-24T00:05:16.381709653Z stdout F dong log so 2 tu talker
2026-05-24T00:05:18.381816019Z stdout F dong log so 3 tu talker

File 0.log ở định dạng CRI: mỗi dòng là <timestamp> <stream> <tag> <nội dung>stdout/stderr là luồng, F (full) hoặc P (partial, dòng dài bị cắt). kubectl logs bóc lại phần nội dung. Có thêm một lớp symlink ở /var/log/containers/ trỏ vào file này, để agent thu log tìm theo một chỗ cố định:

ssh worker-0 'ls -l /var/log/containers/ | grep talker'
talker_log-demo_talker-6b5707f2....log -> /var/log/pods/log-demo_talker_<uid>/talker/0.log

Kubelet xoay log

Log không được phình mãi. Kubelet xoay file theo kích thước:

kubectl get --raw /api/v1/nodes/worker-0/proxy/configz | jq '.kubeletconfig |
  {containerLogMaxSize, containerLogMaxFiles}'
containerLogMaxSize: 10Mi
containerLogMaxFiles: 5

Mỗi file log tối đa 10Mi, giữ tối đa 5 file (0.log hiện tại + các file xoay). Một điểm cần nhớ: kubectl logs chỉ đọc file hiện tại — log đã xoay phải lấy thẳng từ node. Vì vậy log container là tạm: chết theo pod, lại bị giới hạn dung lượng, nên không thể dựa vào nó làm nơi lưu log lâu dài.

Log thành phần hệ thống: journald

Log container là một loại; log của chính các thành phần Kubernetes là loại khác. Trên cụm này chúng chạy bằng systemd (Part I), nên log vào journald:

ssh worker-0    'sudo journalctl -u kubelet        -n 1 -o short-iso'
ssh controller-0 'sudo journalctl -u kube-apiserver -n 1 -o short-iso'
... worker-0 kubelet[23367]: ... "Finished parsing log file" path="/var/log/pods/log-demo_talker_.../0.log"
... controller-0 kube-apiserver[7449]: ... apf_controller.go:493 "Update CurrentCL" plName="exempt" ...

Đây là chỗ cụm tự dựng khác cụm kubeadm: kubeadm chạy control plane bằng static pod, nên log apiserver/scheduler nằm trong /var/log/pods như container. Cụm ta chạy chúng bằng systemd service (Bài 7–8), nên log của chúng ở journald — xem bằng journalctl -u <service> chứ không phải kubectl logs. (Dòng apiserver ở trên tình cờ lộ APF — chủ đề Bài 66.)

Cụm không tự gom log

Có một điểm dễ hiểu nhầm: Kubernetes không có logging cấp cụm sẵn. Log nằm rải trên từng node, tạm và bị xoay. Để gom về một chỗ truy vấn được, mô hình phổ biến là node logging agent — một DaemonSet (Bài 26) chạy trên mọi node, mount hostPath: /var/log, tail các file ở /var/log/containers/ rồi đẩy về hệ thống lưu trữ ngoài (Loki, Elasticsearch, CloudWatch...):

   mỗi node:  /var/log/containers/*.log
                   │ tail
                   ▼
   agent (DaemonSet, hostPath /var/log)  ──►  kho log ngoài cụm

Vì là DaemonSet, agent tự có mặt trên node mới và tự biến mất khi node rút — đúng lý do DaemonSet hợp cho việc theo-node. Cách khác là sidecar (Bài 19) cho ứng dụng ghi log ra file thay vì stdout. Điểm chung: log phải rời node, vì lưu trên node thì mất theo node.

🧹 Dọn dẹp

kubectl delete namespace log-demo

Bài này chỉ tạo một pod in log rồi đọc; phần còn lại là xem file và journald. Không cài agent thật (đó là lựa chọn hạ tầng tùy nơi). Lệnh dùng trong bài ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 65-logging.

Tổng kết

kubectl logs đọc qua kubelet từ file /var/log/pods/<ns>_<pod>_<uid>/<container>/0.log mà runtime ghi, định dạng CRI (timestamp stream F/P nội dung), có symlink ở /var/log/containers/ cho agent. Kubelet xoay log theo containerLogMaxSize (10Mi) và containerLogMaxFiles (5), và kubectl logs chỉ thấy file hiện tại — nên log container là tạm. Log của thành phần hệ thống đi đường khác: cụm tự dựng chạy chúng bằng systemd nên log ở journald (journalctl -u kubelet/-u kube-apiserver), khác cụm kubeadm để control plane là static pod. Kubernetes không gom log cấp cụm; mô hình chuẩn là một node logging agent chạy như DaemonSet, mount /var/log, đẩy log ra kho ngoài — vì log lưu trên node sẽ mất theo node.

Log cho ta sự kiện rời rạc. Bài 66 sang số liệu liên tục: endpoint /metrics định dạng Prometheus mà apiserver và kubelet phơi ra, traces, và API Priority and Fairness — cơ chế apiserver tự bảo vệ khỏi quá tải, mà ta vừa thoáng thấy trong log ở trên.

Related Posts