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ớ 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 Scheduled → Pulled → Created → Started 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á.