شبكات Kubernetes والتخزين

فئات التخزين والتوفير الديناميكي

18 دقيقة الدرس 8 من 31

فئات التخزين والتوفير الديناميكي

في الدرس السابق تعلمت أن PersistentVolumes وPersistentVolumeClaims تفصل تعريفات الـ pod عن التخزين المادي الكامن تحتها. لكن التوفير المسبق اليدوي لـ PV لكل فريق تطوير يطلب تخزينًا غير مستدام تشغيليًا على نطاق واسع — إذ يستلزم تدخلًا بشريًا لكل قاعدة بيانات أو cache جديدة. التوفير الديناميكي يحل هذه المشكلة: يرسل المطور PersistentVolumeClaim يصف الحجم والخصائص المطلوبة، فيُنشئ المُوفّر (provisioner) — وهو controller يعمل داخل الكلاستر — موارد التخزين الحقيقية تلقائيًا، ويربطها بـ PV جديد، ويعيد حجمًا جاهزًا للتركيب. وكائن API الذي يحكم هذا السلوك هو StorageClass.

تشريح فئة التخزين StorageClass

فئة التخزين هي مورد على مستوى الكلاستر (لا تنتمي إلى namespace) تُحدد ثلاثة أشياء: المُوفّر (provisioner) (أي driver يُنشئ الحجم)، والمعاملات (parameters) (تهيئة خاصة بالـ driver مثل نوع القرص، ومستوى IOPS، ومفتاح التشفير)، وسياسة الاسترداد (reclaimPolicy) (ما يحدث لموارد التخزين الأساسية عند حذف PVC المالكة لها).

# StorageClass حقيقية لـ AWS EBS gp3 عبر EBS CSI driver apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: ebs-gp3 annotations: storageclass.kubernetes.io/is-default-class: "true" # كل PVC بدون storageClassName تحصل على هذه provisioner: ebs.csi.aws.com volumeBindingMode: WaitForFirstConsumer # <-- حيوي للكلاسترات متعددة مناطق التوافر reclaimPolicy: Delete # Delete | Retain | Recycle (مهجورة) allowVolumeExpansion: true parameters: type: gp3 iops: "6000" throughput: "250" # MB/s encrypted: "true" kmsKeyId: "arn:aws:kms:us-east-1:123456789012:key/mrk-abc123" --- # StorageClass مميزة لأحمال العمل الحساسة لزمن الاستجابة (io2 Block Express) apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: ebs-io2-high-perf provisioner: ebs.csi.aws.com volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Retain # احتفظ بـ EBS حتى لو حُذف PVC — للتدقيق والاسترداد allowVolumeExpansion: true parameters: type: io2 iopsPerGB: "50" encrypted: "true"
فئة التخزين الافتراضية مهمة جدًا. أي PVC لا تحدد storageClassName تحصل على الافتراضي في الكلاستر. على EKS، الافتراضي الجاهز هو StorageClass قديمة من نوع gp2 — سابقة لـ gp3 وأكثر تكلفة بالغيغابايت مقابل أداء أساسي أسوأ. من أولى تعديلات البنية التحتية في أي مشروع EKS تعيين StorageClass من نوع gp3 كافتراضية وإلغاء الافتراضية عن gp2. إهمال هذا يؤدي إلى إنفاق كل فريق بصمت على أقراص gp2.

المُوفّرون: In-Tree مقابل CSI

تاريخيًا شحنت Kubernetes مشغّلات الأحجام مدمجةً في الـ controller-manager binary (تسمى موفري "in-tree"، مثل kubernetes.io/aws-ebs). هذه مهجورة الآن وأُزيلت من إصدارات Kubernetes الحديثة. البديل الحديث هو Container Storage Interface (CSI) — مواصفة gRPC محايدة بين البائعين تتيح لموردي التخزين شحن مشغّلاتهم كأحمال عمل عادية في Kubernetes (Deployments وDaemonSets)، منفصلة تمامًا عن دورة إصدار Kubernetes. كل كلاستر إنتاجي اليوم يجب أن يستخدم مشغّلات CSI حصرًا.

أبرز موفري CSI الشائعة:

  • ebs.csi.aws.com — AWS EBS (تخزين كتلي)
  • efs.csi.aws.com — AWS EFS (ملفات، ReadWriteMany)
  • disk.csi.azure.com — Azure Managed Disks
  • pd.csi.storage.gke.io — GCP Persistent Disk
  • rbd.csi.ceph.com — Ceph RBD (مُدار ذاتيًا)
  • driver.longhorn.io — Longhorn (مُدار ذاتيًا، تخزين كتلي مُتكرر)

volumeBindingMode: فخ مناطق التوافر المتعددة

هذا الحقل هو المصدر الأكثر شيوعًا للحوادث الإنتاجية المتعلقة بالتخزين في الكلاسترات السحابية. له قيمتان:

  • Immediate — يُنشئ الموفر PV ويربطه فور إنشاء PVC. يحدث هذا قبل جدولة أي pod، لذا يُنشأ الحجم في منطقة توافر عشوائية. عندما يحاول المجدول بعد ذلك وضع الـ pod، قد يختار عقدة في منطقة توافر مختلفة حيث لا يمكن الوصول إلى حجم EBS — يظل الـ pod في حالة Pending إلى الأبد مع خطأ volume node affinity conflict.
  • WaitForFirstConsumer — يُؤجّل إنشاء PV حتى يتم جدولة pod يرجع إلى PVC. يختار المجدول العقدة أولًا، ثم يُنشئ الموفر الحجم في نفس منطقة توافر تلك العقدة. هذا هو الوضع الصحيح الوحيد للتخزين الكتلي المُدرك للمناطق في الكلاسترات متعددة مناطق التوافر.
WaitForFirstConsumer binding prevents AZ mismatch Immediate Binding — BROKEN PVC created Provisioner creates EBS in us-east-1a PV bound AZ: us-east-1a Scheduler picks node in us-east-1b Pod: Pending volume affinity conflict! WaitForFirstConsumer — CORRECT PVC created PV pending (not yet provisioned) Scheduler picks node in us-east-1c Provisioner EBS in us-east-1c Pod: Running volume mounted
الربط الفوري (Immediate) يُنشئ حجم EBS قبل الجدولة مما يسبب تعارض مناطق التوافر. WaitForFirstConsumer يؤجل الإنشاء حتى تُعرف العقدة، ليضمن أن الحجم والعقدة في نفس المنطقة.

سياسات الاسترداد: ما يحدث عند حذف PVC

تتحكم reclaimPolicy في StorageClass بدورة حياة موارد التخزين الأساسية بعد حذف PVC التي ترتبط بها. هناك ثلاث قيم، لكن القيمتين العمليتين فقط هما:

  • Delete (الافتراضي لمعظم StorageClasses السحابية) — يحذف مشغّل CSI الموارد الأساسية (مثلًا: يُنهي حجم EBS) فور تحرير PV. هذا فعّال لكنه خطير: حذف PVC في namespace خاطئة سيُدمر بيانات الإنتاج بصورة لا رجعة فيها. اقرن دائمًا StorageClasses بسياسة Delete مع حماية حذف PVC (مثل سياسات النسخ الاحتياطي عبر Velero أو webhook للتحقق يمنع حذف PVC في namespaces الحساسة).
  • Retain — ينتقل PV إلى مرحلة Released وتُحفظ موارد التخزين الأساسية. يجب على المدير حذف كائن PV يدويًا وتنظيف الموارد الأساسية اختياريًا. هذه هي السياسة الصحيحة لأي طبقة تحمل بيانات لا يمكن تحمّل خسارتها — قواعد البيانات، ومخازن الـ blobs، وسجلات التدقيق. يمكن إعادة إرفاق البيانات المستردة بإنشاء PV جديد يشير إلى نفس الموارد الأساسية وPVC جديد مع volumeName مطابق.
سياسة الاسترداد تُحدد عند إنشاء PV، لا عند حذفه. تُنسخ reclaimPolicy في StorageClass إلى PV وقت التوفير. تغيير StorageClass لاحقًا لا أثر له على PVs الموجودة. إذا احتجت تغيير سياسة الاسترداد على PV مرتبط (مثلًا: وفّرته بـ Delete لكنك تريد Retain قبل حذف PVC)، قم بتعديل PV مباشرة: kubectl patch pv <pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'. افعل هذا قبل حذف PVC.

توسيع الحجم: تكبير PVC بدون توقف

عندما يمتلئ حجم ما، الاستجابة الصحيحة ليست توفير حجم جديد ونقل البيانات — بل توسيع PVC الحالية في مكانها. لكي يعمل هذا، يجب أن تتحقق ثلاثة شروط: يجب أن تمتلك StorageClass الخاصية allowVolumeExpansion: true، ويجب أن ينفّذ مشغّل CSI استدعاءات RPC الخاصة بـ ControllerExpandVolume وNodeExpandVolume، وللأحجام ذات نظام الملفات يجب أن يكتمل التوسع على جانب العقدة أثناء تركيب الحجم.

تدعم مشغّلات CSI الحديثة لجميع مزودي السحابة الكبار التوسيع أثناء التشغيل — يُعاد تحديد حجم EBS عبر AWS API ويُوسَّع نظام الملفات (ext4 أو XFS) دون فصل التركيب. للمشغّلات القديمة أو المُدارة ذاتيًا التي تدعم التوسع في وضع عدم الاتصال فقط، يجب حذف الـ pod أولًا، والسماح بفصل PV، ثم تعديل PVC وإعادة تشغيل الـ pod.

# 1. التحقق من أن StorageClass تسمح بالتوسيع kubectl get sc ebs-gp3 -o jsonpath='{.allowVolumeExpansion}' # المتوقع: true # 2. تعديل PVC لطلب مساحة أكبر kubectl edit pvc my-database-data -n production # تحت spec.resources.requests.storage، غيّر "50Gi" إلى "100Gi" # أو تعديل تلقائي: kubectl patch pvc my-database-data -n production \ -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}' # 3. مراقبة تنفيذ التوسيع — تتغير شروط PVC: kubectl get pvc my-database-data -n production -w # سترى الشروط تتناوب: # Resizing -> FileSystemResizePending -> (تُزال عند الاكتمال) # 4. تأكيد الحجم الجديد من داخل الـ pod kubectl exec -n production deployment/my-database -- df -h /data # يجب أن يُظهر نظام الملفات 100Gi متاحة # 5. استرداد حالة توسيع عالق (FileSystemResizePending لكن الـ pod لا يرى الحجم الجديد) # يحدث هذا عندما يتوقف التوسع على جانب العقدة. احذف الـ pod لفصل الحجم # وإعادة إرفاقه مما يُطلق resize على جانب العقدة عند التركيب: kubectl delete pod -n production -l app=my-database # يعيد الـ pod تشغيله، يُركّب الحجم، يستدعي مشغّل CSI NodeExpandVolume، ويُوسَّع نظام الملفات
ابدأ بطلبات تخزين PVC محافظة واعتمد على التوسيع بدلًا من الإفراط في التوفير. البدء بـ 20 Gi والتوسع إلى 50 Gi عند الحاجة أقل تكلفة ويُبقي مخزون التخزين منظمًا. قم بتهيئة تنبيهات على kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes > 0.80 في Prometheus لتتوسع قبل امتلاء القرص، لا بعد انهيار التطبيق.

الخلاصة: تصميم StorageClass على نطاق الإنتاج

يمتلك الكلاستر الناضج عادةً ثلاث إلى خمس StorageClasses تُرسم على طبقات متمايزة من حيث التكلفة والأداء. تصميم تمثيلي لكلاستر AWS EKS قد يبدو هكذا: StorageClass افتراضية ebs-gp3 لأحمال العمل العامة، وفئة ebs-io2 لقواعد البيانات التي تتطلب IOPS متوقعة، وفئة efs-shared لأحمال العمل read-write-many مثل بيانات تدريب ML أو الإعدادات المشتركة، وفئة local-nvme (باستخدام local-path أو TopoLVM CSI) لأحمال العمل المؤقتة الحساسة لزمن الاستجابة التي تتحمل فقدان البيانات عند فشل العقدة.

كل StorageClass تتعامل مع بيانات الإنتاج يجب أن تمتلك reclaimPolicy: Retain، وallowVolumeExpansion: true، وvolumeBindingMode: WaitForFirstConsumer. هذه الإعدادات الثلاثة معًا تمنع تعارضات مناطق التوافر، وفقدان البيانات العرضي، والمعاناة التشغيلية من إعادة توفير الأحجام لزيادة السعة.

دعم لقطات الحجم يسير جنبًا إلى جنب مع StorageClasses. تتيح مشغّلات CSI التي تنفّذ snapshot RPC إنشاء VolumeSnapshotClass (مشابهة لـ StorageClass) وأخذ لقطات متسقة مع الأعطال لـ PVCs عبر kubectl apply -f volumesnapshot.yaml. اللقطات هي أساس خطوط النسخ الاحتياطي والاسترداد لأحمال العمل ذات الحالة، وشرط أساسي للاختبار الآمن لعمليات ترحيل الأحجام أو تغييرات المخطط في كلاسترات الإنتاج.