Chuyển đến nội dung chính

BÀI 11: PERSISTENT STORAGE VÀ CSI

Quản lý storage với PersistentVolumes, PersistentVolumeClaims, StorageClasses. CSI drivers bắt buộc thay in-tree plugins (đã xóa K8s 1.31). Dynamic provisioning, volume snapshots, VolumeAttributesClass mới (K8s 1.29+).

🔒 DevSecOps — Bài 11 BÀI 11: PERSISTENT STORAGE VÀ CSI

KUBERNETES: TỪ CƠ BẢN ĐẾN NÂNG CAO

Module 3: Configuration & Storage

xdev.asia

Persistent Storage và CSI trong Kubernetes

Containers mang tính ephemeral — khi một container restart hay Pod được schedule lại trên node khác, tất cả dữ liệu bên trong bị mất. Đây là vấn đề nghiêm trọng với stateful workloads như databases, message queues, và file storage. Kubernetes giải quyết vấn đề này với một hệ thống storage phong phú, và từ Kubernetes 1.30-1.31, in-tree storage plugins đã bị loại bỏ hoàn toàn, thay thế bởi Container Storage Interface (CSI) standardized drivers.

Vòng Đời Storage: Từ Ephemeral Đến Persistent

emptyDir: Ephemeral Storage

emptyDir tạo một thư mục trống khi Pod được tạo và tồn tại suốt vòng đời của Pod. Khi Pod bị xóa, dữ liệu mất hoàn toàn. Phù hợp cho cache, temporary files, sharing data giữa containers trong cùng Pod.

apiVersion: v1
kind: Pod
metadata:
  name: shared-storage-pod
spec:
  containers:
  - name: writer
    image: busybox
    command: ["/bin/sh", "-c"]
    args: ["while true; do date >> /shared/log.txt; sleep 1; done"]
    volumeMounts:
    - name: shared-data
      mountPath: /shared
  - name: reader
    image: busybox
    command: ["/bin/sh", "-c"]
    args: ["tail -f /shared/log.txt"]
    volumeMounts:
    - name: shared-data
      mountPath: /shared
  volumes:
  - name: shared-data
    emptyDir:
      sizeLimit: 500Mi  # Giới hạn kích thước
      medium: Memory    # Lưu trong RAM (tmpfs) - nhanh hơn nhưng dùng memory

hostPath: Node-Local Storage

hostPath mount một path trên node host vào container. Dữ liệu tồn tại khi container restart nhưng mất khi Pod được schedule sang node khác. Không nên dùng trong production trừ khi có lý do đặc biệt (system daemons, monitoring agents).

apiVersion: v1
kind: Pod
metadata:
  name: host-path-pod
spec:
  containers:
  - name: app
    image: nginx:1.27
    volumeMounts:
    - name: host-volume
      mountPath: /data
  volumes:
  - name: host-volume
    hostPath:
      path: /data/myapp
      type: DirectoryOrCreate  # Tạo directory nếu chưa tồn tại

PersistentVolume và PersistentVolumeClaim

Khái Niệm Cốt Lõi

Kubernetes tách biệt việc cung cấp storage (admin) và sử dụng storage (developer) qua hai abstraction:

  • PersistentVolume (PV): Cluster-level storage resource do admin tạo hoặc dynamically provisioned. Đại diện cho một piece of storage thực tế (NFS share, cloud disk, etc.)
  • PersistentVolumeClaim (PVC): User request for storage. Developer chỉ cần khai báo "tôi cần 10Gi storage với ReadWriteOnce" mà không cần biết storage ở đâu.

PersistentVolume Definition

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-database-001
  labels:
    type: ssd
    environment: production
spec:
  capacity:
    storage: 100Gi
  volumeMode: Filesystem  # hoặc Block
  accessModes:
  - ReadWriteOnce  # Chỉ một node mount được tại một thời điểm
  persistentVolumeReclaimPolicy: Retain  # Giữ lại data sau khi PVC released
  storageClassName: premium-ssd
  # CSI volume source (thay vì in-tree)
  csi:
    driver: ebs.csi.aws.com
    volumeHandle: vol-0a1b2c3d4e5f67890
    fsType: ext4
    volumeAttributes:
      storage.kubernetes.io/csiProvisionerIdentity: "1234567890"

PersistentVolumeClaim

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: database-storage
  namespace: production
spec:
  accessModes:
  - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 50Gi
  storageClassName: premium-ssd
  # Selector để bind với specific PV (optional)
  selector:
    matchLabels:
      type: ssd
      environment: production
# Xem trạng thái PVC
kubectl get pvc -n production

# Output:
# NAME               STATUS   VOLUME              CAPACITY   ACCESS MODES   STORAGECLASS   AGE
# database-storage   Bound    pv-database-001     100Gi      RWO            premium-ssd    5m

Sử Dụng PVC trong Pod

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: production
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
      volumes:
      - name: postgres-storage
        persistentVolumeClaim:
          claimName: database-storage

Access Modes

Kubernetes định nghĩa 4 access modes, cho biết cách volume có thể được mount:

  • ReadWriteOnce (RWO): Một node có thể mount read-write. Phổ biến nhất với block storage (EBS, GCE PD). Từ K8s 1.22+, RWO cho phép nhiều Pods trên CÙNG node đọc-ghi.
  • ReadOnlyMany (ROX): Nhiều nodes có thể mount read-only cùng lúc. Phù hợp với shared config/data.
  • ReadWriteMany (RWX): Nhiều nodes mount read-write. Yêu cầu network filesystem như NFS, CephFS, Azure Files. Quan trọng: block storage (EBS, GCE PD) KHÔNG hỗ trợ RWX.
  • ReadWriteOncePod (RWOP): Chỉ một Pod duy nhất trên toàn cluster có thể mount. Mạnh hơn RWO — đảm bảo exclusive access ở Pod level, không chỉ node level. Yêu cầu CSI driver hỗ trợ.

StorageClass: Dynamic Provisioning

Tại Sao Cần StorageClass?

Thay vì admin phải tạo PV thủ công, StorageClass cho phép dynamic provisioning — Kubernetes tự động tạo PV khi có PVC request. Admin chỉ cần định nghĩa "class" storage (ví dụ: ssd-fast, hdd-cheap, nfs-shared).

# StorageClass với AWS EBS CSI Driver
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ebs-gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
  kmsKeyId: arn:aws:kms:ap-southeast-1:123456789012:key/mrk-abc123
volumeBindingMode: WaitForFirstConsumer  # Đợi Pod được schedule trước khi tạo volume
reclaimPolicy: Delete  # Xóa PV khi PVC bị xóa
allowVolumeExpansion: true  # Cho phép resize PVC
---
# StorageClass cho Longhorn
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: longhorn-replicated
provisioner: driver.longhorn.io
parameters:
  numberOfReplicas: "3"
  staleReplicaTimeout: "2880"
  fromBackup: ""
  fsType: "ext4"
volumeBindingMode: Immediate
reclaimPolicy: Delete
allowVolumeExpansion: true

Reclaim Policy

  • Delete: PV và underlying storage bị xóa khi PVC xóa. Mặc định với dynamic provisioning. Phù hợp với ephemeral workloads.
  • Retain: PV được giữ lại (trạng thái Released) khi PVC xóa. Admin phải manually reclaim. Phù hợp với production databases cần bảo vệ dữ liệu.
  • Recycle: Deprecated, không nên dùng.

CSI: Container Storage Interface

Tại Sao In-Tree Plugins Bị Loại Bỏ?

Trước đây, Kubernetes có nhiều in-tree storage plugins được compile trực tiếp vào core Kubernetes binary (aws-ebs, gce-pd, azure-disk, cephfs, nfs...). Điều này tạo ra nhiều vấn đề:

  • Bug trong storage plugin có thể crash toàn bộ kube-apiserver/kubelet
  • Release cycle của storage drivers bị gắn với Kubernetes release
  • Khó maintain khi số lượng plugins tăng
  • Storage vendors không thể ship fixes độc lập

CSI (Container Storage Interface) giải quyết tất cả vấn đề này bằng cách standardize interface giữa Kubernetes và storage providers. Drivers chạy như separate Pods, có thể update độc lập.

Timeline Loại Bỏ In-Tree Plugins

  • K8s 1.26-1.28: Nhiều in-tree plugins deprecated
  • K8s 1.29: In-tree NFS và nhiều plugins chuyển thành deprecated, CSI bắt buộc
  • K8s 1.30: In-tree NFS plugin bị xóa
  • K8s 1.31: In-tree CephFS, Ceph RBD plugins bị xóa hoàn toàn
  • K8s 1.32+: Tiếp tục loại bỏ các in-tree plugins còn lại

Popular CSI Drivers

Cloud Providers

# AWS EBS CSI Driver
helm repo add aws-ebs-csi-driver https://kubernetes-sigs.github.io/aws-ebs-csi-driver
helm upgrade --install aws-ebs-csi-driver \
  aws-ebs-csi-driver/aws-ebs-csi-driver \
  --namespace kube-system \
  --set enableVolumeResizing=true \
  --set enableVolumeSnapshot=true

# GCP Filestore CSI Driver
kubectl apply -k "github.com/kubernetes-sigs/gcp-filestore-csi-driver/deploy/kubernetes/overlays/stable"

Longhorn: Open Source Distributed Storage

# Kiểm tra prerequisites
curl -sfL https://raw.githubusercontent.com/longhorn/longhorn/v1.7.0/scripts/environment_check.sh | bash

# Cài Longhorn qua Helm
helm repo add longhorn https://charts.longhorn.io
helm repo update

helm install longhorn longhorn/longhorn \
  --namespace longhorn-system \
  --create-namespace \
  --version 1.7.0 \
  --set defaultSettings.defaultReplicaCount=3 \
  --set defaultSettings.storageMinimalAvailablePercentage=15 \
  --set defaultSettings.storageOverProvisioningPercentage=200

# Verify
kubectl get pods -n longhorn-system
kubectl get storageclass
# Longhorn StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: longhorn-fast
  annotations:
    storageclass.kubernetes.io/is-default-class: "false"
provisioner: driver.longhorn.io
parameters:
  numberOfReplicas: "3"
  staleReplicaTimeout: "30"
  fromBackup: ""
  fsType: ext4
  dataLocality: "best-effort"  # Prefer local replica
  diskSelector: "ssd"          # Hanya gunakan SSD disks
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

Rook/Ceph: Enterprise Storage

# Cài Rook Ceph Operator
kubectl apply -f https://raw.githubusercontent.com/rook/rook/v1.15.0/deploy/examples/crds.yaml
kubectl apply -f https://raw.githubusercontent.com/rook/rook/v1.15.0/deploy/examples/common.yaml
kubectl apply -f https://raw.githubusercontent.com/rook/rook/v1.15.0/deploy/examples/operator.yaml

# Tạo Ceph cluster
kubectl apply -f https://raw.githubusercontent.com/rook/rook/v1.15.0/deploy/examples/cluster.yaml

# Verify
kubectl get CephCluster -n rook-ceph

Volume Snapshots

Khái Niệm

Volume Snapshots là tính năng GA từ K8s 1.20, cho phép tạo point-in-time snapshot của PVC. Cần CSI driver hỗ trợ snapshot capability và snapshot controller được cài.

Cài Snapshot Controller

# Cài snapshot CRDs và controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.0.0/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.0.0/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.0.0/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml

kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.0.0/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.0.0/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml

VolumeSnapshotClass

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: csi-aws-vsc
  annotations:
    snapshot.storage.kubernetes.io/is-default-class: "true"
driver: ebs.csi.aws.com
deletionPolicy: Delete  # hoặc Retain
parameters:
  tagSpecification_1: "key=Name,value=k8s-snapshot"
  tagSpecification_2: "key=Environment,value=production"

Tạo VolumeSnapshot

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: postgres-snapshot-20260330
  namespace: production
  labels:
    backup-type: daily
    date: "2026-03-30"
spec:
  volumeSnapshotClassName: csi-aws-vsc
  source:
    persistentVolumeClaimName: database-storage  # PVC cần snapshot
# Xem trạng thái snapshot
kubectl get volumesnapshot -n production
kubectl describe volumesnapshot postgres-snapshot-20260330 -n production

# Output:
# Name:         postgres-snapshot-20260330
# Namespace:    production
# Status:
#   Bound Volume Snapshot Content Name: snapcontent-abc123
#   Creation Time: 2026-03-30T10:00:00Z
#   Ready To Use: true
#   Restore Size: 50Gi

Restore từ Snapshot

# Tạo PVC mới từ snapshot
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-restored-20260330
  namespace: production
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
  storageClassName: ebs-gp3
  dataSource:
    name: postgres-snapshot-20260330
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
# Verify restored PVC
kubectl get pvc postgres-restored-20260330 -n production

# Deploy postgres với PVC được restored
kubectl patch deployment postgres -n production \
  --patch '{"spec":{"template":{"spec":{"volumes":[{"name":"postgres-storage","persistentVolumeClaim":{"claimName":"postgres-restored-20260330"}}]}}}}'

VolumeAttributesClass (K8s 1.29+)

Vấn Đề Với Cách Tiếp Cận Cũ

Trước đây, nếu muốn thay đổi IOPS hoặc throughput của một volume (ví dụ từ gp3 3000 IOPS lên 10000 IOPS), bạn phải xóa PVC, tạo StorageClass mới, và tạo PVC mới — một quy trình phức tạp và gây downtime.

VolumeAttributesClass (VAC) là API mới (Beta K8s 1.31) cho phép thay đổi các mutable volume attributes như IOPS và throughput mà không cần xóa PVC.

# Định nghĩa VolumeAttributesClass
apiVersion: storage.k8s.io/v1beta1
kind: VolumeAttributesClass
metadata:
  name: silver
driverName: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
---
apiVersion: storage.k8s.io/v1beta1
kind: VolumeAttributesClass
metadata:
  name: gold
driverName: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "10000"
  throughput: "500"
# PVC ban đầu với silver class
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: database-storage
  namespace: production
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 100Gi
  storageClassName: ebs-gp3
  volumeAttributesClassName: silver  # Bắt đầu với silver
# Nâng cấp lên gold (tăng IOPS) không cần xóa PVC
kubectl patch pvc database-storage -n production \
  --patch '{"spec":{"volumeAttributesClassName":"gold"}}'

# Kiểm tra trạng thái modify
kubectl describe pvc database-storage -n production
# Events:
#   Normal ModifyVolumeComplete: Volume attributes successfully modified

Tổng Kết

Storage trong Kubernetes đã trưởng thành đáng kể với CSI là nền tảng bắt buộc từ K8s 1.30-1.31. Những điểm cần ghi nhớ:

  • emptyDir cho ephemeral shared storage trong Pod, PVC/PV cho persistent data
  • CSI bắt buộc: không còn in-tree plugins từ K8s 1.30+, phải migrate sang CSI drivers
  • StorageClass với dynamic provisioning là cách tiêu chuẩn — admin định nghĩa class, developer chỉ cần claim
  • Longhorn là lựa chọn tốt cho on-premise clusters với distributed replicated storage
  • Volume Snapshots cho phép backup/restore point-in-time, cần CSI driver và snapshot controller
  • VolumeAttributesClass (K8s 1.29+ Beta) cho phép thay đổi IOPS/throughput không cần xóa PVC
  • Luôn sử dụng WaitForFirstConsumer binding mode với cloud volumes để tránh cross-AZ attachment issues