Blogs

صفر أخطاء، صفر كائنات: ما الذي لا يخبرك به "التشغيل"؟

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

إحصائيات أعمالنا

الخبرة وراء كل قصة نشاركها

تواصل معنا
العملاء الذين تم خدمتهم

0+

العملاء الذين تم خدمتهم

المشاريع المنجزة

0+

المشاريع المنجزة

سنوات الخبرة

0+

سنوات الخبرة

تنفيذ التراخيص

0+

تنفيذ التراخيص

Blogs7 min read21 Sept 2026
قائمة التحقق التي تكشف أعطال البنية التحتية الصامتة

كنا نقيّم حزمة تسجيل تعتمد على Loki على كوبيرنيتس لبيئة جديدة، وندفع بيانات حقيقية عبرها من البداية إلى النهاية بدلاً من الاعتماد على لوحات المعلومات. كانت جميع الـ Pods في حالة Running، ولم تحدث أي عمليات إعادة تشغيل، وكانت جميع لوحات المعلومات تعرض الحالة باللون الأخضر. ولكن عندما جلسنا للتحقق من طبقة التخزين، والتأكد من أن البيانات تصل فعليًا إلى التخزين وليس فقط أن الـ Pods تعمل بشكل سليم، اكتشفنا أن مخزن الكائنات لم يكن يحتوي على أي سجلات على الإطلاق طوال فترة الاختبار.

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

قام Helm بإنشاء قالب نظيف، لكن الإعداد الذي أنتجه لم يكن له أي تأثير. كانت الـ Pods تعمل دون إعادة تشغيل، لكن عمليات الكتابة التي كان من المفترض أن تنفذها لم تصل إلى التخزين مطلقًا.

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

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


الجزء الأول: أعطال لا تملك أي وسيلة للإبلاغ عن نفسها


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

كان هناك مفتاح في Helm يتم عرضه بشكل صحيح لكنه لا يؤدي أي وظيفة. تم وضع بيانات اعتماد S3 الخاصة بـ Loki داخل Secret، وتمت الإشارة إليها باستخدام loki.extraEnvFrom.

تم تنفيذ helm template دون أي أخطاء. ونجحت عملية التثبيت. ثم دخلت الـ Pod في حالة CrashLoop وظهرت الرسالة: لا يوجد دور EC2 عبر IMDS.

كان AWS SDK قد مر عبر سلسلة بيانات الاعتماد بالكامل: متغيرات البيئة، ثم ملفات الإعداد، ثم بيانات مثيل الجهاز عبر IMDS، لكنه لم يجد أي شيء؛ لأن المفتاح كان يجب أن يكون تحت global: وليس loki:.

لا يقوم Helm بالتحقق من المفاتيح غير المعروفة؛ بل يقبلها ثم يتجاهلها بصمت. يمكن لـ YAML الصحيح موضوع في المكان الخطأ أن يتم عرضه بشكل مثالي، لكنه لا يؤدي أي وظيفة.


الجزء الثاني: نمط الفشل نفسه، ولكن على نطاق أكبر


كان السؤال الذي قاد المرحلة التالية من الاختبار بسيطًا:

هل كان Loki يخزن أي شيء بالفعل؟

واتضح أن المشكلة كانت من الفئة نفسها تمامًا، ولكن هذه المرة كانت غير مرئية في كل طبقة من المفترض أن تكتشفها.

الحالة الظاهرة: كانت جميع وحدات loki-write الثلاث في حالة Running، ولم تحدث أي عمليات إعادة تشغيل، ولم تظهر حالة CrashLoopBackOff، ولم يكن هناك أي شيء في kubectl get pods يشير إلى وجود مشكلة تستدعي الانتباه.

لم يكن هناك أي شيء في عملية النشر يوحي بوجود مشكلة.

ما كشفه البحث في السجلات

عند البحث داخل السجلات، وجدنا أن كل Pod كانت تُصدر الخطأ نفسه بشكل مستمر، بمعدل يتجاوز ألف ظهور تقريبًا كل بضع ساعات:

PutObject returned 501 NotImplemented, AWS chunked encoding not supported

كان رمز الحالة مهمًا جدًا.

  • 403 كان سيشير إلى مشكلة في بيانات الاعتماد.
  • 404 كان سيشير إلى عدم وجود الـ Bucket.
  • أما 501 فيعني أن الخادم فهم الطلب، لكنه لا يدعم طريقة تنفيذ هذا الطلب.

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

التأكد من أن المشكلة كانت فقدانًا حقيقيًا للبيانات وليس تراكمًا مؤقتًا

كان علينا التأكد من أن البيانات لم تكن مجرد تراكم في قائمة انتظار مؤقتة.

عند عرض محتويات الـ Bucket المستهدف، ظهرت قائمة البادئات فارغة. ولم تنجح عملية الكتابة ولو مرة واحدة طوال الفترة التي كان النظام يعمل فيها.

أما وحدات التخزين المحلية PVCs الخاصة بالكتابة، وهي الأقراص المحلية التي تقوم بتخزين البيانات مؤقتًا قبل رفعها، فلم تكن مستخدمة إلا بنسبة 0.1%.

لو كانت الـ chunks تتراكم محليًا في انتظار عودة التخزين للعمل، لارتفع استخدام القرص. لكنه لم يرتفع.

كانت عمليات الكتابة تُعاد محاولتها نحو ثماني مرات لكل chunk تقريبًا، ثم يتم التخلص منها.

كان هذا فقدانًا فعليًا للبيانات، وليس تراكمًا مؤقتًا: كل chunk أنتجه مسار الكتابة كان يُرفض ويُحذف، وفي نهاية فترة الاختبار كان الـ Bucket يحتوي على صفر من الكائنات.

السبب الجذري

الإصدار grafana/loki:3.6.11 يتضمن إصدارًا من AWS SDK يقوم تلقائيًا بتغليف عمليات الرفع باستخدام ترميز aws-chunked مع التحقق من سلامة البيانات.

يدعم AWS S3 الحقيقي هذا الأسلوب، لكن نقطة النهاية المتوافقة مع S3 والمبنية على OCI خلف الـ Bucket لدينا لم تكن تدعمه.

كنا قد ضبطنا بالفعل المتغير:

AWS_REQUEST_CHECKSUM_CALCULATION=when_required

على هذه الـ Pods كإجراء احترازي عام ضد هذا النوع من الإعدادات الافتراضية في SDK، كما تحققنا من سلوكه باستخدام aws CLI، الذي يحترم هذا المتغير.

لكن ذلك لم يغير شيئًا هنا.

والسبب هو أن عميل Go المضمّن داخل Loki لا يقرأ هذا المتغير.

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

الطريق إلى الأمام

بدلًا من الاستمرار في تجربة متغيرات البيئة مع عميل لا يحترمها، انتقلنا إلى عميل مخزن الكائنات المستند إلى Thanos في Loki، والذي يعتمد على minio-go بدلًا من AWS SDK.

تم تفعيله باستخدام:

loki.storage.use_thanos_objstore: true

بالإضافة إلى كتلة object_store التي تتضمن نقطة النهاية والمنطقة وإعدادات TLS.

لكن هذا تطلب التعامل مع اختلافين في المخطط مقارنة بإعداد s3.endpoint القديم:

  • الإعداد الجديد يتطلب اسم مضيف مجردًا دون بادئة الـ scheme.
  • لم يعد يحتوي على s3ForcePathStyle، بينما يتم ملء bucket_name تلقائيًا من bucketNames الموجود مسبقًا.

هذا العميل ببساطة لا يستخدم المسار الافتراضي نفسه الخاص بالـ checksum وترميز chunked، وبعد تفعيله توقفت الأخطاء.


الدرس ليس في الإصلاح، بل في عملية التحقق


خطآن، ومشكلة أساسية واحدة:

مكوّن يقوم بالعمل بطريقة خاطئة بصمت، بينما تُظهر جميع الإشارات السطحية — حالة الـ Pod، ورموز الخروج، ومتغيرات البيئة الموجودة — أن كل شيء يعمل بشكل سليم.

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

في هذه الحالة، استمر النظام في العمل لمدة تقارب 36 ساعة. وخلال هذه الفترة، أنتج مسار الكتابة ما يُقدّر بأكثر من 300 كائن كان من المفترض أن تصل إلى الـ Bucket؛ لكن تم رفض كل واحد منها والتخلص منه، وظل عدد الكائنات في التخزين صفرًا.

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

وهنا تكمن الفكرة القابلة للتطبيق في أي بيئة أخرى: قائمة التحقق التي تكشف هذه المشكلات قبل أن تكلفك أيامًا من العمل:

  • وجود قالب Helm نظيف أو خطة Terraform ناجحة لا يعني أن المفتاح وصل إلى المكان الصحيح. ابحث في المخرجات التي تم إنشاؤها عن الحقل المحدد الذي تتوقع وجوده.
  • نجاح أداة CLI في الاتصال بخدمة خلفية يثبت أن مسار الكود الخاص بأداة CLI يعمل، لكنه لا يثبت أن العميل المضمّن داخل تطبيقك يعمل. عميل HTTP مختلف يعني إعدادات افتراضية مختلفة، وقد يعني أيضًا أخطاء مختلفة.
  • من منظور kubectl get، تبدو حالتا "Waiting" و**"Stuck"** متشابهتين. والطريقة الوحيدة للتمييز بينهما هي معرفة الشرط الذي ينتظر النظام تحققه، وبشكل دقيق.
  • في كل ما يتعلق بالتخزين، فإن الدليل الوحيد على نجاح عملية الكتابة هو التحقق من عدد الكائنات الموجودة في الطرف الآخر. أما سجلات التطبيق، ورموز خروج الـ SDK، واختبارات CLI السريعة، فجميعها تتوقف عند طبقة واحدة قبل إثبات وصول البيانات فعليًا إلى التخزين.

لا شيء من هذا معقد أو غير مألوف.

إنه ببساطة الفرق بين بنية تحتية تبدو صحيحة وبنية تحتية تم التحقق منها وأُجبرت على إثبات أنها تعمل بالفعل.

Comments

Loading comments...

ابقَ على اطلاع
مع نيو.

احصل على تحديثات المنتجات والرؤى والإعلانات من نيو تك سوليوشنز.

بالاشتراك، توافق على تلقي التحديثات. تواصل معنا بخصوص بياناتك في أي وقت.