CSI وعمليات التخزين الحالتية
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 — يعرض نقطة نهاية صحة لحجرة برنامج التشغيل.
دورة حياة وحدة التخزين: التوصيل والتدريج والنشر
عند جدولة Pod يحتاج إلى PVC، تسير الخطوات التالية بترتيب صارم:
- CreateVolume — يستدعي external-provisioner برنامج تشغيل وحدة التحكم لإنشاء كائن التخزين الخام (معرِّف EBS volume أو Ceph image وما إلى ذلك).
- ControllerPublishVolume — يستدعي external-attacher برنامج تشغيل وحدة التحكم لتوصيل وحدة التخزين بالعقدة الهدف (يُعيِّن هذا إلى استدعاء EC2
AttachVolumeبالنسبة لـEBS). - NodeStageVolume — يطلب Kubelet من برنامج تشغيل العقدة تهيئة الجهاز الكتلي وتركيبه في دليل تدريج عام على العقدة. يحدث هذا مرة واحدة لكل وحدة تخزين لكل عقدة.
- 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 — كائن اللقطة الفعلي على الواجهة الخلفية، إما مُوفَّر مسبقاً من المسؤول أو مُنشَأ ديناميكياً.
تغيير حجم وحدة التخزين أثناء التشغيل
إذا كان StorageClass يحتوي على allowVolumeExpansion: true وطبَّق برنامج تشغيل CSI كلاً من ControllerExpandVolume وNodeExpandVolume، يمكنك تكبير PVC مباشرةً دون إعادة تشغيل Pod:
استراتيجيات النسخ الاحتياطي لأحمال العمل الحالتية
على نطاق الشركات الكبرى، تُستخدَم ثلاثة أنماط نسخ احتياطي معاً بدلاً من استخدام أي منها بمعزل:
- النسخ الاحتياطي على مستوى التطبيق: استخدم محرك قاعدة البيانات نفسه —
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 الأكثر قابلية للنقل.
أنماط فشل بيئة الإنتاج لأحمال العمل الحالتية
تُشكِّل الأعطال التالية الغالبية العظمى من حوادث التخزين في كتل 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 لمنع إخلاء إضافة العقدة عن طريق الخطأ.
allowVolumeExpansion: true منذ اليوم الأول — إضافته لاحقاً يتطلب إعادة إنشاء StorageClass. (2) جدوِل VolumeSnapshots الآلية مع TTL. (3) أجرِ تدريبات استعادة أسبوعية من نسخ Velero الاحتياطية في namespace تجريبي. (4) عيِّن reclaimPolicy: Retain على PVs الإنتاجية حتى لا يؤدي حذف PVC إلى تدمير القرص الأساسي فوراً. (5) استخدم volumeBindingMode: WaitForFirstConsumer لضمان توفير وحدات التخزين في نفس منطقة التوفر كـPod التي تستهلكها — إدخال/إخراج البيانات عبر مناطق التوفر مكلف وبطيء في EBS.