Kiến trúc control plane, node và đường đi của một lệnh kubectl
Mục tiêu học tập
- Kể tên và mô tả đúng vai trò của bốn thành phần control plane:
kube-apiserver,etcd,kube-scheduler,kube-controller-manager. - Mô tả vai trò của
kubelet,kube-proxyvà container runtime trên một node. - Vẽ lại được đường đi của một lệnh
kubectltừ máy bạn tới container đang chạy, theo đúng thứ tự. - Nhìn trạng thái Pod (
Pending,ContainerCreating,Running,CrashLoopBackOff) và đoán được thành phần nào đang gặp vấn đề.
Ôn nhanh (2 phút)
Bạn đã biết container: image là khuôn, container là tiến trình đang chạy, được cô lập bằng namespace và cgroup của Linux. Kubernetes không tạo container trực tiếp. Nó giao việc đó cho container runtime (containerd, CRI-O) qua chuẩn CRI (Container Runtime Interface). Nói cách khác: kubelet là người ra lệnh, runtime là người thi hành.
[!NOTE] Chỉ có
kube-apiserverđược phép nói chuyện vớietcd. Mọi thành phần khác, kể cảkubectl, đều đi qua API server. Đây là câu hỏi phỏng vấn kinh điển.
Lý thuyết
Bức tranh tổng thể
Một cluster gồm control plane (bộ não, ra quyết định) và các node (cơ bắp, chạy container thật).
flowchart LR
subgraph cp["Control plane"]
API["🧠<br/>kube-apiserver"]
ETCD["🗄️<br/>etcd"]
SCH["📅<br/>kube-scheduler"]
CM["🔁<br/>kube-controller-manager"]
end
subgraph wk["Worker node"]
KL["🤖<br/>kubelet"]
CR["📦<br/>container runtime"]
POD["🟢<br/>Pod"]
KP["🔀<br/>kube-proxy"]
end
KUBECTL["⌨️<br/>kubectl"] --> API
API <--> ETCD
CM --> API
SCH --> API
API --> KL
KL --> CR
CR --> POD
KP --> POD
Control plane
| Thành phần | Vai trò | Câu ghi nhớ |
|---|---|---|
kube-apiserver |
Cổng vào duy nhất, xử lý authn/authz/admission, đọc ghi etcd |
Không có nó thì cluster "câm" |
etcd |
Key-value store phân tán, nguồn sự thật duy nhất | Mất etcd là mất cluster |
kube-scheduler |
Chọn node cho Pod chưa được gán | Chỉ quyết định "ở đâu", không tạo gì cả |
kube-controller-manager |
Chạy các vòng reconcile (Deployment, ReplicaSet, Node, Job...) | Luôn kéo trạng thái thật về trạng thái mong muốn |
kube-apiserver là stateless nên bạn có thể chạy nhiều bản sao sau load balancer. etcd thì ngược lại: cần quorum, cụm 3 node chịu được 1 node hỏng, cụm 2 node thì không chịu được node nào.
Node
- kubelet: agent chạy trên mỗi node. Nó watch API server để tìm Pod có
spec.nodeNametrùng tên node mình, rồi gọi runtime qua CRI để pull image và start container. Sau đó báo status ngược lại. - kube-proxy: lập trình
iptableshoặcipvsđể traffic vào Service VIP được chuyển tới Pod IP phía sau. - Container runtime: containerd/CRI-O, thực thi việc tạo container thật.
Đường đi của một lệnh kubectl
1. `kubectl` đọc `~/.kube/config`, lấy địa chỉ API server và thông tin xác thực (cert hoặc token).
2. `kubectl` dựng HTTP request (ví dụ `POST /apis/apps/v1/namespaces/default/deployments`) và gửi tới `kube-apiserver`.
3. API server chạy **authentication**: bạn là ai?
4. API server chạy **authorization** (RBAC): bạn có được phép `create` Deployment không?
5. **Admission controllers** chạy: gán giá trị mặc định (mutating), kiểm tra ràng buộc (validating), ví dụ giới hạn resource, chặn image từ registry lạ.
6. Object được ghi vào `etcd`, API server trả về `201 Created` cho `kubectl`.
7. Controller tương ứng (Deployment controller) đang watch, thấy object mới và tạo `ReplicaSet`.
8. ReplicaSet controller tạo các Pod, mỗi Pod chưa có `spec.nodeName`.
9. `kube-scheduler` thấy Pod "trần", chạy filter (đủ CPU/RAM, taint, affinity) rồi score, chọn node và ghi binding.
10. `kubelet` trên node đó thấy Pod được gán, pull image, tạo container, cập nhật status: `Pending` → `ContainerCreating` → `Running`.
11. Khi Pod Ready, EndpointSlice được cập nhật; `kube-proxy` trên các node đọc và sửa rules để traffic vào Service.
Điểm cốt lõi: ==mọi thứ là bất đồng bộ và dựa trên vòng reconcile==. kubectl chỉ ghi mong muốn vào etcd, không ai "ra lệnh" trực tiếp cho container cả.
Ví dụ minh họa
Chạy kubectl create deployment web --image=nginx --replicas=2 rồi lần theo từng bước:
kubectlgửiPOST .../deploymentstới API server.- API server xác thực, kiểm tra RBAC, chạy admission, ghi vào
etcd, trả201. - Deployment controller tạo ReplicaSet tên
web-7d9c.... - ReplicaSet controller tạo 2 Pod
web-7d9c-abcdevàweb-7d9c-fghij,nodeNamerỗng. - Scheduler chấm điểm, gán cả hai vào
node-1. - kubelet trên
node-1pullnginx, start container, báo statusRunning. kubectl get podssau đó chỉ đọc lại từetcdqua API server, không hỏi kubelet.
Kiểm chứng bằng event, đây là nơi các thành phần "ký tên":
kubectl get pods -w
kubectl describe pod web-7d9c-abcde | grep -A10 Events
Bạn sẽ thấy Scheduled (do scheduler) đứng trước Pulled, Created, Started (do kubelet). Đó chính là bằng chứng của ranh giới trách nhiệm.
[!TIP] Muốn thấy request thật mà
kubectlgửi đi:kubectl get pods -v=8. Rất hữu ích khi debug lỗi 403 RBAC hoặc API version.
Thực hành có hướng dẫn
Bài 1: Sắp xếp luồng. Sắp xếp lại cho đúng thứ tự: (a) scheduler gán node, (b) API server ghi vào etcd, (c) kubelet start container, (d) ReplicaSet controller tạo Pod, (e) kubectl gửi request.
Xem đáp án
e → b → d → a → c. Lưu ý: controller tạo Pod trước khi scheduler gán node, vì Pod sinh ra với nodeName rỗng.
Bài 2: Quan sát bằng mắt. Trên cluster local (kind/minikube), chạy:
kubectl create deployment api --image=nginx --replicas=3
kubectl get pods -o wide
kubectl describe pod <một-pod> | sed -n '/Events/,$p'
Ghi lại 3 loại event và chỉ rõ event nào do scheduler, event nào do kubelet sinh ra.
Gợi ý
Scheduled = scheduler. Pulled, Created, Started = kubelet + runtime. Nếu có FailedScheduling nghĩa là Pod chưa từng tới được node nào.
Bài 3: Truy lỗi. Ba tình huống, hãy đoán thành phần hỏng và trạng thái Pod tương ứng:
kube-schedulerchết.kube-controller-managerchết.kubelettrên một node chết.
Xem đáp án
- Deployment và ReplicaSet vẫn tạo Pod, nhưng Pod kẹt ở
Pendingvì không ai gán node. - Deployment được tạo nhưng không có ReplicaSet, hoặc scale không xảy ra:
kubectl get rstrống. Pod đang chạy vẫn chạy tiếp. - Node chuyển
NotReadysau vài chu kỳ heartbeat, Pod trên node đó không được cập nhật status và dần bị đánh dấu lỗi; tài nguyên thì không giải phóng ngay.
Góc chuyên gia
- Đừng sửa
etcdbằng tay. Mọi thay đổi phải qua API server để admission và controller còn thấy được. kubectl getđọcetcd, không đọc node. Trạng thái có thể trễ vài giây so với thực tế. Đừng hoảng khi thấy PodRunningmà app chưa lên.Pendinglà chuyện của scheduler,ContainerCreatinglà chuyện của kubelet. Phân biệt được hai cái này là tiết kiệm hàng giờ debug.kubectl logs,exec,port-forwardđi qua API server rồi mới tới kubelet. Vì vậy API server quá tải có thể làmexecchậm, dù app hoàn toàn khỏe.- Control plane mặc định bị taint
NoSchedule, nên đừng ngạc nhiên khi node control plane không chạy Pod workload. - Quy tắc ngón tay cái về
etcd: luôn chạy 3 hoặc 5 thành viên, backup snapshot định kỳ, và không đặtetcdchung ổ đĩa IOPS thấp với workload nặng.
Tóm tắt và bước tiếp theo
Bạn vừa đi hết một vòng: kubectl → API server (authn, authz, admission) → etcd → controller tạo Pod → scheduler gán node → kubelet start container → kube-proxy mở đường. Mọi thứ nối với nhau bằng watch và vòng reconcile, không phải bằng lệnh trực tiếp.
Điền nhanh để tự kiểm tra:
Thành phần duy nhất giao tiếp trực tiếp với etcd là {{c1::kube-apiserver}}.
{{c2::kube-scheduler}} chọn node cho Pod qua hai pha filter và score.
Trên node, {{c3::kubelet}} nhận Pod đã được gán và gọi CRI để tạo container.
{{c4::kube-controller-manager}} chạy các vòng reconcile, ví dụ tạo ReplicaSet từ Deployment.
Nếu etcd mất {{c5::quorum}}, cluster sẽ không nhận được thay đổi nào nữa.
Bài tiếp theo: Pod, ReplicaSet, Deployment. Chúng ta sẽ mổ xẻ chính cái vòng reconcile vừa nhắc ở trên: Deployment quản lý ReplicaSet thế nào, rollout và rollback diễn ra ra sao, và scheduler ra quyết định dựa trên những tiêu chí gì.
[
{ "front": "Điểm vào duy nhất của mọi lệnh kubectl là gì?", "back": "kube-apiserver; kubectl không bao giờ nói chuyện trực tiếp với node." },
{ "front": "Thành phần nào lưu trạng thái thật của cluster?", "back": "etcd, key-value store phân tán và nhất quán." },
{ "front": "Ai là thành phần duy nhất được giao tiếp với etcd?", "back": "kube-apiserver." },
{ "front": "kube-scheduler làm gì?", "back": "Chọn node cho Pod chưa có nodeName bằng filter rồi score, sau đó ghi binding." },
{ "front": "kube-controller-manager làm gì?", "back": "Chạy các controller trong vòng reconcile để kéo trạng thái thật về trạng thái mong muốn." },
{ "front": "kubelet làm gì?", "back": "Agent trên node, watch Pod được gán cho node mình, gọi container runtime qua CRI và báo status về API server." },
{ "front": "kube-proxy làm gì?", "back": "Lập trình iptables hoặc IPVS để traffic tới Service VIP được chuyển tới Pod IP." },
{ "front": "Pod kẹt ở Pending thường do đâu?", "back": "Chưa được scheduler gán node, hoặc không node nào thỏa điều kiện lọc." },
{ "front": "Pod ở ContainerCreating lâu thường do đâu?", "back": "kubelet đang pull image hoặc mount volume, không phải lỗi scheduler." },
{ "front": "Admission controller chạy ở đâu trong luồng?", "back": "Trong kube-apiserver, sau authentication và authorization, trước khi ghi vào etcd." },
{ "front": "Cụm etcd 3 node chịu được mấy node hỏng?", "back": "1 node, vì cần quorum 2/3." },
{ "front": "Vì sao kubectl get pods có thể trả về trạng thái hơi cũ?", "back": "Vì nó đọc từ etcd qua API server, không hỏi trực tiếp kubelet trên node." },
{ "front": "Lệnh nào in ra chi tiết HTTP request mà kubectl gửi?", "back": "Thêm cờ -v=8, ví dụ kubectl get pods -v=8." },
{ "front": "Control plane có chạy workload mặc định không?", "back": "Không, thường bị taint NoSchedule để chỉ chạy thành phần hệ thống." }
]