Phần 5 — Storage: Volume, PersistentVolume, PVC, StorageClass, CSI
Ý kiến
0
Chưa có ý kiến nào. Hãy là người đầu tiên chia sẻ!
Chưa có ý kiến nào. Hãy là người đầu tiên chia sẻ!
Phần cuối series K8s: Cluster API (CAPI) quản lý cluster bằng K8s API, FinOps với OpenCost + Karpenter + right-sizing, AI/ML workloads (GPU, Kubeflow, KServe, vLLM, Argo Workflows, gang scheduling), Edge K8s và WebAssembly.
Multi-tenancy K8s (soft: namespace+RBAC+Quota, HNC, Capsule, vCluster; hard: cluster riêng) và multi-cluster: management cluster, GitOps ApplicationSet, Cluster API, Karmada, Crossplane, observability liên cluster.
Service Mesh chuyên sâu cho K8s: Istio (sidecar + ambient), Linkerd (proxy Rust nhẹ), Cilium Service Mesh (eBPF sidecarless) — mTLS, VirtualService, AuthorizationPolicy, canary, mirror, multi-cluster, đo overhead, khi không nên dùng mesh.
Series Kubernetes Toàn Tập — 13 phần:
Phần 5 — Storage ← bạn đang đọc
Container chết là dữ liệu trong filesystem container biến mất. Đó là spec của Docker, K8s thừa hưởng. Để app cần persistent data — database, queue, file upload — sống được trên K8s, bạn cần hiểu mô hình storage của K8s.
Phần này phân tích ba lớp khái niệm:
Volume — gắn vào pod, lifecycle theo pod.
PersistentVolume / PersistentVolumeClaim — storage tách khỏi lifecycle pod.
StorageClass / CSI — provisioning động.
Volume trong K8s được khai báo trong spec.volumes của pod, mount vào container qua volumeMounts. Khi pod chết, volume cũng kết thúc (trừ một số loại đặc biệt).
apiVersion: v1
kind: Pod
metadata:
name: cache-app
spec:
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: cache
mountPath: /var/cache
volumes:
- name: cache
emptyDir:
sizeLimit: 1Gi
medium: "" # "Memory" để dùng tmpfs (RAM)
Tạo khi pod start, xoá khi pod chết.
Dùng cho cache, temp file, hoặc share data giữa container trong cùng pod.
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: Socket
Nguy hiểm trong production: pod có thể truy cập filesystem node. Chỉ dùng cho system DaemonSet (log agent, node-exporter).
Sẽ nói kỹ ở Phần 6. Đây là cách inject config/secret vào pod như file.
Cho phép mount nhiều source (ConfigMap, Secret, ServiceAccount token, downwardAPI) cùng một mount point. Đây là cách K8s 1.20+ inject SA token short-lived.
volumes:
- name: podinfo
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "namespace"
fieldRef:
fieldPath: metadata.namespace
Đây là core abstraction của K8s storage. Ý tưởng: tách cung cấp storage (admin) khỏi tiêu thụ storage (developer).
| Object | Ai tạo | Đại diện cho |
|---|---|---|
PersistentVolume (PV) | Admin hoặc StorageClass tự sinh | Một disk thật (EBS, GCEPD, Ceph RBD…) |
PersistentVolumeClaim (PVC) | Developer | Yêu cầu storage: dung lượng + access mode + class |
Pod gắn vào PVC, K8s match PVC ↔ PV. Khi pod chết, PVC vẫn còn → pod mới mount lại được data cũ.
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-001
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
server: 10.0.0.50
path: /exports/data
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: ssd
apiVersion: v1
kind: Pod
metadata:
name: postgres
spec:
containers:
- name: pg
image: postgres:16
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: data
| Mode | Viết tắt | Ý nghĩa |
|---|---|---|
ReadWriteOnce | RWO | Mount RW bởi 1 node (nhiều pod cùng node OK) |
ReadOnlyMany | ROX | Nhiều node mount read-only |
ReadWriteMany | RWX | Nhiều node cùng mount RW (NFS, CephFS, GlusterFS…) |
ReadWriteOncePod | RWOP | Chỉ 1 pod cụ thể mount RW (K8s 1.27 stable) |
Chú ý: không phải storage backend nào cũng support mọi mode. Block storage cloud (EBS, GCE PD, Azure Disk) thường chỉ RWO. Muốn RWX cần file storage (EFS, Filestore, NFS, CephFS).
Khi PVC bị delete, PV xử lý thế nào?
Retain — PV vẫn còn, status Released, admin phải tự cleanup. An toàn nhất cho data quan trọng.
Delete — PV và underlying storage (EBS volume…) bị xoá. Mặc định của hầu hết dynamic provisioner cloud.
Recycle — đã deprecated, không dùng.
Cài đặt Retain trên PVC chứa data sản xuất là cứu mạng khi ai đó accidentally kubectl delete pvc.
Tạo PV thủ công mỗi lần app cần là không khả thi. StorageClass mô tả kiểu storage, và CSI driver tự sinh PV khi có PVC mới.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true"
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
Các field quan trọng:
provisioner — CSI driver dùng để tạo storage thực.
parameters — params chuyển cho provisioner.
reclaimPolicy — Retain hay Delete (mặc định Delete).
allowVolumeExpansion — cho resize PVC tăng dung lượng online.
volumeBindingMode:
Immediate — bind ngay khi tạo PVC.
WaitForFirstConsumer — chờ đến khi pod được schedule rồi mới tạo PV ở zone phù hợp. Nên dùng cho cloud multi-AZ để tránh case PV ở zone-a, pod schedule ở zone-b.
Developer apply PVC với storageClassName: ssd.
Provisioner watch PVC → tạo disk thật (vd. EBS gp3 10Gi) → tạo PV mô tả disk đó.
K8s bind PVC ↔ PV.
Pod schedule, kubelet gọi CSI để attach + mount.
Khi PVC không khai báo storageClassName, K8s dùng class có annotation is-default-class: "true". Mỗi cluster nên có đúng 1 default class.
Trước K8s 1.20, mọi storage plugin (in-tree) phải nằm trong source code Kubernetes — không scale với số nhà cung cấp. CSI là spec chuẩn cho mọi storage vendor implement driver out-of-tree. Hôm nay 100% storage K8s đều qua CSI.
CSI driver thường gồm:
Controller plugin — chạy như StatefulSet hoặc Deployment, gọi cloud API tạo/xoá volume.
Node plugin — chạy DaemonSet trên mọi node, gọi mount/unmount.
| Driver | Use case |
|---|---|
ebs.csi.aws.com | EBS trên AWS |
pd.csi.storage.gke.io | Persistent Disk GCP |
disk.csi.azure.com | Azure Disk |
| Longhorn | Distributed block storage cho K8s on-prem (Rancher) |
| Rook + Ceph | Ceph operator, block + file + object |
| OpenEBS | Container-attached storage, nhiều engine |
| NFS CSI | NFS share |
| Portworx | Commercial storage cho K8s |
CSI cho phép snapshot online qua VolumeSnapshotClass + VolumeSnapshot:
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: snap-2026-05-11
spec:
volumeSnapshotClassName: csi-aws-vsc
source:
persistentVolumeClaimName: data
Restore bằng cách tạo PVC mới với dataSource trỏ về snapshot:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-restored
spec:
storageClassName: ssd
dataSource:
name: snap-2026-05-11
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gi
StorageClass có allowVolumeExpansion: true, edit PVC tăng dung lượng:
kubectl patch pvc data -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
Driver sẽ resize disk; tuỳ filesystem, có thể cần restart pod (XFS) hoặc không (ext4 online resize). Không giảm dung lượng được.
| Workload | Storage nên chọn |
|---|---|
| Stateless web | emptyDir hoặc không storage |
| Database (Postgres, MySQL) | Block RWO, SSD/NVMe, snapshot bật, replication ở tầng app |
| Kafka, Elasticsearch | Block RWO, tách disk theo broker/node |
| App cần share file (CMS, upload) | NFS/EFS RWX (hoặc dùng object storage S3) |
| Đối tượng/blob | Không dùng PV — dùng object storage (S3, MinIO, SeaweedFS) qua SDK |
| Backup | Object storage qua Velero/Restic, không phải PV |
Nguyên tắc: chỉ data thực sự cần block/file POSIX mới dùng PV. Mọi thứ khác đưa lên object storage — vận hành đơn giản hơn nhiều.
K8s 1.23+ cho phép declare PVC inline trong pod, lifecycle theo pod. Tốt cho scratch lớn không vừa emptyDir:
spec:
containers:
- name: app
volumeMounts:
- name: scratch
mountPath: /scratch
volumes:
- name: scratch
ephemeral:
volumeClaimTemplate:
spec:
accessModes: [ReadWriteOnce]
storageClassName: ssd
resources:
requests:
storage: 100Gi
Reclaim policy = Retain cho data sản xuất. Mất 1 phút để admin clean PV cũ; mất 1 PVC = mất database.
Volume snapshot định kỳ bằng VolumeSnapshot hoặc Velero. Snapshot chỉ là backup nhanh; vẫn cần off-site backup ngoài cluster.
WaitForFirstConsumer cho cloud multi-AZ.
Đừng dùng hostPath trừ system workload — pod khác có thể dùng path đó.
Đo IOPS trước khi promote DB lên K8s. EBS gp3 baseline thấp hơn nhiều người tưởng.
StatefulSet + volumeClaimTemplates, không dùng cùng 1 PVC cho nhiều replica.
Backup test restore. Snapshot chưa từng restore = không có backup.
Đừng share filesystem giữa 2 process write nếu không phải distributed FS. NFS share giữa nhiều pod write file riêng OK. Hai pod cùng ghi cùng file = corrupt.
# PVC stuck Pending?
kubectl describe pvc data
# Event sẽ nói: no PV match, hoặc provisioner không chạy
# Pod stuck ContainerCreating, lỗi mount?
kubectl describe pod <name>
# Tìm event 'FailedMount', 'AttachVolume failed'
# CSI driver pod chết?
kubectl get pods -n kube-system | grep csi
kubectl logs -n kube-system <csi-controller-pod> -c csi-provisioner
# Xem PV và PVC binding
kubectl get pv,pvc -A
# Disk usage trong container
kubectl exec -it <pod> -- df -h
Volume — lifecycle theo pod, dùng cho cache/scratch.
PV + PVC — storage tách lifecycle, dùng cho data lâu dài.
StorageClass + CSI — provisioning động, không phải tạo PV tay.
Chọn access mode đúng workload (RWO cho block, RWX cho file share).
Reclaim Retain cho data quan trọng, snapshot định kỳ, test restore.
Object storage cho blob, không nhồi lên PVC.
Phần 6 sẽ nói về cách inject config và secret vào workload mà không phải rebuild image.