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 (ชั่วคราว) หมายความว่า:
- Pod ถูก Restart: ข้อมูลใน Container File System จะหายไปทั้งหมด
- Pod ถูก Reschedule: อาจย้ายไป Node อื่น ข้อมูลบน Node เดิมเข้าถึงไม่ได้
- Container Crash: kubelet จะสร้าง Container ใหม่ด้วย File System ที่สะอาด
- Multiple Containers: Container หลายตัวใน Pod เดียวกันต้องแชร์ข้อมูลกัน
Kubernetes จึงสร้างระบบ Volume ที่แยก Lifecycle ของ Storage ออกจาก Pod ให้ข้อมูลอยู่รอดแม้ Pod จะถูกลบหรือสร้างใหม่
Ephemeral vs Persistent Storage
| คุณสมบัติ | Ephemeral (ชั่วคราว) | Persistent (ถาวร) |
|---|---|---|
| Lifecycle | ผูกกับ Pod | อิสระจาก Pod |
| Pod Restart | ข้อมูลหาย | ข้อมูลอยู่ |
| Pod Delete | ข้อมูลหาย | ข้อมูลอยู่ |
| Use Case | Cache, Temp files | Database, User uploads |
| ตัวอย่าง | emptyDir, Container FS | PV + 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 |
|---|---|---|---|
| ReadWriteOnce | RWO | อ่าน/เขียนจาก Node เดียว | Database (PostgreSQL, MySQL) |
| ReadOnlyMany | ROX | อ่านจากหลาย Node | Static Content, Config |
| ReadWriteMany | RWX | อ่าน/เขียนจากหลาย Node | Shared File Storage (NFS, CephFS) |
| ReadWriteOncePod | RWOP | อ่าน/เขียนจาก Pod เดียวเท่านั้น | Single-writer Database |
Reclaim Policies — จะทำอะไรเมื่อ PVC ถูกลบ
| Policy | พฤติกรรม | เมื่อไหร่ใช้ |
|---|---|---|
| Retain | เก็บ PV และข้อมูลไว้ ต้อง Manual Cleanup | Production 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
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 ยอดนิยม
| Driver | Storage Type | Access Modes | Use Case |
|---|---|---|---|
| AWS EBS CSI | Block (EBS) | RWO | Database, Single-pod workloads |
| AWS EFS CSI | File (NFS) | RWX | Shared file storage |
| GCE PD CSI | Block (Persistent Disk) | RWO, ROX | GKE workloads |
| Azure Disk CSI | Block (Managed Disk) | RWO | AKS workloads |
| Azure File CSI | File (SMB/NFS) | RWX | Shared storage on Azure |
| NFS CSI | File (NFS) | RWX | On-premises shared storage |
| Ceph RBD | Block | RWO | On-premises block storage |
| CephFS | File | RWX | On-premises file storage |
| Longhorn | Block (Distributed) | RWO, RWX | Distributed storage for K8s |
| OpenEBS | Block/File | RWO, RWX | Container-native storage |
| Rook-Ceph | Block/File/Object | RWO, RWX | Production-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
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
Storage Performance — IOPS & Throughput
| Storage Type | IOPS | Throughput | Latency | เหมาะกับ |
|---|---|---|---|---|
| AWS gp3 | 3,000-16,000 | 125-1,000 MB/s | ~1ms | General workloads |
| AWS io2 | 100-64,000 | 256-1,000 MB/s | <1ms | High-perf Database |
| Local NVMe | 100,000+ | 2,000+ MB/s | <0.1ms | Latency-critical |
| NFS/EFS | Elastic | Elastic | ~5ms | Shared file access |
| CephFS | Depends on cluster | Depends on cluster | ~2ms | Shared 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
- Database (OLTP): ใช้ Block Storage (EBS gp3/io2) แบบ RWO สำหรับ Low Latency
- Shared Files: ใช้ File Storage (EFS/NFS/CephFS) แบบ RWX
- Analytics/Data Lake: ใช้ Object Storage (S3) ผ่าน CSI Driver หรือ SDK
- Cache/Temp: ใช้ emptyDir หรือ Local Volume
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 ให้เหมาะสม
- Production: ใช้
Retainเพื่อป้องกันข้อมูลหาย - Dev/Test: ใช้
Deleteเพื่อไม่ให้เปลือง Storage
5. Monitor และ Alert
- ตั้ง Alert เมื่อ PVC ใช้เกิน 80% เพื่อขยายก่อนเต็ม
- Monitor IOPS และ Latency เพื่อดูว่า Storage เป็น Bottleneck หรือไม่
- ตรวจสอบ PV ที่ Status เป็น Released แต่ยังไม่ถูกลบ (Orphaned PVs)
6. Backup Strategy
- ใช้ Velero สำหรับ Cluster-level Backup
- ใช้ VolumeSnapshot สำหรับ Point-in-time Recovery
- ทดสอบ Restore เป็นประจำ (Backup ที่ Restore ไม่ได้ ไม่ใช่ Backup)
- เก็บ Backup ข้าม Region สำหรับ Disaster Recovery
เลือก Storage Solution ที่เหมาะสม
| สถานการณ์ | แนะนำ | เหตุผล |
|---|---|---|
| AWS EKS ทั่วไป | EBS CSI (gp3) | ราคาถูก Performance ดี |
| AWS EKS Shared Storage | EFS CSI | รองรับ RWX ใช้ง่าย |
| GKE | GCE PD CSI | Native integration ดี |
| AKS | Azure Disk CSI | Native integration ดี |
| On-Premises (เริ่มต้น) | Longhorn | ติดตั้งง่าย UI ดี |
| On-Premises (Production) | Rook-Ceph | Enterprise-grade HA |
| On-Premises (NFS มีอยู่แล้ว) | NFS CSI | ใช้ Infrastructure เดิม |
| High-Performance Database | Local PV + Replication | Latency ต่ำสุด |
สรุป
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 ของคุณปลอดภัยและพร้อมใช้งานเสมอ
