StatefulSets وتخزين البيانات المستمرة في Kubernetes

نشر تطبيقات مُحَفَّظة مع التوسع المُرَتَّب والبيانات المستمرة

Kubernetes StatefulSets هي الحل المثالي لإدارة تطبيقات تتطلب هويات مستقرة، تخزين دائم، ونمط توزيع منظم—وهي ضرورية للقواعد البيانات، الأنظمة الموزعة، وطبقات التخزين المؤقت.

إذا كنت جديدًا في Kubernetes أو تقوم بإعداد مجموعة خوادم، ففكر في استكشاف توزيعات Kubernetes مثل k3s أو MicroK8s للاستخدام في التطوير، أو تثبيت Kubernetes باستخدام Kubespray لمستوى إنتاجي من المجموعات.

عرض في مقهى هذا الصورة الرائعة تم إنشاؤها بواسطة نموذج AI Flux 1 dev.

ما هي StatefulSets؟

StatefulSets هي كائن واجهة برمجة تطبيقات (API) في Kubernetes تم تصميمه خصيصًا لإدارة التطبيقات ذات الحالة. على عكس Deployments التي تتعامل مع جميع Pods كأنها قابلة للتبديل، فإن StatefulSets تحافظ على هوية فريدة لكل Pod مع ضمانات حول الترتيب والاختلاف.

الميزات الرئيسية:

  • مُعرِّفات الشبكة المستقرة: يحصل كل Pod على اسم مضيف متوقع يظل ثابتًا عبر إعادة التشغيل
  • تخزين دائم: طلبات PersistentVolumeClaims المخصصة التي ترافق Pods عبر إعادة التعيين
  • توزيع منظم: يتم إنشاء Pods تسلسليًا (0، 1، 2…) وتُنهى في ترتيب معاكس
  • تحديثات منظمة: تتم التحديثات التدريجية في ترتيب، مما يضمن استقرار التطبيق

StatefulSets ضرورية للتطبيقات مثل PostgreSQL، MySQL، MongoDB، Cassandra، Elasticsearch، Kafka، ZooKeeper، Redis، و etcd—أي مهمة حيث تهم هوية Pod والاحتفاظ بالبيانات.

فهم التخزين المستمر في Kubernetes

يوفر Kubernetes طبقة تجريد متقدمة للتخزين تفصل إدارة التخزين عن دورة حياة Pod:

مكونات التخزين

PersistentVolume (PV): قطعة تخزين في المجموعة تُنشَأ من قبل المشرف أو تُنشَأ ديناميكيًا عبر StorageClass. توجد PV بشكل مستقل عن Pods.

PersistentVolumeClaim (PVC): طلب للتخزين من قبل Pod. ترتبط PVCs بـ PVs المتاحة التي تتطابق مع متطلباتها (الحجم، وضع الوصول، فئة التخزين).

StorageClass: تحدد “فئات” مختلفة من التخزين مع مزودين مختلفين (AWS EBS، GCE PD، Azure Disk، NFS، إلخ) ومواصفات مثل التكرار، مستويات الأداء، وسياسات النسخ الاحتياطي.

وضعيات الوصول

  • ReadWriteOnce (RWO): يتم تثبيت الحجم كقراءة/كتابة بواسطة عقدة واحدة
  • ReadOnlyMany (ROX): يتم تثبيت الحجم كقراءة فقط بواسطة عدة عقد
  • ReadWriteMany (RWX): يتم تثبيت الحجم كقراءة/كتابة بواسطة عدة عقد (يتطلب مكونات تخزين خاصة)

بنية تخزين StatefulSet

تستخدم StatefulSets volumeClaimTemplates لإنشاء PersistentVolumeClaims تلقائيًا لكل نسخة من Pod. هذا يختلف جذريًا عن Deployments:

كيف تعمل volumeClaimTemplates

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-service
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "fast-ssd"
      resources:
        requests:
          storage: 10Gi

عند إنشاء هذا StatefulSet:

  1. ينشئ Kubernetes Pod mysql-0 و PVC data-mysql-0
  2. ثم ينشئ Pod mysql-1 و PVC data-mysql-1
  3. أخيرًا ينشئ Pod mysql-2 و PVC data-mysql-2

يحصل كل Pod على حجم تخزين مستقل بسعة 10 جيجابايت. إذا تم حذف mysql-1 أو إعادة تعيينه، يعيد Kubernetes إنشاءه ويربطه بنفس PVC data-mysql-1، مما يحافظ على جميع البيانات.

إنشاء خدمة بدون عنوان (Headless Service) لـ StatefulSets

تتطلب StatefulSets خدمة بدون عنوان (Headless Service) لتقديم هويات شبكة مستقرة:

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None  # هذا يجعلها خدمة بدون عنوان
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

هذا ينشئ دخول DNS لكل Pod:

  • mysql-0.mysql-service.default.svc.cluster.local
  • mysql-1.mysql-service.default.svc.cluster.local
  • mysql-2.mysql-service.default.svc.cluster.local

يمكن للتطبيقات الاتصال مباشرة بنسخ Pod المحددة باستخدام هذه الأسماء المستقرة.

أنماط توزيع StatefulSet

للفرق التي تدير توزيعات Kubernetes المعقدة، توفر Helm Charts طريقة قوية لتعبئة ونشر StatefulSets مع التمكين، إدارة الإصدارات، وإدارة الاعتماديات. يبسط Helm إدارة إعدادات StatefulSet عبر بيئات مختلفة.

التوسع التسلسلي

عند التوسع من 3 إلى 5 نسخ:

kubectl scale statefulset mysql --replicas=5

ينشئ Kubernetes Pods تسلسليًا: mysql-3 → ينتظر الجاهزية → mysql-4

عند التوسع من 5 إلى 3:

kubectl scale statefulset mysql --replicas=3

ينهي Kubernetes في ترتيب معاكس: mysql-4 → ينتظر إنهاء → mysql-3

التحديثات التدريجية

تدعم StatefulSets استراتيجيتين لتحديثات:

OnDelete: تحديثات يدوية—Pods تُحدث فقط عند حذفها RollingUpdate: تحديثات تلقائية تسلسلية في ترتيب معاكس

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2  # تحديث Pods فقط ذات الترتيب >= 2

يتيح معلمة partition تنفيذ عمليات تجريبية—يمكنك تحديث Pods ذات الأرقام العالية أولاً ثم اختبارها قبل نشرها على جميع النسخ.

أفضل الممارسات في التخزين

التخصيص الديناميكي

استخدم دائمًا StorageClasses لتحديد الحجم التخزيني بشكل ديناميكي:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
allowVolumeExpansion: true
reclaimPolicy: Retain

allowVolumeExpansion: يسمح بتوسيع PVC دون إعادة إنشائه reclaimPolicy: Retain يحتفظ ببيانات PV بعد حذف PVC، Delete يحذفها تلقائيًا

سياسات الاحتفاظ بـ PVC

يدعم Kubernetes 1.23+ persistentVolumeClaimRetentionPolicy:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain    # احتفظ بـ PVCs عند حذف StatefulSet
    whenScaled: Delete     # حذف PVCs عند التوسع

الخيارات:

  • Retain: احتفظ بـ PVCs (السلوك الافتراضي، الأ安全)
  • Delete: حذف PVCs تلقائيًا (مفيد للبيئات التطويرية)

استراتيجيات النسخ الاحتياطي

ال快照ات: استخدم موارد VolumeSnapshot لإنشاء نسخ احتياطية في وقت معين

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot-20251113
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: data-mysql-0

النسخ الاحتياطية على مستوى التطبيق: استخدم أدوات مثل mysqldump، pg_dump، أو Velero للنسخ الاحتياطي المخصص للقواعد البيانات

التكرار الموزع: أعد تكوين تكرار التطبيق (تكرار MySQL، تكرار البث في PostgreSQL) كخط دفاع أول

حالات استخدام واقعية

مجموعة قواعد بيانات (PostgreSQL)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
        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
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "standard"
      resources:
        requests:
          storage: 20Gi

ذاكرة تخزين مؤقتة موزعة (Redis)

للمجموعات Redis، تحتاج إلى StatefulSet وتكوين دقيق:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-service
  replicas: 6
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        command:
        - redis-server
        - "--appendonly"
        - "yes"
        - "--appendfsync"
        - "everysec"
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 5Gi

قافلة الرسائل (Kafka)

تتطلب Kafka تخزينًا دائمًا للسجلات وهويات شبكة مستقرة لتنسيق المُنسق:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-service
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
      - name: kafka
        image: confluentinc/cp-kafka:7.5.0
        ports:
        - containerPort: 9092
          name: kafka
        env:
        - name: KAFKA_BROKER_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        volumeMounts:
        - name: data
          mountPath: /var/lib/kafka/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi

المراقبة والتحقيق في الأخطاء

للحصول على مرجع شامل لتعليمات Kubernetes المستخدمة في هذا القسم، راجع قائمة الأوامر المختصرة لـ Kubernetes.

فحص حالة StatefulSet

# عرض تفاصيل StatefulSet
kubectl get statefulset mysql
kubectl describe statefulset mysql

# فحص ترتيب إنشاء Pod وحالة
kubectl get pods -l app=mysql -w

# عرض حالة PVC
kubectl get pvc
kubectl describe pvc data-mysql-0

المشكلات الشائعة

Pod عالق في الانتظار: تحقق من حالة PVC وتوافر التخزين

kubectl describe pod mysql-0
kubectl get events --sort-by='.lastTimestamp'

التخزين ممتلئ: توسع PVC إذا سمح StorageClass بذلك

kubectl patch pvc data-mysql-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

Pod لا يمكنه إنهاء التشغيل: تحقق من قفلات على مستوى التطبيق أو مشاكل في إلغاء تثبيت الحجم

kubectl delete pod mysql-0 --grace-period=0 --force

المقاييس التي يجب مراقبتها

  • استخدام التخزين: مراقبة سعة PVC ونسبة الاستخدام
  • أداء I/O: تتبع IOPS، معدل النقل، والتأخير
  • إعادة تشغيل Pod: إعادة التشغيل المتكررة قد تشير إلى مشاكل في التخزين
  • وقت ربط PVC: التأخير في الربط يشير إلى مشاكل في التخصيص

استراتيجيات الهجرة

عند الهجرة إلى StatefulSets، تأكد من أن مجموعة خوادم Kubernetes مُعدة بشكل صحيح. للاستخدام في المختبرات المنزلية أو المجموعات الصغيرة، راجع مقارنتنا الشاملة لـ توزيعات Kubernetes لاختيار المنصة المناسبة لاحتياجاتك.

من Deployment إلى StatefulSet

  1. إنشاء StatefulSet مع volumeClaimTemplates
  2. تقليل Deployment بشكل لطيف
  3. استعادة البيانات من النسخ الاحتياطية إلى Pods في StatefulSet
  4. تحديث مراجع DNS/Service
  5. حذف Deployment القديم و PVCs

النسخ الاحتياطية قبل الهجرة

# إنشاء نسخ احتياطية لـ PVCs الحالية
kubectl get pvc -o yaml > pvc-backup.yaml

# إنشاء نسخ احتياطية للحجم
kubectl apply -f volume-snapshot.yaml

اعتبارات الأمان

تشفير التخزين

تفعيل التشفير عند الراحة باستخدام معلمات StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: encrypted-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd-1234

التحكم في الوصول

استخدم RBAC لتحديد من يمكنه إنشاء/تعديل StatefulSets و PVCs:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: statefulset-manager
rules:
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["get", "list", "create", "delete"]

سياسات الشبكة

تقييد الاتصال بين Pods:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-policy
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 3306

تحسين الأداء

فئات الأداء في التخزين

اختر فئات StorageClass المناسبة بناءً على المهمة:

  • IOPS مرتفع: قواعد بيانات ذات قراءة/كتابة عشوائية (gp3، io2)
  • معدل نقل مرتفع: تجميع السجلات، التحليلات (st1، sc1)
  • متوازن: تطبيقات عامة (gp3)

توزيع Pods

استخدم تجنب توزيع Pods لتفريق StatefulSet Pods عبر مناطق توفر:

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: mysql
            topologyKey: topology.kubernetes.io/zone

طلبات وحدود الموارد

حدد موارد مناسبة لضمان الأداء المستقر:

resources:
  requests:
    cpu: "2"
    memory: "4Gi"
    ephemeral-storage: "10Gi"
  limits:
    cpu: "4"
    memory: "8Gi"
    ephemeral-storage: "20Gi"

الأنماط المتقدمة

تطبيق حالة مع حاويات بدائية

استخدم حاويات بدائية لتهيئة قاعدة البيانات:

spec:
  template:
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          if [[ ! -d /var/lib/mysql/mysql ]]; then
            mysqld --initialize-insecure --datadir=/var/lib/mysql
          fi          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql

Pods متعددة الحاويات لـ Sidecars

أضف Sidecars للنسخ الاحتياطية أو وكلاء المراقبة:

spec:
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        # ... إعدادات mysql ...
      - name: backup-sidecar
        image: mysql-backup:latest
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          readOnly: true

ConfigMaps للاعدادات الديناميكية

فصل الاعدادات عن تعريف StatefulSet:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    max_connections=200
    innodb_buffer_pool_size=2G    
---
spec:
  template:
    spec:
      containers:
      - name: mysql
        volumeMounts:
        - name: config
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: config
        configMap:
          name: mysql-config

روابط مفيدة