Kubernetes’te Kalıcı Depolama: PersistentVolume ve PVC Kullanımı

Kubernetes ile uygulama deploy etmeye başladığınızda, ilk başta her şey güzel görünür. Pod’lar ayağa kalkar, servisler birbirleriyle konuşur, hayat güzeldir. Ta ki pod’unuz restart edip tüm verilerini kaybedene kadar. İşte bu noktada PersistentVolume ve PVC kavramlarıyla tanışmak zorunda kalırsınız. Bu yazıda, Kubernetes’te kalıcı depolama yönetimini gerçek dünya senaryolarıyla ve pratik örneklerle ele alacağız.

Kalıcı Depolama Neden Gerekli?

Kubernetes pod’ları özünde geçici yapılardır. Bir pod öldüğünde, yeniden başladığında ya da farklı bir node’a taşındığında, container içindeki tüm veriler uçup gider. Bu durum stateless uygulamalar için sorun değildir ama bir MySQL veritabanı, bir Elasticsearch cluster’ı ya da kullanıcı upload’larını tutan bir uygulama söz konusu olduğunda durum tamamen farklıdır.

Kubernetes bu sorunu çözmek için birkaç katmanlı bir soyutlama sistemi kullanır. Bu sistemi anlamak, başlangıçta biraz kafa karıştırıcı olabilir ama mantığı kavradıktan sonra ne kadar zarif bir tasarım olduğunu göreceksiniz.

Temel Kavramlar

Kalıcı depolama konusunda üç ana kavramla karşılaşırsınız:

  • PersistentVolume (PV): Cluster yöneticisi tarafından sağlanan, fiziksel depolama kaynağını temsil eden nesnedir. NFS, iSCSI, cloud provider disk’leri veya local disk olabilir.
  • PersistentVolumeClaim (PVC): Kullanıcı ya da uygulama tarafından yapılan depolama talebidir. “Bana 10GB depolama ver” gibi düşünebilirsiniz.
  • StorageClass: Dinamik provisioning için kullanılan şablon yapısıdır. PVC oluşturulduğunda otomatik olarak PV yaratır.

Bu ilişkiyi şöyle düşünebilirsiniz: PV bir depo rafıdır, PVC ise o raftan yer talep etmektir. StorageClass ise depoda boş raf olmadığında otomatik yeni raf sipariş eden sistemdir.

PersistentVolume Oluşturma

Önce manuel PV oluşturmayı görelim. Bu yöntem genellikle “static provisioning” olarak adlandırılır ve özellikle on-premise ortamlarda sıkça kullanılır.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-nfs-pv
  labels:
    type: nfs
    environment: production
spec:
  capacity:
    storage: 50Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  nfs:
    server: 192.168.1.100
    path: "/exports/kubernetes/data"
EOF

Burada birkaç önemli parametreyi açıklayalım:

  • capacity.storage: PV’nin toplam kapasitesi
  • volumeMode: Filesystem (dizin olarak mount) veya Block (ham blok cihaz) seçenekleri vardır
  • accessModes: Erişim modunu belirler
  • persistentVolumeReclaimPolicy: PVC silindiğinde ne yapılacağını belirler
  • storageClassName: Bu PV’nin hangi storage class’a ait olduğunu tanımlar

Access mode’lar için üç seçenek vardır:

  • ReadWriteOnce (RWO): Tek bir node tarafından okunup yazılabilir
  • ReadOnlyMany (ROX): Birden fazla node tarafından sadece okunabilir
  • ReadWriteMany (RWX): Birden fazla node tarafından okunup yazılabilir

Reclaim policy seçenekleri ise şunlardır:

  • Retain: PVC silinince PV korunur, veriler kaybolmaz ama PV tekrar kullanılamaz hale gelir
  • Delete: PVC silinince PV ve arkasındaki depolama otomatik silinir
  • Recycle: Eski bir yöntemdir, genellikle artık önerilmez

PersistentVolumeClaim Oluşturma

PV hazır olduğuna göre şimdi PVC oluşturalım:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-app-pvc
  namespace: production
spec:
  accessModes:
    - ReadWriteMany
  volumeMode: Filesystem
  resources:
    requests:
      storage: 20Gi
  storageClassName: manual
  selector:
    matchLabels:
      type: nfs
      environment: production
EOF

PVC oluşturduktan sonra durumunu kontrol edelim:

kubectl get pvc -n production
kubectl describe pvc my-app-pvc -n production

# PV durumunu da kontrol edelim
kubectl get pv my-nfs-pv

PVC durumu Bound ise her şey yolundadır. Pending durumunda kalıyorsa, genellikle eşleşen bir PV bulunamadığı anlamına gelir. Lost durumu ise bağlı olduğu PV’nin artık mevcut olmadığını gösterir.

Pod’a PVC Bağlama

Şimdi bu PVC’yi gerçek bir pod’a nasıl bağlayacağımıza bakalım. Bir PostgreSQL deployment örneği üzerinden gidelim:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres-db
  namespace: production
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15
        ports:
        - containerPort: 5432
        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: my-app-pvc
EOF

Dikkat etmeniz gereken önemli bir nokta var: PostgreSQL için PGDATA environment variable’ını mount edilen dizinin bir alt dizinine ayarlıyoruz. Bu, PostgreSQL’in mount noktasında “lost+found” gibi dizinler gördüğünde şikayet etmesini önler.

StorageClass ve Dinamik Provisioning

Gerçek dünya Kubernetes ortamlarında genellikle manuel PV oluşturmazsınız. Bunun yerine StorageClass kullanarak dinamik provisioning yaparsınız. Cloud provider’larda bu genellikle kutudan çıkar şekilde gelir ama on-premise ortamlarda biraz daha fazla kurulum gerekir.

AWS EBS kullanan bir StorageClass örneği:

cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
  annotations:
    storageclass.kubernetes.io/is-default-class: "false"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
allowVolumeExpansion: true
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
EOF
  • provisioner: Hangi CSI driver’ının kullanılacağını belirtir
  • volumeBindingMode: Immediate (PVC oluşturulunca anında PV yarat) veya WaitForFirstConsumer (pod schedule edilene kadar bekle) seçenekleri vardır
  • allowVolumeExpansion: Sonradan boyut büyütmeye izin verir
  • reclaimPolicy: PVC silinince ne yapılacağı

WaitForFirstConsumer modu özellikle multi-zone cluster’larda kritiktir. Bu mod sayesinde PV, pod’un schedule edildiği zone’da oluşturulur. Aksi takdirde farklı zone’larda PV ve pod oluşabilir ve pod volume’a erişemez.

Dinamik provisioning kullanan bir PVC örneği:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: elasticsearch-data
  namespace: logging
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Gi
  storageClassName: fast-ssd
EOF

Bu PVC’yi oluşturduğunuzda (ya da bunu kullanan bir pod schedule edildiğinde, binding mode’a göre), Kubernetes otomatik olarak 100GB’lık bir EBS volume oluşturacak ve PV ile eşleştirecektir.

StatefulSet ile PVC Kullanımı

StatefulSet’ler, özellikle veritabanları ve dağıtık sistemler için tasarlanmıştır. Her pod için otomatik olarak ayrı PVC oluşturmaları en önemli özellikleridir. Bir Redis cluster örneği:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-cluster
  namespace: caching
spec:
  serviceName: redis-headless
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        ports:
        - containerPort: 6379
        command:
          - redis-server
          - /etc/redis/redis.conf
        volumeMounts:
        - name: redis-data
          mountPath: /data
        - name: redis-config
          mountPath: /etc/redis
      volumes:
      - name: redis-config
        configMap:
          name: redis-configuration
  volumeClaimTemplates:
  - metadata:
      name: redis-data
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: fast-ssd
      resources:
        requests:
          storage: 10Gi
EOF

StatefulSet’in volumeClaimTemplates bölümü tam burada devreye girer. Her pod için (redis-cluster-0, redis-cluster-1, redis-cluster-2) ayrı PVC’ler otomatik oluşturulur. Adlandırma şeması şöyledir: redis-data-redis-cluster-0, redis-data-redis-cluster-1 ve böyle devam eder.

Bu PVC’lerin önemli bir özelliği, StatefulSet silinse bile PVC’lerin korunmasıdır. Bu, yanlışlıkla StatefulSet silseniz bile verilerinizin güvende kalacağı anlamına gelir. Tabii bu aynı zamanda manuel temizlik yapmanız gerektiği anlamına da gelir.

Volume Snapshot ile Yedekleme

Production ortamında volume snapshot’ları kritik öneme sahiptir. Kubernetes’te VolumeSnapshot API’si bu iş için kullanılır:

# Önce VolumeSnapshotClass tanımlayalım
cat <<EOF | kubectl apply -f -
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: ebs-snapshot-class
  annotations:
    snapshot.storage.kubernetes.io/is-default-class: "true"
driver: ebs.csi.aws.com
deletionPolicy: Delete
EOF

# Snapshot alalım
cat <<EOF | kubectl apply -f -
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: postgres-backup-$(date +%Y%m%d)
  namespace: production
spec:
  volumeSnapshotClassName: ebs-snapshot-class
  source:
    persistentVolumeClaimName: my-app-pvc
EOF

Bu snapshot’tan yeni bir PVC oluşturmak için:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-restored
  namespace: production
spec:
  dataSource:
    name: postgres-backup-20240115
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 50Gi
EOF

Gerçek Dünya Senaryosu: Production Sorun Giderme

Bir gece alarm geldi: Production’daki MySQL pod’u CrashLoopBackOff durumunda. Kontrol etmeye başlıyorsunuz:

# Pod durumunu kontrol et
kubectl get pods -n production -l app=mysql

# PVC durumunu kontrol et
kubectl get pvc -n production

# Olayları incele
kubectl describe pod mysql-deployment-7d9f8b6c-xk2nl -n production

# PV'nin gerçekten bağlı olup olmadığını kontrol et
kubectl get pv | grep mysql

# Node'daki mount durumunu kontrol et (node'a SSH ile bağlandıktan sonra)
# Hangi node'da çalıştığını öğren
kubectl get pod mysql-deployment-7d9f8b6c-xk2nl -n production -o wide

# Node üzerinde
mount | grep kubernetes
df -h | grep kubernetes

# PVC'nin event log'larına bak
kubectl get events -n production --field-selector involvedObject.name=mysql-pvc --sort-by='.lastTimestamp'

Sık karşılaştığım sorunlar şunlardır:

  • FailedMount: Volume’un node’a mount edilememesi. Genellikle CSI driver sorunu veya cloud provider permission problemidir.
  • Insufficient storage: PV boyutunun yetersiz kalması. allowVolumeExpansion: true ayarlıysa PVC’yi genişletebilirsiniz.
  • Multi-attach error: RWO volume’un birden fazla node’a mount edilmeye çalışılması. Bu genellikle node failure sonrası eski pod hala terminating durumundayken yeni pod başlatılmaya çalışıldığında olur.

Multi-attach sorununu çözmek için:

# Eski pod'u zorla sil
kubectl delete pod <stuck-pod-name> -n production --grace-period=0 --force

# PV'yi describe ederek hangi node'a bağlı olduğunu gör
kubectl describe pv <pv-name>

PVC Boyutu Genişletme

Uygulamanız büyüdükçe disk ihtiyacı da büyür. StorageClass’ınızda allowVolumeExpansion: true ayarlıysa, PVC boyutunu büyütmek oldukça basittir:

# Mevcut PVC boyutunu kontrol et
kubectl get pvc my-app-pvc -n production

# PVC'yi edit ile genişlet
kubectl edit pvc my-app-pvc -n production
# spec.resources.requests.storage değerini artırın

# Veya patch ile
kubectl patch pvc my-app-pvc -n production -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'

# İşlemin tamamlanmasını bekle ve kontrol et
kubectl get pvc my-app-pvc -n production -w

Bazı volume türleri için filesystem’i genişletmek amacıyla pod’u restart etmeniz gerekebilir. Kubernetes genellikle bunu pod yeniden başladığında otomatik yapar, ancak bazı durumlarda pod içinden manuel müdahale gerekebilir.

PV Recycle ve Manuel Temizlik

Production’da sık karşılaşılan bir durum: PVC silindi ama PV hala “Released” durumunda ve tekrar kullanmak istiyorsunuz. Retain policy kullandıysanız PV’yi elle temizlemeniz gerekir:

# Released durumundaki PV'leri listele
kubectl get pv | grep Released

# PV'nin claimRef'ini temizle (bu PV'yi tekrar Available yapar)
kubectl patch pv my-nfs-pv -p '{"spec":{"claimRef": null}}'

# Veya doğrudan edit ile
kubectl edit pv my-nfs-pv
# spec.claimRef bölümünü silin

# Durumu kontrol et
kubectl get pv my-nfs-pv

Bunu yapmadan önce disk üzerindeki verileri backup almayı unutmayın. PV’yi tekrar Available yaptığınızda, yeni bir PVC bu PV’ye bağlanabilir ve eski veriler görünür olabilir.

StorageClass Bazlı İzleme ve Kapasite Yönetimi

Production’da PV/PVC durumlarını düzenli takip etmek önemlidir:

# Tüm PV'lerin özetini gör
kubectl get pv -o custom-columns='NAME:.metadata.name,CAPACITY:.spec.capacity.storage,STATUS:.status.phase,CLAIM:.spec.claimRef.name,STORAGECLASS:.spec.storageClassName'

# Namespace bazında PVC kullanımını listele
kubectl get pvc --all-namespaces -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,STATUS:.status.phase,VOLUME:.spec.volumeName,CAPACITY:.status.capacity.storage,STORAGECLASS:.spec.storageClassName'

# Orphaned PVC'leri bul (hiçbir pod tarafından kullanılmayan)
kubectl get pvc --all-namespaces -o json | jq '.items[] | select(.status.phase == "Bound") | {namespace: .metadata.namespace, name: .metadata.name}'

# StorageClass bazında kullanım özetini çıkar
kubectl get pv -o json | jq -r '.items[] | "(.spec.storageClassName) (.spec.capacity.storage)"' | sort | uniq -c

Bu komutları cron job olarak çalıştırıp çıktılarını bir monitoring sistemine besleyebilirsiniz. Prometheus ve Grafana kullanıyorsanız, kube-state-metrics PV/PVC durumlarını otomatik olarak expose eder.

Güvenlik Konuları

PV/PVC kullanımında güvenlik genellikle göz ardı edilir. Dikkat etmeniz gereken noktalar:

  • fsGroup: Pod güvenlik bağlamında fsGroup ayarlayarak volume’a erişim yetkisini belirleyin
  • readOnly mount: Yalnızca okuma gereken volume’ları readOnly: true ile mount edin
  • Encryption: StorageClass parametrelerinde şifrelemeyi etkinleştirin
  • RBAC: PVC oluşturma yetkisini namespace bazında kısıtlayın

Güvenli bir pod spec örneği:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: secure-app
  namespace: production
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 2000
  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: data-volume
      mountPath: /app/data
    - name: config-volume
      mountPath: /app/config
      readOnly: true
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
  volumes:
  - name: data-volume
    persistentVolumeClaim:
      claimName: app-data-pvc
  - name: config-volume
    persistentVolumeClaim:
      claimName: app-config-pvc
      readOnly: true
EOF

Sonuç

Kubernetes’te kalıcı depolama yönetimi, ilk bakışta karmaşık görünse de mantığını kavradıktan sonra oldukça sistematik bir yapıya sahip olduğunu görürsünüz. PV, PVC ve StorageClass üçlüsü birlikte çalışarak depolama altyapısını uygulama geliştiricilerinden soyutlar ve yönetimini kolaylaştırır.

Production ortamında dikkat etmeniz gereken en kritik noktaları özetleyecek olursam:

  • Mutlaka Retain policy kullanın önemli veriler için. Delete policy yanlış bir silme işleminde sizi ağlatabilir.
  • StorageClass seçimine dikkat edin. Fast SSD her şey için gerekli değildir, doğru depolama tipini seçmek hem performans hem de maliyet açısından önemlidir.
  • Volume snapshot’larını otomatize edin. Manuel backup’a güvenmek risklidir.
  • WaitForFirstConsumer binding mode’unu tercih edin multi-zone cluster’larda. Zone affinity sorunlarının önüne geçer.
  • PVC boyutlarını başlangıçta biraz cömert tutun. Sonradan büyütmek mümkün ama küçültmek (çoğu durumda) imkansızdır.
  • Orphaned PV ve PVC’leri düzenli temizleyin. Hem maliyet hem de yönetim karmaşıklığı açısından önemlidir.

Stateful uygulamalarla çalışmak her zaman biraz daha dikkat ve planlama gerektirir. Ama doğru yapılandırıldığında, Kubernetes’in storage yönetimi gerçekten güçlü ve esnek bir altyapı sağlar. Verileriniz güvende, uygulamalarınız mutlu olsun.

Yorum yapın