Sơ đồ kiến trúc Kubernetes có ở mọi nơi. Bài này làm khác: tắt từng thành phần một trên cụm đang chạy và đo xem cái gì hỏng, cái gì không.

Bộ nhớ từng thành phần và kết quả khi tắt từng cái

Bộ nhớ thật của từng thành phần

Đo RSS trực tiếp trong node:

Thành phần kB
kube-apiserver 270.832
kube-controller-manager 108.112
kubelet 83.768
containerd 70.656
coredns × 2 62.092 + 60.332
kube-scheduler 56.488
etcd 55.708
kube-proxy 49.016
kindnetd 47.072
local-path-provisioner 41.196

kube-apiserver chiếm 265 MB — gấp gần năm lần etcd.

Điều này ngược với hình dung phổ biến rằng etcd là thành phần nặng. etcd chỉ lưu trữ; apiserver là cửa duy nhất vào cụm, phải xác thực, phân quyền, kiểm tra hợp lệ, chuyển đổi phiên bản API, và phục vụ hàng loạt watch cho mọi thành phần khác.

Tắt kube-scheduler

pod cũ:  Running
pod mới: Pending   Pending

Pod đang chạy không bị ảnh hưởng gì. Pod mới nằm ở Pending vô thời hạn.

Scheduler chỉ làm đúng một việc: quyết định pod chạy ở đâu. Nó ghi tên node vào spec.nodeName và hết trách nhiệm. Sau đó kubelet trên node đó nhận việc.

Bật lại thì hai pod chuyển sang Running gần như ngay lập tức — chúng vẫn nằm chờ trong etcd suốt thời gian đó.

Tắt kube-controller-manager

Xoá một pod của deployment 2 bản, đợi 20 giây:

còn 1/2  ->  KHÔNG được thay thế

Bật lại controller-manager thì đủ 2 pod ngay lập tức.

Đây là thành phần thực hiện lời hứa ở phần 41: giữ số bản thật bằng số bản khai báo. Nó chạy một tập vòng lặp điều hoà — ReplicaSet, Deployment, Node, Endpoint, và hàng chục cái khác — mỗi vòng so trạng thái thật với mong muốn rồi sửa chênh lệch.

Không có nó, Kubernetes trở thành một nơi lưu cấu hình. Mọi thứ đang chạy vẫn chạy, nhưng không có gì được sửa nữa.

Tắt kube-apiserver

kubectl get pods:  client rate limiter Wait returned an error: context deadline exceeded
số container nginx vẫn chạy:  4

kubectl chết hoàn toàn. Ứng dụng thì không.

Bốn container nginx vẫn chạy và vẫn phục vụ. kubelet đã có bản kê pod cần chạy và nó tiếp tục giữ chúng sống mà không cần hỏi ai.

Phục hồi khi bật lại: ~6 giây.

Tắt etcd

kubectl get nodes:  Unable to connect to the server
container apiserver vẫn chạy:  1
container nginx vẫn chạy:      4

Chú ý dòng giữa: container apiserver vẫn chạy, nhưng vô dụng.

apiserver không có trạng thái riêng. Nó là một tầng dịch giữa API HTTP và etcd. Mất etcd là mất tất cả — dù tiến trình apiserver trông vẫn khoẻ trong mọi phép kiểm tra tiến trình.

Đây là lý do kiểm tra sức khoẻ theo "tiến trình có chạy không" là kiểu kiểm tra tệ nhất, và nó lặp lại bài học ở phần 31 của sê-ri Kafka.

Phục hồi etcd + apiserver: ~36 giây, chậm hơn sáu lần so với chỉ apiserver — vì apiserver phải đợi etcd sẵn sàng rồi mới khởi động được.

Kết luận thực dụng

Control plane chết không làm ứng dụng chết. Nó làm cụm mất khả năng tự chữa.

Và bạn chỉ phát hiện ra điều đó vào lần tiếp theo có thứ gì hỏng. Cụm mất control plane trông hoàn toàn bình thường: mọi trang web vẫn tải, mọi API vẫn trả lời. Cho tới khi một pod chết và không ai thay nó.

Đây là lý do control plane cần được theo dõi riêng, và cần chạy nhiều bản trên nhiều máy. Cụm sản xuất thường có 3 bản etcd và 3 bản apiserver.

Thành phần trên node

Ba thứ chạy trên mọi node, kể cả control plane:

kubelet — 83.768 kB. Nó là thứ thật sự chạy container. Nó hỏi apiserver "pod nào thuộc về node của tôi", rồi bảo containerd tạo container, rồi báo cáo trạng thái ngược lại. Đây là thành phần duy nhất không chạy dưới dạng pod — nó là dịch vụ hệ thống, vì phải có nó thì pod mới chạy được.

containerd — 70.656 kB. Runtime thật sự. Kubernetes không tự chạy container; nó nói chuyện với runtime qua giao diện CRI.

kube-proxy — 49.016 kB. Dựng luật iptables (hoặc IPVS) để Service hoạt động. Phần sau về Service sẽ đo nó.

Vòng đời một lời gọi kubectl

Gộp lại thành một chuỗi, và mỗi mũi tên là một thành phần đã đo ở trên:

kubectl apply
   -> kube-apiserver     xác thực, phân quyền, kiểm tra hợp lệ
   -> etcd               ghi trạng thái mong muốn
   -> kube-controller-manager   thấy Deployment mới, tạo ReplicaSet, tạo Pod
   -> kube-scheduler     gán pod vào node
   -> kubelet            trên node đó, thấy pod của mình
   -> containerd         tạo container

Sáu bước, và mỗi bước đi qua apiserver. Không thành phần nào nói chuyện trực tiếp với thành phần khác — tất cả đều đọc và ghi qua API. Đó là lý do apiserver là thành phần nặng nhất, và cũng là lý do nó là điểm hỏng đáng lo nhất.

Thử ba mươi giây

Tự xem chuỗi trên chạy:

kubectl get events -A --sort-by=.lastTimestamp -w &
kubectl create deployment thu --image=nginx:alpine

Bạn sẽ thấy ScheduledPulledCreatedStarted lần lượt hiện ra, mỗi dòng do một thành phần khác nhau ghi. Đó là toàn bộ kiến trúc, quan sát từ bên ngoài.

Phần sau đo Pod: vì sao nó là đơn vị nhỏ nhất chứ không phải container, và một cái bẫy về thời gian xoá.