Home > Blog > tech

Kubernetes Storage คืออะไร? สอน PV, PVC, StorageClass และ CSI Driver สำหรับ Stateful Apps 2026

kubernetes storage persistent volumes guide
Kubernetes Storage Persistent Volumes Guide 2026
2026-04-08 | tech | 3500 words

Kubernetes ถูกออกแบบมาสำหรับ Stateless Applications แต่ในโลกจริง แอปพลิเคชันส่วนใหญ่ต้องการเก็บข้อมูล ไม่ว่าจะเป็น Database, File Storage หรือ Session Data การจัดการ Storage ใน Kubernetes จึงเป็นทักษะที่สำคัญอย่างยิ่ง โดยเฉพาะเมื่อต้องรัน Stateful Applications เช่น PostgreSQL, MySQL, MongoDB หรือ Elasticsearch บน K8s

บทความนี้จะสอนทุกเรื่องเกี่ยวกับ Kubernetes Storage ตั้งแต่พื้นฐาน Volume, PersistentVolume (PV), PersistentVolumeClaim (PVC), StorageClass, CSI Drivers ไปจนถึง Backup ด้วย Velero และ Best Practices สำหรับ Production

ทำไม Storage ใน Kubernetes ถึงต่างจากปกติ?

ใน Traditional Server เมื่อคุณเขียนไฟล์ลง Disk มันจะอยู่ตรงนั้นจนกว่าคุณจะลบ แต่ใน Kubernetes Pod มีลักษณะ Ephemeral (ชั่วคราว) หมายความว่า:

Kubernetes จึงสร้างระบบ Volume ที่แยก Lifecycle ของ Storage ออกจาก Pod ให้ข้อมูลอยู่รอดแม้ Pod จะถูกลบหรือสร้างใหม่

Ephemeral vs Persistent Storage

คุณสมบัติEphemeral (ชั่วคราว)Persistent (ถาวร)
Lifecycleผูกกับ Podอิสระจาก Pod
Pod Restartข้อมูลหายข้อมูลอยู่
Pod Deleteข้อมูลหายข้อมูลอยู่
Use CaseCache, Temp filesDatabase, User uploads
ตัวอย่างemptyDir, Container FSPV + PVC

Kubernetes Volume Types

emptyDir — แชร์ข้อมูลระหว่าง Containers

สร้าง Directory ว่างเมื่อ Pod ถูกสร้าง และลบเมื่อ Pod ถูกลบ ใช้แชร์ไฟล์ระหว่าง Containers ใน Pod เดียวกัน:

apiVersion: v1
kind: Pod
metadata:
  name: shared-data-pod
spec:
  containers:
  - name: writer
    image: busybox
    command: ["/bin/sh", "-c"]
    args:
    - while true; do
        echo "$(date) - Hello from writer" >> /data/log.txt;
        sleep 5;
      done
    volumeMounts:
    - name: shared-data
      mountPath: /data

  - name: reader
    image: busybox
    command: ["/bin/sh", "-c"]
    args:
    - while true; do
        cat /data/log.txt;
        sleep 10;
      done
    volumeMounts:
    - name: shared-data
      mountPath: /data

  volumes:
  - name: shared-data
    emptyDir: {}
    # emptyDir บน Memory (RAM Disk) สำหรับ Performance
    # emptyDir:
    #   medium: Memory
    #   sizeLimit: 256Mi

hostPath — ใช้ Directory บน Node

Mount Directory จาก Host Node เข้า Pod ใช้สำหรับ Development หรือ DaemonSets ที่ต้องเข้าถึง Host File System:

apiVersion: v1
kind: Pod
metadata:
  name: hostpath-pod
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: host-logs
      mountPath: /var/log/host
      readOnly: true
  volumes:
  - name: host-logs
    hostPath:
      path: /var/log
      type: DirectoryOrCreate  # Directory, DirectoryOrCreate, File, FileOrCreate
คำเตือน: hostPath ไม่ควรใช้ใน Production เพราะข้อมูลผูกกับ Node ถ้า Pod ย้าย Node จะเข้าถึงข้อมูลไม่ได้ และมีความเสี่ยงด้าน Security เนื่องจากเข้าถึง Host File System ได้

configMap และ secret — Inject Configuration

# ConfigMap Volume
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  nginx.conf: |
    server {
      listen 80;
      server_name localhost;
      location / {
        root /usr/share/nginx/html;
      }
    }
  app.properties: |
    database.host=postgres-svc
    database.port=5432
    cache.ttl=300
---
apiVersion: v1
kind: Pod
metadata:
  name: config-pod
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: config-volume
      mountPath: /etc/nginx/conf.d
    - name: app-props
      mountPath: /app/config
  volumes:
  - name: config-volume
    configMap:
      name: app-config
      items:
      - key: nginx.conf
        path: default.conf
  - name: app-props
    configMap:
      name: app-config
      items:
      - key: app.properties
        path: application.properties

---
# Secret Volume
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  username: YWRtaW4=       # base64 encoded "admin"
  password: cGFzc3dvcmQ=   # base64 encoded "password"
---
apiVersion: v1
kind: Pod
metadata:
  name: secret-pod
spec:
  containers:
  - name: app
    image: myapp
    volumeMounts:
    - name: db-creds
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: db-creds
    secret:
      secretName: db-credentials
      defaultMode: 0400  # Read-only สำหรับ Owner เท่านั้น

PersistentVolume (PV) — Storage Resource

PersistentVolume (PV) คือ Storage Resource ใน Cluster ที่ถูกสร้างโดย Admin หรือ Dynamic Provisioner เปรียบเสมือน "ก้อน Disk" ที่พร้อมให้ใช้งาน PV มี Lifecycle แยกจาก Pod ทำให้ข้อมูลอยู่รอดแม้ Pod จะถูกลบ:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-data
  labels:
    type: nfs
    environment: production
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteMany           # หลาย Pod อ่าน/เขียนได้พร้อมกัน
  persistentVolumeReclaimPolicy: Retain  # เก็บข้อมูลไว้เมื่อ PVC ถูกลบ
  storageClassName: nfs-storage
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    server: 192.168.1.100
    path: /exports/data

---
# PV สำหรับ AWS EBS
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-ebs-database
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce           # Pod เดียวเท่านั้นที่เขียนได้
  persistentVolumeReclaimPolicy: Retain
  storageClassName: gp3
  csi:
    driver: ebs.csi.aws.com
    volumeHandle: vol-0abcdef1234567890
    fsType: ext4

Access Modes — โหมดการเข้าถึง

Modeย่อความหมายUse Case
ReadWriteOnceRWOอ่าน/เขียนจาก Node เดียวDatabase (PostgreSQL, MySQL)
ReadOnlyManyROXอ่านจากหลาย NodeStatic Content, Config
ReadWriteManyRWXอ่าน/เขียนจากหลาย NodeShared File Storage (NFS, CephFS)
ReadWriteOncePodRWOPอ่าน/เขียนจาก Pod เดียวเท่านั้นSingle-writer Database
สำคัญ: ไม่ใช่ทุก Storage Backend จะรองรับทุก Access Mode เช่น AWS EBS รองรับแค่ RWO เท่านั้น ถ้าต้องการ RWX ต้องใช้ EFS (NFS) หรือ CephFS แทน

Reclaim Policies — จะทำอะไรเมื่อ PVC ถูกลบ

Policyพฤติกรรมเมื่อไหร่ใช้
Retainเก็บ PV และข้อมูลไว้ ต้อง Manual CleanupProduction Database ที่ข้อมูลสำคัญ
Deleteลบ PV และ Backend Storage ด้วยDev/Test Environment หรือ Dynamic Provisioning
Recycleลบข้อมูลใน Volume แล้วให้ใช้ใหม่ (Deprecated)ไม่แนะนำ ใช้ Dynamic Provisioning แทน

PersistentVolumeClaim (PVC) — ขอใช้ Storage

PVC คือ "ใบขอใช้" Storage ที่ Developer สร้างขึ้นเพื่อบอกว่าต้องการ Storage ขนาดเท่าไหร่ Access Mode อะไร Kubernetes จะหา PV ที่ตรงตามเงื่อนไขมา Bind ให้:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
  storageClassName: gp3          # เลือก StorageClass
  # selector:                    # เลือก PV เฉพาะเจาะจง (Optional)
  #   matchLabels:
  #     type: ssd
  #     environment: production

---
# ใช้ PVC ใน Pod
apiVersion: v1
kind: Pod
metadata:
  name: postgres
spec:
  containers:
  - name: postgres
    image: postgres:16
    env:
    - name: POSTGRES_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: password
    - name: PGDATA
      value: /var/lib/postgresql/data/pgdata
    ports:
    - containerPort: 5432
    volumeMounts:
    - name: postgres-storage
      mountPath: /var/lib/postgresql/data
    resources:
      requests:
        memory: "256Mi"
        cpu: "250m"
      limits:
        memory: "1Gi"
        cpu: "1"
  volumes:
  - name: postgres-storage
    persistentVolumeClaim:
      claimName: postgres-data-pvc
# ตรวจสอบ PV และ PVC
kubectl get pv
kubectl get pvc
kubectl describe pvc postgres-data-pvc

# ตัวอย่าง Output:
# NAME                STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS
# postgres-data-pvc   Bound    pv-ebs-db    50Gi       RWO            gp3

StorageClass — Dynamic Provisioning

แทนที่ Admin จะต้องสร้าง PV ล่วงหน้า StorageClass ช่วยให้ Kubernetes สร้าง PV อัตโนมัติเมื่อมีคนสร้าง PVC:

# StorageClass สำหรับ AWS EBS gp3
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"  # Default StorageClass
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
  fsType: ext4
reclaimPolicy: Delete
allowVolumeExpansion: true      # อนุญาตให้ขยาย Volume ได้
volumeBindingMode: WaitForFirstConsumer  # สร้าง Volume เมื่อ Pod ถูก Schedule

---
# StorageClass สำหรับ AWS EBS io2 (High Performance)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: io2-high-perf
provisioner: ebs.csi.aws.com
parameters:
  type: io2
  iops: "10000"
  encrypted: "true"
reclaimPolicy: Retain           # เก็บข้อมูลไว้เมื่อ PVC ลบ
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

---
# StorageClass สำหรับ NFS (ReadWriteMany)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client
provisioner: nfs-subdir-external-provisioner
parameters:
  pathPattern: "${.PVC.namespace}-${.PVC.name}"
  onDelete: retain
reclaimPolicy: Delete

---
# StorageClass สำหรับ GCE PD
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: pd.csi.storage.gke.io
parameters:
  type: pd-ssd
  replication-type: regional-pd  # Regional HA
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
# ดู StorageClass ทั้งหมด
kubectl get storageclass
kubectl get sc

# Output:
# NAME            PROVISIONER          RECLAIMPOLICY   VOLUMEBINDINGMODE
# gp3 (default)   ebs.csi.aws.com     Delete          WaitForFirstConsumer
# io2-high-perf   ebs.csi.aws.com     Retain          WaitForFirstConsumer
# nfs-client      nfs-provisioner      Delete          Immediate
volumeBindingMode: ใช้ WaitForFirstConsumer เสมอสำหรับ Cloud Storage เพื่อให้ Volume ถูกสร้างใน Availability Zone เดียวกับ Pod ป้องกันปัญหา Volume อยู่คนละ Zone กับ Node

CSI (Container Storage Interface) Drivers

CSI เป็นมาตรฐานที่ทำให้ Storage Vendors สามารถสร้าง Driver สำหรับ Kubernetes ได้อย่างเป็นระบบ โดยไม่ต้องแก้ไข Kubernetes Core Code:

CSI Drivers ยอดนิยม

DriverStorage TypeAccess ModesUse Case
AWS EBS CSIBlock (EBS)RWODatabase, Single-pod workloads
AWS EFS CSIFile (NFS)RWXShared file storage
GCE PD CSIBlock (Persistent Disk)RWO, ROXGKE workloads
Azure Disk CSIBlock (Managed Disk)RWOAKS workloads
Azure File CSIFile (SMB/NFS)RWXShared storage on Azure
NFS CSIFile (NFS)RWXOn-premises shared storage
Ceph RBDBlockRWOOn-premises block storage
CephFSFileRWXOn-premises file storage
LonghornBlock (Distributed)RWO, RWXDistributed storage for K8s
OpenEBSBlock/FileRWO, RWXContainer-native storage
Rook-CephBlock/File/ObjectRWO, RWXProduction-grade distributed storage

ติดตั้ง AWS EBS CSI Driver

# ติดตั้งด้วย Helm
helm repo add aws-ebs-csi-driver https://kubernetes-sigs.github.io/aws-ebs-csi-driver
helm repo update

helm install aws-ebs-csi-driver aws-ebs-csi-driver/aws-ebs-csi-driver \
  --namespace kube-system \
  --set controller.serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=\
    arn:aws:iam::ACCOUNT_ID:role/EBS_CSI_DriverRole

# ตรวจสอบ
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver
kubectl get csidrivers

Longhorn — Distributed Storage สำหรับ On-Premises

# ติดตั้ง Longhorn
helm repo add longhorn https://charts.longhorn.io
helm repo update

helm install longhorn longhorn/longhorn \
  --namespace longhorn-system \
  --create-namespace \
  --set defaultSettings.defaultDataPath="/var/lib/longhorn"

# Longhorn สร้าง StorageClass ให้อัตโนมัติ
kubectl get sc longhorn

# ใช้งาน Longhorn PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 20Gi

StatefulSets — Stateful Applications

StatefulSet เป็น Controller ที่ออกแบบมาสำหรับ Stateful Applications โดยจะสร้าง PVC แยกสำหรับแต่ละ Pod และรักษา Identity ที่คงที่:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres-headless
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
          name: postgres
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "2"

  # VolumeClaimTemplate — สร้าง PVC แยกสำหรับแต่ละ Pod
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: gp3
      resources:
        requests:
          storage: 50Gi

---
# Headless Service สำหรับ StatefulSet
apiVersion: v1
kind: Service
metadata:
  name: postgres-headless
spec:
  clusterIP: None
  selector:
    app: postgres
  ports:
  - port: 5432
    targetPort: 5432
# StatefulSet จะสร้าง Pods ตามลำดับ:
# postgres-0  →  PVC: data-postgres-0
# postgres-1  →  PVC: data-postgres-1
# postgres-2  →  PVC: data-postgres-2

# แต่ละ Pod มี DNS ที่คงที่:
# postgres-0.postgres-headless.default.svc.cluster.local
# postgres-1.postgres-headless.default.svc.cluster.local
# postgres-2.postgres-headless.default.svc.cluster.local

# ตรวจสอบ
kubectl get statefulset postgres
kubectl get pods -l app=postgres
kubectl get pvc -l app=postgres

Volume Snapshots — สำรองข้อมูล

# VolumeSnapshotClass — กำหนดวิธีสร้าง Snapshot
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: ebs-snapshot-class
driver: ebs.csi.aws.com
deletionPolicy: Delete
parameters:
  tagSpecification_1: "Department=Engineering"

---
# สร้าง Volume Snapshot จาก PVC
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: postgres-snapshot-20260408
spec:
  volumeSnapshotClassName: ebs-snapshot-class
  source:
    persistentVolumeClaimName: data-postgres-0

---
# Restore จาก Snapshot — สร้าง PVC ใหม่
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-restored
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: gp3
  resources:
    requests:
      storage: 50Gi
  dataSource:
    name: postgres-snapshot-20260408
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
# ตรวจสอบ Snapshot
kubectl get volumesnapshot
kubectl describe volumesnapshot postgres-snapshot-20260408

# CronJob สำหรับ Scheduled Snapshots
apiVersion: batch/v1
kind: CronJob
metadata:
  name: db-snapshot-daily
spec:
  schedule: "0 2 * * *"    # ทุกวัน ตี 2
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: snapshot-creator
          containers:
          - name: snapshot
            image: bitnami/kubectl
            command:
            - /bin/sh
            - -c
            - |
              DATE=$(date +%Y%m%d-%H%M)
              cat <<EOF | kubectl apply -f -
              apiVersion: snapshot.storage.k8s.io/v1
              kind: VolumeSnapshot
              metadata:
                name: postgres-snap-$DATE
              spec:
                volumeSnapshotClassName: ebs-snapshot-class
                source:
                  persistentVolumeClaimName: data-postgres-0
              EOF
          restartPolicy: OnFailure

Volume Cloning — คัดลอก Volume

# Clone PVC — สร้าง PVC ใหม่จาก PVC ที่มีอยู่
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-clone
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: gp3
  resources:
    requests:
      storage: 50Gi
  dataSource:
    name: data-postgres-0     # PVC ต้นทาง
    kind: PersistentVolumeClaim
Use Case: Volume Cloning เหมาะสำหรับสร้าง Dev/Test Environment จาก Production Data โดยไม่กระทบ Production และเร็วกว่าการ Backup + Restore

Expanding PVCs — ขยาย Volume

# StorageClass ต้องมี allowVolumeExpansion: true

# ขยาย PVC จาก 50Gi เป็น 100Gi
kubectl patch pvc data-postgres-0 -p '{
  "spec": {
    "resources": {
      "requests": {
        "storage": "100Gi"
      }
    }
  }
}'

# ตรวจสอบสถานะ
kubectl get pvc data-postgres-0
# STATUS อาจเป็น "FileSystemResizePending" ก่อนจะเป็น "Bound"

# บาง CSI Driver ต้อง Restart Pod เพื่อ Resize Filesystem
kubectl delete pod postgres-0
# StatefulSet จะสร้าง Pod ใหม่อัตโนมัติ

Storage สำหรับ Database ใน K8s

การรัน Database ใน Kubernetes ต้องพิจารณาเรื่อง Storage เป็นพิเศษ:

PostgreSQL HA ด้วย CloudNativePG

# ติดตั้ง CloudNativePG Operator
kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.22/releases/cnpg-1.22.yaml

# สร้าง PostgreSQL Cluster
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: postgres-cluster
spec:
  instances: 3
  primaryUpdateStrategy: unsupervised

  storage:
    storageClass: gp3
    size: 100Gi

  walStorage:
    storageClass: io2-high-perf    # WAL ใช้ Disk เร็ว
    size: 20Gi

  backup:
    barmanObjectStore:
      destinationPath: "s3://my-backup-bucket/postgres/"
      s3Credentials:
        accessKeyId:
          name: aws-creds
          key: ACCESS_KEY_ID
        secretAccessKey:
          name: aws-creds
          key: ACCESS_SECRET_KEY
    retentionPolicy: "30d"

  resources:
    requests:
      memory: "1Gi"
      cpu: "1"
    limits:
      memory: "4Gi"
      cpu: "4"

  monitoring:
    enablePodMonitor: true

Local Persistent Volumes

สำหรับ Performance สูงสุด สามารถใช้ Local NVMe/SSD ของ Node ได้:

# Local StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-ssd
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

---
# Local PV ที่ผูกกับ Node เฉพาะ
apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv-node1
spec:
  capacity:
    storage: 500Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-ssd
  local:
    path: /mnt/ssd/data         # Path บน Node
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - worker-node-1
ข้อควรระวัง: Local PV ไม่มี Replication ถ้า Node พัง ข้อมูลหาย ใช้เฉพาะกรณีที่ Application มี Replication ของตัวเอง (เช่น Cassandra, Elasticsearch) หรือเป็น Cache ที่สร้างใหม่ได้

Storage Performance — IOPS & Throughput

Storage TypeIOPSThroughputLatencyเหมาะกับ
AWS gp33,000-16,000125-1,000 MB/s~1msGeneral workloads
AWS io2100-64,000256-1,000 MB/s<1msHigh-perf Database
Local NVMe100,000+2,000+ MB/s<0.1msLatency-critical
NFS/EFSElasticElastic~5msShared file access
CephFSDepends on clusterDepends on cluster~2msShared storage on-prem
# ทดสอบ Storage Performance ด้วย fio
apiVersion: batch/v1
kind: Job
metadata:
  name: storage-benchmark
spec:
  template:
    spec:
      containers:
      - name: fio
        image: ljishen/fio
        command:
        - fio
        - --name=randwrite
        - --ioengine=libaio
        - --direct=1
        - --bs=4k
        - --iodepth=32
        - --rw=randwrite
        - --size=1G
        - --numjobs=1
        - --filename=/data/test
        - --output-format=json
        volumeMounts:
        - name: test-vol
          mountPath: /data
      volumes:
      - name: test-vol
        persistentVolumeClaim:
          claimName: benchmark-pvc
      restartPolicy: Never

Monitoring Storage

# ดู Storage Usage ทั้ง Cluster
kubectl get pv --sort-by=.spec.capacity.storage
kubectl get pvc --all-namespaces

# ดู Usage ใน Pod
kubectl exec -it postgres-0 -- df -h /var/lib/postgresql/data

# Prometheus Metrics สำหรับ Storage
# kubelet_volume_stats_capacity_bytes     — ขนาด Volume ทั้งหมด
# kubelet_volume_stats_used_bytes         — ใช้ไปเท่าไหร่
# kubelet_volume_stats_available_bytes    — เหลือเท่าไหร่
# kubelet_volume_stats_inodes             — จำนวน inodes ทั้งหมด
# kubelet_volume_stats_inodes_used        — inodes ที่ใช้ไป

# Prometheus Alert Rule สำหรับ Disk เกือบเต็ม
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: storage-alerts
spec:
  groups:
  - name: storage
    rules:
    - alert: PVCAlmostFull
      expr: |
        (kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) > 0.85
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "PVC {{ $labels.persistentvolumeclaim }} is 85% full"
        description: "PVC {{ $labels.persistentvolumeclaim }} in namespace {{ $labels.namespace }} is {{ $value | humanizePercentage }} full"

    - alert: PVCCriticallyFull
      expr: |
        (kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) > 0.95
      for: 1m
      labels:
        severity: critical
      annotations:
        summary: "PVC {{ $labels.persistentvolumeclaim }} is 95% full - URGENT"

Backup ด้วย Velero

Velero เป็นเครื่องมือ Backup/Restore สำหรับ Kubernetes ที่สามารถ Backup ทั้ง Resources (YAML) และ Persistent Volume Data:

# ติดตั้ง Velero CLI
brew install velero   # macOS
# หรือดาวน์โหลดจาก GitHub Releases

# ติดตั้ง Velero ใน Cluster (AWS)
velero install \
  --provider aws \
  --plugins velero/velero-plugin-for-aws:v1.9.0 \
  --bucket my-velero-bucket \
  --backup-location-config region=ap-southeast-1 \
  --snapshot-location-config region=ap-southeast-1 \
  --secret-file ./credentials-velero

# Backup ทั้ง Namespace
velero backup create production-backup \
  --include-namespaces production \
  --wait

# Backup เฉพาะ App
velero backup create postgres-backup \
  --include-namespaces database \
  --selector app=postgres \
  --wait

# Scheduled Backup
velero schedule create daily-backup \
  --schedule="0 3 * * *" \
  --include-namespaces production,database \
  --ttl 720h    # เก็บ 30 วัน

# Restore
velero restore create --from-backup production-backup

# Restore เฉพาะบาง Resource
velero restore create --from-backup production-backup \
  --include-resources persistentvolumeclaims,persistentvolumes

# ดู Backup ทั้งหมด
velero backup get
velero backup describe production-backup --details

Storage Best Practices

1. เลือก Storage ให้เหมาะกับ Workload

2. ตั้ง Resource Requests/Limits

# กำหนด Storage Quota ต่อ Namespace
apiVersion: v1
kind: ResourceQuota
metadata:
  name: storage-quota
  namespace: development
spec:
  hard:
    requests.storage: "500Gi"           # ขอ Storage รวมได้ไม่เกิน 500Gi
    persistentvolumeclaims: "20"        # สร้าง PVC ได้ไม่เกิน 20 อัน
    gp3.storageclass.storage.k8s.io/requests.storage: "300Gi"  # gp3 ไม่เกิน 300Gi

3. ใช้ WaitForFirstConsumer

# ป้องกัน Volume ถูกสร้างใน AZ ที่ไม่มี Node
volumeBindingMode: WaitForFirstConsumer

4. ตั้ง Reclaim Policy ให้เหมาะสม

5. Monitor และ Alert

6. Backup Strategy

เลือก Storage Solution ที่เหมาะสม

สถานการณ์แนะนำเหตุผล
AWS EKS ทั่วไปEBS CSI (gp3)ราคาถูก Performance ดี
AWS EKS Shared StorageEFS CSIรองรับ RWX ใช้ง่าย
GKEGCE PD CSINative integration ดี
AKSAzure Disk CSINative integration ดี
On-Premises (เริ่มต้น)Longhornติดตั้งง่าย UI ดี
On-Premises (Production)Rook-CephEnterprise-grade HA
On-Premises (NFS มีอยู่แล้ว)NFS CSIใช้ Infrastructure เดิม
High-Performance DatabaseLocal PV + ReplicationLatency ต่ำสุด

สรุป

Kubernetes Storage เป็นหัวข้อที่กว้างและสำคัญสำหรับการรัน Stateful Applications ใน Production การเข้าใจแนวคิดของ PV, PVC, StorageClass และ CSI Driver จะช่วยให้คุณออกแบบ Storage Architecture ที่เหมาะสมกับ Workload ของคุณ

สิ่งที่ควรจำคือ: เลือก Storage Type ให้เหมาะกับ Workload ตั้ง Reclaim Policy เป็น Retain สำหรับ Production ใช้ WaitForFirstConsumer เสมอ Monitor Disk Usage และมี Backup Strategy ที่ดี เพราะข้อมูลคือทรัพย์สินที่มีค่าที่สุดของทุกองค์กร

เริ่มต้นด้วยการทดลองสร้าง PVC กับ StorageClass ใน Development Cluster แล้วค่อยขยายไปใช้ StatefulSet สำหรับ Database จากนั้นตั้ง Monitoring Alert และ Backup Schedule ให้ครบ แล้วคุณจะมั่นใจว่าข้อมูลใน Kubernetes Cluster ของคุณปลอดภัยและพร้อมใช้งานเสมอ


Back to Blog | iCafe Forex | SiamLanCard | Siam2R