kube-apiserver là cửa duy nhất vào cụm và là thành phần nặng nhất — phần 43 đo được 265 MB, gấp năm lần etcd. Bài này đo xem nó làm gì để đáng chừng đó.
Một lệnh kubectl tốn gì
| Tổng | Phần máy chủ | |
|---|---|---|
kubectl version --client (không gọi mạng) |
24 ms | — |
kubectl get --raw /healthz |
28 ms | 4 ms |
kubectl get pods |
32 ms | 8 ms |
kubectl create configmap |
29 ms | 5 ms |
Binary kubectl khởi động tốn 24 mili giây. Phần việc thật của apiserver — xác thực, phân quyền, đọc hoặc ghi etcd — chỉ 4 đến 8 mili giây, tức 15–25% thời gian một lệnh.
Hệ quả thực dụng: một kịch bản gọi kubectl một nghìn lần tốn 24 giây chỉ để khởi động tiến trình, trước khi cụm làm bất cứ việc gì.
Cách xử lý:
# thay vì lặp kubectl
for f in *.yaml; do kubectl apply -f $f; done
# gộp thành một lời gọi
kubectl apply -f ./thu-muc/
Với công cụ chạy thường xuyên, dùng thư viện client (client-go, kubernetes cho Python) thay vì gọi binary — nó giữ kết nối và bỏ hẳn 24 ms đó.
watch: cách Kubernetes không phải hỏi liên tục
Mở kubectl get pods -w, rồi tạo một pod:
sự kiện đến sau 59 ms (đã gồm cả thời gian chạy kubectl run)
Và bốn sự kiện cho một vòng đời pod:
wtest Pending
wtest Pending
wtest ContainerCreating
wtest Running
Mỗi lần trạng thái đổi là một sự kiện được đẩy tới, không phải hỏi tới.
Đây là cơ chế nền của toàn bộ Kubernetes. kube-scheduler không hỏi "có pod nào chưa xếp chỗ không" mỗi giây — nó mở một watch và ngồi đợi. kube-controller-manager cũng vậy, kubelet cũng vậy.
Nếu mọi thành phần đều hỏi định kỳ, apiserver sẽ phải phục vụ hàng nghìn truy vấn mỗi giây trên một cụm lớn. Với watch, nó chỉ gửi khi có gì đổi.
Kỹ thuật này cũng là lý do apiserver tốn bộ nhớ: nó giữ một bộ nhớ đệm của mọi object và một danh sách người đang theo dõi cho từng loại tài nguyên.
Cụm rỗng vẫn gọi ba lượt mỗi giây
Đo apiserver_request_total trên cụm không có ai dùng:
89 lượt trong 30 giây = 3,0 lượt/giây
Không ai chạy kubectl, không ai triển khai gì. Ba lượt mỗi giây là:
kubeletbáo cáo trạng thái node và pod- các vòng lặp điều hoà của
controller-manager schedulercập nhật thông tin node- các bộ kiểm tra sức khoẻ và gia hạn khoá lãnh đạo
apiserver không bao giờ rảnh. Trên cụm 100 node, con số này nhân lên tương ứng — và đó là lý do nó chiếm 265 MB.
Chuỗi một yêu cầu đi qua
xác thực
-> phân quyền (RBAC)
-> admission (mutating)
-> kiểm tra hợp lệ
-> admission (validating)
-> ghi etcd
Sáu bước cho mỗi lời gọi ghi.
Xác thực — bạn là ai. Chứng chỉ client, token của ServiceAccount, hoặc nhà cung cấp OIDC.
Phân quyền — bạn được làm gì. RBAC so hành động với các Role và ClusterRole đã gắn.
Admission — chỗ thú vị nhất, và cũng nguy hiểm nhất. Đây là nơi các bộ điều khiển có thể sửa object (mutating) hoặc từ chối nó (validating). Lưới dịch vụ chèn sidecar ở đây; các công cụ chính sách chặn manifest không đạt chuẩn ở đây.
Điểm nguy hiểm: webhook admission nằm ngay giữa đường ghi. Webhook chậm hoặc chết làm mọi lệnh apply chậm hoặc hỏng theo — kể cả với object nó không quan tâm, nếu failurePolicy: Fail và bộ lọc quá rộng.
Đây là một trong những cách làm cụm ngừng hoạt động hoàn toàn mà không thành phần lõi nào hỏng. Khi kubectl apply bỗng treo 30 giây rồi lỗi, danh sách webhook là chỗ nhìn đầu tiên:
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations
Giới hạn tốc độ ở hai phía
Phía client, client-go mặc định QPS 50, Burst 100. Vượt là kubectl tự chờ, không báo lỗi. Dấu hiệu là dòng Throttling request khi chạy với -v=6.
Phía máy chủ, API Priority and Fairness chia yêu cầu vào các mức ưu tiên. Lưu lượng của kubelet và leader-election được ưu tiên hơn lệnh kubectl của con người — nên khi cụm quá tải, thứ chậm trước là công cụ của bạn, còn cụm vẫn tự vận hành.
Đây là thiết kế đúng, và cũng là lý do "cụm chậm" có thể chỉ có nghĩa "kubectl của tôi chậm".
Hai lệnh để nhìn vào bên trong
# xem kubectl thật sự gọi những gì
kubectl get pods -v=8 2>&1 | grep -E "^I.*(GET|POST|PUT)"
# xem apiserver đang làm gì
kubectl get --raw /metrics | grep apiserver_request_duration_seconds_bucket | head
Lệnh đầu rất hữu ích khi gỡ lỗi phân quyền: nó cho bạn đúng đường dẫn API mà kubectl gọi, và đó là thứ cần khai trong Role.
Thử ba mươi giây
Tự tách chi phí client khỏi chi phí máy chủ:
time (for i in $(seq 1 20); do kubectl version --client >/dev/null; done)
time (for i in $(seq 1 20); do kubectl get --raw /healthz >/dev/null; done)
Hiệu hai con số chia cho 20 là thời gian thật của một vòng gọi tới apiserver. Nếu nó vượt vài chục mili giây trên cụm không tải, etcd hoặc webhook admission là chỗ đáng xem.
Phần sau đo Deployment và ReplicaSet: cơ chế đứng sau việc cập nhật không gián đoạn.