🔄 Deployment, rolling update và rollback

Mục tiêu bài học

Sau bài này, bạn phải làm được 4 việc trong khoảng 2 đến 3 phút mỗi việc, đúng kiểu đề CKA:

  • Tạo Deployment bằng lệnh imperative và bằng YAML.
  • Cập nhật image an toàn, hiểu maxSurge và maxUnavailable đang làm gì.
  • Theo dõi rollout bằng kubectl rollout status và kubectl get rs.
  • Rollback về revision trước, hoặc về một revision cụ thể bằng --to-revision.

Ôn nhanh

Ở Module 1 bạn đã dùng kubectl create ... --dry-run=client -o yaml để sinh YAML nhanh và kubectl explain để tra field. Hai thói quen đó quay lại ngay trong bài này: mọi Deployment trong đề thi đều nên bắt đầu bằng dry-run rồi sửa YAML, thay vì gõ tay từ đầu.

Một điểm nhắc lại: kubectl trên cluster lab của bạn có quyền admin, nhưng đề thi luôn ghi rõ namespace. Mất điểm vì quên -n là lỗi phổ biến nhất của người mới.

Ví dụ mẫu từng bước

Đề bài kiểu thi: "Tạo Deployment tên web, image nginx:1.25, 3 replicas. Cập nhật lên nginx:1.26 theo kiểu rolling update với maxSurge=1, maxUnavailable=0. Sau đó rollback về revision 1."

Bước 1. Tạo Deployment bằng lệnh imperative.

kubectl create deployment web --image=nginx:1.25 --replicas=3
kubectl get deploy,rs,pod -l app=web

Bạn sẽ thấy một ReplicaSet tên dạng web-7d9f8c6b5 và 3 Pod. Tên ReplicaSet chính là tên-deployment + hash của pod template.

Bước 2. Đặt change-cause trước khi sửa, để history có ý nghĩa.

kubectl annotate deployment/web kubernetes.io/change-cause="nginx:1.26"

kubectl rollout history chỉ hiển thị cột CHANGE-CAUSE khi bạn gắn annotation này. Cờ --record ngày xưa đã bị bỏ, đừng dùng nữa.

Bước 3. Cấu hình rolling update.

kubectl patch deployment web -p '{"spec":{"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'

Lưu ý: sửa strategy không làm Pod restart, nó chỉ có hiệu lực ở lần update template tiếp theo.

Bước 4. Cập nhật image và theo dõi.

kubectl set image deployment/web nginx=nginx:1.26
kubectl rollout status deployment/web

Tên container ở đây là nginx (lấy từ tên image khi tạo bằng kubectl create deployment). Nếu sai tên container, lệnh báo unable to find container named ....

Kết quả mong đợi:

Waiting for deployment "web" rollout to finish: 1 out of 3 new replicas have been updated...
deployment "web" successfully rolled out

Trong lúc chạy, mở terminal thứ hai:

kubectl get rs -w

Bạn sẽ thấy ReplicaSet cũ giảm dần, ReplicaSet mới tăng dần. Với maxUnavailable=0, cluster luôn giữ đủ 3 Pod Ready, nên ReplicaSet mới phải scale lên trước (surge thêm 1 Pod) rồi mới hạ Pod cũ.

Bước 5. Xem history và rollback.

kubectl rollout history deployment/web
kubectl rollout undo deployment/web --to-revision=1
kubectl rollout status deployment/web

Kiểm tra lại image đang chạy:

kubectl get deploy web -o jsonpath='{.spec.template.spec.containers[0].image}'

Kết quả: nginx:1.25.

Thực hành có hướng dẫn

Làm lần lượt 4 bài, bấm giờ như thi thật. Bài nào quá thời gian thì ghi lại lệnh đã quên.

Bài tập 1: Tạo Deployment bằng YAML từ dry-run (5 phút)

  1. Sinh YAML:
kubectl create deployment api --image=nginx:1.25 --replicas=2 --dry-run=client -o yaml > api.yaml
  1. Mở file, sửa replicas thành 4, thêm strategy rolling update với maxSurge: 25%, maxUnavailable: 25%.
  2. Apply và kiểm tra:
kubectl apply -f api.yaml
kubectl get deploy api -o wide
kubectl describe deploy api | grep -A5 StrategyType

Kết quả mong đợi: Deployment api có 4 Pod Ready, phần Strategy hiển thị RollingUpdateStrategy: 25% max unavailable, 25% max surge.

Tự kiểm tra: maxSurge tính theo đơn vị gì khi bạn ghi 25%? (Trả lời: phần trăm của số replicas, làm tròn lên.)

Bài tập 2: Rolling update có kiểm soát (8 phút)

  1. Đặt maxSurge=1, maxUnavailable=0 cho api.
  2. Chạy song song hai lệnh: kubectl rollout status deployment/api và kubectl get pod -w.
  3. Trong lúc rollout đang chạy, thực hiện kubectl set image deployment/api nginx=nginx:1.26.

Quan sát: số Pod Ready không bao giờ xuống dưới 4, và tổng số Pod có lúc lên 5.

  1. Thử tiếp kubectl rollout pause deployment/api, đổi image một lần nữa, rồi kubectl rollout resume deployment/api.

Kết quả mong đợi: Khi pause, ReplicaSet mới bị đứng yên, không có Pod mới nào được tạo thêm. Sau resume, rollout chạy tiếp.

Bài tập 3: Gây lỗi ImagePullBackOff rồi rollback (7 phút)

Đây là tình huống đề thi rất hay gặp: "Deployment đang lỗi, hãy đưa nó về trạng thái chạy tốt trước đó".

  1. Gây lỗi:
kubectl set image deployment/api nginx=nginx:1.99-khong-ton-tai
kubectl rollout status deployment/api --timeout=30s

Lệnh thứ hai sẽ treo rồi thoát với lỗi timed out waiting for the condition.

  1. Chẩn đoán:
kubectl get pods
kubectl describe pod <pod-mới-nhất> | tail -n 15

Bạn sẽ thấy ErrImagePull rồi ImagePullBackOff trong phần Events.

  1. Rollback:
kubectl rollout undo deployment/api
kubectl rollout status deployment/api

Điểm quan trọng: các Pod cũ vẫn còn chạy trong suốt thời gian lỗi, vì maxUnavailable chặn việc hạ Pod cũ khi Pod mới chưa Ready. Đây chính là lý do rolling update an toàn hơn Recreate.

Bài tập 4: Rollback về revision cụ thể (5 phút)

  1. Thực hiện 3 lần đổi image, mỗi lần đặt kubernetes.io/change-cause mô tả rõ (ví dụ nginx:1.26, nginx:1.27).
  2. Xem history:
kubectl rollout history deployment/api
  1. Xem chi tiết một revision:
kubectl rollout history deployment/api --revision=2
  1. Quay về revision 2:
kubectl rollout undo deployment/api --to-revision=2

Kết quả mong đợi: image trở về đúng giá trị của revision 2. History sẽ có một revision mới mang template cũ, nên đừng ngạc nhiên nếu số revision cũ không còn xuất hiện với đúng số đó nữa.

flowchart TD
    D["🚀<br/>Deployment api"]
    RS1["📦<br/>ReplicaSet cũ<br/>nginx:1.25"]
    RS2["📦<br/>ReplicaSet mới<br/>nginx:1.26"]
    P1["🟢<br/>Pod cũ Ready"]
    P2["🟡<br/>Pod mới đang khởi động"]
    D --> RS1
    D --> RS2
    RS1 --> P1
    RS2 --> P2

Bảng lệnh rollout cần thuộc lòng

Lệnh Tác dụng
kubectl rollout status deployment/web Theo dõi tiến trình, thoát khi xong hoặc fail
kubectl rollout history deployment/web Liệt kê các revision
kubectl rollout history deployment/web --revision=2 Xem chi tiết image của revision 2
kubectl rollout undo deployment/web Quay về revision ngay trước
kubectl rollout undo deployment/web --to-revision=1 Quay về revision chỉ định
kubectl rollout pause / resume Tạm dừng / chạy tiếp
kubectl rollout restart deployment/web Tạo revision mới, thay toàn bộ Pod, giữ nguyên image

Lỗi thường gặp và cách xử lý

Hiện tượng Nguyên nhân Cách xử lý
unable to find container named ... Sai tên container trong kubectl set image Lấy tên đúng: kubectl get deploy web -o jsonpath='{.spec.template.spec.containers[*].name}'
Rollout treo mãi, kubectl get pods thấy ImagePullBackOff Sai tag image hoặc private registry thiếu imagePullSecret kubectl rollout undo để thoát nhanh, sau đó sửa tag
field is immutable khi apply Bạn sửa spec.selector Xoá và tạo lại Deployment, selector của apps/v1 không đổi được
History chỉ hiện <none> ở CHANGE-CAUSE Chưa gắn annotation kubernetes.io/change-cause Annotate trước mỗi lần đổi template
invalid: spec.strategy.rollingUpdate.maxUnavailable: Invalid value: 0 ... maxSurge: 0 Cả hai field cùng bằng 0 Phải để ít nhất một field lớn hơn 0
Rollout xong nhưng Pod vẫn restart liên tục Lỗi application, không phải lỗi Deployment Kiểm tra kubectl logs và kubectl describe pod
Sửa strategy mà không thấy Pod đổi Strategy không nằm trong pod template Bình thường, strategy chỉ áp dụng ở lần update sau

[!NOTE] Mẹo thi: khi đề cho một Deployment đang lỗi và yêu cầu "khôi phục phiên bản trước", đừng mất thời gian đọc log application. Chạy kubectl rollout history, chọn revision đúng, kubectl rollout undo --to-revision=N, rồi kubectl rollout status. Xong trong khoảng 60 giây.

[!WARNING] Sửa Deployment bằng kubectl edit rất dễ tạo revision rác, vì mỗi lần lưu có thể đổi template nhiều lần. Trong thi, dùng kubectl set image hoặc kubectl patch để mỗi thao tác là một revision rõ ràng.

Góc chuyên gia

Vài điều mình ước biết trước khi thi:

  • kubectl set image nhanh hơn kubectl edit rất nhiều, và không sợ lưu nhầm. Dùng nó làm mặc định.
  • ==rollout undo không xoá revision, nó tạo revision mới từ template cũ.== Nên bạn có thể undo nhiều lần liên tiếp mà vẫn quay lại được các bản trước, miễn là chưa vượt revisionHistoryLimit (mặc định 10).
  • Luôn đặt kubernetes.io/change-cause trước khi đổi template. Đây là điểm "dễ lấy" trong các đề yêu cầu "ghi lại lý do thay đổi".
  • Trước khi nộp, chạy đúng 3 lệnh kiểm tra: kubectl get deploy -o wide, kubectl rollout status deployment/<tên>, kubectl get pod. Ba lệnh này bắt được gần hết lỗi.
  • Với Deployment gắn PVC ReadWriteOnce nhiều replica, hãy dùng strategy.type: Recreate, vì rolling update sẽ làm nhiều Pod tranh nhau mount cùng volume và bị Multi-Attach error.
  • Đề có thể yêu cầu "scale rồi update". Thứ tự an toàn: scale trước, update sau, để maxUnavailable tính trên số replica cuối cùng.
  • Alias tiết kiệm thời gian thật: alias k=kubectl và export do="--dry-run=client -o yaml". Gõ k create deploy api --image=nginx $do > api.yaml nhanh hơn hẳn.

Tóm tắt và bước tiếp theo

Bốn điều cần mang ra khỏi bài này:

  1. Deployment quản lý ReplicaSet, ReplicaSet quản lý Pod. Mỗi lần đổi pod template là một ReplicaSet mới, tức một revision mới.
  2. maxSurge và maxUnavailable quyết định tốc độ và độ an toàn của rolling update, mặc định 25% mỗi cái.
  3. Bộ lệnh kubectl rollout status / history / undo / pause / resume / restart là công cụ chính, thuộc lòng cả 6.
  4. Khi rollout lỗi, kubectl rollout undo là cách thoát nhanh nhất, và Pod cũ vẫn an toàn nhờ maxUnavailable.

Kiểm tra nhanh bằng cloze dưới đây trước khi sang bài sau:

Deployment quản lý Pod thông qua object trung gian là {{c1::ReplicaSet}}.
Với strategy RollingUpdate, hai tham số kiểm soát tốc độ là maxSurge và {{c2::maxUnavailable}}, mặc định đều bằng {{c3::25%}}.
Lệnh theo dõi tiến trình rollout là: kubectl rollout {{c4::status}} deployment/web.
Quay về revision 1 bằng: kubectl rollout undo deployment/web {{c5::--to-revision=1}}.
Ghi lý do thay đổi vào history bằng annotation {{c6::kubernetes.io/change-cause}}.

Flashcard ôn tập

[
  { "front": "Deployment quản lý Pod thông qua object nào?", "back": "ReplicaSet, và ReplicaSet quản lý trực tiếp các Pod." },
  { "front": "Hai field điều khiển rolling update nằm ở đâu?", "back": "spec.strategy.rollingUpdate với maxSurge và maxUnavailable." },
  { "front": "Giá trị mặc định của maxSurge và maxUnavailable?", "back": "25% cho cả hai." },
  { "front": "Lệnh theo dõi tiến trình rollout?", "back": "kubectl rollout status deployment/<tên>" },
  { "front": "Lệnh xem lịch sử revision?", "back": "kubectl rollout history deployment/<tên>, thêm --revision=N để xem chi tiết." },
  { "front": "Lệnh rollback về revision ngay trước?", "back": "kubectl rollout undo deployment/<tên>" },
  { "front": "Lệnh rollback về một revision cụ thể?", "back": "kubectl rollout undo deployment/<tên> --to-revision=N" },
  { "front": "Làm sao để cột CHANGE-CAUSE có dữ liệu?", "back": "Gắn annotation kubernetes.io/change-cause trước khi đổi pod template." },
  { "front": "Strategy nào gây downtime?", "back": "Recreate: xoá hết Pod cũ rồi mới tạo Pod mới." },
  { "front": "Field giới hạn số revision được giữ lại?", "back": "spec.revisionHistoryLimit, mặc định là 10." },
  { "front": "Điều gì xảy ra khi sửa spec.selector của Deployment?", "back": "Báo lỗi field is immutable, phải xoá và tạo lại Deployment." },
  { "front": "Lệnh restart toàn bộ Pod mà không đổi image?", "back": "kubectl rollout restart deployment/<tên>" },
  { "front": "Vì sao rollout bị treo khi image sai tag?", "back": "Pod mới ImagePullBackOff và không Ready, maxUnavailable giữ Pod cũ nên rollout chờ mãi." },
  { "front": "Lệnh tạm dừng và chạy tiếp rollout?", "back": "kubectl rollout pause deployment/<tên> và kubectl rollout resume deployment/<tên>" }
]

Bài tiếp theo: DaemonSet, Job, CronJob. Deployment phù hợp với workload chạy dài hạn và cần rolling update, nhưng CKA còn hỏi hai kiểu khác: chạy đúng một Pod trên mỗi node (DaemonSet) và workload chạy một lần hoặc theo lịch (Job, CronJob). Bài sau sẽ đi vào cú pháp và các bẫy thường gặp của hai loại này.