Sê-ri này đã đo chi phí quan sát của một tiến trình đơn lẻ khá kỹ. Kubernetes đổi bài toán đó theo một cách mà ít ai tính trước: bản thân cụm cũng là một nguồn phát chỉ số, và nó phát ngay cả khi bạn chưa triển khai gì. Phần này đo đúng cái phần thêm vào ấy.
Hệ đo là một cụm k3s thật chạy trong container, một node, dựng lên rồi xoá đi. Cái được đếm là số chuỗi chỉ số — mỗi dòng trong bản phơi ra của một endpoint là một chuỗi — ở hai nguồn: cAdvisor của kubelet (chỉ số về container) và /metrics của control plane.
Bảng số liệu
| Trạng thái cụm | Chuỗi cAdvisor | Byte mỗi lần thu thập |
|---|---|---|
| Rỗng (chỉ 1 pod CoreDNS) | 572 | 122,2 KB |
| Thêm 10 pod | 2 442 | 757,2 KB |
| Thêm 30 pod | 6 182 | 2 027,1 KB |
| Xoá sạch, về lại rỗng | 572 | 122,2 KB |
Ba lần chạy độc lập cho ra những con số này lệch nhau không quá 3 byte. Hệ số góc tính được từ ba cặp điểm khác nhau:
(2 442 − 572) / 10 = 187,0 chuỗi mỗi pod
(6 182 − 2 442) / 20 = 187,0 chuỗi mỗi pod
(6 182 − 572) / 30 = 187,0 chuỗi mỗi pod
Đúng 187 chuỗi cho mỗi pod, tuyến tính hoàn hảo, và 62,0 KB mỗi lần thu thập.
Pod dùng để đo là pause — một container không làm gì cả, không nghe cổng nào, không tiêu CPU, không ghi log. Nó vẫn tốn đúng 187 chuỗi như một dịch vụ thật.
Điều đáng nhớ
Một pod tốn nhiều byte hơn cả cái node. Trong 122,2 KB của cụm rỗng, 62,0 KB thuộc về pod CoreDNS duy nhất; phần còn lại — 57,4 KB và 385 chuỗi — mới là chỉ số của node. Nói cách khác, pod thứ nhất đã đắt hơn toàn bộ phần nền, và pod thứ hai lại thêm chừng ấy nữa.
Control plane phình lên và không co lại. Đây là con số làm tôi phải kiểm ba lần. Tạo một namespace, triển khai 3 pod trong đó, rồi xoá sạch cả namespace — không còn lại một đối tượng nào:
Chuỗi /metrics |
|
|---|---|
| Trước | 25 923 |
| Sau khi đã xoá sạch | 30 503 |
+4 580 chuỗi vĩnh viễn, tăng 17,7%, từ một thí nghiệm để lại con số không. Để so sánh: 30 pod đang chạy tốn 5 610 chuỗi cAdvisor — nhưng số đó biến mất cùng với pod. Ba pod sống một phút rồi chết để lại lượng chuỗi bằng 8,2 lần thứ chúng chiếm lúc còn sống.
Nhưng nó bão hoà. Lặp lại đúng chu trình ấy bốn lần nữa, mỗi lần một tên namespace hoàn toàn mới:
vong 1: 30 503 -> 30 503 (+0)
vong 2: 30 503 -> 30 503 (+0)
vong 3: 30 503 -> 30 503 (+0)
vong 4: 30 503 -> 30 505 (+2)
Vì sao
Toàn bộ 4 580 chuỗi mới nằm trong các thùng histogram của apiserver, không có họ chỉ số nào mới xuất hiện:
| Họ chỉ số | Trước | Sau |
|---|---|---|
apiserver_request_duration_seconds_bucket |
4 632 | 6 048 |
apiserver_request_sli_duration_seconds_bucket |
2 970 | 4 268 |
apiserver_request_body_size_bytes_bucket |
1 504 | 2 592 |
etcd_request_duration_seconds_bucket |
4 200 | 4 344 |
Apiserver gắn nhãn chỉ số theo loại tài nguyên, động từ và mã trả về — chứ không theo tên đối tượng. Mỗi tổ hợp mới sinh ra một bộ thùng histogram đầy đủ, và bộ ấy nằm lại trong sổ đăng ký mãi mãi.
Xoá một namespace là thao tác chạm vào mọi loại tài nguyên: Kubernetes phải duyệt qua toàn bộ danh mục để dọn rác. Lần đầu làm việc đó, hàng loạt tổ hợp (tài nguyên, động từ, mã) chưa từng gặp được tạo ra cùng lúc. Lần thứ hai trở đi thì chúng đã có sẵn — nên +0.
Đây là chỗ trực giác dễ sai theo cả hai hướng. Nhìn +4 580 mà chưa lặp lại thì sẽ kết luận "cụm càng hoạt động càng phình, không có điểm dừng" — sai. Số chuỗi của control plane bị chặn bởi số loại thao tác cụm từng làm, chứ không phải số lần làm. Một cụm chạy một triệu pod mỗi ngày không tốn nhiều chuỗi apiserver hơn cụm chạy mười pod, miễn là chúng làm cùng những loại việc.
Nghĩa là gì trong thực tế
Lấy 187 chuỗi mỗi pod, chu kỳ thu thập 15 giây, và 2,16 byte mỗi mẫu (con số đã đo ở phần 19):
187 chuoi x 5 760 lan thu thap moi ngay = 1 077 120 mau
1 077 120 x 2,16 byte = 2,33 MB moi pod moi ngay
- 100 pod → 233 MB mỗi ngày → 7,0 GB mỗi 30 ngày, chỉ riêng chỉ số container, chưa tính log, chưa tính trace, chưa tính chỉ số của chính ứng dụng.
- Con số ấy không phụ thuộc pod làm gì. Một job cron chạy 5 giây rồi thoát vẫn được tính chỉ số như một dịch vụ chạy suốt, trong lúc nó còn sống.
- Nếu bạn chạy CI sinh pod liên tục, chi phí thật nằm ở chỗ khác: mỗi pod chỉ tốn chỉ số lúc nó còn sống, nhưng nhãn
podthì đổi mỗi lần, và trong Prometheus mỗi giá trị nhãn mới là một chuỗi mới cần lưu.
Việc cần làm là bỏ bớt, không phải thu thập thêm: cAdvisor phơi ra hàng chục chỉ số cho mỗi container mà phần lớn cụm không bao giờ truy vấn. Cắt xuống còn nhóm CPU, bộ nhớ, và số lần khởi động lại là giảm được phần lớn trong 187 chuỗi đó — và đó là thao tác lọc lúc thu thập, không mất gì.
Chỗ tôi không kết luận được
Cụm đo là k3s một node, không phải cụm nhiều node có kube-state-metrics. Trong cụm thật, kube-state-metrics còn thêm một lớp chỉ số nữa cho từng đối tượng API, mà tôi không đo ở đây. Con số 187 là của riêng cAdvisor.
Số 4 580 cũng gắn với đúng cụm này, đúng phiên bản này, đúng lần đầu tiên nó gặp thao tác xoá namespace. Cụm của bạn có tập tổ hợp khác. Cái đáng mang đi không phải con số, mà là hình dạng: một bậc nhảy một lần, rồi phẳng.
Lần đo hỏng: hai endpoint khác nhau trả về cùng một thứ
Bước đầu tiên tôi đọc bốn nguồn chỉ số và ghi lại số dòng:
/metrics 24 258 dong
/api/v1/nodes/<node>/proxy/metrics 24 352 dong
/api/v1/nodes/<node>/proxy/metrics/cadvisor 571 dong
Hai dòng đầu là apiserver và kubelet — hai thành phần khác hẳn nhau, mà lệch nhau chưa tới 0,4%. Con số ấy hoàn toàn nằm trong khoảng hợp lý, và nếu tôi ghi thẳng vào bài thì không ai phát hiện được.
Cách kiểm mất ba mươi giây: tìm một chỉ số mà chỉ một trong hai được phép có. apiserver_request_total có 200 dòng ở cả hai. Kubelet không phơi ra chỉ số ấy. So trực tiếp thì 23 652 trong số 24 258 dòng là giống hệt nhau.
Nguyên nhân: k3s gói apiserver, kubelet, scheduler và controller-manager vào một tiến trình duy nhất, dùng chung một sổ đăng ký chỉ số. Hỏi endpoint nào cũng ra cùng một thứ. Đó là đặc thù của k3s chứ không phải của Kubernetes, và nó khiến mọi con số "control plane" trong bài này là số gộp — tôi giữ chúng vì phần phình và phần bão hoà vẫn đo đúng, nhưng không được đem so với một cụm nhiều thành phần tách rời.
Bài học lặp lại lần thứ ba trong sê-ri này: một con số trông hợp lý không tự tố cáo mình. Thứ bắt được nó là một phép kiểm chuẩn bị trước — ở đây là "chỉ số nào chỉ được phép xuất hiện ở một bên".
Thử ba mươi giây
Chạy trên cụm của bạn:
kubectl get --raw "/api/v1/nodes/$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')/proxy/metrics/cadvisor" \
| grep -vc '^#'
Chia con số đó cho số pod trên node ấy. Nhân với số node, nhân với 5 760, nhân với 2,16 byte. Đó là hoá đơn mỗi ngày cho phần chỉ số mà bạn chưa từng chủ động bật.