Deployment giả định các bản thay thế được cho nhau. Với cơ sở dữ liệu thì không. Bài này đo ba thứ StatefulSet có mà Deployment không.
Khởi động tuần tự
db-0 Running sau 4,3 giây
db-1 Running sau 9,6 giây
db-2 Running sau 14,9 giây
Mỗi bản chờ bản trước sẵn sàng rồi mới bắt đầu. Deployment tạo cả ba cùng lúc.
Thu gọn thì ngược lại:
db-2 biến mất sau 1,1 giây
db-1 biến mất sau 2,2 giây
Xoá từ số lớn tới số nhỏ.
Thứ tự này quan trọng với hệ thống có khái niệm "bản đầu tiên": db-0 thường là bản chính, và các bản sau kết nối tới nó để đồng bộ. Khởi động song song sẽ khiến db-1 tìm db-0 khi nó chưa có.
Với hệ thống không cần thứ tự, đặt podManagementPolicy: Parallel để bỏ hẳn ràng buộc này và khởi động nhanh gấp ba.
Danh tính ổn định
Tên DNS riêng cho từng bản, qua headless service (phần 56):
db-0.db -> 10.244.3.112
db-1.db -> 10.244.1.40
db-2.db -> 10.244.2.113
PVC riêng cho từng bản, sinh từ volumeClaimTemplates:
data-db-0 data-db-1 data-db-2
Xoá pod db-1 rồi để nó tạo lại:
| tên | db-1 — giữ nguyên |
| podIP | ...40 → ...41 — đổi |
| dữ liệu | "du lieu cua db-1" — còn |
| PVC | data-db-1 Bound — giữ nguyên |
IP đổi, mọi thứ khác không. Đó là toàn bộ ý nghĩa của "danh tính ổn định": ứng dụng khác gọi db-1.db và luôn tới đúng bản đó, với đúng dữ liệu của nó.
Với Deployment, pod mới có tên ngẫu nhiên và không có gì gắn nó với dữ liệu cũ.
Thu gọn không xoá PVC
scale --replicas=1 -> db-1, db-2 biến mất
PVC còn nguyên: data-db-0 data-db-1 data-db-2 đều Bound
scale --replicas=3 -> db-1 đọc lại "du lieu cua db-1"
Đây là hành vi có chủ ý: thu gọn tạm thời không được làm mất dữ liệu.
Nhưng nó là cái bẫy chi phí: thu gọn để tiết kiệm thì vẫn trả tiền đĩa. Xoá PVC là việc tay, và không lùi lại được (phần 62 — reclaimPolicy: Delete là mặc định).
Từ Kubernetes 1.27 có persistentVolumeClaimRetentionPolicy để đổi hành vi này:
spec:
persistentVolumeClaimRetentionPolicy:
whenScaled: Delete # xoá PVC khi thu gọn
whenDeleted: Retain # giữ khi xoá cả StatefulSet
Mặc định cả hai là Retain.
serviceName là bắt buộc, và nó phải là headless
spec:
serviceName: db
Không có Service headless tương ứng thì tên db-0.db không phân giải được, và StatefulSet mất một nửa giá trị của nó.
Đây là chỗ dễ quên vì StatefulSet vẫn tạo pod bình thường mà không báo lỗi gì — chỉ có DNS không hoạt động.
Khi nào dùng, khi nào không
Dùng StatefulSet khi cần ít nhất một trong ba:
- Tên ổn định — bản này phải gọi được bản kia theo tên cố định. Mọi hệ thống có bầu chọn hoặc phân mảnh đều cần.
- Thứ tự — bản chính phải lên trước bản sao.
- Ổ đĩa riêng cho từng bản — mỗi bản một tập dữ liệu, không dùng chung.
Cơ sở dữ liệu, hàng đợi, hệ thống đồng thuận (etcd, ZooKeeper), lưu trữ phân tán — tất cả đều cần.
Không dùng khi: ứng dụng không trạng thái. StatefulSet chậm hơn khi triển khai, phức tạp hơn khi vận hành, và không cho gì thêm.
Một lỗi thường gặp theo chiều ngược lại: dùng Deployment cho cơ sở dữ liệu với một PVC RWO dùng chung. Bản thứ hai sẽ không gắn được volume (phần 62), và triệu chứng là pod kẹt ở ContainerCreating mà không rõ vì sao.
Cập nhật cuốn chiếu cũng ngược
StatefulSet cập nhật từ số lớn xuống số nhỏ, mỗi lần một bản, chờ bản đó Ready rồi mới sang bản kế.
Với 20 bản và mỗi bản khởi động 30 giây, đó là mười phút cho một lần triển khai. Deployment với maxUnavailable=25% xong trong một phần thời gian đó (phần 50).
partition cho phép triển khai từng phần:
updateStrategy:
rollingUpdate: {partition: 2}
Chỉ bản có số ≥ 2 được cập nhật. Hạ dần partition là cách triển khai theo kiểu canary cho ứng dụng có trạng thái.
Node chết: cùng vấn đề, hậu quả nặng hơn
Phần 42 và 49 đã đo: node chết thì pod ma vẫn báo Running nhiều phút.
Với StatefulSet hậu quả nặng hơn, vì Kubernetes không được phép tạo db-1 mới khi db-1 cũ có thể vẫn đang chạy — hai bản cùng tên ghi vào cùng dữ liệu là hỏng dữ liệu.
Nên nó chờ. Và nếu node không bao giờ quay lại, bạn phải xoá pod bằng tay với --force:
kubectl delete pod db-1 --force --grace-period=0
Lệnh này nói với Kubernetes: "tôi bảo đảm bản cũ đã chết". Nếu bạn sai — node chỉ mất mạng chứ chưa chết — thì hai bản cùng ghi, và đó là cách mất dữ liệu.
Thử ba mươi giây
Kiểm StatefulSet của bạn có headless service không:
kubectl get statefulset <ten> -o jsonpath='{.spec.serviceName}{"\n"}'
kubectl get svc <ten-service> -o jsonpath='{.spec.clusterIP}{"\n"}'
Dòng thứ hai phải là None. Nếu nó là một IP, service không phải headless và tên <ten>-0.<service> sẽ không phân giải — StatefulSet của bạn đang chạy mà thiếu mất phần định danh.
Phần sau đo DaemonSet: một bản trên mỗi node, và nó phản ứng trong 0,1 giây khi tập node đổi.