Bạn viết trong bản thiết kế "phòng này 50 mét vuông", nhưng khi bước vào đo lại thì nó 410 — vì giữa lúc bạn nộp bản vẽ và lúc xây, có một kỹ sư nào đó lặng lẽ sửa con số. Tệ hơn: bản vẽ gốc trong ngăn tủ vẫn ghi 50, nên ai giở ra xem cũng tin là 50. Đó chính xác là chuyện xảy ra khi VPA chạy ở Auto mode, và nó phá vỡ một giả định mà rất nhiều đội GitOps đang dựa vào. HPA thêm bản; VPA sửa requests của từng bản. Bài này đo VPA trên cụm thật và tìm ra nó làm hai việc khác nhau mà nhiều người tưởng là một.
Khuyến nghị đầu tiên sau khoảng một phút
Ứng dụng khai requests: cpu=500m, memory=512Mi. Dùng thật: 338m CPU, 83Mi bộ nhớ — khai thừa rõ ràng. VPA sau ~60 giây:
lowerBound: cpu=25m mem=262144k
target: cpu=410m mem=262144k
upperBound: cpu=8856410m mem=2372108436351
target là con số dùng được: 410m — cao hơn mức dùng thật 338m một chút, đúng như mong đợi vì VPA cộng biên an toàn. upperBound thì 8.856 nhân và 2,2 terabyte. Vô nghĩa. Đó không phải lỗi: VPA chưa đủ dữ liệu lịch sử nên khoảng tin cậy rộng gần như vô hạn. Phải để nó chạy vài ngày trước khi con số có ý nghĩa — và phần dưới sẽ cho thấy vì sao điều đó ảnh hưởng tới hành vi chứ không chỉ tới độ chính xác.
Bộ nhớ khuyến nghị là 262144k = 250Mi, đúng bằng mức sàn mặc định của VPA, dù ứng dụng chỉ dùng 83Mi. VPA không khuyến nghị thấp hơn 250Mi trừ khi bạn khai minAllowed.
Auto mode: hai cơ chế khác nhau
Đây là phần bất ngờ nhất.
1) Bộ admission ghi đè requests lúc tạo pod
Deployment khai: {"cpu":"50m","memory":"64Mi"}
Pod thực tế có: {"cpu":"410m","memory":"262144k"}
VPA khuyến nghị: {"cpu":"410m","memory":"262144k"}
Tôi đặt requests: cpu=50m trong Deployment. Pod chạy ra với 410m. Bộ admission của VPA chặn ở giữa (phần 46 — admission nằm ngay trên đường ghi) và thay giá trị. Manifest nói một đằng, pod chạy một nẻo — và kubectl get deployment vẫn hiện con số cũ. Với GitOps, điều này gây bối rối thật: kho Git là nguồn sự thật cho Deployment, nhưng không phải cho requests mà pod thực sự nhận.
2) Bộ updater giết pod cũ — nhưng chỉ khi cần
Pod cũ có requests: cpu=500m, khoảng tin cậy [187m .. 196122m].
500m nằm TRONG khoảng -> KHÔNG bị giết
sau 110 giây vẫn nguyên, RESTARTS=0
VPA không giết pod chỉ vì target khác requests hiện tại. Nó chỉ giết khi requests nằm ngoài khoảng tin cậy.
Vì sao VPA trông như "không làm gì"
Ghép hai điều trên lại:
- Khoảng tin cậy ban đầu rất rộng (
upperBound8.856 nhân), nên gần như mọirequestsđều nằm trong. - Chỉ pod mới nhận giá trị khuyến nghị, qua bộ admission.
Nên bật VPA Auto rồi ngồi chờ nó sửa pod đang chạy là chờ vô ích. Muốn thấy hiệu quả ngay thì phải:
kubectl rollout restart deployment/<ten>
Hoặc chờ tới khi VPA thu thập đủ dữ liệu và khoảng tin cậy hẹp lại — thường là vài ngày. Đây là lý do khuyến nghị phổ biến là bắt đầu với updateMode: "Off": để VPA quan sát và đưa khuyến nghị, con người đọc rồi tự sửa manifest. Bạn được giá trị chính của VPA (biết nên khai bao nhiêu) mà không có phần khó đoán.
Bốn chế độ
Off chỉ khuyến nghị, không đụng gì <- nên bắt đầu ở đây
Initial chỉ đặt requests lúc pod được TẠO
Recreate giết pod để áp giá trị mới
Auto hiện tại giống Recreate
Auto được thiết kế để một ngày nào đó dùng cơ chế sửa tại chỗ, nhưng hiện tại nó vẫn giết pod. Và đó là điểm yếu lớn nhất của VPA: nó phải giết pod để đổi requests. Với ứng dụng khởi động chậm hoặc có trạng thái, việc bị giết định kỳ là cái giá thật. Kubernetes 1.27 có tính năng thử nghiệm InPlacePodVerticalScaling cho phép đổi requests mà không khởi động lại. Khi nó ổn định, VPA sẽ hữu dụng hơn nhiều.
VPA và HPA không dùng chung được
VPA và HPA theo CPU cùng lúc trên một Deployment sẽ đá nhau:
- Tải tăng → HPA thêm bản → CPU mỗi bản giảm → VPA hạ
requests→ tỉ lệ CPU/requests tăng → HPA thêm bản nữa.
Kết hợp được khi HPA dùng chỉ số khác: số yêu cầu mỗi giây, độ dài hàng đợi (phần 66). Khi ấy hai bộ điều khiển nhìn hai thứ khác nhau và không xung đột.
Quy tắc thực dụng:
- Tải thay đổi theo thời gian → HPA. Thêm bản là cách đúng.
- Không biết nên khai bao nhiêu → VPA ở chế độ
Off, đọc khuyến nghị, sửa tay. - Cả hai → HPA theo chỉ số ứng dụng, VPA
Offđể hiệu chỉnhrequests.
Ba tham số nên khai
spec:
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed: {cpu: 50m, memory: 64Mi}
maxAllowed: {cpu: 2, memory: 2Gi}
controlledResources: [cpu, memory]
maxAllowed là quan trọng nhất: không có nó, một đỉnh tải bất thường có thể khiến VPA khuyến nghị con số lớn tới mức không node nào xếp nổi pod — và pod nằm Pending mãi (phần 47). minAllowed chặn chiều ngược lại: VPA hạ requests quá thấp khiến pod thành gần BestEffort và bị đuổi trước (phần 55).
Mẫu số chung: cái bạn khai không phải cái đang chạy, và hai vòng điều khiển cùng tín hiệu sẽ dao động
Bài này gói hai bài học lớn. Thứ nhất: khi có một tầng sửa đổi nằm giữa lúc bạn khai báo và lúc thứ đó chạy, cái bạn viết không còn là cái đang chạy — và công cụ đọc bản khai gốc sẽ nói dối bạn. Bộ admission của VPA viết lại requests trên đường ghi; manifest ghi 50m, pod chạy 410m, get deployment vẫn hiện 50m. Đây là họ hàng của Lombok/AspectJ trong Java: tệp .java không có getter nào, nhưng lớp .class biên dịch ra thì có — đọc mã nguồn không cho biết bytecode thật sự làm gì; của một mutating webhook, một proxy viết lại header, một ORM tự thêm cột mặc định, một trình transpiler đổi mã. Bài học: khi nghi có kẻ sửa giữa đường, hãy soi hiện vật đang chạy chứ đừng soi bản gốc — và biết rằng "nguồn sự thật là Git" vỡ ngay tại một tầng admission biến đổi.
Thứ hai, một luật của lý thuyết điều khiển: hai vòng phản hồi độc lập cùng lái theo một tín hiệu sẽ đánh nhau và dao động. VPA và HPA cùng nhìn CPU tạo ra vòng lặp tự khuếch đại: thêm bản → CPU/bản giảm → hạ requests → lại thêm bản. Cùng hình dạng với hai thermostat trong một phòng tranh nhau, hai bộ đệm cùng vô hiệu hoá một khoá, hai luồng cùng chỉnh một biến không phối hợp. Cách chữa luôn giống nhau: cho mỗi vòng điều khiển một đầu vào trực giao — HPA nhìn số request/giây, VPA nhìn mức dùng để chỉnh requests, và chúng thôi giẫm chân.
Và một lưu ý nhỏ mà upperBound 8.856 nhân nhắc lại: một thống kê chưa đủ mẫu là nhiễu đội lốt câu trả lời — khoảng tin cậy rộng vô nghĩa cho tới khi có đủ dữ liệu, y như một tỉ lệ tính trên hai lần thử hay một A/B test đọc quá sớm. Đừng hành động theo con số trước khi nó đủ chín.
Mẫu số chung: soi hiện vật đang chạy chứ không phải bản khai khi có tầng sửa ở giữa; cho mỗi vòng phản hồi một tín hiệu riêng để chúng không dao động; và đừng tin một thống kê trước khi nó đủ mẫu.
Muốn tự soi, xem VPA khuyến nghị gì so với những gì bạn đang khai:
kubectl get vpa -A -o custom-columns=\
NS:.metadata.namespace,TEN:.metadata.name,\
CHE_DO:.spec.updatePolicy.updateMode,\
CPU:.status.recommendation.containerRecommendations[0].target.cpu,\
RAM:.status.recommendation.containerRecommendations[0].target.memory
So với requests trong manifest. Chênh lệch lớn theo chiều khai thừa là tiền đang lãng phí; chênh lệch theo chiều khai thiếu là pod sắp bị đuổi khi node cạn.
Phần sau đo Cluster Autoscaler — và nói rõ phần nào tôi không dựng được để đo.