الأدلة التقنية

أنواع التخزين وواجهة S3: أين تضع بيانات تطبيقك؟

أين تضع قاعدة البيانات وملفات المستخدمين والنسخ الاحتياطية، ولماذا يناسب كلاً منها نوع مختلف من التخزين.

أنواع التخزين وواجهة S3: أين تضع بيانات تطبيقك؟

كل تطبيق تشغله يكتب بياناته في مكان ما، فقاعدة البيانات تكتب جداولها، والتطبيق يحفظ الصور والفواتير التي يرفعها المستخدمون، وسكربت النسخ الاحتياطي يضع ملفاته في مكان ثالث، وفي البداية يذهب كل ذلك إلى القرص نفسه على السيرفر نفسه دون أن يفكر أحد في السؤال، إلى أن يمتلئ القرص في منتصف الليل، أو يضيف الفريق سيرفراً ثانياً للتطبيق فيكتشف أن الصور التي رفعت على الأول لا تظهر على الثاني، أو يضع أحدهم قاعدة البيانات على مجلد مشترك عبر الشبكة لأنه «أسهل في النسخ» فتبدأ الأخطاء الغريبة. وهنا يظهر السؤال الذي كان يجب أن يطرح من البداية: أين يجب أن تعيش بيانات هذا التطبيق، على قرص محلي، أم في مجلد مشترك، أم في Bucket؟

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

  • المشكلة: أعراض اختيار المكان الخطأ للبيانات.
  • تخزين الكتل Block Storage وتخزين الملفات File Storage وتخزين الكائنات Object Storage، بتشبيه واحد وأمثلة حقيقية وجدول مقارنة.
  • واجهة S3 ببساطة: ال Buckets والمفاتيح Keys والبيانات الوصفية Metadata والنسخ Versioning وقواعد دورة الحياة Lifecycle والروابط الموقعة Presigned URLs والصلاحيات والرفع المجزأ Multipart Upload.
  • أدوات تخزين الكائنات التي تستضيفها بنفسك: MinIO وما الذي تغير فيه، وGarage وSeaweedFS وCeph RGW.
  • Ceph في قسم واحد صريح: مما يتكون، وأقل عدد معقول من السيرفرات، ومتى يكون أكبر من حاجتك.
  • تجربة عملية مع Garage على Docker: Bucket ومفتاح ورفع وتنزيل ورابط موقع، ثم مقارنة سريعة مع SeaweedFS في النسخ Versioning.
  • الاختيار في الحالات الشائعة، ثم الأخطاء الشائعة وقائمة تحقق.

ولن نعيد هنا شرح RAID والأقراص HDD وSSD وNVMe وقاعدة النسخ الاحتياطي 3-2-1، فقد شرحناها في مصطلحات الاستضافة الذاتية، أما ZFS وأجهزة NAS وبروتوكولات SMB وNFS وiSCSI بالتفصيل فلها مواضيع مخطط لها في قسم العتاد في خارطة الطريق، وهنا نذكر منها ما يلزم لفهم الفرق بين الأنواع فقط. وكذلك شبكة توصيل المحتوى CDN لها مقالها الخاص شبكة توصيل المحتوى CDN للمبتدئين.

ماذا يحدث عندما تضع البيانات في المكان الخطأ؟

أغلب مشكلات التخزين التي تقابلها في الاستضافة الذاتية لا تأتي من عطل في العتاد، وإنما من بيانات وضعت في نوع من التخزين صمم لاستخدام آخر، وهذه أشهر الأعراض:

  • قاعدة البيانات على مجلد مشترك عبر الشبكة: يضع أحدهم مجلد بيانات PostgreSQL أو MySQL على مجلد NFS أو SMB في جهاز NAS لأن المساحة هناك أكبر، فتذهب كل كتابة صغيرة عبر الشبكة وتنتظر الرد، وتبطئ الاستعلامات دون سبب واضح، والأسوأ أن قاعدة البيانات تعتمد على أن الكتابة التي أكدها نظام الملفات قد وصلت فعلاً إلى القرص، وهذا الضمان يصبح أضعف وأصعب في الفهم عندما يكون بينك وبين القرص شبكة وسيرفر آخر وذاكرة مؤقتة Cache في الطريق. والحل الأنسب هو قرص محلي سريع، وأداء القرص وأثره على قاعدة البيانات مشروح في كيف تختار خادماً افتراضياً وفي PostgreSQL للجداول الضخمة.
  • رفع المستخدمين يملأ قرص السيرفر: التطبيق يحفظ كل صورة وفاتورة ومرفق في مجلد بجانب الكود، فيكبر المجلد شهراً بعد شهر حتى يمتلئ قرص النظام فيتوقف كل شيء، ومنه قاعدة البيانات التي تشاركه القرص. ثم عندما تضيف سيرفراً ثانياً للتطبيق خلف موزع الحمل Load Balancer يرفع المستخدم صورته على السيرفر الأول ثم يطلبها من الثاني فلا يجدها، وهذه المشكلة ذكرناها في مدخل إلى تصميم الأنظمة.
  • الملفات تختفي عند استبدال ال Container: التطبيق يكتب في مسار داخل ال Container نفسه دون Volume، فيعمل كل شيء أسابيع، ثم تحدث ال Image وتعيد إنشاء ال Container فتختفي كل الملفات، والسبب أن طبقة الكتابة داخل ال Container تحذف معه، أما البيانات التي يجب أن تبقى فمكانها ال Volumes التي تحدثنا عن نسخها في دليل تثبيت Docker.
  • النسخة الاحتياطية على السيرفر نفسه: ملف النسخة في مجلد على القرص نفسه، فإذا تعطل القرص أو حذف السيرفر أو وصل إليه برنامج فدية Ransomware ذهبت البيانات ونسختها معاً.

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

الأنواع الثلاثة: كتل وملفات وكائنات

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

  • الخدمة الأولى أن تستأجر غرفة فارغة مرقمة الأرفف، والشركة لا تعرف ماذا تضع فيها ولا كيف ترتبه، فأنت تقرر كل شيء وتصل إلى أي رف مباشرة وبسرعة، ولكن الغرفة لك وحدك. وهذا هو تخزين الكتل Block Storage.
  • الخدمة الثانية غرفة أرشيف مشتركة فيها خزائن وأدراج عليها أسماء، ولكل موظف مفتاح لبعض الأدراج، ويدخلها الجميع في الوقت نفسه ويفتحون الملف نفسه. وهذا هو تخزين الملفات File Storage.
  • الخدمة الثالثة مكتب استلام وتسليم مثل مكتب حفظ المعاطف، فتسلم الصندوق كاملاً وتأخذ إيصالاً عليه رقم، وعندما تعود بالإيصال تستلم الصندوق كاملاً، ولا يسمح لك بأن تفتح الصندوق في المكتب وتغير ورقة واحدة فيه، وإنما تسلم صندوقاً جديداً بالإيصال نفسه. وهذا هو تخزين الكائنات Object Storage.

والصورة التالية تبين ما يراه التطبيق نفسه في كل نوع من الأنواع الثلاثة:

ثلاثة أعمدة لسيرفر تطبيق واحد: في Block يكتب في الكتلة رقم 4182 على قرص خام بلا أسماء عبر النواة محلياً أو iSCSI، وفي File يفتح الملف /shared/q3.xlsx في شجرة مجلدات عبر POSIX أو NFS أو SMB، وفي Object يرسل PUT إلى /photos/u42/a.jpg عبر HTTP إلى Bucket فيه قائمة مفاتيح، وتحت كل عمود الأمثلة والاستخدام الشائع
رقم كتلة، أو مسار ملف، أو مفتاح عبر HTTP: ثلاث طرق للوصول إلى البيانات

تخزين الكتل Block Storage

تخزين الكتل هو قرص خام يقسم إلى كتل Blocks متساوية الحجم ولكل كتلة رقم، ولا يعرف القرص شيئاً عن الملفات أو المجلدات، فنظام التشغيل هو الذي يضع فوقه نظام ملفات File System مثل ext4 أو XFS، أو تستخدمه قاعدة البيانات أو الآلة الافتراضية Virtual Machine كقرص كامل. والأمثلة التي تقابلها كل يوم:

  • القرص المحلي في سيرفرك، NVMe أو SSD أو HDD.
  • القرص الإضافي Volume الذي تشتريه من مزود ال VPS وتربطه بالسيرفر فيظهر كجهاز جديد مثل /dev/sdb، وهو غالباً قرص على شبكة المزود، ولكنه يظهر لنظامك كقرص محلي.
  • بروتوكول iSCSI الذي يقدم قرصاً عبر الشبكة من جهاز تخزين SAN أو NAS، فيراه السيرفر قرصاً عادياً.
  • Ceph RBD، وهو قرص موزع على عدة سيرفرات، وتستخدمه Proxmox وOpenStack لأقراص الآلات الافتراضية.

ونقطة قوته أنه الأسرع والأقل تأخيراً Latency، ويقبل الكتابة العشوائية الصغيرة في أي موضع، لذلك هو المكان الطبيعي لقواعد البيانات وأقراص الآلات الافتراضية. ونقطة ضعفه أن القرص الواحد يربط عادة بسيرفر واحد في الوقت نفسه، والسبب أن نظام الملفات العادي مثل ext4 لا يتحمل أن يكتب فيه جهازان معاً، فإذا ربطت القرص نفسه بسيرفرين وكتب كل منهما دون تنسيق فسوف يتلف نظام الملفات.

تخزين الملفات File Storage

تخزين الملفات هو نظام ملفات فيه مجلدات وأسماء وصلاحيات وأقفال Locks، ويصل إليه التطبيق بالمسار مثل /shared/finance/q3.xlsx، فيفتح الملف ويقرأ جزءاً منه ويعدل سطراً فيه ويغلقه. وقد يكون محلياً، أي نظام ملفات على قرص السيرفر مثل ext4 أو XFS أو ZFS، وقد يكون مشتركاً عبر الشبكة، وهنا تأتي البروتوكولات:

  • NFS: الأشهر بين سيرفرات Linux، فيربط السيرفر مجلداً من جهاز آخر Mount ويتعامل معه كأنه محلي.
  • SMB أو CIFS: الأشهر مع أجهزة Windows والمكاتب، وهو ما تراه عندما تفتح «مجلد الشبكة» في مستكشف الملفات.
  • CephFS: نظام ملفات موزع فوق Ceph، يربطه عدة سيرفرات في الوقت نفسه.

ونقطة قوته أنه مفهوم للجميع، فالموظف يعرف المجلدات، والبرامج القديمة تعمل عليه دون أي تعديل، وعدة أجهزة تشترك في الملفات نفسها. ونقطة ضعفه أن التوسع فيه أصعب، فمجلد واحد فيه ملايين الملفات يصبح بطيئاً في العرض والنسخ، ومشاركته بين مواقع بعيدة عبر الإنترنت ليست ما صمم له.

تخزين الكائنات Object Storage

تخزين الكائنات يحفظ كل ملف ككائن Object كامل داخل حاوية مسطحة اسمها Bucket، ولكل كائن مفتاح Key هو اسمه، وبيانات وصفية Metadata مثل نوع المحتوى، ويصل إليه التطبيق عبر HTTP بطلبات مثل PUT وGET وDELETE، والواجهة التي أصبحت المعيار هي واجهة Amazon S3. والفرق المهم عن تخزين الملفات أنك لا تعدل جزءاً من الكائن، وإنما ترفعه كاملاً من جديد بالمفتاح نفسه، تماماً كالصندوق في مكتب الحفظ.

ونقطة قوته أنه يتوسع إلى أحجام كبيرة جداً بسهولة، ويصل إليه أي سيرفر أو متصفح من أي مكان عبر HTTP، وفيه أدوات جاهزة للصلاحيات والروابط المؤقتة والنسخ والحذف التلقائي، لذلك هو المكان الطبيعي لملفات المستخدمين والنسخ الاحتياطية والوسائط. ونقطة ضعفه أنه أبطأ من القرص في كل طلب، لأن كل عملية طلب HTTP كامل، وأنه لا يناسب البرامج التي تتوقع نظام ملفات وتعدل أجزاء من الملفات، لذلك لا تضع عليه قاعدة بيانات.

المقارنة بين الأنواع الثلاثة

المعيارBlockFileObject
طريقة الوصولرقم الكتلة عبر نظام التشغيل، محلياً أو iSCSIمسار الملف عبر نظام الملفات، محلياً أو NFS وSMBمفتاح عبر HTTP بواجهة S3
زمن الاستجابة Latencyالأقل، خاصة NVMe المحليمنخفض محلياً، وأعلى عبر الشبكةالأعلى، لأن كل عملية طلب HTTP
التوسع Scalingبحجم القرص أو المصفوفةمحدود بالسيرفر الذي يقدم المجلديتوسع بإضافة سيرفرات وأقراص
المشاركة بين الأجهزةجهاز واحد عادةعدة أجهزة في الشبكة نفسهاأي عدد من الأجهزة والمتصفحات من أي مكان
تعديل جزء من الملفنعمنعملا، يرفع الكائن كاملاً
الاستخدام الشائعقواعد البيانات والآلات الافتراضيةالملفات المشتركة للفريق والبرامج القديمةملفات المستخدمين والنسخ الاحتياطية والوسائط والملفات الثابتة
أدوات تستضيفها بنفسكقرص محلي وLVM وiSCSI وCeph RBDNFS وSamba وTrueNAS وCephFSGarage وSeaweedFS وCeph RGW

واجهة S3 ببساطة

بدأت S3 كخدمة من Amazon، ثم أصبحت واجهتها البرمجية API هي اللغة التي يتحدث بها كل تخزين كائنات تقريباً، فإذا قال تطبيق إنه يدعم S3 فهذا يعني غالباً أنه يستطيع أن يتعامل مع Garage أو SeaweedFS أو Ceph أو Cloudflare R2 بتغيير عنوان ال Endpoint فقط، وهذا ما يجعل فهم مصطلحاتها مفيداً حتى لو لم تستخدم Amazon أبداً.

ال Buckets والمفاتيح ووهم المجلدات

ال Bucket حاوية لها اسم، وفيها الكائنات، ولكل كائن مفتاح Key هو اسمه الكامل مثل uploads/invoices/invoice-1042.pdf. وقد يتساءل البعض: إذا كان المفتاح فيه / فأين المجلدات؟ والإجابة أنه لا توجد مجلدات أصلاً، فال Bucket قائمة مسطحة من المفاتيح، والشرطة المائلة Slash حرف عادي في المفتاح، والمجلدات التي تراها في الواجهات والأدوات يرسمها الكلاينت عندما يطلب المفاتيح التي تبدأ ببادئة Prefix معينة ويطلب تجميعها عند الحرف /، وهذا ما يشرحه توثيق S3 عن تنظيم الكائنات بالبادئات.

ولكل كائن بيانات وصفية Metadata، منها ما يحدده النظام مثل Content-Type والحجم وتاريخ التعديل وال ETag، ومنها ما تضيفه أنت بمفاتيح تبدأ بـ x-amz-meta-، كما في توثيق البيانات الوصفية. والصورة التالية تبين كائناً واحداً من التجربة العملية في هذا المقال، والقائمة المسطحة التي يحفظها ال Bucket فعلاً، والشجرة التي يرسمها الكلاينت منها:

ثلاثة أقسام: الأول كائن واحد فيه المفتاح uploads/invoices/invoice-1042.pdf والبيانات 13 بايت والبيانات الوصفية Content-Type application/pdf وx-amz-meta-customer 1042 وال ETag ورقم النسخة عند تفعيل النسخ، والثاني Bucket اسمه backups فيه قائمة مسطحة من خمسة مفاتيح، والثالث شجرة مجلدات يرسمها الكلاينت من البادئة والفاصل /
ال Bucket يحفظ قائمة مسطحة، والمجلدات يرسمها الكلاينت من البادئة Prefix والفاصل Delimiter

النسخ Versioning وقواعد دورة الحياة Lifecycle

عندما ترفع كائناً بمفتاح موجود فإن الجديد يحل محل القديم، وعندما تحذفه يذهب، إلا إذا فعلت النسخ Versioning على ال Bucket، فعندها يحتفظ التخزين بكل نسخة سابقة برقم VersionId، والحذف لا يحذف شيئاً فعلاً، وإنما يضع علامة حذف Delete Marker فوق النسخ، وتستطيع أن تستعيد أي نسخة سابقة. وهذا مهم جداً للنسخ الاحتياطية، والسبب أن سكربتاً خاطئاً أو مخترقاً يكتب فوق ملفاتك لن يمحو النسخ السابقة. وفوق النسخ هناك قفل الكائنات Object Lock الذي يمنع حذف النسخة أو تعديلها حتى تاريخ تحدده، حتى على من يملك المفاتيح، وله موضوع مخطط له في قسم التخزين في خارطة الطريق عن النسخ الاحتياطي ضد برامج الفدية.

أما قواعد دورة الحياة Lifecycle Rules فهي قواعد تطبق على الكائنات تلقائياً، مثل «احذف ما تحت البادئة tmp/ بعد 7 أيام»، أو «احذف النسخ السابقة بعد 90 يوماً» حتى لا يكبر ال Bucket بلا نهاية بعد تفعيل النسخ، أو «ألغ الرفع المجزأ الذي لم يكتمل بعد يومين».

الروابط الموقعة Presigned URLs

لنفرض أن لديك تطبيقاً فيه فواتير لكل عميل، وأن الفواتير في Bucket خاص، فكيف ينزل العميل فاتورته؟ الحل الأول الذي يخطر على البال أن تجعل ال Bucket عاماً، وهذا الحل غير مناسب إطلاقاً، لأن كل من يخمن رابط فاتورة يستطيع تنزيلها. والحل الثاني أن يمر الملف عبر التطبيق، فيقرأه التطبيق من التخزين ويرسله للمتصفح، وهذا يعمل ولكنه يحمل التطبيق كل البايتات، فملف فيديو من 2 GB يمر كاملاً عبر سيرفر التطبيق وذاكرته. والحل الأنسب هو الرابط الموقع Presigned URL: يتحقق التطبيق أن هذا العميل يملك الفاتورة، ثم يوقع رابطاً بمفتاحه السري لهذا الكائن وحده ولمدة قصيرة، ويعطيه للمتصفح، فينزل المتصفح الملف من التخزين مباشرة. ولاحظ أن التوقيع يحسب في التطبيق نفسه دون أي اتصال بالتخزين، والتخزين عند الطلب يعيد حساب التوقيع ويتحقق من الوقت، كما في الصورة التالية:

مخطط من خمس خطوات: المتصفح يطلب الفاتورة 1042 من التطبيق وهو مسجل دخوله، والتطبيق يتحقق من المستخدم ويوقع الرابط بمفتاحه دون الاتصال بالتخزين، ثم يعيد الرابط الموقع وفيه X-Amz-Credential وX-Amz-Date وX-Amz-Expires=60 وX-Amz-Signature، ثم يطلب المتصفح الرابط من التخزين مباشرة، والتخزين يرد بـ 200 إذا كان التوقيع والوقت صحيحين، و403 Invalid signature إذا تغير الرابط، و400 Date is too old إذا انتهت المدة
المفتاح السري لا يغادر التطبيق، والملف لا يمر عبره

والرابط الموقع مثل الإيصال في مكتب الحفظ، فمن يحمله يستلم الصندوق، لذلك اجعل مدته قصيرة بقدر الحاجة، فأداة AWS CLI تجعل المدة الافتراضية ساعة والحد الأقصى 7 أيام كما في توثيق الأمر presign، ولا تضع الرابط في سجلات Logs عامة أو في مستودع كود.

📌
في يوليو 2020 نشر فريق أبحاث الذكاء الاصطناعي في Microsoft على GitHub رابطاً لتنزيل نماذج مفتوحة المصدر من تخزين Azure، والرابط فيه توقيع SAS، وهو مقابل الرابط الموقع في Azure، ولكن التوقيع كان على حساب التخزين كله وليس على الملفات المقصودة، وبصلاحية تحكم كامل وليس قراءة فقط، ثم مدد تاريخ انتهائه في 2021 إلى عام 2051. وفي 22 يونيو 2023 اكتشف باحثو Wiz أن الرابط يكشف 38 TB من البيانات الخاصة، منها نسخ احتياطية لجهازي موظفين فيها كلمات مرور ومفاتيح وأكثر من 30 ألف رسالة داخلية، فأبلغوا Microsoft التي ألغت التوقيع في 24 يونيو 2023، كما في تقرير Wiz. والدرس أن الرابط الموقع يكون لكائن واحد، وللقراءة فقط، ولمدة قصيرة.

الصلاحيات: مفاتيح الوصول وسياسات ال Bucket

التطبيق يدخل إلى S3 بمفتاحين: معرف المفتاح Access Key ID والمفتاح السري Secret Access Key، وكل طلب يوقع بهما. ثم هناك طريقتان لتحديد الصلاحيات: صلاحيات مرتبطة بالمفتاح أو المستخدم، أي ماذا يستطيع هذا المفتاح أن يفعل، وسياسة ال Bucket Bucket Policy وهي مستند JSON على ال Bucket نفسه يحدد من يستطيع ماذا، ومنها السياسة التي تجعل ال Bucket عاماً للقراءة. ولأن ال Buckets العامة بالخطأ كانت من أشهر أسباب تسرب البيانات، أعلنت Amazon أنها من أبريل 2023 تفعل منع الوصول العام Block Public Access تلقائياً على كل Bucket جديد. ولاحظ أن الأدوات المستضافة ذاتياً تختلف هنا، فبعضها يدعم سياسات ال Bucket كاملة، وبعضها مثل Garage له نظام صلاحيات خاص به، كما سنرى في الجدول بعد قليل.

والقاعدة العملية: مفتاح منفصل لكل تطبيق، بصلاحية على ال Bucket الخاص به فقط، والمفتاح الذي يكتب النسخ الاحتياطية لا يملك صلاحية الحذف إن أمكن، والسبب أن المفتاح المسروق من سيرفر واحد يجب ألا يفتح بيانات كل التطبيقات.

الرفع المجزأ والاتساق

الرفع المجزأ Multipart Upload يقسم الملف الكبير إلى أجزاء ترفع بالتوازي، وإذا انقطع جزء يعاد هو وحده، وتنصح Amazon به للملفات من 100 MB فما فوق، وعدد الأجزاء من 1 إلى 10,000. والأدوات مثل AWS CLI وrclone تستخدمه تلقائياً للملفات الكبيرة. وانتبه إلى أن الرفع الذي بدأ ولم يكتمل تبقى أجزاؤه محفوظة وتأخذ مساحة حتى تكمله أو تلغيه، لذلك يوصي التوثيق بقاعدة Lifecycle تلغي الرفع غير المكتمل بعد عدد من الأيام.

أما الاتساق Consistency فهو سؤال: إذا كتبت كائناً ثم قرأته فوراً، فهل أقرأ النسخة الجديدة؟ في البدايات كانت S3 تعد بالاتساق النهائي Eventual Consistency في بعض الحالات، أي أن القراءة قد تعيد النسخة القديمة لفترة قصيرة، ثم في 1 ديسمبر 2020 أعلنت Amazon أن S3 أصبحت تقدم اتساقاً قوياً Strong Read-After-Write Consistency لكل الطلبات، ومنها عرض القوائم List. ويجدر الانتباه إلى أن هذا وعد Amazon لخدمتها، أما الأدوات المتوافقة مع S3 فلكل منها ضماناتها، فاقرأ توثيق الأداة التي تختارها قبل أن تبني عليه منطقاً حساساً.

تخزين الكائنات على سيرفرك

عندما تريد واجهة S3 دون مزود سحابي، فأمامك عدة أدوات، ولكل منها حجم وفلسفة مختلفة، ونبدأ بأشهرها لأن وضعه تغير كثيراً في السنة الأخيرة.

MinIO: ما الذي تغير؟

كان MinIO لسنوات الخيار الأول لمن يريد S3 على سيرفره، وهو برخصة AGPL-3.0، ولكن ما حدث للنسخة المجتمعية Community Edition في 2025 و2026 يجعلنا لا ننصح به لمشروع جديد، وهذه التغييرات كما نشرتها MinIO نفسها في مستودعها:

  • مايو 2025: في ملاحظات الإصدار RELEASE.2025-05-24T17-08-30Z أعلنت أن واجهة الإدارة المدمجة Embedded UI Console أصبحت مهملة Deprecated ونقلت إلى مشروع منفصل، وأن تسجيل الدخول عبر LDAP وOIDC أزيل منها وأصبح جزءاً من منتجها التجاري AIStor.
  • 15 أكتوبر 2025: حدثت المستودع لتقول إن النسخة المجتمعية أصبحت توزع ككود مصدري فقط Source-only، وفي ملاحظات الإصدار نفسه، وهو إصدار لإصلاح ثغرة أمنية، كانت طريقة التثبيت المذكورة البناء بالأمر go install، وللحاويات أن تبني ال Image بنفسك من الكود.
  • 3 ديسمبر 2025: أضافت إلى ملف README أن المشروع في وضع الصيانة Maintenance Mode، فلا ميزات جديدة ولا طلبات دمج، والإصلاحات الأمنية الحرجة تدرس حالة بحالة.
  • 12 فبراير 2026: غيرت النص إلى أن المستودع لم يعد يصان THIS REPOSITORY IS NO LONGER MAINTAINED، واقترحت بدلاً منه AIStor Free وAIStor Enterprise، ثم أصبح المستودع مؤرشفاً للقراءة فقط في 25 أبريل 2026 بحسب ما تعرضه صفحته على GitHub.

وهذا لا يعني أن تركيباتك الحالية توقفت عن العمل، ولكنه يعني أنها لن تحصل على إصلاحات أمنية من المستودع المفتوح، لذلك إذا كان لديك MinIO يعمل فخطط لنقل بياناته إلى أداة أخرى، وبما أن الأدوات كلها تتحدث S3 فالنقل ممكن بأداة مثل rclone، ولهذا موضوع مخطط له في قسم التخزين.

Garage

Garage من تطوير مجموعة Deuxfleurs الفرنسية، برخصة AGPL-3.0، وهو مكتوب بلغة Rust وصمم كما تقول صفحة أهدافه لعدة مواقع متصلة عبر الإنترنت العادي، مثل مكتب وبيت ومركز بيانات، وأن يكون خفيفاً وبسيطاً في التشغيل، ومن أهدافه المعلنة ألا يسعى لأعلى أداء ممكن ولا لكل ميزات S3، فهو ينسخ البيانات كاملة ولا يستخدم ترميز المحو Erasure Coding. ويعمل على سيرفر واحد للتجربة، أما في الإنتاج Production فالأمثلة التي يذكرها توثيقه تعمل على عدة سيرفرات في ثلاثة مواقع.

ونقاط قوته أنه خفيف وسهل الفهم، ويتحمل التأخير بين المواقع، ورفعه مجزأ وروابطه الموقعة تعمل. ونقاط ضعفه أن صفحة التوافق مع S3 تذكر أن النسخ Versioning وObject Lock وسياسات ال Bucket غير موجودة، وأن Lifecycle مدعوم جزئياً للحذف وإلغاء الرفع غير المكتمل فقط، وهذا مهم إذا كنت تريده للنسخ الاحتياطية ضد برامج الفدية.

SeaweedFS

SeaweedFS برخصة Apache-2.0، وهو مكتوب بلغة Go، ونظام تخزين موزع يقدم أكثر من واجهة، منها S3 ونظام ملفات عبر ال Filer، وصمم في الأصل لعدد كبير جداً من الملفات الصغيرة. وبحسب صفحة واجهة S3 في توثيقه فهو يدعم النسخ Versioning وObject Lock وسياسات ال Bucket والروابط الموقعة والرفع المجزأ، ويدعم Lifecycle دون قواعد النقل بين الفئات Transition.

ونقاط قوته أن ميزات S3 فيه أوسع من Garage، وأنه يتوسع إلى أحجام كبيرة، ويبدأ بأمر واحد على سيرفر واحد. ونقاط ضعفه أن مكوناته أكثر، فهناك Master وVolume Server وFiler وبوابة S3، وفهمها كلها يحتاج وقتاً، وأنه دون ملف إعداد للهويات يقبل أي مفتاح كما سنرى في التجربة، لذلك يجب أن تضبط الهويات والمفاتيح قبل أي استخدام حقيقي.

Ceph RGW

Ceph RGW أو Object Gateway هو بوابة S3 وSwift فوق Ceph، وCeph برخصة LGPL-2.1 أو LGPL-3 كما في ملف الرخصة. ونقاط قوته أنه يدعم جزءاً واسعاً من واجهة S3 بحسب جدول الميزات، ومنها النسخ وسياسات ال Bucket وLifecycle ومنع الوصول العام، ويدعم Object Lock كما في عمليات ال Bucket، ويأتي مع تخزين الكتل والملفات في النظام نفسه. ونقاط ضعفه أنه يحتاج Ceph كاملاً تحته، وهذا موضوع القسم التالي.

المقارنة بين الأدوات

الأداةالرخصةالأنسب لهأقل عدد من السيرفراتالتعقيدVersioning وObject Lock
GarageAGPL-3.0مجموعات صغيرة موزعة على عدة مواقع1 للتجربة، وعدة سيرفرات في 3 مواقع للإنتاجمنخفضغير موجودين
SeaweedFSApache-2.0ملايين الملفات والأحجام الكبيرة، وميزات S3 أوسع1 للتجربة، وأكثر من سيرفر للتكرارمتوسطمدعومان
Ceph RGWLGPL-2.1 أو LGPL-3مؤسسة تحتاج Block وFile وObject معاً3 للإنتاجمرتفعمدعومان
MinIO (المجتمعية)AGPL-3.0لا ننصح به لمشروع جديدغير منطبقغير منطبقالمستودع لم يعد يصان

Ceph في قسم واحد صريح

Ceph نظام تخزين موزع يجمع أقراص عدة سيرفرات في مخزن واحد، ويقدم منه الأنواع الثلاثة معاً، وفكرته الأساسية بحسب توثيق البنية أن كل شيء في الأسفل يحفظ ككائنات في طبقة اسمها RADOS، ثم فوقها ثلاث واجهات، كما في الصورة التالية:

مخطط Ceph: في الأعلى آلات افتراضية تستخدم RBD كقرص، وسيرفرات تربط مجلداً عبر CephFS مع MDS، وتطبيقات تستخدم RGW عبر S3، وكلها فوق طبقة RADOS التي فيها CRUSH يحسب مكان الكائن من PG إلى osd.1 وosd.4 وosd.8، وثلاثة MONs وMGR، وثلاثة سيرفرات في كل منها ثلاثة OSDs، وثلاث نسخ من الكائن نسخة على كل سيرفر
ثلاث واجهات فوق RADOS، وCRUSH يضع كل نسخة على سيرفر مختلف

والمكونات كما يعرفها توثيق مكونات ال Cluster:

  • OSD: خدمة تدير وحدة تخزين واحدة، غالباً قرصاً كاملاً، فتحفظ الكائنات وتنسخها وتستعيدها وتعيد توزيعها، والتوثيق يقول إن عددها يجب أن يكون على الأقل بعدد النسخ، وثلاثة للتكرار في الإنتاج.
  • MON: المراقب Monitor يحفظ خرائط حالة ال Cluster ويتولى المصادقة، والتوثيق يقول ثلاثة للتكرار، ويجب أن تعمل أغلبيتها وإلا يتوقف ال Cluster، وواحد يكفي لتجربة فقط.
  • MGR: المدير Manager يتابع المقاييس ويستضيف لوحة التحكم Dashboard وأدوات الإدارة، واثنان على الأقل للتوفر العالي.
  • MDS: سيرفر البيانات الوصفية لنظام الملفات CephFS، ولا تحتاجه إلا إذا استخدمت CephFS.
  • CRUSH: الخوارزمية التي تحسب مكان كل كائن، فتضعه في مجموعة مواضع Placement Group، ثم تحسب على أي OSDs تحفظ نسخ هذه المجموعة، وتتجنب أن تضع نسختين على السيرفر نفسه، وكل كلاينت يحسب المكان بنفسه دون أن يسأل جدولاً مركزياً.

والقيمة الافتراضية لعدد النسخ في كل Pool هي ثلاث نسخ بحسب إعدادات ال Pools، أي أن كل 1 TB من البيانات يأخذ 3 TB من الأقراص، ومع ثلاث نسخ وقاعدة «نسخة لكل سيرفر» فأقل Cluster معقول للإنتاج ثلاثة سيرفرات، وكثير من الفرق تبدأ بأربعة أو خمسة حتى يبقى للنظام مكان يعيد فيه بناء النسخ عندما يتعطل سيرفر. والإصدارات النشطة اليوم بحسب صفحة الإصدارات هي Tentacle من السلسلة 20.2 وSquid من السلسلة 19.2.

هل تحتاج Ceph؟ إذا كان لديك سيرفر أو اثنان، وفريق صغير لا يوجد فيه من يتابع التخزين بشكل يومي، فالإجابة غالباً لا، والسبب أن Ceph يحتاج ثلاثة سيرفرات على الأقل وشبكة سريعة بينها، ويحتاج من يفهم ماذا يعني أن تصبح مجموعة مواضع في حالة Degraded في الثالثة فجراً. أما إذا كان لديك Cluster من Proxmox بثلاثة سيرفرات أو أكثر، وتريد أن تنتقل الآلات الافتراضية بينها دون قرص مشترك من جهاز واحد، فهنا يصبح Ceph منطقياً جداً. وله موضوع مخطط له «Ceph للمبتدئين» في قسم التخزين في خارطة الطريق.

تجربة عملية: Garage على Docker

الآن سوف نقوم بتشغيل Garage على سيرفر واحد داخل Docker، ثم ننشئ Bucket ومفتاحاً، ونرفع ملفين بأداة AWS CLI، ونرى أن المجلدات مجرد بادئات، ثم نولد رابطاً موقعاً ونجربه بأداة curl، ونكسره عمداً حتى نرى الأخطاء. ولن نثبت AWS CLI ولا curl على السيرفر، وإنما نشغلهما من Images الرسمية على شبكة Docker نفسها، وإذا لم يكن Docker مثبتاً لديك فابدأ من دليل تثبيت Docker.

تشغيل Garage

نبدأ بملف الإعداد كما في دليل البداية السريعة مع تعديل المسارات، حيث يولد الأمر openssl السر بين العقد وتوكن الإدارة:

mkdir -p ~/lab-storage && cd ~/lab-storage
cat > garage.toml <<EOF
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"

replication_factor = 1

rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"
rpc_secret = "$(openssl rand -hex 32)"

[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage.localhost"

[admin]
api_bind_addr = "[::]:3903"
admin_token = "$(openssl rand -base64 32)"
EOF

docker network create lab-storage-net
docker volume create lab-storage-garage-data
docker run -d --name lab-storage-garage --network lab-storage-net \
  -p 127.0.0.1:39000:3900 \
  -v "$PWD/garage.toml:/etc/garage.toml:ro" \
  -v lab-storage-garage-data:/var/lib/garage \
  dxflrs/garage:v2.3.0 /garage server --single-node

docker exec lab-storage-garage /garage status

والمخرج سوف يكون كما يلي، بعد حذف أسطر السجل INFO:

==== HEALTHY NODES ====
ID                Hostname      Address         Tags       Zone  Capacity    DataAvail          Version
0c1d72d5d1181cc4  6f319a4e2b97  127.0.0.1:3901  [default]  dc1   1006.9 GiB  724.9 GiB (72.0%)  v2.3.0

في الإعداد أعلاه لاحظ التالي:

  • القيمة replication_factor = 1 تعني نسخة واحدة من كل كائن، وهي مناسبة للتجربة فقط، فإذا تعطل هذا القرص ضاعت البيانات، وفي الإنتاج تكون 3 على ثلاث مناطق.
  • الخيار --single-node يجعل Garage ينشئ تخطيط ال Cluster Layout لعقدة واحدة تلقائياً، ولذلك ظهرت العقدة في المنطقة dc1 دون أوامر layout assign وlayout apply التي تحتاجها مع عدة عقد.
  • نشرنا منفذ S3 على 127.0.0.1 فقط، أما منفذ الإدارة 3903 ومنفذ التواصل بين العقد 3901 فلم ننشرهما، وأسباب ذلك في شبكات Docker وأفضل الممارسات.
  • البيانات والبيانات الوصفية في Volume اسمه lab-storage-garage-data، فلا تضيع إذا أعدت إنشاء ال Container.

Bucket ومفتاح وصلاحية

في Garage ينشأ ال Bucket والمفتاح من سطر الأوامر، ثم تعطي المفتاح صلاحية على ال Bucket:

docker exec lab-storage-garage /garage bucket create backups
docker exec lab-storage-garage /garage key create app-key
docker exec lab-storage-garage /garage bucket allow --read --write backups --key app-key

والمخرج سوف يكون كما يلي، وقد اختصرنا المفتاح السري:

==== ACCESS KEY INFORMATION ====
Key ID:              GK4747f72ad3393c14211d8fa0
Key name:            app-key
Secret key:          d664debd…(مختصر)
Created:             2026-10-06 03:09:50.979 +00:00
Validity:            valid
Expiration:          never

Can create buckets:  false

==== BUCKET INFORMATION ====
Bucket:          8fc1c30bfb2a78a23fa88029ba3428751a1bf66dacd7fbc1d8cc55bae7e00a2d
Created:         2026-10-06 03:09:50.659 +00:00

Size:            0 B (0 B)
Objects:         0

Website access:  false

Global alias:    backups

==== KEYS FOR THIS BUCKET ====
Permissions  Access key                           Local aliases
RW           GK4747f72ad3393c14211d8fa0  app-key

ولاحظ أن المفتاح لا يستطيع إنشاء Buckets Can create buckets: false، وأن صلاحيته قراءة وكتابة على backups فقط، وهذا هو المفتاح الذي يأخذه التطبيق، أما الإدارة فتبقى في سطر الأوامر على السيرفر.

الرفع والعرض: المجلدات مجرد بادئات

نضع بيانات المفتاح في ملف بيئة Environment File يقرأه AWS CLI، ثم نرفع ملفين بمفتاحين فيهما /، ونضيف إلى الفاتورة نوع المحتوى وبياناً وصفياً خاصاً:

cat > aws.env <<EOF
AWS_ACCESS_KEY_ID=GK4747f72ad3393c14211d8fa0
AWS_SECRET_ACCESS_KEY=<the secret key>
AWS_DEFAULT_REGION=garage
AWS_ENDPOINT_URL=http://lab-storage-garage:3900
EOF
chmod 600 aws.env
mkdir -p files
echo "pg_dump of shop db, 2026-10-06" > files/shop.sql
echo "invoice 1042" > files/invoice-1042.pdf

aws() { docker run --rm --network lab-storage-net --env-file aws.env -v "$PWD/files:/data" amazon/aws-cli:latest "$@"; }
aws s3 cp /data/shop.sql s3://backups/db/2026/10/shop.sql
aws s3 cp /data/invoice-1042.pdf s3://backups/uploads/invoices/invoice-1042.pdf --content-type application/pdf --metadata customer=1042
aws s3 ls s3://backups
aws s3 ls s3://backups --recursive

والمخرج سوف يكون كما يلي:

upload: ../data/shop.sql to s3://backups/db/2026/10/shop.sql
upload: ../data/invoice-1042.pdf to s3://backups/uploads/invoices/invoice-1042.pdf
                           PRE db/
                           PRE uploads/
2026-10-06 03:10:05         31 db/2026/10/shop.sql
2026-10-06 03:10:07         13 uploads/invoices/invoice-1042.pdf

الأمر الأول عرض PRE db/ وPRE uploads/ وكأنهما مجلدان، والثاني عرض الحقيقة: مفتاحان كاملان في قائمة واحدة. ولنرى كيف تصنع الأداة هذه «المجلدات»، نطلب القائمة بالواجهة الخام مع بادئة وفاصل، ثم نقرأ البيانات الوصفية للفاتورة:

aws s3api list-objects-v2 --bucket backups --prefix uploads/ --delimiter /
aws s3api head-object --bucket backups --key uploads/invoices/invoice-1042.pdf

والمخرج سوف يكون كما يلي:

{
    "CommonPrefixes": [
        {
            "Prefix": "uploads/invoices/"
        }
    ],
    "RequestCharged": null,
    "Prefix": "uploads/"
}
{
    "AcceptRanges": "bytes",
    "LastModified": "2026-10-06T03:10:07+00:00",
    "ContentLength": 13,
    "ETag": "\"31a477a1c2e26dedcf1da662e5d262a4\"",
    "ContentType": "application/pdf",
    "Metadata": {
        "customer": "1042"
    }
}

في المخرج أعلاه لاحظ التالي:

  • التخزين لم يعد «مجلداً» اسمه invoices، وإنما جمع المفاتيح التي تبدأ بـ uploads/ وقطعها عند أول / بعد البادئة، وأعادها في CommonPrefixes، وهذا كل ما تفعله أي واجهة رسومية تعرض لك مجلدات.
  • البيان الوصفي الذي أضفناه بالخيار --metadata customer=1042 رجع في Metadata، وعلى مستوى HTTP يرسل كترويسة x-amz-meta-customer كما سنرى بعد قليل.

والآن نكسر الوهم عمداً، فنحذف الملف الوحيد تحت db/ ثم نعرض ال Bucket:

aws s3 rm s3://backups/db/2026/10/shop.sql
aws s3 ls s3://backups
delete: s3://backups/db/2026/10/shop.sql
                           PRE uploads/

اختفى «المجلد» db/ مع آخر مفتاح يبدأ به، لأنه لم يكن موجوداً في الأصل.

الرابط الموقع من الداخل

نجرب أولاً أن نطلب الفاتورة دون أي توقيع، ثم نولد رابطاً موقعاً صالحاً 60 ثانية ونطلبه، ونستخدم curl من Image رسمية على الشبكة نفسها:

curl() { docker run --rm --network lab-storage-net curlimages/curl:latest "$@"; }
curl -s -i http://lab-storage-garage:3900/backups/uploads/invoices/invoice-1042.pdf
URL=$(aws s3 presign s3://backups/uploads/invoices/invoice-1042.pdf --expires-in 60)
echo "$URL"
curl -s -i "$URL"

والمخرج سوف يكون كما يلي:

HTTP/1.1 403 Forbidden
content-type: application/xml
access-control-allow-origin: *
content-length: 238
date: Tue, 06 Oct 2026 03:10:41 GMT

<?xml version="1.0" encoding="UTF-8"?><Error><Code>AccessDenied</Code><Message>Forbidden: Garage does not support anonymous access yet</Message><Resource>/backups/uploads/invoices/invoice-1042.pdf</Resource><Region>garage</Region></Error>
http://lab-storage-garage:3900/backups/uploads/invoices/invoice-1042.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=GK4747f72ad3393c14211d8fa0%2F20261006%2Fgarage%2Fs3%2Faws4_request&X-Amz-Date=20261006T031043Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host&X-Amz-Signature=08beacb5b850eabb1c238bd86de71b94f0ad54ac37748164e8de6d138042b884
HTTP/1.1 200 OK
last-modified: Tue, 06 Oct 2026 03:10:07 GMT
accept-ranges: bytes
etag: "31a477a1c2e26dedcf1da662e5d262a4"
content-type: application/pdf
x-amz-meta-customer: 1042
content-length: 13
date: Tue, 06 Oct 2026 03:10:46 GMT

invoice 1042

في المخرج أعلاه لاحظ التالي:

  • الطلب دون توقيع رفض بـ 403، وGarage كما تقول رسالة الخطأ لا يقبل الوصول المجهول Anonymous Access عبر واجهة S3، والنشر العام عنده يكون بتفعيل وضع الموقع Website على Bucket بعينه.
  • الرابط الموقع فيه معرف المفتاح العام X-Amz-Credential والتاريخ والمدة X-Amz-Expires=60 والتوقيع X-Amz-Signature، ولا يوجد فيه المفتاح السري، فالتوقيع يثبت أن من يملك المفتاح السري وافق على هذا الطلب بالذات.
  • الرد يحمل نوع المحتوى والبيان الوصفي x-amz-meta-customer، أي أن المتصفح سوف يعرف أنه PDF.

والآن نكسره بطريقتين: نغير المسار في الرابط نفسه، أي نحاول استخدام التوقيع لملف آخر، ثم ننتظر حتى تنتهي الستون ثانية ونطلب الرابط الأصلي:

curl -s -i "${URL/invoice-1042.pdf/shop.sql}"
sleep 60
curl -s -i "$URL"
HTTP/1.1 403 Forbidden
content-type: application/xml
access-control-allow-origin: *
content-length: 203
date: Tue, 06 Oct 2026 03:10:59 GMT

<?xml version="1.0" encoding="UTF-8"?><Error><Code>AccessDenied</Code><Message>Forbidden: Invalid signature</Message><Resource>/backups/uploads/invoices/shop.sql</Resource><Region>garage</Region></Error>
HTTP/1.1 400 Bad Request
content-type: application/xml
access-control-allow-origin: *
content-length: 213
date: Tue, 06 Oct 2026 03:11:51 GMT

<?xml version="1.0" encoding="UTF-8"?><Error><Code>InvalidRequest</Code><Message>Bad request: Date is too old</Message><Resource>/backups/uploads/invoices/invoice-1042.pdf</Resource><Region>garage</Region></Error>

المسار جزء مما وقع، فأي تغيير فيه يعطي Invalid signature، وبعد انتهاء المدة رفض الرابط الأصلي نفسه بالرسالة Date is too old. وإذا رأيت هذه الرسالة مع رابط جديد لم تنته مدته، فأول ما تفحصه هو ساعة السيرفر الذي وقع الرابط، لأن التوقيع يحمل وقت التوقيع، وساعة متأخرة أو متقدمة دقائق تكفي لرفضه.

ماذا عن النسخ Versioning؟

نحاول تفعيل النسخ على ال Bucket نفسه في Garage:

aws s3api put-bucket-versioning --bucket backups --versioning-configuration Status=Enabled
aws: [ERROR]: An error occurred (NotImplemented) when calling the PutBucketVersioning operation: Unimplemented action: PutBucketVersioning

وهذا يطابق ما تقوله صفحة التوافق، فلا يوجد Versioning في Garage. وللمقارنة شغلنا SeaweedFS 4.48 ببوابة S3 بأمر واحد على الشبكة نفسها، ثم فعلنا النسخ، ورفعنا ملفاً مرتين بالمفتاح نفسه، ثم حذفناه، ثم عرضنا النسخ:

docker run -d --name lab-storage-seaweed --network lab-storage-net chrislusf/seaweedfs:4.48 server -s3 -dir=/data
sw() { docker run --rm --network lab-storage-net -e AWS_ACCESS_KEY_ID=any -e AWS_SECRET_ACCESS_KEY=any -e AWS_DEFAULT_REGION=us-east-1 -e AWS_ENDPOINT_URL=http://lab-storage-seaweed:8333 --entrypoint sh amazon/aws-cli:latest -c "$1"; }
sw 'aws s3 mb s3://backups && aws s3api put-bucket-versioning --bucket backups --versioning-configuration Status=Enabled && aws s3api get-bucket-versioning --bucket backups'
sw 'echo "config v1" > /tmp/c.txt && aws s3 cp /tmp/c.txt s3://backups/app/config.txt >/dev/null && echo "config v2" > /tmp/c.txt && aws s3 cp /tmp/c.txt s3://backups/app/config.txt >/dev/null && aws s3 rm s3://backups/app/config.txt && aws s3 ls s3://backups --recursive; aws s3api list-object-versions --bucket backups --prefix app/ --query "{Versions:Versions[].{VersionId:VersionId,IsLatest:IsLatest,Size:Size},DeleteMarkers:DeleteMarkers[].{VersionId:VersionId,IsLatest:IsLatest}}"'

والمخرج سوف يكون كما يلي:

make_bucket: backups
{
    "Status": "Enabled"
}
delete: s3://backups/app/config.txt
{
    "Versions": [
        {
            "VersionId": "67242d9de7cb2d163abc40178b52f039",
            "IsLatest": false,
            "Size": 10
        },
        {
            "VersionId": "67242d9e250f054752655fd0a220b209",
            "IsLatest": false,
            "Size": 10
        }
    ],
    "DeleteMarkers": [
        {
            "VersionId": "67242d9db65d5365441ef3f5c046dbb8",
            "IsLatest": true
        }
    ]
}

الأمر aws s3 ls لم يعرض شيئاً بعد الحذف، ولكن النسختين ما زالتا موجودتين، وفوقهما علامة الحذف Delete Marker هي الأحدث IsLatest: true. ونستعيد النسخة الأولى برقمها:

sw 'aws s3api get-object --bucket backups --key app/config.txt --version-id 67242d9e250f054752655fd0a220b209 /tmp/old.txt >/dev/null; cat /tmp/old.txt'
config v1

ولاحظ أننا استخدمنا المفتاح any ونجح كل شيء، والسبب أننا لم نعط SeaweedFS ملف هويات، فقبل الطلبات دون تحقق، وهذا مقبول في تجربة على شبكة Docker داخلية، أما على سيرفر حقيقي فاضبط الهويات قبل أي شيء آخر.

التنظيف

نحذف ما أنشأناه فقط بأسمائه:

docker rm -f lab-storage-garage lab-storage-seaweed
docker volume rm lab-storage-garage-data
docker network rm lab-storage-net
rm -f aws.env

وانتبه إلى أن Image SeaweedFS تنشئ Volume بلا اسم لمسار /data، فاعرضه بالأمر docker volume ls واحذفه باسمه، ولا تستخدم docker volume prune على سيرفر عليه خدمات أخرى، لأنه قد يحذف Volumes تخص خدمات أخرى لا يوجد لها Container في تلك اللحظة.

أي نوع تختار لكل حالة؟

الصورة التالية تجمع الحالات الأكثر شيوعاً في الاستضافة الذاتية والنوع المناسب لكل منها، ثم نشرح كل حالة:

ست بطاقات: قاعدة البيانات على قرص محلي NVMe أو Volume وليس على مجلد NFS أو SMB، وتطبيق ملفات الفريق مثل Seafile وNextcloud على قرص محلي أو S3 من اليوم الأول، وملفات المستخدمين في تخزين الكائنات مع حفظ المفتاح في قاعدة البيانات وتقديمها برابط موقع، والنسخ الاحتياطية في تخزين كائنات في موقع آخر مع النسخ أو Object Lock، ووسائط الموقع في تخزين الكائنات مع CDN، ومجلد المكتب المشترك عبر SMB أو NFS على NAS مع نسخة خارجه
أزرق للكتل، وأصفر للملفات، وبرتقالي للكائنات
  • قاعدة البيانات: على قرص محلي، ويفضل NVMe، أو Volume من مزود ال VPS الذي يظهر كقرص، ولا تضعها على مجلد NFS أو SMB، والسبب ما ذكرناه في بداية المقال من التأخير وضمانات الكتابة. ونسخها الاحتياطية تذهب إلى تخزين الكائنات.
  • تطبيق ملفات الفريق مثل Seafile أو Nextcloud: القرص المحلي يكفي لأغلب الفرق، وهو ما استخدمناه في دليل تثبيت Seafile. وإذا أردت تخزين الكائنات خلفه فقرر ذلك قبل أول رفع، والسبب أن التطبيق يحفظ الملفات في ال Bucket بطريقته الخاصة وليس بأسمائها، فتوثيق Seafile 13 يشرح إعداد S3 بثلاثة Buckets لسجل التعديلات Commits وكائنات نظام الملفات FS والكتل Blocks، وتوثيق Nextcloud يقول إن تفعيل التخزين الأساسي على S3 في نسخة قائمة يجعل كل ملفاتها الموجودة غير قابلة للوصول، وأن أسماء الملفات والمجلدات تبقى في قاعدة البيانات فقط.
  • ملفات المستخدمين في تطبيق ويب: في تخزين الكائنات من اليوم الأول، فالتطبيق يرفع الملف ويحفظ مفتاحه فقط في قاعدة البيانات، ويقدمه برابط موقع إذا كان خاصاً، فلا يمتلئ قرص السيرفر، ويستطيع أي عدد من نسخ التطبيق الوصول إلى الملفات نفسها. وانتبه إلى حدود حجم الرفع في كل طبقة كما في حدود حجم رفع الملفات.
  • النسخ الاحتياطية: في تخزين كائنات في مكان آخر غير السيرفر، مع Versioning، ومع Object Lock إذا كانت الأداة تدعمه، ومفتاح لا يملك صلاحية الحذف، والسبب أن المخترق الذي يصل إلى السيرفر يصل إلى كل ما يستطيع السيرفر حذفه، كما شرحنا في التحقيق الرقمي للمبتدئين.
  • وسائط الموقع من صور وفيديو وملفات تنزيل: في تخزين الكائنات، وأمامه CDN تحفظ نسخاً قريبة من الزوار، ويبقى ال Bucket خاصاً، وتفاصيل ذلك في شبكة توصيل المحتوى CDN للمبتدئين، وإذا كنت تفكر في Cloudflare R2 فقد ذكرناه وبدائله في خدمات Cloudflare وبدائلها.
  • مجلد مشترك لأجهزة المكتب: هنا تخزين الملفات هو الأنسب، SMB لأجهزة Windows أو NFS لسيرفرات Linux على جهاز NAS، مع نسخة احتياطية خارج ال NAS نفسه، لأن RAID في ال NAS لا يحميك من الحذف.

أخطاء شائعة وقائمة تحقق

  • Bucket عام بالخطأ: أحدهم جعل ال Bucket عاماً حتى تظهر الصور في الموقع، فأصبحت الفواتير التي في ال Bucket نفسه عامة أيضاً. افصل الملفات العامة في Bucket خاص بها، واجعل الباقي خاصاً تقدمه بروابط موقعة.
  • لا يوجد Versioning في Bucket النسخ الاحتياطية: سكربت خاطئ يكتب ملفاً فارغاً فوق آخر نسخة سليمة بالاسم نفسه، فتذهب النسخة. فعل النسخ مع قاعدة Lifecycle تحذف النسخ القديمة بعد مدة تحددها.
  • أسرار في البيانات الوصفية أو في المفاتيح: البيانات الوصفية ترجع في ترويسات HTTP لكل من يقرأ الكائن، كما رأينا في x-amz-meta-customer، والمفاتيح تظهر في الروابط والسجلات، لذلك لا تضع فيها كلمات مرور أو توكنات أو بيانات شخصية.
  • تخزين كائنات على قرص واحد دون نسخة: Garage أو SeaweedFS على سيرفر واحد بنسخة واحدة يعطيك واجهة S3 جميلة، ولكنه ليس أكثر أماناً من مجلد على القرص نفسه، فإذا تعطل القرص ضاع كل شيء. واجهة S3 لا تعني التكرار، والتكرار يأتي من عدة أقراص وعدة سيرفرات ونسخة في مكان آخر.
  • رفع مجزأ لم يكتمل: أجزاء الرفع الذي انقطع تبقى وتأخذ مساحة لا تظهر في عرض الملفات، فأضف قاعدة Lifecycle تلغيها.
  • روابط موقعة طويلة العمر: رابط صالح 7 أيام في رسالة بريد أو سجل يصبح باباً مفتوحاً طوال تلك المدة، فاجعل المدة دقائق للتنزيل المباشر.
  • مفتاح واحد لكل التطبيقات: إذا سرق من سيرفر واحد فتح كل ال Buckets، فاجعل لكل تطبيق مفتاحاً وBucket.
  • البدء بمشروع جديد على MinIO المجتمعي: المستودع لم يعد يصان كما شرحنا، فاختر أداة تصلها التحديثات الأمنية.

وقائمة التحقق قبل أن تعتمد على أي تخزين:

  • هل قاعدة البيانات على قرص محلي وليس على مجلد شبكة؟
  • هل الملفات التي يرفعها المستخدمون خارج قرص النظام، وتصل إليها كل نسخ التطبيق؟
  • هل كل بيانات ال Containers المهمة في Volumes أو في تخزين خارجي، وجربت إعادة إنشاء ال Container دون أن يضيع شيء؟
  • هل كل Bucket خاص إلا ما قصدت أن يكون عاماً، ولكل تطبيق مفتاح بأقل صلاحية؟
  • هل Bucket النسخ الاحتياطية في مكان آخر، مع Versioning أو Object Lock، وجربت الاستعادة منه فعلاً؟
  • هل لديك قاعدة Lifecycle للنسخ القديمة والرفع غير المكتمل؟
  • هل عدد النسخ في تخزين الكائنات الذي تستضيفه يناسب أهمية البيانات، وتعرف ماذا يحدث إذا تعطل قرص أو سيرفر؟

الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

  • تخزين الكتل Block قرص خام سريع لجهاز واحد، ومكانه قواعد البيانات والآلات الافتراضية.
  • تخزين الملفات File مجلدات وأسماء وصلاحيات يشترك فيها عدة أجهزة، ومكانه الملفات المشتركة للفريق والبرامج التي تتوقع نظام ملفات.
  • تخزين الكائنات Object قائمة مسطحة من المفاتيح عبر HTTP بواجهة S3، ومكانه ملفات المستخدمين والنسخ الاحتياطية والوسائط، والمجلدات فيه وهم يرسمه الكلاينت من البادئات.
  • الرابط الموقع يسمح للمتصفح بتنزيل كائن خاص مباشرة دون أن يمر الملف بالتطبيق، فاجعله لكائن واحد وللقراءة ولمدة قصيرة.
  • مستودع MinIO المجتمعي لم يعد يصان منذ فبراير 2026، وGarage خفيف للمواقع المتعددة ولكن دون Versioning وObject Lock، وSeaweedFS أوسع في ميزات S3، وCeph يقدم الأنواع الثلاثة ولكنه يحتاج ثلاثة سيرفرات على الأقل وفريقاً يتابعه.
  • واجهة S3 لا تعني أن بياناتك محمية، فالحماية من عدة نسخ ونسخة احتياطية في مكان آخر جربت الاستعادة منها.

وفي المقالات القادمة من قسم التخزين بإذن الله سوف نتحدث عن تشغيل تخزين الكائنات على سيرفرك بالتفصيل، والنسخ الاحتياطي إلى S3 مع Object Lock، ثم Ceph للمبتدئين.

دليلكيف تعمل شبكة توصيل المحتوى CDN وما الذي تحفظهلماذا يبطؤ موقعك على الزائر البعيد، وكيف تقرب شبكة CDN ملفاتك منه، وكيف تمنعها من حفظ الصفحات الخاصة.دليلSeafile: مزامنة ملفات فريقك ومشاركتها على سيرفرككيف تثبت Seafile على سيرفرك لتزامن ملفات فريقك وتشاركها بدل Dropbox وOneDrive، مع نسخة احتياطية تستطيع استعادتها.دليلمصطلحات الاستضافة الذاتية Self-hosting للمبتدئينمرجع مبسط لأهم المصطلحات التي سوف تقابلها في أدلة الاستضافة الذاتية، بأسمائها الإنجليزية كما تظهر في لوحات التحكم، مع شرح قصير لكل منها.دليلتثبيت Docker Engine وCompose على Ubuntuكيف تثبت Docker على Ubuntu من المستودع الرسمي، وتضبطه من البداية حتى لا يمتلئ القرص أو تنكشف منافذك للإنترنت.دليلخدمات Cloudflare بكلمات بسيطة وبدائلها مفتوحة المصدرما الذي تقدمه كل خدمة من Cloudflare، وما المتاح منها مجاناً، وما البديل مفتوح المصدر الذي تشغله على سيرفرك.

سجل التحديثات

  • أكتوبر 2026: كتابة المقال، وتنفيذ التجربة على Garage v2.3.0 وSeaweedFS 4.48 مع AWS CLI 2.37.9 وcurl 8.22.0 على Docker Engine 29.8.0.
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.