Pod, ReplicaSet, Deployment: cơ chế điều khiển, scheduling và rollout
Mục tiêu học tập
- Viết được manifest Deployment đầy đủ và giải thích được vai trò của
replicas,selector,template,strategy. - Mô tả được chuỗi sở hữu Deployment → ReplicaSet → Pod và vòng lặp reconcile đứng sau nó.
- Giải thích được scheduler chọn node như thế nào, và vì sao một Pod rơi vào trạng thái
Pending. - Theo dõi, dừng và rollback một lần rollout bằng
kubectlmà không đoán mò.
Ôn tập nhanh
Bài trước bạn đã đi hết đường đi: kubectl → API server → etcd → controller tạo Pod → scheduler gán node → kubelet start container. Điểm cần nhớ: API server chỉ ghi trạng thái mong muốn vào etcd, còn controller manager mới là thứ liên tục nhìn trạng thái thực tế và sửa cho khớp. Bài này chính là mổ xẻ mấy controller đó.
Lý thuyết
Pod: đơn vị nhỏ nhất, không phải container
Pod là một nhóm 1 hoặc nhiều container dùng chung network namespace (chung IP, chung port space) và có thể dùng chung volume. Bạn không tạo container trực tiếp, bạn tạo Pod. restartPolicy mặc định là Always, nên container chết sẽ được kubelet restart tại chỗ. Pod có các phase: Pending, Running, Succeeded, Failed.
Mẹo thực chiến: hãy tạo Pod trực tiếp một lần để hiểu, rồi đừng bao giờ làm vậy nữa trong môi trường thật. Pod trần không được ai giám sát, chết là mất.
flowchart TD
D["🚀<br/>Deployment web"] --> R1["📦<br/>ReplicaSet web-7d9f (rev 2)"]
D --> R0["🗄️<br/>ReplicaSet web-5c2a (rev 1, đã scale 0)"]
R1 --> P1["🟢<br/>Pod web-7d9f-x1"]
R1 --> P2["🟢<br/>Pod web-7d9f-x2"]
ReplicaSet: giữ đúng số lượng
ReplicaSet (RS) đảm bảo luôn có đúng replicas Pod khớp selector.matchLabels đang tồn tại. Hai field phải khớp nhau: label trong spec.template.metadata.labels và spec.selector.matchLabels. Lệch nhau thì API server từ chối manifest, đây là lỗi newcomer hay gặp nhất.
Sign in to view the full content of this lesson. Sign in
RS nhận nuôi Pod qua ownerReferences. Khi bạn xóa Deployment, garbage collector đi theo chuỗi owner và dọn luôn RS cùng Pod. Đó là lý do kubectl get pods sạch bóng sau khi xóa.
Deployment: quản lý RS, không quản lý Pod
Deployment là lớp trên RS. Mỗi khi spec.template thay đổi (image, env, label trong template...), Deployment controller tạo một RS mới với hash mới, rồi scale RS mới lên và RS cũ xuống theo strategy. RS cũ được giữ lại với replicas: 0 để làm bản rollback.
Hai chiến lược:
| Strategy | Hành vi | Downtime |
|---|---|---|
RollingUpdate (mặc định) |
Thay Pod dần, tôn trọng maxSurge và maxUnavailable |
Không, nếu có readinessProbe |
Recreate |
Tắt hết Pod cũ rồi mới tạo mới | Có |
maxSurge: 1 cho phép tạo thêm 1 Pod vượt số replica. maxUnavailable: 0 cấm mất Pod Ready nào trong lúc rollout. Với replicas: 3, cấu hình này nghĩa là tối đa 4 Pod tồn tại, tối thiểu 3 Pod Ready. ==Rollout không bao giờ vượt quá replicas + maxSurge và không bao giờ xuống dưới replicas - maxUnavailable.==
Vòng lặp reconcile: level-triggered
Controller không phản ứng kiểu "có event thì làm một lần rồi quên". Nó liên tục: đọc trạng thái mong muốn, đọc trạng thái thực tế, tính hiệu, rồi hành động. Vì vậy nếu bạn xóa tay một Pod của RS, RS sẽ tạo lại ngay, và nếu ai đó sửa tay, controller vẫn kéo về đúng số.
Scheduler chọn node thế nào
Scheduler chạy hai pha:
- Filtering: loại node không hợp lệ. Lý do thường gặp: không đủ
resources.requests,nodeSelector/nodeAffinitykhông khớp,taintsmà Pod không cótolerations, nodeNotReadyhoặc đang cordon. - Scoring: chấm điểm các node còn lại (ví dụ cân bằng tải theo mức sử dụng, ưu tiên node đã có image). Điểm cao nhất thắng.
Nếu mọi node bị loại ở pha 1, Pod nằm Pending và kubectl describe pod sẽ in ra lý do trong phần Events. Đặt nodeName trực tiếp trong spec sẽ bỏ qua scheduler hoàn toàn, chỉ dùng khi debug.
Ví dụ có hướng dẫn
Manifest dưới đây đủ dùng cho 90% workload stateless:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
revisionHistoryLimit: 5
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27.1
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
Đọc từ trên xuống: replicas: 3 là trạng thái mong muốn. selector và label app: web trong template phải khớp. maxUnavailable: 0 chỉ an toàn khi có readinessProbe, vì nếu không có probe, Pod được coi là Ready ngay khi container start, dù app chưa sẵn sàng nhận traffic. resources.requests là con số scheduler dùng để lọc node, thiếu nó thì mọi Pod đều "vừa" mọi node cho tới khi node hết tài nguyên thật.
Rollout và rollback:
kubectl apply -f web.yaml
kubectl rollout status deployment/web
kubectl set image deployment/web web=nginx:1.27.2
kubectl rollout history deployment/web
kubectl rollout undo deployment/web
rollout undo không "chạy lại lệnh cũ". Nó tìm RS của revision trước và scale RS đó lên lại, scale RS hiện tại xuống 0. Vì vậy manifest cũ phải còn trong lịch sử, đừng đặt revisionHistoryLimit: 0.
Thực hành có hướng dẫn
Bài 1. Viết web.yaml như ví dụ trên nhưng đổi image thành nginx:1.27.0 và thêm label tier: frontend cho cả selector lẫn template. Apply, kiểm tra kubectl get rs và kubectl get pods -l app=web.
Gợi ý và đáp án
Nhớ sửa label ở hai chỗ trong cùng một lần apply, nếu chỉ sửa một chỗ API server báo selector does not match template labels. Sau khi apply, bạn sẽ thấy đúng 1 RS và 3 Pod. Chạy kubectl get pods -o wide để xem scheduler đặt chúng lên node nào.
Bài 2. Theo dõi rollout: mở terminal 1 chạy kubectl get pods -w, terminal 2 chạy kubectl set image deployment/web web=nginx:1.27.1. Đếm số Pod tối đa bạn thấy tại một thời điểm.
Đáp án
Tối đa 4 Pod (3 + maxSurge: 1), và không lúc nào dưới 3 Pod Ready vì maxUnavailable: 0. Bạn sẽ thấy RS mới được tạo với hash khác, RS cũ tụt dần về 0. Nếu thấy 6 Pod cùng lúc, bạn đang dùng giá trị mặc định maxSurge: 25%, tức Deployment chưa nhận strategy của bạn.
Bài 3. Gây lỗi có kiểm soát: chạy kubectl set image deployment/web web=nginx:khong-ton-tai. Sau 30 giây, xem kubectl get pods, kubectl describe pod <tên>, rồi kubectl rollout undo deployment/web.
Đáp án
Pod mới ở trạng thái ImagePullBackOff, nhưng vì maxUnavailable: 0, 3 Pod cũ vẫn chạy phục vụ traffic. kubectl get pods cho thấy cả Pod cũ lẫn Pod lỗi. rollout undo đưa bạn về revision trước và dọn Pod lỗi. Đây là lý do maxUnavailable: 0 đáng giá: rollout hỏng không kéo sập dịch vụ.
Bài 4 (scheduler). Thêm resources.requests.cpu: 4000m vào container rồi apply. Giải thích vì sao Pod Pending.
Đáp án
Pha filtering loại mọi node vì không node nào còn đủ 4 CPU khả dụng. kubectl describe pod sẽ in 0/3 nodes are available: 3 Insufficient cpu. Xóa hoặc giảm request rồi apply lại, Pod sẽ được schedule.
Góc chuyên gia
- Đừng dùng tag
latest. Deployment không tạo RS mới khi nội dung image đổi mà tag không đổi. Bạn sẽapplymà chẳng có rollout nào. kubectl set imagevàkubectl applydễ đá nhau.set imagesửa trên cluster, file YAML vẫn ghi image cũ. Lầnapplysau sẽ âm thầm rollback. Chọn một nguồn sự thật và bám theo nó.- Thiếu readinessProbe = rollout nhanh nhưng rủi ro. Không có probe, K8s đẩy traffic vào Pod ngay khi container start. Có probe, rollout chậm hơn nhưng an toàn.
selectorlà immutable. Đổiselector.matchLabelstrên Deployment đang tồn tại sẽ bị từ chối. Cách xử lý: xóa và tạo lại, hoặc tạo tên mới rồi chuyển dần.kubectl rollout pause/resumehữu ích khi bạn muốn sửa vài field rồi mới cho rollout chạy một lần, tránh tạo nhiều revision rác.- Quy tắc ngón tay: stateless → Deployment; cần tên ổn định, thứ tự, storage riêng từng bản → đó là bài của StatefulSet, chưa cần trong module này.
Tóm tắt và bước tiếp theo
Bạn vừa nắm chuỗi Deployment → ReplicaSet → Pod, vòng lặp reconcile kéo trạng thái thực tế về mong muốn, hai pha filtering/scoring của scheduler, và cách RollingUpdate phối hợp với maxSurge/maxUnavailable để rollout không gây downtime. Rollback chỉ là scale RS cũ lên lại.
Pod của bạn giờ đã chạy, nhưng chưa ai nói chuyện được với chúng: IP Pod đổi mỗi lần restart. Bài tiếp theo, Service và Ingress, sẽ giải quyết đúng vấn đề đó: làm sao đưa traffic ổn định vào một tập Pod luôn thay đổi.
Deployment quản lý ReplicaSet, còn ReplicaSet quản lý trực tiếp {{c1::Pod}}.
Mỗi khi spec.template thay đổi, Deployment tạo ra một {{c2::ReplicaSet}} mới và giữ RS cũ lại để rollback.
Scheduler chọn node qua hai pha: {{c3::filtering}} để loại node không hợp lệ, rồi scoring để chấm điểm.
Với replicas: 3, maxSurge: 1 và maxUnavailable: 0, số Pod tối đa tồn tại cùng lúc là {{c4::4}}.
Lệnh {{c5::kubectl rollout undo}} đưa Deployment về revision trước đó bằng cách scale ReplicaSet cũ lên lại.
Thiếu {{c6::resources.requests}} thì scheduler không có cơ sở để lọc node theo tài nguyên.
[
{ "front": "Pod là gì?", "back": "Đơn vị nhỏ nhất K8s schedule được, gồm 1 hoặc nhiều container chung network namespace và volume." },
{ "front": "Khi bạn apply một Deployment, ai tạo Pod?", "back": "Deployment tạo ReplicaSet, ReplicaSet controller tạo Pod." },
{ "front": "Hai field nào phải khớp nhau trong ReplicaSet?", "back": "spec.selector.matchLabels và label trong spec.template.metadata.labels." },
{ "front": "ownerReferences dùng để làm gì?", "back": "Đánh dấu Pod thuộc ReplicaSet nào, để garbage collector xóa theo chuỗi khi owner bị xóa." },
{ "front": "Vòng lặp reconcile hoạt động theo kiểu gì?", "back": "Level-triggered: liên tục so sánh trạng thái mong muốn với thực tế và sửa sai lệch." },
{ "front": "Scheduler chọn node qua mấy pha?", "back": "Hai pha: filtering (loại node không hợp lệ) rồi scoring (chấm điểm node còn lại)." },
{ "front": "Pod Pending thường do nguyên nhân gì?", "back": "Không node nào qua được filter: thiếu CPU/memory, taint không tolerate, nodeSelector hoặc affinity không khớp." },
{ "front": "maxSurge là gì?", "back": "Số Pod (hoặc phần trăm) được phép vượt quá replicas trong lúc rollout." },
{ "front": "maxUnavailable là gì?", "back": "Số Pod (hoặc phần trăm) được phép không Ready trong lúc rollout." },
{ "front": "Deployment tạo ReplicaSet mới khi nào?", "back": "Khi spec.template thay đổi: image, env, label trong template, resources..." },
{ "front": "Lệnh rollback Deployment về revision trước?", "back": "kubectl rollout undo deployment/<tên>" },
{ "front": "Lệnh xem lịch sử revision của Deployment?", "back": "kubectl rollout history deployment/<tên>" },
{ "front": "Chiến lược Recreate khác RollingUpdate thế nào?", "back": "Recreate tắt hết Pod cũ rồi mới tạo mới, gây downtime; RollingUpdate thay dần từng Pod." },
{ "front": "Vì sao tránh tag latest trong Deployment?", "back": "Không đổi tag thì không có spec.template mới, nên không có rollout dù image nội dung đã đổi." }
]