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

CSI وعمليات التخزين الحالتية

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

CSI وعمليات التخزين الحالتية

غطّى الدرسان السابقان كيف تُجرِّد Kubernetes التخزين في PersistentVolumes وكيف تُحرِّك StorageClasses التوفير الديناميكي. يتعمق هذا الدرس طبقةً إضافية: واجهة تخزين الحاويات (CSI) — نظام الإضافات الذي يجعل كل مزوِّد وكل موفِّر لقطات وكل واجهة خلفية لتخزين السحابة قابلةً للتوصيل دون المساس بنواة Kubernetes. ستتعلم أيضاً اللقطات وأنماط النسخ الاحتياطي وأنماط الفشل التي تعثر فيها الفرق التي تُشغِّل قواعد البيانات وقوائم الرسائل في بيئة الإنتاج.

ما هو CSI ولماذا حلّ محل الإضافات الداخلية

قبل CSI، كانت برامج تشغيل التخزين (AWS EBS وGCE PD وCeph RBD وNFS) تعيش داخل شفرة مصدر Kubernetes. كان كل إصدار من برنامج التشغيل مقروناً بإصدار Kubernetes — فأي خطأ في برنامج تشغيل Ceph يعني انتظار إصدار Kubernetes الثانوي التالي. تفصل مواصفة CSI (التي تقودها CNCF Storage SIG) برامج التشغيل فصلاً تاماً: البرنامج عبارة عن مجموعة من خدمات gRPC يستدعيها Kubelet والشريط الجانبي external-provisioner عبر مقبس Unix. لا تعرف نواة Kubernetes شيئاً عن EBS؛ برنامج تشغيل CSI هو الذي يعرف.

يُشحَن كل برنامج تشغيل CSI كـDaemonSet (إضافة العقدة) مع Deployment (إضافة وحدة التحكم)، متصلاً بعدة حاويات جانبية تُديرها Kubernetes Storage SIG:

  • external-provisioner — يراقب PVCs ويستدعي CreateVolume على برنامج التشغيل.
  • external-attacher — يستدعي ControllerPublishVolume لتوصيل الجهاز الكتلي بالعقدة الهدف.
  • external-resizer — يُطلِق ControllerExpandVolume عند زيادة طلب التخزين في PVC.
  • external-snapshotter — يراقب كائنات VolumeSnapshot ويستدعي CreateSnapshot.
  • node-driver-registrar — يسجِّل مقبس إضافة العقدة مع Kubelet.
  • liveness-probe — يعرض نقطة نهاية صحة لحجرة برنامج التشغيل.
CSI Architecture: control plane and node plugin flow Control Plane API Server ext-provisioner ext-snapshotter ext-attacher CSI Controller Driver Pod (Deployment) Storage Backend AWS EBS / Ceph / NFS Cloud API / Storage API CSI Node Driver Pod (DaemonSet) Kubelet NodeStageVolume NodePublishVolume gRPC API Unix socket Volume ID App Pod (reads/writes) mount
بنية CSI: تُوفِّر إضافة وحدة التحكم وحدات التخزين وتوصّلها؛ وتُثبِّتها إضافة العقدة داخل Pods.

دورة حياة وحدة التخزين: التوصيل والتدريج والنشر

عند جدولة Pod يحتاج إلى PVC، تسير الخطوات التالية بترتيب صارم:

  1. CreateVolume — يستدعي external-provisioner برنامج تشغيل وحدة التحكم لإنشاء كائن التخزين الخام (معرِّف EBS volume أو Ceph image وما إلى ذلك).
  2. ControllerPublishVolume — يستدعي external-attacher برنامج تشغيل وحدة التحكم لتوصيل وحدة التخزين بالعقدة الهدف (يُعيِّن هذا إلى استدعاء EC2 AttachVolume بالنسبة لـEBS).
  3. NodeStageVolume — يطلب Kubelet من برنامج تشغيل العقدة تهيئة الجهاز الكتلي وتركيبه في دليل تدريج عام على العقدة. يحدث هذا مرة واحدة لكل وحدة تخزين لكل عقدة.
  4. NodePublishVolume — يقوم Kubelet بتركيب المسار المُدرَّج داخل نظام ملفات حاوية Pod. يحدث هذا مرة واحدة لكل Pod.

الإيقاف هو عكس ذلك تماماً. يُعدّ فهم هذه الآلية أمراً بالغ الأهمية عند تشخيص Pod عالق في ContainerCreating: تحقق من أحداث Pod عبر kubectl describe pod، ثم PVC، ثم كائن VolumeAttachment، وأخيراً سجلات برنامج تشغيل CSI للعقدة.

لقطات وحدات التخزين

قدَّم CSI واجهة برمجية Kubernetes من الدرجة الأولى للقطات وحدات التخزين عبر ثلاثة Custom Resource Definitions مُثبَّتة جنباً إلى جنب مع الشريط الجانبي external-snapshotter:

  • VolumeSnapshotClass — إعداد مُحدَّد ببرنامج التشغيل (مشابه لـStorageClass)، كأي سياسة لقطة يجب استخدامها.
  • VolumeSnapshot — طلب المستخدم: "أنشئ لقطة من هذا PVC الآن."
  • VolumeSnapshotContent — كائن اللقطة الفعلي على الواجهة الخلفية، إما مُوفَّر مسبقاً من المسؤول أو مُنشَأ ديناميكياً.
# 1. تحقق من تثبيت CRDs للقطات (يتم عادةً عبر مثبّت برنامج تشغيل CSI) kubectl get crd volumesnapshots.snapshot.storage.k8s.io # 2. أنشئ VolumeSnapshotClass (مثال برنامج تشغيل EBS CSI) cat <<'EOF' | kubectl apply -f - apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshotClass metadata: name: ebs-vsc driver: ebs.csi.aws.com deletionPolicy: Delete parameters: tagSpecification_1: "Key=Environment,Value=production" EOF # 3. التقط لقطة من PVC موجود cat <<'EOF' | kubectl apply -f - apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshot metadata: name: postgres-snap-20250601 namespace: production spec: volumeSnapshotClassName: ebs-vsc source: persistentVolumeClaimName: postgres-data EOF # 4. انتظر الجاهزية وافحص kubectl -n production get volumesnapshot postgres-snap-20250601 -w # ستتغير READY TO USE إلى true عند اتساق اللقطة على الواجهة الخلفية # 5. الاستعادة: أنشئ PVC جديداً من اللقطة cat <<'EOF' | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: postgres-restore namespace: production spec: storageClassName: ebs-sc dataSource: name: postgres-snap-20250601 kind: VolumeSnapshot apiGroup: snapshot.storage.k8s.io accessModes: [ReadWriteOnce] resources: requests: storage: 100Gi EOF
اللقطة مقابل النسخة الاحتياطية: اللقطة CSI هي نسخة في لحظة زمنية مُخزَّنة على نفس الواجهة الخلفية (لقطة EBS تعيش في S3 لكن ضمن نفس حساب AWS). تحمي من تلف البيانات والحذف العرضي، لكنها لا تحمي من فشل المنطقة أو اختراق الحساب. اللقطة ليست نسخة احتياطية — تحتاج إلى نسخة خارج الكتلة (Velero أو نسخ S3 عبر المناطق أو أداة نسخ احتياطي مخصصة) لضمان التعافي الحقيقي من الكوارث.

تغيير حجم وحدة التخزين أثناء التشغيل

إذا كان StorageClass يحتوي على allowVolumeExpansion: true وطبَّق برنامج تشغيل CSI كلاً من ControllerExpandVolume وNodeExpandVolume، يمكنك تكبير PVC مباشرةً دون إعادة تشغيل Pod:

# وسِّع PVC لطلب مزيد من التخزين kubectl -n production patch pvc postgres-data -p \ '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}' # راقب تدفق تغيير الحجم: سيُظهر شرط PVC FileSystemResizePending ثم يُزال kubectl -n production get pvc postgres-data -w # يوسِّع برنامج تشغيل CSI الجهاز الكتلي أولاً (ControllerExpandVolume)، # ثم يُغيِّر Kubelet حجم نظام الملفات عند التركيب التالي (NodeExpandVolume). # بالنسبة لـext4/xfs يحدث التغيير أثناء التشغيل؛ لا يحتاج Pod للتوقف. # تحقق داخل Pod الجاري kubectl -n production exec -it postgres-0 -- df -h /var/lib/postgresql/data
تقليص وحدة التخزين غير مدعوم. لا تسمح CSI في Kubernetes بتقليل حجم PVC. إذا كنت بحاجة لاسترداد مساحة، فيجب نسخ البيانات احتياطياً وحذف PVC وإعادة إنشائه بالحجم الأصغر ثم الاستعادة. خطِّط لسعة التخزين بعناية من البداية؛ الإفراط الطفيف في التوفير أرخص من نافذة صيانة.

استراتيجيات النسخ الاحتياطي لأحمال العمل الحالتية

على نطاق الشركات الكبرى، تُستخدَم ثلاثة أنماط نسخ احتياطي معاً بدلاً من استخدام أي منها بمعزل:

  • النسخ الاحتياطي على مستوى التطبيق: استخدم محرك قاعدة البيانات نفسه — pg_dump لـPostgreSQL أو mysqldump أو نسخ موضوع Kafka. هذه نسخ متسقة تطبيقياً وقابلة للنقل عبر مزودي السحابة وقابلة للاختبار. شغِّلها كـCronJobs وابثّ ناتجها إلى التخزين الكائني (S3 أو GCS) مع التشفير في حالة السكون.
  • لقطة CSI مع VolumeSnapshotSchedule: استخدم أداة مثل Velero أو نمط المشغّل لجدولة VolumeSnapshots الآلية. اللقطات سريعة (نسخ عند الكتابة) وتُستعاد في دقائق. تكمِّل النسخ الاحتياطية على مستوى التطبيق لكن لا ينبغي أن تحل محلها لأن اللقطات تلتقط حالة الجهاز الكتلي الخام التي قد لا تكون متسقة تطبيقياً إذا كانت قاعدة البيانات تحتوي على كتابات غير مُفرَّغة.
  • Velero (النسخ الاحتياطي للكتلة): تنسخ Velero بيانات كائن Kubernetes (Deployments وPVCs وSecrets وConfigMaps) مع بيانات PVC اختيارياً عبر لقطات CSI أو نسخ ملفات restic/kopia. هذا هو النهج القياسي لنقل namespace بالكامل أو ترحيل الكتلة. تخزِّن Velero البيانات في التخزين الكائني وهي حل DR الأكثر قابلية للنقل.
# تثبيت Velero مع إضافة لقطة AWS CSI (مختصر - راجع وثائق Velero للإعداد الكامل لـIAM) velero install \ --provider aws \ --plugins velero/velero-plugin-for-aws:v1.9.0 \ --bucket my-velero-backups \ --backup-location-config region=us-east-1 \ --snapshot-location-config region=us-east-1 \ --features=EnableCSIVolumeSnapshots # أنشئ نسخة احتياطية مجدولة لـnamespace الإنتاج كل 6 ساعات velero schedule create prod-6h \ --schedule="0 */6 * * *" \ --include-namespaces production \ --ttl 72h # شغِّل نسخة احتياطية يدوية فورية velero backup create prod-manual-20250601-1430 \ --include-namespaces production \ --wait # اعرض النسخ الاحتياطية وتحقق من الحالة velero backup get velero backup describe prod-manual-20250601-1430 --details # استعادة namespace في كتلة جديدة أو namespace مختلف velero restore create --from-backup prod-manual-20250601-1430 \ --namespace-mappings production:production-restored

أنماط فشل بيئة الإنتاج لأحمال العمل الحالتية

تُشكِّل الأعطال التالية الغالبية العظمى من حوادث التخزين في كتل Kubernetes الإنتاجية:

  • تعليق الفصل عند استنزاف العقدة: عند استنزاف عقدة، تستدعي Kubernetes ControllerUnpublishVolume (الفصل). إذا كانت العقدة لا تستجيب (عطل النواة أو تجزئة الشبكة)، تعتبر واجهة API السحابية أن وحدة التخزين لا تزال متصلة وترفض استدعاء الفصل. لـEBS مهلة توصيل افتراضية مدتها 6 دقائق. الحل هو استخدام kubectl delete node <name> بعد التأكد من موت العقدة، مما يسمح بحذف كائن VolumeAttachment بالقوة ويُطلِق إعادة التوصيل على عقدة سليمة.
  • خطأ التوصيل المتعدد: وحدات التخزين الكتلية (EBS وAzure Disk) تدعم فقط ReadWriteOnce. إذا أُعيدت جدولة Pod من StatefulSet قبل انتهاء Pod الأصلي كلياً، تحاول Kubernetes توصيل نفس وحدة التخزين بعقدتين في وقت واحد ويفشل التوصيل بخطأ Multi-Attach error. الحل: عيِّن terminationGracePeriodSeconds على مستوى Pod يمنح التطبيق وقتاً لتفريغ الكتابات والخروج بأمان.
  • تلف نظام الملفات عند الحذف القسري: يتجاوز حذف Pod بـ--grace-period=0 --force خطافات الإيقاف التدريجي. إذا كانت قاعدة البيانات في منتصف عملية كتابة، قد يكون للجهاز الكتلي سجل غير نظيف. اسمح دائماً لمحرك قاعدة البيانات بمعالجة الإيقاف (SIGTERM ثم نقطة تفتيش ثم الخروج) ولا تحذف Pods الحالتية قسراً إلا إذا تأكدت من موت العقدة.
  • حجرة برنامج تشغيل CSI على نفس العقدة مع حمل العمل: إذا تعطلت إضافة العقدة، لا يستطيع Kubelet استدعاء NodePublishVolume وستتعلق Pods الجديدة على تلك العقدة في ContainerCreating. راقب DaemonSet الخاص بـCSI بـPodDisruptionBudget لمنع إخلاء إضافة العقدة عن طريق الخطأ.
قائمة التحقق التشغيلية لأحمال العمل الحالتية في الإنتاج: (1) مكِّن allowVolumeExpansion: true منذ اليوم الأول — إضافته لاحقاً يتطلب إعادة إنشاء StorageClass. (2) جدوِل VolumeSnapshots الآلية مع TTL. (3) أجرِ تدريبات استعادة أسبوعية من نسخ Velero الاحتياطية في namespace تجريبي. (4) عيِّن reclaimPolicy: Retain على PVs الإنتاجية حتى لا يؤدي حذف PVC إلى تدمير القرص الأساسي فوراً. (5) استخدم volumeBindingMode: WaitForFirstConsumer لضمان توفير وحدات التخزين في نفس منطقة التوفر كـPod التي تستهلكها — إدخال/إخراج البيانات عبر مناطق التوفر مكلف وبطيء في EBS.