Deployment và ReplicaSet: Giữ Ứng Dụng Luôn Sống
Bài 3 kết lại bằng một vấn đề: pod trần bị xóa là mất hẳn, không tự chữa lành. Deployment giải quyết trọn vẹn chuyện đó và hơn thế. Đây là đối tượng bạn sẽ khai báo nhiều nhất trong đời làm Kubernetes, nên ta dành cả bài mổ xẻ và chứng minh từng lời hứa của nó.
Ba tầng: Deployment → ReplicaSet → Pod
Trước hết hiểu quan hệ. Bạn khai báo Deployment; nó tạo và quản lý một ReplicaSet; ReplicaSet đảm bảo đúng số Pod đang chạy.
Deployment "web" (bạn quản lý cái này)
│ quản lý
▼
ReplicaSet "web-5687..." (đảm bảo: luôn có 3 pod)
│ tạo & giữ
▼
Pod Pod Pod (3 bản sao thật sự chạy)
- ReplicaSet là controller có một việc duy nhất: "giữ cho luôn có đúng N pod khớp nhãn này". Pod chết → nó dựng pod mới. Đây là cơ chế self-healing.
- Deployment đứng trên ReplicaSet, thêm khả năng cập nhật phiên bản (rolling update) và rollback. Khi bạn đổi image, Deployment tạo một ReplicaSet mới và chuyển dần pod sang.
Thực tế bạn hầu như chỉ làm việc với Deployment; ReplicaSet do nó tự quản. Nhưng biết ba tầng này thì đọc kubectl get mới không bỡ ngỡ.
Khai báo một Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 3 # trạng thái mong muốn: 3 bản sao
selector:
matchLabels:
app: web # Deployment quản pod nào? pod có nhãn app=web
template: # "khuôn" để đúc pod
metadata:
labels:
app: web # nhãn dán lên mỗi pod (phải khớp selector)
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 80
Ba phần đáng chú ý trong spec:
replicas: 3— trạng thái mong muốn. Đây là con số control loop sẽ luôn giữ.selector.matchLabels— Deployment dùng nhãn để biết pod nào thuộc về mình. Đây là cách Kubernetes "buộc" controller với pod (chi tiết label ở Bài 6).template— cái khuôn để đúc pod. Để ý phần dướitemplate.specgiống hệt spec của một Pod ở Bài 3 — vì nó đúng là vậy: Deployment đúc pod theo khuôn này. Nhãn trongtemplatephải khớpselector, nếu không Kubernetes báo lỗi.
apply và quan sát ba tầng
kubectl apply -f deployment.yaml
kubectl rollout status deployment/web
deployment.apps/web created
Waiting for deployment "web" rollout to finish: 2 of 3 updated replicas are available...
deployment "web" successfully rolled out
Giờ nhìn cả ba tầng cùng lúc:
kubectl get deploy,rs,pods -l app=web
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/web 3/3 3 3 1s
NAME DESIRED CURRENT READY AGE
replicaset.apps/web-5687994c96 3 3 3 1s
NAME READY STATUS RESTARTS AGE
pod/web-5687994c96-9n9j7 1/1 Running 0 1s
pod/web-5687994c96-m6glb 1/1 Running 0 1s
pod/web-5687994c96-wtt7h 1/1 Running 0 1s
Đọc tên pod thấy ngay quan hệ: web (Deployment) → web-5687994c96 (ReplicaSet, hậu tố băm) → web-5687994c96-9n9j7 (Pod, thêm hậu tố ngẫu nhiên). Tên pod sinh tự động và không đoán trước được — đừng bao giờ hard-code tên pod.
Self-healing: xóa một pod, xem điều kỳ diệu
Đây là lời hứa lớn nhất. Xóa thẳng một pod:
kubectl delete pod web-5687994c96-9n9j7
kubectl get pods -l app=web
pod "web-5687994c96-9n9j7" deleted from default namespace
NAME READY STATUS RESTARTS AGE
web-5687994c96-m6glb 1/1 Running 0 11s
web-5687994c96-qsk6p 1/1 Running 0 5s ← pod MỚI, vừa sinh
web-5687994c96-wtt7h 1/1 Running 0 11s
Vẫn đúng 3 pod. Cái bị xóa biến mất, nhưng một pod mới (qsk6p, AGE chỉ 5s) đã được dựng ngay để bù. ReplicaSet thấy "muốn 3, đang có 2" và lập tức kéo về 3 — đúng control loop ở Bài 0. So với pod trần ở Bài 3 (xóa là mất hẳn), khác biệt một trời một vực. Đây là lý do tồn tại của Deployment.
Scale: lên và xuống bằng một lệnh
kubectl scale deployment/web --replicas=5
deployment.apps/web scaled
Sau vài giây, đếm lại: 5 pod. Đổi replicas rồi apply lại YAML cũng cho kết quả y hệt (và nên ưu tiên cách này để Git là nguồn sự thật). Giảm về 3 thì 2 pod thừa bị xóa. Scale trong Kubernetes nhẹ nhàng đúng nghĩa một con số.
Rolling update: cập nhật không downtime
Giờ đến phần Deployment toả sáng. Cập nhật image sang phiên bản mới:
kubectl set image deployment/web nginx=nginx:1.28-alpine
kubectl rollout status deployment/web
deployment.apps/web image updated
Waiting for deployment "web" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 1 old replicas are pending termination...
deployment "web" successfully rolled out
Để ý cách diễn ra: Kubernetes không giết cả 3 pod cũ rồi mới dựng pod mới (như thế sẽ downtime). Nó thay dần — dựng pod mới, chờ sẵn sàng, mới xoá một pod cũ, lặp lại. Luôn có pod phục vụ trong suốt quá trình. Nhìn ReplicaSet sẽ thấy rõ cơ chế:
kubectl get rs -l app=web
NAME DESIRED CURRENT READY AGE
web-55588d64c9 3 3 3 14s ← RS mới (1.28): 3 pod
web-5687994c96 0 0 0 39s ← RS cũ (1.27): đã rút về 0
Deployment giữ cả hai ReplicaSet: cái mới scale lên 3, cái cũ scale về 0 (nhưng không xoá). Giữ lại RS cũ chính là thứ làm rollback khả thi.
Rollback: lùi lại khi hỏng
ReplicaSet cũ còn đó, nên lùi về phiên bản trước chỉ là một lệnh. Xem lịch sử rồi undo:
kubectl rollout history deployment/web
kubectl rollout undo deployment/web
REVISION CHANGE-CAUSE
1 <none>
2 <none>
deployment.apps/web rolled back
Kiểm tra image sau rollback:
kubectl get deployment web -o jsonpath='{.spec.template.spec.containers[0].image}'
nginx:1.27-alpine
Đã về lại 1.27. Cơ chế: undo chỉ việc scale RS cũ (1.27) lên lại và RS mới (1.28) về 0 — cũng là một rolling update theo chiều ngược. Khi deploy bản lỗi lúc nửa đêm, một lệnh rollout undo cứu bạn. (Mẹo: thêm --record hoặc đặt annotation kubernetes.io/change-cause để cột CHANGE-CAUSE có nội dung, lịch sử dễ đọc hơn.)
Tổng kết
Deployment là cách chuẩn để chạy ứng dụng không trạng thái trên Kubernetes. Nó quản một ReplicaSet, và ReplicaSet giữ đúng replicas pod luôn sống — pod chết là tự dựng lại (self-healing, khác hẳn pod trần). Scale chỉ là đổi một con số. Cập nhật image kích hoạt rolling update: thay pod dần, không downtime, bằng cách dựng RS mới và rút RS cũ về 0 — và vì RS cũ được giữ lại, rollout undo lùi về phiên bản trước trong một lệnh. Luôn để Git giữ YAML làm nguồn sự thật.
Ta đã chạy được 3 pod khoẻ mạnh, nhưng mỗi pod một IP và IP thay đổi mỗi lần dựng lại. Làm sao gọi chúng qua một địa chỉ ổn định, và chia tải giữa chúng? Bài 5: Service.