Thuê một tầng văn phòng, bạn nghĩ giới hạn là diện tích sàn — thuê gấp đôi mét vuông thì kê được gấp đôi bàn. Nhưng quy định phòng cháy đặt một trần khác, độc lập với diện tích: tối đa 110 người mỗi tầng, dù tầng rộng cỡ nào. Thế là căn phòng rộng gấp đôi không chứa gấp đôi nhân viên — nó chỉ cho mỗi người nhiều chỗ ngồi hơn, và phần diện tích dôi ra bạn trả tiền mà không dùng được. Node Kubernetes bị chặn bởi đúng một cái trần bất ngờ như vậy, và bài này đo tài nguyên biến mất ở đâu trước khi ứng dụng đầu tiên kịp chạy.
Phần trước đo chi phí của một cụm thêm vào. Ta đo chi phí bên trong một cụm.
Chi phí cố định của mỗi node
kubelet 108 MB
containerd 99 MB
------
207 MB mỗi node
Hai tiến trình này chạy trên mọi node và không bao giờ tắt. Cụm 50 node là 10 GB RAM cho riêng hai thứ đó. Cộng thêm phần chạy trong pod:
23 pod hệ thống toàn cụm 1.473 Mi
ảnh container trên 1 node 2,4 GB (28 ảnh)
Con số 2,4 GB đáng để ý riêng, vì nó là đĩa, và nó chỉ tăng. kubelet tự dọn ảnh khi đĩa đầy tới ngưỡng, nhưng ngưỡng đó là 85% — nghĩa là bình thường bạn cứ tích ảnh cho tới lúc gần hết chỗ.
Một pod rỗng tốn gì
memory.current: 462.848 byte
pids.current: 2
Nửa megabyte và hai tiến trình cho một pod không chạy gì cả. Đó là container pause giữ chỗ các namespace của Linux. Nhỏ, nhưng nhân với 110 pod thì thành 48 MB mỗi node chỉ để giữ chỗ. Và mỗi pod còn kéo theo vài chục quy tắc iptables, một mục trong danh sách của kubelet, một đối tượng trong etcd.
Trần thật không phải RAM mà là số pod
allocatable.pods = 110 trên MỌI node
Cả bốn node đều 110, dù mỗi node có 16 CPU và 16 GB. Đây là mặc định của kubelet (--max-pods), không liên quan gì tới kích thước node — đúng cái trần phòng cháy độc lập diện tích. Hệ quả:
15.971 Mi / 110 pod = 145 Mi mỗi pod nếu xếp đầy
Nếu pod của bạn nhỏ hơn 145 MB thì bạn chạm trần số lượng trước khi chạm trần bộ nhớ, và phần RAM còn lại không dùng được vào việc gì. Điều này đảo ngược một trực giác phổ biến: node to gấp đôi không cho gấp đôi số pod. Nó chỉ cho mỗi pod nhiều chỗ hơn. Với khối lượng công việc gồm nhiều pod nhỏ — hàng đợi, cron, microservice nhẹ — nhiều node nhỏ tận dụng tốt hơn ít node to.
Con số 110 nâng được, nhưng phải nâng có cân nhắc: mỗi pod thêm vào là thêm việc cho kubelet và thêm quy tắc iptables, và kube-proxy chậm dần khi số quy tắc lớn.
Con số hoàn toàn bịa, nếu bạn đo trên kind
cụm báo 62,4 GiB RAM, 64 CPU
máy thật 15,6 GiB RAM, 16 CPU
Cả bốn node đều báo 15971 Mi. Đó là cùng một 15,6 GB đếm bốn lần. Nguyên nhân: mỗi node kind là một container, và một container nhìn thấy tài nguyên của cả máy chủ. Không có gì ngăn bốn node cùng khai rằng chúng có 16 GB.
Hậu quả thực tế: mọi kết quả xếp lịch đo trên kind đều lạc quan gấp bốn lần. Scheduler tin những con số đó, chấp nhận pod xin tổng cộng 60 GB, rồi máy thật bắt đầu tráo đổi trang nhớ. Phép thử "cụm chịu được bao nhiêu" chạy trên kind không nói lên gì về cụm thật. Nó cũng giải thích một chuyện tôi gặp ở phần trước: pod xin 900 Gi bị từ chối với Insufficient memory, nhưng ranh giới bị từ chối nằm ở 16 GB mỗi node chứ không phải ở 15,6 GB toàn máy.
allocatable bằng capacity — và đó là bất thường
sc-worker CPU 16 -> 16 RAM 15971 Mi -> 15971 Mi (mất 0 Mi)
kind không đặt --kube-reserved hay --system-reserved, nên không giữ lại gì cho hệ thống. Toàn bộ RAM của node được coi là dùng được cho pod. Cụm quản lý thật thì ngược lại: chúng giữ lại một phần đáng kể theo công thức bậc thang của nhà cung cấp, và phần đó không nhỏ. Đó là con số của nhà cung cấp, không phải con số tôi đo được ở đây — hãy đọc trên chính cụm của bạn:
kubectl get node <ten> -o jsonpath='{.status.capacity}{"\n"}{.status.allocatable}{"\n"}'
Chênh lệch giữa hai dòng là phần bạn trả tiền mà không dùng được. Trên cụm quản lý, con số đó thường làm người ta ngạc nhiên.
Gộp lại: tiền đi đâu
| Khoản | Đo được | Ghi chú |
|---|---|---|
| kubelet + containerd | 207 MB/node | Cố định, không bao giờ tắt |
| Pod hệ thống | 1.473 Mi | Tăng theo số node (DaemonSet) |
| Ảnh container | 2,4 GB/node | Chỉ tăng cho tới ngưỡng dọn |
| Sandbox mỗi pod | 0,44 MB | × số pod |
capacity − allocatable |
0 ở đây | Đáng kể trên cụm quản lý |
| Trần 110 pod/node | — | Chặn trước RAM với pod nhỏ |
Bốn khoản đầu cộng lại là vài phần trăm của một node lớn, và một phần đáng kể của một node nhỏ. Đó là lý do node quá nhỏ không kinh tế: 207 MB trên node 2 GB là 10%, trên node 32 GB là 0,6%. Còn hai dòng cuối thì không phải chi phí tài nguyên mà là chi phí quy hoạch — chúng làm cho con số bạn tưởng mình có khác con số bạn thật sự dùng được.
Chi phí không đo bằng byte
Ba khoản lớn nhất không hiện trong bất kỳ lệnh nào:
- Người. Ai nâng cấp cụm, ai trực khi nó hỏng, ai đọc ghi chú phát hành của mỗi phiên bản.
- Độ trễ chẩn đoán. Loạt bài này có bốn phần chỉ nói về việc tìm ra cái gì đang hỏng. Với một tiến trình chạy trên máy chủ thì
psvàtaillà đủ. - Số thứ phải học. Requests, limits, probe, PDB, RBAC, NetworkPolicy — mỗi cái là một chỗ để cấu hình sai theo cách im lặng.
Với ba dịch vụ và một cơ sở dữ liệu, ba khoản đó lớn hơn mọi lợi ích. Ranh giới thực dụng vẫn như ở các phần trước: Kubernetes đáng giá khi bạn có nhiều thứ giống nhau cần vận hành theo cùng một cách.
Mẫu số chung: ràng buộc gắt nhất thường không phải cái bạn đang tối ưu
Bài này gói hai điều đúng ở mọi hệ có hạn mức. Thứ nhất: một tài nguyên có nhiều trần song song (RAM, số pod, số quy tắc iptables), và cái nhỏ nhất chặn trước — nên tối ưu đúng cái trần không-phải-ràng-buộc thì chẳng thêm được gì. Ở đây trần là con số 110, không phải RAM: mua thêm RAM không cho thêm pod. Thứ hai: chi phí cố định mỗi đơn vị chỉ đáng khi đơn vị đủ lớn — 207 MB là 10% một node 2 GB nhưng 0,6% một node 32 GB, đúng lý do node quá nhỏ không kinh tế.
Trong thế giới Java và hệ chạy dịch vụ, đây đúng là chuyện thông lượng bị chặn bởi trần gắt nhất trong chuỗi: connection pool 20, thread pool 200, và max_connections của CSDL là 100 — thông lượng bị 100 chặn, tăng hai cái kia vô ích. Và bộ nhớ: -Xmx, giới hạn bộ nhớ container, và RAM máy — cái thấp nhất quyết định, JVM xin quá giới hạn container là bị OOMKill dù máy còn RAM. Còn cái bẫy "đo trên kind sai gấp bốn lần" là bài học chung: đo năng lực trên môi trường khác môi trường thật (container thấy tài nguyên host, laptop dev khác server prod) cho một con số lạc quan vô dụng — đúng kiểu benchmark chạy nhầm chỗ.
Mẫu số chung: một hệ bị chặn bởi ràng buộc gắt nhất trong nhiều trần song song, không phải bởi cái bạn để ý nhất — nên trước khi thêm tài nguyên, tìm xem cái nào thật sự chặn; và chi phí cố định mỗi đơn vị chỉ khấu hao tốt khi đơn vị đủ lớn. Câu hỏi đúng khi "hết chỗ" không phải "thêm RAM/CPU được không" mà là "trần nào chạm trước — và tôi có đang đo nó trên đúng môi trường không". Với Kubernetes, cột cần nhìn là Mi/pod khả dụng, không phải tổng RAM.
Muốn tự xem cụm của bạn còn thật sự bao nhiêu chỗ, tính cả trần số pod:
kubectl get nodes -o json | python3 -c '
import sys, json
def mi(s):
return int(s[:-2]) / 1024 if s.endswith("Ki") else int(s[:-2])
for n in json.load(sys.stdin)["items"]:
a = n["status"]["allocatable"]; c = n["status"]["capacity"]
ram, pods = mi(a["memory"]), int(a["pods"])
print("%-22s %6.0f Mi cho %3d pod = %4.0f Mi/pod (giu lai %.0f Mi)" % (
n["metadata"]["name"], ram, pods, ram / pods, mi(c["memory"]) - ram))
'
Cột Mi/pod là con số quan trọng nhất và ít người biết. Nếu pod trung bình của bạn nhỏ hơn nó nhiều thì bạn đang trả tiền cho RAM sẽ không bao giờ dùng tới, và nên cân nhắc node nhỏ hơn hoặc nâng --max-pods.
Phần sau: bảo mật image và chuỗi cung ứng — đo việc quét và ký.