Metrics Server và HorizontalPodAutoscaler

K
Kai··6 min read

Bảy part qua, cụm của ta chạy được nhưng "mù" — kubectl top báo Metrics API not available, không ai đo pod đang dùng bao nhiêu CPU/RAM. Đến Part VIII (autoscaling) thì điều đó thành rào cản: HorizontalPodAutoscaler muốn tăng/giảm số bản sao theo tải thì phải có số liệu tải. Nên bài này có hai nửa: cài Metrics Server — add-on đầu tiên ta thêm vào cụm tự dựng — và dùng số liệu của nó để dựng HPA. Nửa đầu vấp đúng một cái bẫy thường gặp của cụm dựng-tay, và sửa nó cũng là một bài học hay về aggregation layer.

Vì sao cụm chưa có metrics

kubectl top lấy số từ API metrics.k8s.io, mà tài liệu nói rõ: "The metrics.k8s.io API is usually provided by an add-on named Metrics Server, which needs to be launched separately." Cụm dựng bằng kubeadm/managed có sẵn (hoặc cài kèm); cụm KTHW của ta thì trống — ta chưa cài. Tải manifest chính thức (ghim v0.8.1) và thêm một cờ:

curl -fsSL https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml \
  -o metrics-server.yaml
# kubelet của ta dùng cert tự ký (Bài 11), metrics-server cần bỏ qua verify:
#   thêm "- --kubelet-insecure-tls" vào args của Deployment
kubectl apply -f metrics-server.yaml

--kubelet-insecure-tls cần vì metrics-server kết nối tới kubelet qua HTTPS và muốn xác thực cert phục vụ của kubelet; cert ấy (Bài 11) ký bởi CA nội bộ và SAN không khớp tên metrics-server mong đợi, nên ta bảo nó bỏ qua verify (chấp nhận được trong cụm tự quản). Manifest tạo Deployment, Service, RBAC, và một APIService v1beta1.metrics.k8s.io — đăng ký metrics-server vào aggregation layer của API server.

Cái bẫy KTHW: control plane không tới được pod

Pod metrics-server lên Running, nhưng APIService thì fail:

kubectl get apiservice v1beta1.metrics.k8s.io -o jsonpath='{.status.conditions[?(@.type=="Available")].message}'
failing or missing response from https://10.32.0.90:443/...: context deadline exceeded

10.32.0.90ClusterIP của Service metrics-server. API server (chạy trên controller-0/1/2) cố gọi tới ClusterIP đó nhưng không tới được. Lý do nằm ở kiến trúc cụm: ở Part I, ta chỉ cài kube-proxy + CNI trên worker (Bài 12–14), không trên control plane. Mà ClusterIP là một IP ảo do kube-proxy dịch (DNAT) — control plane không có kube-proxy nên không biết 10.32.0.90 là gì. API server gọi vào hư không → timeout.

May là ta đường tới pod IP. Route VPC ở Bài 14 (10.200.0.0/24→worker-0...) là cấp subnet, control plane cũng dùng — kiểm chứng:

ssh controller-0 'ping -c2 10.200.0.55'      # pod IP của metrics-server
# 0% packet loss

Control plane ping được pod IP (qua route VPC), chỉ không tới được ClusterIP. Lời giải: bảo aggregator dial thẳng pod IP thay vì ClusterIP, bằng cờ --enable-aggregator-routing=true trên API server:

# thêm vào ExecStart của kube-apiserver trên CẢ 3 controller, rồi restart:
#   --enable-aggregator-routing=true

Cờ này khiến API server tự phân giải endpoint (pod IP) của Service đích và gọi trực tiếp — bỏ qua ClusterIP. Vì control plane tới được pod IP qua route VPC, aggregation layer thông. Đây là cách sửa thường dùng cho mọi cụm mà API server không nằm trên pod network (đúng kiểu KTHW). Sau khi 3 API server khởi động lại:

kubectl get apiservice v1beta1.metrics.k8s.io -o jsonpath='{.status.conditions[?(@.type=="Available")].status}'
# True
kubectl top nodes
NAME       CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
worker-0   21m          1%       469Mi           12%
worker-1   18m          0%       440Mi           11%

kubectl top ra số thật — cụm giờ "thấy được" tải. (Số CPU/memory này chính là node.stats mà eviction signal của Bài 38 dùng.)

HorizontalPodAutoscaler

Có metrics rồi, dựng HPA. Tài liệu: "a HorizontalPodAutoscaler automatically updates a workload resource (such as a Deployment ...), with the aim of automatically scaling capacity to match demand." — scale ngang (thêm pod), khác scale dọc (thêm tài nguyên cho pod sẵn có, là VPA của Bài 40). Deploy một app có khai requests.cpu (HPA tính utilization theo request) rồi tạo HPA:

# Deployment cpu-app: 1 replica, container requests cpu 100m, chạy sleep
kubectl autoscale deployment cpu-app --cpu-percent=50 --min=1 --max=4
kubectl get hpa cpu-app
NAME      REFERENCE            TARGETS       MINPODS   MAXPODS   REPLICAS
cpu-app   Deployment/cpu-app   cpu: 0%/50%   1         4         1

0%/50% — CPU hiện 0%, mục tiêu 50% (của request 100m, tức 50m tuyệt đối). Đang nhàn nên giữ 1 bản sao. Công thức HPA, theo tài liệu:

desiredReplicas = ceil[ currentReplicas × (currentMetricValue / desiredMetricValue) ]

Giờ đốt CPU trong một pod (yes > /dev/null ngốn trọn một core) và theo dõi:

POD=$(kubectl get pods -l app=cpu-app -o jsonpath='{.items[0].metadata.name}')
kubectl exec $POD -- sh -c 'yes > /dev/null &'
# theo dõi:
kubectl get hpa cpu-app -w
t=15s:  REPLICAS=4
...
currentCPU=249% target=50% current=4 desired=4

Pod đốt CPU dùng 1000m = 1000% của request 100m. HPA tính desired = ceil(1 × 1000/50) = 20, nhưng bị chặn ở max=4 nên scale lên 4. Sau khi có 4 bản sao (chỉ 1 đốt, 3 nhàn), utilization trung bình = (1000 + 0 + 0 + 0)/4 / 100m ≈ 249% — vẫn trên 50% nên giữ ở max 4. Event xác nhận:

kubectl get events --field-selector involvedObject.name=cpu-app,reason=SuccessfulRescale
Normal  SuccessfulRescale  New size: 4; reason: cpu resource utilization (percentage of request) above target

HPA controller chạy trong kube-controller-manager (Bài 8), kiểm tra định kỳ — "the default interval is 15 seconds". Nó bỏ qua nếu tỉ lệ gần 1.0 (dung sai 0.1 mặc định), tránh giật cục quanh ngưỡng.

Scale xuống chậm có chủ đích

Khi dừng burner, CPU về 0 — nhưng HPA không hạ pod ngay. Có một stabilization window mặc định 5 phút cho scale-down: HPA chờ tải thấp ổn định mới giảm, tránh "đập" số pod lên xuống liên tục khi tải dao động. (Scale-up thì nhanh, không có cửa sổ chờ mặc định — vì thiếu pod nguy hiểm hơn thừa pod.) Đây là lý do trong thực tế bạn thấy pod tăng nhanh khi tải lên nhưng giảm từ tốn sau khi tải xuống.

🧹 Dọn dẹp

kubectl delete hpa cpu-app
kubectl delete deployment cpu-app --now

Xóa HPA + Deployment (kéo theo pod và burner). Giữ lại Metrics Server — nó là hạ tầng dùng chung cho HPA, VPA (Bài 40) và kubectl top về sau; cũng giữ cờ --enable-aggregator-routing trên API server (cần cho mọi aggregated API sau này). Cụm còn CoreDNS + metrics-server. Manifest ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 39-metrics-hpa.

Tổng kết

Cụm tự dựng không có metrics cho tới khi ta cài Metrics Server (add-on đầu tiên) — và vấp cái bẫy KTHW: API server trên control plane không tới được ClusterIP của metrics-server vì control plane không chạy kube-proxy/CNI. Sửa bằng --enable-aggregator-routing=true để aggregator dial thẳng pod IP (control plane tới được qua route VPC của Bài 14). Sau đó kubectl top hoạt động. HorizontalPodAutoscaler dùng số liệu ấy để scale ngang: tính desiredReplicas = ceil(currentReplicas × current/target), theo % của request. Ta đốt CPU một pod (1000% request) → HPA scale 1→4 (chặn ở max), event SuccessfulRescale; controller kiểm tra mỗi 15s, có dung sai 0.1, và scale-down chờ stabilization 5 phút. Đây là phản ứng "thêm pod" — đối nghịch với "giết pod" của Part VII.

Bài 40 sang scale dọc: VerticalPodAutoscaler (đề xuất/đặt lại request-limit cho pod sẵn có thay vì thêm pod) và các resource manager ở tầng node (CPU Manager, Memory Manager, Topology Manager — gán CPU/NUMA chính xác cho workload nhạy độ trễ).