Bốn mươi phần vừa qua đều dựa vào các con số đọc được từ cụm. Bài này quay lại đo chính những con số đó.
Cùng một container, bốn con số bộ nhớ
Container giới hạn 1 Gi, ghi rồi đọc một tệp 400 MB.
memory.current 413 Mi
inactive_file (bộ đệm tệp) 400 Mi
anon (bộ nhớ thật của app) 0 Mi
working_set = current - inactive_file
13 Mi
kubectl top 12 Mi
413 hay 12? Cả hai đều đúng — chúng trả lời hai câu hỏi khác nhau.
memory.current là tổng bộ nhớ mà cgroup này đang giữ, gồm cả bộ đệm tệp mà kernel giữ hộ. working_set là phần không thu hồi được — phần mà nếu thiếu thì container chết.
400 MB kia là bộ đệm tệp sạch: kernel vứt đi trong tích tắc khi cần chỗ. Container này không hề gần giới hạn 1 Gi.
Đây là nguồn của phần lớn báo động giả
Bảng điều khiển Grafana quen thuộc vẽ container_memory_usage_bytes. Đó chính là memory.current — 413 Mi, tức 40% của giới hạn, và nó tăng đều theo lượng tệp ứng dụng đọc.
Đội trực nhìn đường cong đi lên, kết luận "rò bộ nhớ", nâng limit. Đường cong lại đi lên tiếp — vì bộ đệm tệp luôn nở ra lấp đầy chỗ được cho.
Chỉ số đúng để cảnh báo là container_memory_working_set_bytes. Nó là thứ OOM killer nhìn vào, và là thứ kubectl top hiển thị.
Nếu bảng điều khiển của bạn dùng chỉ số kia, đây là một dòng đáng sửa ngay hôm nay:
container_memory_working_set_bytes{container!=""}
/ on(pod,container) group_left
kube_pod_container_resource_limits{resource="memory"}
Còn khi muốn biết ứng dụng thật sự cấp phát bao nhiêu — để đặt requests — thì nhìn container_memory_rss hoặc anon trong memory.stat, ở phép đo trên là 0 Mi.
CPU: kubectl top chậm hơn thực tế 30 giây
Tôi cho container quay vòng lặp bận rồi lấy mẫu mỗi 8 giây, đối chiếu với usage_usec của cgroup.
| giây | kubectl top |
usage_usec |
|---|---|---|
| 10 | 106m | 8.340.332 |
| 20 | 106m | 16.444.064 |
| 30 | 1000m | 24.565.380 |
| 40 | 1000m | 30.339.002 |
| 50 | 1001m | 30.342.276 |
| 60 | 210m | 30.345.826 |
| 70 | 210m | 30.350.182 |
| 80 | 1m | 30.353.865 |
usage_usec ngừng tăng từ khoảng giây 42 — CPU đã rảnh. kubectl top vẫn báo 1001m ở giây 50 và 210m ở giây 70, mãi giây 80 mới về 1m.
Độ trễ đến từ chuỗi: kubelet cập nhật theo chu kỳ, metrics-server lấy mẫu 15 giây một lần, mỗi mẫu tính trên cửa sổ 10 giây.
Có một chi tiết nhỏ hơn nhưng đáng chú ý: các cặp giá trị lặp lại y hệt (106/106, 1000/1000, 210/210). Hỏi hai lần cách nhau 8 giây vẫn nhận đúng một mẫu. kubectl top không đo gì cả — nó đọc lại mẫu gần nhất mà metrics-server còn giữ.
Nên đừng dùng kubectl top để kết luận về một đợt tăng tải vừa xảy ra. Đợt tải ngắn hơn 15 giây có thể không lọt vào mẫu nào.
Điều này cũng giải thích luôn kết quả đo ở bài về HPA: HPA đọc chính nguồn này, nên độ trễ trên là sàn của mọi phản ứng tự động mở rộng.
metrics-server không giữ lịch sử
window: 10.021s
API metrics.k8s.io chỉ trả về một mẫu: cửa sổ gần nhất. Không có tham số thời gian, không có lịch sử, không lưu xuống đĩa. metrics-server khởi động lại là mất sạch.
Nó được thiết kế đúng như vậy, cho hai người dùng: HPA và kubectl top. Cả hai chỉ cần biết "ngay bây giờ".
Từ đó ra ranh giới rõ ràng:
| Cần gì | Dùng gì |
|---|---|
HPA, VPA, kubectl top |
metrics-server |
| Đồ thị, cảnh báo, điều tra sự cố cũ | Prometheus |
Và cả hai đều lấy dữ liệu từ cùng một nguồn: cAdvisor nằm trong kubelet, đọc cgroup. Chúng khác nhau ở chỗ lấy mẫu bao lâu một lần và giữ lại bao lâu — chứ không phải ở chỗ đo cái gì.
Đó cũng là câu trả lời cho "vì sao hai con số không khớp": khác cửa sổ, khác thời điểm lấy mẫu, và thường là khác cả chỉ số được chọn.
Ba chỉ số đáng cảnh báo hơn cả CPU và RAM
Sau bốn mươi bài, đây là ba thứ tôi thấy đáng đặt cảnh báo nhất mà bảng điều khiển mặc định thường không có:
# 1. Bóp CPU — pod chậm mà CPU vẫn dưới limit
rate(container_cpu_cfs_throttled_periods_total[5m])
/ rate(container_cpu_cfs_periods_total[5m]) > 0.25
# 2. Pod khởi động lại — nguyên nhân gốc thường đã mất log
increase(kube_pod_container_status_restarts_total[1h]) > 0
# 3. Rò tiến trình — chạm trần PID trước khi chạm trần RAM
container_processes / 19162 > 0.5
Cái đầu tiên là cái hay bị bỏ sót nhất: pod bị bóp CPU trông hệt như "ứng dụng chậm", và mọi đồ thị CPU đều bình thường vì nó đúng là dưới limit — đó chính là lý do nó bị bóp.
Thử ba mươi giây
Xem chênh lệch giữa hai cách đo bộ nhớ trên cụm của bạn:
for p in $(kubectl get pods -A -o jsonpath='{range .items[?(@.status.phase=="Running")]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}'); do
ns=${p%%/*}; n=${p##*/}
v=$(kubectl exec -n "$ns" "$n" -- sh -c \
'cur=$(cat /sys/fs/cgroup/memory.current 2>/dev/null); inf=$(awk "/^inactive_file /{print \$2}" /sys/fs/cgroup/memory.stat 2>/dev/null); [ -n "$cur" ] && echo "$((cur/1048576)) $(((cur-inf)/1048576))"' 2>/dev/null)
[ -n "$v" ] && echo "$v $p"
done | awk '{d=$1-$2; if (d>50) printf "%4d Mi bo dem (current %d, working_set %d) %s\n", d, $1, $2, $3}'
Mỗi dòng in ra là một pod mà bảng điều khiển đang phóng đại lượng bộ nhớ. Chênh càng lớn thì cảnh báo hiện tại càng vô nghĩa.
Phần sau: sự kiện của cụm — cái gì được ghi lại, và nó biến mất sau bao lâu.