Sau bài này bạn làm được: mount volume đúng loại cho từng use case; hiểu PVC/PV dynamic provisioning; biết khi nào dùng StatefulSet vs Deployment.
Volume types — từ tạm thời đến bền vững
emptyDir — scratch space
volumes:
- name: scratch
emptyDir: {} # Mặc định: disk node
# emptyDir:
# medium: Memory # tmpfs — nhanh nhưng tính vào memory limit
# sizeLimit: 100Mi
- Tạo khi pod start, xoá khi pod bị xoá (không phải khi container restart).
- Dùng cho: cache tạm, shared data giữa containers trong pod, sort/transform.
- Không bền vững — pod mất = data mất.
hostPath — mount từ node
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: Socket
Cảnh báo lớn:
- Pod trên node khác → path khác → data khác.
- Rủi ro bảo mật: container access filesystem host.
- Chỉ dùng cho: DaemonSet đọc log host, mount Docker socket (CI runner).
- Không dùng cho persistent data.
PersistentVolume (PV) và PersistentVolumeClaim (PVC)
[StorageClass] ← Admin cấu hình (loại storage + provisioner)
│
▼ dynamic provisioning
[PersistentVolume] ← Đại diện cho disk thật (EBS, GCE PD, NFS, Ceph...)
│
▼ bound
[PersistentVolumeClaim] ← Pod yêu cầu storage
│
▼ mount
[Pod]
PVC — cách pod yêu cầu storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
spec:
accessModes:
- ReadWriteOnce # RWO: 1 node mount (phổ biến nhất)
resources:
requests:
storage: 10Gi
storageClassName: gp3 # StorageClass (cloud provisioner)
Access modes
| Mode | Viết tắt | Ý nghĩa |
|---|---|---|
| ReadWriteOnce | RWO | 1 node mount read-write (block storage) |
| ReadOnlyMany | ROX | Nhiều node mount read-only |
| ReadWriteMany | RWX | Nhiều node mount read-write (NFS, CephFS) |
| ReadWriteOncePod | RWOP | 1 pod duy nhất mount (K8s 1.29+ GA) |
Phổ biến nhất: RWO cho database, app state. RWX khi nhiều pod cần write cùng volume (hiếm, cần NFS hoặc CephFS).
Mount PVC vào Pod
spec:
containers:
- name: app
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: data-pvc
StorageClass — dynamic provisioning
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
provisioner: ebs.csi.aws.com # CSI driver
parameters:
type: gp3
fsType: ext4
reclaimPolicy: Delete # Hoặc Retain
volumeBindingMode: WaitForFirstConsumer # Chờ pod schedule rồi mới provision
allowVolumeExpansion: true # Cho phép resize PVC
reclaimPolicy
| Policy | Khi PVC bị xoá |
|---|---|
Delete | PV (và disk thật) bị xoá → mất data |
Retain | PV giữ lại, admin phải xoá/reuse thủ công |
Production database: dùng Retain + backup strategy.
volumeBindingMode
| Mode | Hành vi |
|---|---|
Immediate | Provision PV ngay khi PVC tạo |
WaitForFirstConsumer | Chờ pod schedule → provision PV ở AZ đúng |
WaitForFirstConsumer quan trọng trên multi-AZ cloud: tránh provision disk ở AZ A mà pod schedule ở AZ B.
Resize PVC
# Nếu StorageClass cho phép (allowVolumeExpansion: true)
kubectl edit pvc data-pvc
# Sửa spec.resources.requests.storage: 20Gi
# Kiểm tra status
kubectl describe pvc data-pvc
# Conditions: FileSystemResizePending → resize khi pod restart
Chỉ tăng, không giảm được. Filesystem resize thường cần pod restart (unmount/remount).
StatefulSet — workload có identity
Khác Deployment:
| Deployment | StatefulSet | |
|---|---|---|
| Pod name | Random (my-app-7d8f9-x2k) | Ordinal (my-db-0, my-db-1) |
| PVC | Shared hoặc không | Mỗi pod có PVC riêng (data-my-db-0) |
| Startup/shutdown | Parallel | Ordered (0 → 1 → 2) |
| DNS | Qua Service | Mỗi pod có DNS riêng (headless) |
StatefulSet spec
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: my-db
spec:
serviceName: my-db # Headless Service (bắt buộc)
replicas: 3
selector:
matchLabels:
app: my-db
template:
metadata:
labels:
app: my-db
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates: # Tự tạo PVC per pod
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3
resources:
requests:
storage: 20Gi
DNS cho StatefulSet
Cần headless Service:
apiVersion: v1
kind: Service
metadata:
name: my-db
spec:
clusterIP: None
selector:
app: my-db
ports:
- port: 5432
DNS: my-db-0.my-db.default.svc.cluster.local, my-db-1.my-db.default.svc.cluster.local…
Khi nào dùng StatefulSet
- Database (PostgreSQL, MySQL, MongoDB).
- Message queue (Kafka, RabbitMQ).
- Distributed system cần stable network identity (etcd, ZooKeeper).
Khi nào KHÔNG dùng StatefulSet
- App stateless (API server, web) → Deployment.
- State lưu ở managed service (RDS, Cloud SQL) → Deployment gọi external DB.
- Bạn chỉ cần shared storage → Deployment + PVC (RWX).
CSI (Container Storage Interface)
K8s không trực tiếp quản lý storage — giao cho CSI driver:
- AWS EBS CSI:
ebs.csi.aws.com - GCE PD CSI:
pd.csi.storage.gke.io - Azure Disk CSI:
disk.csi.azure.com - NFS CSI: NFS provisioner
- Local path:
rancher.io/local-path(dev/test)
# Xem CSI drivers
kubectl get csidrivers
Tóm tắt
- emptyDir: tạm, mất khi pod xoá. hostPath: mount host, nguy hiểm.
- PVC/PV: storage bền vững. StorageClass + CSI driver tự provision.
- reclaimPolicy:
Deletemất data,Retaingiữ lại. - StatefulSet: ordered identity + PVC per pod. Dùng cho database, message queue.
- Nguyên tắc: tránh state trên K8s nếu có managed alternative (RDS, Cloud SQL).
Câu hỏi hay gặp
PVC Pending mãi — nguyên nhân?
Trả lời: (1) StorageClass không tồn tại; (2) WaitForFirstConsumer nhưng chưa có pod yêu cầu; (3) CSI driver chưa cài; (4) Quota hết (cloud disk limit). Kiểm tra Events: kubectl describe pvc.
Xoá StatefulSet có xoá PVC không?
Trả lời: Không. PVC tạo bởi volumeClaimTemplates không bị xoá khi StatefulSet bị xoá. Phải xoá PVC thủ công. Đây là by design — tránh mất data.
Database trên K8s vs managed service — chọn thế nào?
Trả lời: Managed service (RDS, Cloud SQL) nếu: team nhỏ, không muốn quản lý backup/HA/upgrade. K8s StatefulSet nếu: cần portability, on-prem, hoặc dùng operator (CloudNativePG, Zalando PostgreSQL Operator) quản lý lifecycle. Rule of thumb: nếu có thể dùng managed, dùng managed.
Bài tiếp theo (Giai đoạn V): Requests, limits, QoS và OOM — tài nguyên và ổn định cluster.