Camera an ninh ở nhiều cửa hàng chỉ giữ lại một giờ băng, rồi tự ghi đè. Điều đó ổn nếu bạn xem ngay — nhưng vụ trộm lúc hai giờ sáng mà chín giờ mới có người mở lại thì cuốn băng đã sạch trơn, và không ai còn cách nào biết chuyện gì đã xảy ra. Sự kiện của Kubernetes chính là cuốn băng ấy: nó ghi lại vì sao cụm làm điều nó vừa làm, nhưng chỉ giữ đúng một giờ rồi xoá vĩnh viễn. Bài này đo cái hạn dùng đó, cùng hai cách người ta hay đọc nhầm chính cuốn băng.
Phần trước đo nhật ký ứng dụng. Sự kiện là nửa còn lại: nơi Kubernetes tự nói ra vì sao nó làm điều nó vừa làm.
Đúng một giờ
Cụm của tôi chạy liên tục nhiều giờ. Đọc toàn bộ sự kiện:
cũ nhất 61 phút trước
mới nhất 1 phút trước
tổng 623 sự kiện
Không có cấu hình nào đặt --event-ttl trong manifest của API server. 61 phút là mặc định, và nó là trần cứng. Chỗ khác biệt lớn so với nhật ký: nhật ký hết hạn thì tệp đã xoay vòng vẫn còn trên đĩa node, moi ra được. Sự kiện hết hạn thì API server xoá khỏi etcd và không còn bản sao ở đâu cả.
Nên quy tắc thực hành duy nhất đáng nhớ về sự kiện là: chụp trước, đọc sau.
kubectl get events -A -o yaml > su-kien-$(date +%s).yaml
Ba mươi giây gõ lệnh này, hay một giờ sau ngồi đoán. Sự cố lúc 2 giờ sáng mà 9 giờ mới mở máy thì không còn gì để xem.
kubectl events thay cho kubectl get events
Từ 1.28 có một lệnh riêng, và nó tốt hơn hẳn:
kubectl events -n <ns> # sắp xếp theo thời gian sẵn
kubectl events --for pod/<ten> # chỉ một đối tượng
kubectl events -A --watch # theo dõi trực tiếp
kubectl get events mặc định sắp xếp theo tên, nên danh sách nhảy lung tung về thời gian — đo được thứ tự 6m1s, 16s, 16s, 16s, 27s, 6m. Ai cũng phải nhớ thêm --sort-by=.lastTimestamp, còn kubectl events làm sẵn. Nó cũng hiện số lần gộp ngay trên dòng: 36s (x26 over 6m8s).
Gộp sự kiện, và con số dễ đọc nhầm
Một pod crashloop, sau sáu phút:
count |
reason |
lần cuối |
|---|---|---|
| 1 | Scheduled |
— |
| 6 | Pulled |
16:17:00 |
| 6 | Created |
16:17:00 |
| 6 | Started |
16:17:00 |
| 26 | BackOff |
16:19:38 |
Số lần khởi động lại thật sự: 5.
Kubernetes không tạo một bản ghi cho mỗi lần xảy ra. Sự kiện trùng nhau gộp vào một đối tượng, tăng count và cập nhật lastTimestamp. Nhờ vậy một pod hỏng không làm ngập etcd. Nhưng chỗ này đọc rất dễ nhầm. Ba dòng Pulled/Created/Started dừng ở 16:17 — pod thôi khởi động lại từ lúc đó, vì backoff đã leo lên 5 phút. Trong khi BackOff vẫn phát đều mỗi khoảng mười giây và đếm tiếp tới 26.
Đọc BackOff x26 rồi kết luận "pod khởi động lại 26 lần" là sai gấp năm lần. Số đúng chỉ có một chỗ:
kubectl get pod <ten> -o jsonpath='{.status.containerStatuses[0].restartCount}'
Cách phân biệt tổng quát: count của sự kiện đếm số lần kubelet nói về một tình trạng, không đếm số lần tình trạng đó xảy ra. Với sự kiện mô tả trạng thái kéo dài — BackOff, FailedScheduling, Unhealthy — hai con số đó khác hẳn nhau. Dấu hiệu nhận ra nhanh: so firstTimestamp với lastTimestamp. Khoảng cách chia cho count ra đúng vài giây thì đó là sự kiện lặp theo nhịp, không phải sự kiện đếm lần.
Sự kiện sống lâu hơn đối tượng
Tôi xoá hẳn một pod rồi đếm lại:
trước khi xoá 6 sự kiện
sau khi xoá 6 sự kiện
kubectl describe pod -> NotFound
Sự kiện không bị xoá theo. Với một pod đã biến mất — bị ReplicaSet thay, bị đuổi khỏi node, bị người khác xoá — sự kiện là dấu vết duy nhất còn lại. Đây là công cụ mạnh nhất cho câu hỏi hay gặp nhất: "pod của tôi đâu rồi?"
kubectl events -A | grep -iE "evict|preempt|kill|oom"
Ba đáp án thường gặp nằm ngay trong đó: Evicted (node hết tài nguyên), Preempted (pod ưu tiên cao hơn chiếm chỗ), Killing (bị xoá có chủ ý). Sự kiện nói rõ ai làm và vì sao — thứ mà describe trên một pod không còn tồn tại không thể trả lời. Nhưng vẫn trong giới hạn một giờ. Sau đó thì pod đó chưa từng tồn tại, xét theo mọi thứ cụm còn nhớ.
Các reason đáng thuộc
reason |
Nghĩa |
|---|---|
FailedScheduling |
Không node nào nhận, kèm lý do từng node |
Evicted |
kubelet đuổi vì node cạn tài nguyên |
Preempted |
Pod ưu tiên cao hơn chiếm chỗ |
Unhealthy |
Probe trượt — kèm luôn phản hồi thật |
FailedMount |
Volume không gắn được, thường là secret/configmap chưa có |
NodeNotReady |
Node mất liên lạc |
SuccessfulCreate |
ReplicaSet/Job tạo pod — có tên pod mới |
FailedScheduling là cái đáng đọc kỹ nhất: nó liệt kê từng nhóm node và lý do bị loại, đúng cái bộ lọc của scheduler đã tính. Đó là câu trả lời trực tiếp cho "vì sao pod vẫn Pending", không cần đoán.
Gửi sự kiện đi nơi khác
Hạn một giờ là lý do đủ để gom sự kiện ra ngoài. Ba cách, theo mức công sức:
kubectl events -A --watchghi ra tệp — thô sơ, nhưng dùng được ngay trong lúc điều tra.kube-state-metricsphơikube_pod_status_reasonvà bạn bè cho Prometheus — hợp để cảnh báo, không giữ nội dung thông điệp.- Một bộ xuất sự kiện chạy thường trực, đẩy sang nơi lưu nhật ký — giữ được đầy đủ.
Điều đáng cân nhắc trước khi bật cái thứ ba: sự kiện rất ồn. Cụm 623 sự kiện trong một giờ ở trên chỉ có mười mấy pod. Lọc theo type=Warning trước khi lưu là bước gần như bắt buộc.
Mẫu số chung: dữ liệu chẩn đoán có hạn dùng, và một bộ đếm gộp không đếm số lần xảy ra
Hai bài học của bài này vượt khỏi Kubernetes. Thứ nhất: dữ liệu để gỡ lỗi thường tự hết hạn, và cửa sổ giữ nó có thể ngắn hơn thời gian bạn kịp phản ứng. Sự kiện sống một giờ rồi biến mất không dấu vết — nên "chụp trước, đọc sau" không phải mẹo vặt mà là nguyên tắc. Chuyện này lặp lại ở khắp nơi: log xoay vòng ghi đè tệp cũ, dmesg là một ring buffer tự ghi đè, journald có hạn dung lượng, trace phân tán bị lấy mẫu và chỉ giữ ít ngày, console trình duyệt xoá khi tải lại trang, log của một lần chạy CI bị dọn sau vài tuần. Ai từng mất một stack trace vì "để mai xem" đều học được bài này một lần. Quy tắc mang đi: biết TTL của từng nguồn chẩn đoán, và với nguồn dễ bay hơi thì chụp ngay tại thời điểm sự cố — vì bản sao bạn không lấy lúc đó thường không còn tồn tại lúc bạn rảnh.
Thứ hai: một bộ đếm đã gộp/khử trùng đếm số tín hiệu được phát ra, không phải số lần sự việc xảy ra. BackOff x26 là 26 lần kubelet nhắc lại một tình trạng đang kéo dài, không phải 26 lần khởi động lại. Cùng cái bẫy với dòng syslog: last message repeated 40 times (một lỗi lặp, không phải 40 lỗi khác nhau), với một event đã debounce trong giao diện, với một cảnh báo giám sát bắn liên tục suốt một sự cố đang diễn ra (một sự cố, nhiều lần bắn). Muốn số lần thật, phải đọc đúng bộ đếm đo lần xảy ra — ở đây là restartCount — chứ không phải bộ đếm phát tín hiệu.
Mẫu số chung: trước khi tin một nguồn quan sát, hỏi hai câu — "nó giữ được bao lâu" (và chụp ngay nếu ngắn) và "con số này đếm lần xảy ra hay lần được nhắc tới". Bỏ qua câu đầu thì mất bằng chứng; bỏ qua câu sau thì đọc sai gấp mấy lần chính bằng chứng mình đang cầm.
Muốn tự soi, xem sự kiện cảnh báo trong cụm, gom theo lý do:
kubectl get events -A --field-selector type=Warning -o json | python3 -c '
import sys, json
from collections import Counter
c = Counter()
for e in json.load(sys.stdin)["items"]:
c[(e["reason"], e["involvedObject"]["kind"])] += e.get("count", 1)
for (r, k), n in c.most_common(15):
print(f"{n:5d} {r:22} {k}")
'
Cụm khoẻ thì bảng này gần như trống. Có dòng nào đếm hàng trăm là chỗ đang lặp lại một lỗi mà chưa ai nhìn tới — và nó sẽ biến mất khỏi cụm sau một giờ nữa.
Phần sau: kubectl debug và ephemeral container — cách vào xem một pod không có shell, không có curl, không có gì cả.