Tài liệu nói Kubernetes hỗ trợ 5.000 node và 150.000 pod. Bài này đo xem cụm thật sự bắt đầu chậm ở đâu — và chậm ở cái gì.
Thời gian LIST tăng nhanh hơn số đối tượng
Tôi tạo ConfigMap theo từng lô và đo lại sau mỗi lô.
| Số ConfigMap | etcd DB | LIST |
|---|---|---|
| 1.001 | 13,7 MB | 0,08 giây |
| 3.001 | 16,7 MB | 0,23 giây |
| 6.001 | 34,7 MB | 0,81 giây |
| 10.001 | 58,9 MB | 2,53 giây |
| 20.001 | 119,1 MB | 6,79 giây |
20 lần số đối tượng cho 85 lần thời gian. Quan hệ không tuyến tính.
Sáu giây rưỡi cho một lệnh kubectl get đủ để mọi thứ dùng nó trở nên khó chịu — và mọi controller trong cụm đều làm đúng việc đó lúc khởi động.
Nhưng tạo pod thì không chậm đi
0,7 / 1,0 / 1,0 giây với 20.000 đối tượng trong cụm
Đây là chỗ tôi trông đợi sẽ thấy suy giảm và không thấy.
Cụm không chậm nói chung. Cái chậm là LIST và WATCH — tức là kubectl, bảng điều khiển, và mọi controller phải giữ một bản sao trong bộ nhớ.
Phân biệt này quan trọng khi chẩn đoán: "cụm chậm" gần như luôn có nghĩa là "một loại thao tác cụ thể chậm". Hỏi đúng câu hỏi — chậm khi làm gì — sẽ đưa thẳng tới nguyên nhân.
Và nó cũng cho biết ai chịu thiệt trước: không phải ứng dụng của bạn, mà là Argo CD, Prometheus, bộ điều khiển Ingress — những thứ theo dõi mọi đối tượng.
Bộ nhớ của controller là chỗ vỡ thật
Mỗi controller dùng informer giữ toàn bộ đối tượng nó quan tâm trong RAM. 20.000 ConfigMap với 2 KB dữ liệu mỗi cái là khoảng 40 MB — nhân với mỗi controller theo dõi ConfigMap.
Đó là lý do một cụm nhiều đối tượng làm kube-controller-manager phình ra, rồi bị OOM, rồi khởi động lại, rồi phải LIST lại từ đầu — mà LIST bây giờ mất 6,79 giây. Vòng lặp này tự nuôi chính nó.
Cách chữa gốc không phải là cho thêm RAM mà là giảm số đối tượng, hoặc thu hẹp phạm vi theo dõi bằng nhãn.
Hai trần kích thước, và chúng khác nhau
kubectl apply, 1.500.000 byte
metadata.annotations: Too long: may not be more than 262144 bytes
kubectl create, 1.500.000 byte
[]: Too long: may not be more than 1048576 bytes
kubectl create, 1.000.000 byte
configmap/to2 created
Trần thật của một đối tượng là 1.048.576 byte — giới hạn của etcd.
Nhưng kubectl apply chết sớm gấp bốn lần, ở 262.144 byte. Lý do: apply nhét toàn bộ đối tượng vào chú thích kubectl.kubernetes.io/last-applied-configuration để so sánh lần sau, và chú thích có trần riêng.
Đây là cái bẫy đáng nhớ: cùng một tệp YAML, apply từ chối còn create chấp nhận, và thông báo lỗi nói về metadata.annotations — nghe như bạn khai sai chú thích nào đó, trong khi bạn không khai chú thích nào cả.
Cách tránh: dùng kubectl apply --server-side. Server-side apply không dùng chú thích đó, nên nó lấy lại được đủ 1 MB.
Tốc độ tạo
10.000 đối tượng trong 34,3 giây ≈ 290 mỗi giây
Đây không phải trần của hệ thống — nó là trần của một lệnh apply tuần tự, một luồng, gửi từng đối tượng một. Song song hoá thì nhanh hơn nhiều.
Nhưng con số đó vẫn hữu dụng để ước lượng: dựng lại một namespace 5.000 đối tượng từ Git mất khoảng 17 giây bằng một tiến trình. Nếu quy trình khôi phục của bạn giả định "vài giây" thì nên đo lại.
Trần nào chạm trước
Theo thứ tự hay gặp trong thực tế, không theo thứ tự trong tài liệu:
| Trần | Giá trị | Triệu chứng |
|---|---|---|
| Pod mỗi node | 110 (mặc định kubelet) | Pending dù node còn RAM |
| Kích thước đối tượng | 1 MB (262 KB với apply) |
Too long |
| Số đối tượng | không có trần cứng | LIST chậm dần, controller OOM |
| Kích thước etcd | 2 GB mặc định (--quota-backend-bytes) |
Cụm chuyển sang chỉ đọc |
Dòng cuối là dòng nguy hiểm nhất, vì hậu quả của nó không phải "chậm" mà là dừng ghi hoàn toàn. Ở phép đo trên, 20.000 ConfigMap đã chiếm 119 MB — tức khoảng 6% hạn mức mặc định, chỉ với một loại tài nguyên.
Và nhớ chuyện đã đo ở phần 41: sự kiện là nhóm khoá đông nhất trong etcd của một cụm bình thường. Chúng tự hết hạn sau một giờ, nhưng trong một giờ đó chúng vẫn chiếm chỗ.
Bốn việc giảm áp lực
- Đừng tạo đối tượng cho mỗi lần chạy. Job không có
ttlSecondsAfterFinishedtích lại mãi. Một dòng cấu hình xoá được hàng nghìn đối tượng. - Thu hẹp phạm vi theo dõi. Controller và bộ giám sát nên lọc theo nhãn hoặc namespace thay vì theo dõi cả cụm.
- Đừng lưu dữ liệu lớn trong ConfigMap. Trần 1 MB có lý do; thứ gì lớn nên nằm trong volume hoặc kho đối tượng.
- Nén etcd định kỳ. etcd giữ nhiều phiên bản; không nén thì DB phình lên vì lịch sử chứ không vì dữ liệu hiện tại.
Thử ba mươi giây
Xem cụm của bạn đang có bao nhiêu đối tượng, xếp theo loại:
for r in $(kubectl api-resources --verbs=list -o name 2>/dev/null | sort -u); do
n=$(kubectl get "$r" -A --no-headers 2>/dev/null | wc -l | tr -d ' ')
[ "$n" -gt 100 ] && echo "$n $r"
done | sort -rn | head -15
Loại nào lên tới hàng nghìn là loại đáng hỏi "có cần giữ hết không". Thường thủ phạm là Job đã xong, Secret của ServiceAccount đời cũ, hoặc bản ghi lịch sử của một Operator không tự dọn.
Phần sau: GitOps với Argo CD — đo độ trễ từ lúc đẩy commit tới lúc cụm thật sự đổi.