OOMkiller في Docker أصعب مما تعتقد

مرحبا مجددا. تحسبًا لبدء الدورة التدريبية ، أعدت "Java Developer" ترجمة مادة صغيرة أخرى.




في الآونة الأخيرة ، واجه أحد مستخدمي Plumbr APM مشكلة غريبة مع توقف طارئ لحاوية الرصيف مع الرمز 137. كان التكوين بسيطًا مع العديد من الحاويات المتداخلة والأجهزة الظاهرية ، على غرار الدمية المتداخلة:

  • خادم الحديد الخاص مع Ubuntu ؛
  • العديد من حاويات السفن مع Ubuntu ؛
  • Java Virtual Machine داخل حاويات عامل الميناء.

أثناء التحقيق في المشكلة ، وجدنا وثائق Docker حول هذا الموضوع. أصبح من الواضح أن السبب كان إما التوقف اليدوي للحاوية أو نقص الذاكرة والتدخل اللاحق لـ oomkiller (نفاد الذاكرة القاتل).

ننظر إلى سجل النظام ونرى أنه ، في الواقع ، كان يسمى oomkiller :

[138805.608851] java invoked oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=0
[138805.608887] [<ffffffff8116d20e>] oom_kill_process+0x24e/0x3b0
[138805.608916] Task in /docker/264b771811d88f4dbd3249a54261f224a69ebffed6c7f76c7aa3bc83b3aaaf71 killed as a result of limit of /docker/264b771811d88f4dbd3249a54261f224a69ebffed6c7f76c7aa3bc83b3aaaf71
[138805.608902] [<ffffffff8116da84>] pagefault_out_of_memory+0x14/0x90
[138805.608918] memory: usage 3140120kB, limit 3145728kB, failcnt 616038
[138805.608940] memory+swap: usage 6291456kB, limit 6291456kB, failcnt 2837
[138805.609043] Memory cgroup out of memory: Kill process 20611 (java) score 1068 or sacrifice child

كما ترى ، وصلت عملية جافا إلى حد 3145728 كيلوبايت (حوالي 3 جيجابايت) ، مما تسبب في توقف الحاوية. هذا غريب نوعًا ما ، حيث تم إطلاق عامل الميناء نفسه بحد أقصى 4 جيجابايت (في الملف docker-compose).

ربما تعرف أن JVM تفرض أيضًا قيودها على استخدام الذاكرة. على الرغم من أن عامل الميناء نفسه تم ضبطه على حد 4 جيجا بايت ، فقد تم إطلاق JVM مع الخيار Xmx=3GB. يمكن أن يكون هذا أكثر إرباكًا ، ولكن ضع في اعتبارك أن JVM قد تستخدم ذاكرة أكثر مما تم تحديده في -Xmx (انظر المقالة حول تحليل استخدام الذاكرة في JVM ).

حتى نفهم ما يحدث. يجب أن يسمح Docker باستخدام 4 غيغابايت. فلماذا يعمل OOMkiller على 3 جيجا بايت؟ قادنا البحث الإضافي عن المعلومات إلى حقيقة أن هناك قيود أخرى على الذاكرة في نظام التشغيل ، والتي تم نشرها على الأجهزة.

قل الشكر للمجموعات (مجموعات التحكم). cgroups هو محرك نواة Linux لتقييد استخدام الموارد من قبل مجموعات العمليات والتحكم فيه ومحاسبته. مقارنة بالحلول الأخرى (فريق niceأو /etc/security/limits.conf) ، توفر مجموعات c المزيد من المرونة ، حيث يمكنها العمل مع (تحت) مجموعات العمليات.

في حالتنا ، حدت cgroups استخدام الذاكرة إلى 3 غيغابايت (عبر memory.limit_in_bytes). لدينا بعض التقدم!

أظهرت دراسة لذاكرة GC والأحداث باستخدام Plumbr أن JVM يستخدم معظم الوقت حوالي 700 ميجابايت. الاستثناء كان فقط قبل التوقف مباشرة ، عندما كان هناك زيادة في تخصيص الذاكرة. وأعقبه وقفة طويلة من GC. لذلك يبدو أن ما يلي حدث:

  • رمز Java الذي يعمل داخل JVM يحاول الحصول على الكثير من الذاكرة.
  • يطلب JVM ، بعد التحقق من أن حد Xmx البالغ 3 جيجابايت لا يزال بعيدًا ، تخصيص ذاكرة لنظام التشغيل.
  • يتحقق Docker أيضًا ويرى أنه لم يتم الوصول إلى حد 4 غيغابايت أيضًا.
  • يتحقق نواة نظام التشغيل من حد مجموعة cg وهو 3 جيجا بايت ويقتل الحاوية.
  • يتوقف JVM مع الحاوية قبل أن يتمكن من معالجة نفسه OutOfMemoryError.

بفهم هذا ، قمنا بتكوين جميع قيود 2.5 غيغابايت ل docker و 1.5 لجافا. بعد ذلك ، يمكن لـ JVM التعامل مع OutOfMemoryErrorاستثناء OutOfMemoryError ورميه. سمح هذا لـ Plumbr بالقيام بسحرها - للحصول على لقطة ذاكرة مع مقالب المكدس المقابلة وإظهار أنه كان هناك استعلام واحد لقاعدة البيانات ، والذي حاول في حالة معينة تحميل قاعدة البيانات بالكامل تقريبًا.

الموجودات


حتى في مثل هذه الحالة البسيطة ، كانت هناك ثلاث قيود للذاكرة:

  • JVM عبر المعلمة -Xmx
  • عامل الميناء من خلال الخيارات الموجودة في الملف docker-compose
  • نظام التشغيل من خلال المعلمة memory.limit_in_bytes cgroups

وبالتالي ، عندما تواجه قاتل OOM ، يجب الانتباه إلى جميع قيود الذاكرة المعنية.

استنتاج آخر للمطورين عامل ميناء. يبدو أنه ليس من المنطقي السماح لمثل هذه "الدمى المتداخلة" بالعمل حيث يكون حد الذاكرة للحاوية المغلقة أعلى من حد المجموعات. يمكن أن يوفر الفحص البسيط لهذا عند بدء الحاوية بالتحذير المناسب مئات الساعات من التصحيح للمستخدمين.

هذا كل شئ. نحن في انتظارك في الدورة .

All Articles