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 đó.

Chi phí một lệnh kubectl, cơ chế watch, và lưu lượng nền

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à:

  • kubelet báo cáo trạng thái node và pod
  • các vòng lặp điều hoà của controller-manager
  • scheduler cậ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 RoleClusterRole đã 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 kubeletleader-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.