وبلاگ نت‌آرامآموزش، تجربه و راهکارهای زیرساخت

چرا سرویس لینوکس با وجود رم آزاد متوقف می‌شود؟ بررسی MemoryMax

تشخیص OOM داخل cgroup و تنظیم سنجیده MemoryMax در systemd، همراه با روش آزمایش و بازگشت.

انتشار:
توسط تیم فنی نت‌آرام
۷ دقیقه زمان مطالعه
۰ دیدگاه
محتوای تخصصی، ساده و کاربردی
هماهنگ با هویت حرفه‌ای نت‌آرام
بازگشت به لیست مقالات
تصویر شاخص مقاله چرا سرویس لینوکس با وجود رم آزاد متوقف می‌شود؟ بررسی MemoryMax در وبلاگ نت‌آرام
محتوای آموزشی وبلاگ نت‌آرام
برنامه روی سرور مجازی ایران ناگهان متوقف می‌شود، اما وقتی مقدار RAM آزاد را نگاه می‌کنید هنوز فضای قابل استفاده وجود دارد. یکی از علت‌ها این است که خود سرویس در systemd سقف حافظه جداگانه دارد. مثل اتاقی در یک ساختمان بزرگ: حتی اگر بقیه ساختمان جا داشته باشد، وسایل داخل اتاق نمی‌توانند از دیوار آن عبور کنند.
این تنها دلیل پیام Killed نیست. کمبود حافظه کل سیستم، توقف دستی، خطای برنامه یا تصمیم سرویس دیگری هم ممکن است باعث توقف شود. ابتدا شواهد همان لحظه را پیدا می‌کنیم، بعد بسته به علت، مصرف را کاهش می‌دهیم یا محدودیت را با عددی حساب‌شده اصلاح می‌کنیم. دستورهای زیر برای سرویس‌های systemd و بخش حافظه cgroup نسخه ۲ نوشته شده‌اند؛ نام سرویس نمونه است و باید با نام واقعی جایگزین شود.

۱. ببینید چه چیزی متوقف شده است

با SSH یا کنسول وارد ترمینال لینوکس شوید. نام myapp.service را با نام سرویس خود عوض کنید:
کد / دستورsystemctl status myapp.service --no-pager -l sudo journalctl -u myapp.service --since '2 hours ago' --no-pager sudo journalctl -k --since '2 hours ago' --no-pager
اولی وضعیت فعلی و آخرین پیام‌ها را نشان می‌دهد؛ دو دستور بعدی لاگ سرویس و کرنل را در بازه نمونه می‌خوانند. ساعت رخداد را با ساعت قطعی تطبیق دهید. عباراتی مثل oom-kill یا Memory cgroup out of memory همراه با نام گروه یا پردازش می‌توانند نشانه باشند. فقط دیدن کد خروج ۱۳۷ کافی نیست، زیرا هر SIGKILL از جمله توقف اجباری دستی هم می‌تواند آن را تولید کند. در گزارش، نام برنامه، زمان، گروه و علت ثبت‌شده را کنار هم ببینید.
اگر سرویس بعد از مشکل دوباره بالا آمده، لاگ بوت قبلی نیز شاید لازم باشد: sudo journalctl -k -b -1 --no-pager لاگ کرنل بوت قبل را نشان می‌دهد، البته فقط اگر لاگ ماندگار نگه‌داری شده باشد. از انتشار عمومی لاگ‌هایی که مسیر خصوصی یا اطلاعات کاربران دارند خودداری کنید.

۲. حافظه کل سرور را با محدودیت سرویس اشتباه نگیرید

کد / دستورfree -h systemctl show myapp.service -p MemoryCurrent -p MemoryMax -p EffectiveMemoryMax -p ControlGroup -p Slice systemctl cat myapp.service
خروجی free تصویر کل سیستم و مقدار تقریبی available را نشان می‌دهد؛ این عدد سقف یک سرویس نیست. در خروجی دوم، MemoryCurrent مصرف فعلی گروه و MemoryMax مقدار تنظیم‌شده سرویس را نشان می‌دهد. بسته به نسخه systemd، بعضی ویژگی‌ها ممکن است وجود نداشته باشند. EffectiveMemoryMax در نسخه‌های پشتیبان محدودیت مؤثر با درنظرگرفتن والدها را نشان می‌دهد. infinity برای یک واحد به معنی بی‌نهایت بودن RAM واقعی نیست؛ یک slice والد یا خود سیستم هنوز می‌تواند محدودیت داشته باشد.
دستور سوم فایل واحد و فایل‌های تکمیلی یا drop-in را نشان می‌دهد. اگر سرویس داخل یک slice قرار دارد، نام آن را از خروجی بخوانید و محدودیت همان والد را هم با systemctl show نام-slice -p MemoryMax -p EffectiveMemoryMax بررسی کنید. تغییر سقف فرزند نمی‌تواند محدودیت سخت‌گیرانه‌تر والد را برطرف کند. اگر سرویس در کانتینر یا VPS با محدودیت بیرونی اجرا می‌شود، تنظیم میزبان نیز ممکن است سقف مؤثرتری داشته باشد و لازم است با مدیر زیرساخت هماهنگ شود.

۳. شواهد cgroup را هنگام فعال‌بودن سرویس بخوانید

ابتدا نوع cgroup را تشخیص دهید:
کد / دستورstat -fc %T /sys/fs/cgroup systemctl show myapp.service -p ControlGroup --value
اگر دستور اول cgroup2fs نشان دهد، مسیر دوم معمولاً چیزی مانند /system.slice/myapp.service است. برای همین مثال، در زمانی که سرویس فعال است این فایل‌ها را بخوانید؛ مسیر نمونه را دقیقاً با خروجی سرویس خود جایگزین کنید:
کد / دستورcat /sys/fs/cgroup/system.slice/myapp.service/memory.current cat /sys/fs/cgroup/system.slice/myapp.service/memory.max cat /sys/fs/cgroup/system.slice/myapp.service/memory.events
دو عدد اول مصرف و سقف را بر حسب بایت نشان می‌دهند؛ max یعنی در همان سطح سقف صریحی ثبت نشده است. در فایل رویدادها max شمار برخورد به مرز و oom_kill تعداد پردازش‌های کشته‌شده توسط OOM را نشان می‌دهد. این شمارنده‌ها از شروع عمر گروه جمع می‌شوند؛ دو بار در فاصله زمانی بخوانید و افزایش آن‌ها را با زمان رخداد و لاگ مقایسه کنید. مقدار غیرصفر قدیمی به‌تنهایی دلیل قطعیِ اختلال اخیر نیست و oom_kill به‌تنهایی منشأ دقیق محدودیت را ثابت نمی‌کند. اگر گروه بعد از توقف حذف شده باشد، فایل دیگر موجود نیست؛ شواهد journal را نگه دارید و هنگام بازتولید امن در یک دوره کاری مناسب، داده‌ها را دوباره ثبت کنید.
در cgroup نسخه ۱ مسیرها و نام فایل‌ها متفاوت‌اند؛ دستورهای بالا را بی‌تغییر روی آن نسخه اجرا نکنید. همچنین systemd-oomd می‌تواند بر اساس فشار حافظه قبل از OOM کرنل اقدام کند. اگر در لاگ آن نام سرویس و زمان رخداد دیده می‌شود، سیاست oomd و فشار کل سیستم باید جدا بررسی شوند. مستندات کرنل درباره cgroup v2 معنی شمارنده‌ها را توضیح می‌دهد.

۴. اول مصرف غیرعادی را درمان کنید

برای دیدن پردازش‌های بزرگ و مقدار swap سرور:
کد / دستورps -eo pid,ppid,rss,comm --sort=-rss | head -n 16 swapon --show
ستون RSS بر حسب کیلوبایت است و مجموع ساده آن لزوماً مصرف دقیق cgroup نیست؛ حافظه مشترک و کش هم نقش دارند. اگر بعد از هر درخواست مصرف بی‌وقفه بالا می‌رود، نسخه برنامه، تعداد workerها، حجم فایل‌های پردازشی و تنظیم کش را با تیم برنامه بررسی کنید. مثلاً کاهش تعداد worker از چهار به دو، در صورت سازگاری برنامه و ظرفیت، ممکن است پیک هم‌زمان را کم کند؛ تغییر را در ساعت کم‌ترافیک انجام دهید و زمان پاسخ را هم بسنجید. خاموش‌کردن OOM یا حذف همه محدودیت‌ها درمان نشت حافظه نیست.

۵. در صورت نیاز سقف مناسب‌تری تنظیم کنید

اگر اندازه‌گیری نشان می‌دهد بار سالم سرویس موقتاً از سقف فعلی عبور می‌کند و سرور برای آن ظرفیت دارد، یک drop-in بسازید:
کد / دستورsudo systemctl edit myapp.service
در ویرایشگر بازشده نمونه زیر را وارد کنید؛ عددها صرفاً مثال هستند و باید با مصرف واقعی و ظرفیت سرور انتخاب شوند:
کد / دستور[Service] MemoryHigh=768M MemoryMax=1G
MemoryHigh مرزی برای کنترل فشار حافظه است و MemoryMax سقف سخت؛ در cgroup v2 رسیدن به سقف می‌تواند به OOM داخل گروه منجر شود. طبق راهنمای systemd، معمولاً سقف سخت آخرین خط دفاع است. این دو عدد را برای مجموع هم‌زمان سرویس‌ها، سیستم‌عامل و فضای ذخیره در نظر بگیرید، نه فقط RAM آزاد لحظه‌ای. برای نسخه قدیمی یا cgroup v1 ممکن است رفتار و گزینه‌های قابل استفاده متفاوت باشند.
پس از ذخیره، صحت تنظیم و زمان مناسب اعمال را بررسی کنید:
کد / دستورsudo systemctl daemon-reload systemctl cat myapp.service systemctl show myapp.service -p MemoryHigh -p MemoryMax -p EffectiveMemoryMax
برای اینکه سرویس قطع نشود، اثر تغییر روی پردازش فعال و روش اعمال را ابتدا در محیط آزمایشی نسخه خود بررسی کنید؛ در بسیاری از سناریوها بازراه‌اندازی سرویس لازم است. در زمان مجاز و پس از اطمینان از امکان بازگشت، sudo systemctl restart myapp.service را اجرا و بلافاصله وضعیت برنامه را کنترل کنید. اگر سرویس حساس است، از کاربر یا مدیر آن هماهنگی بگیرید و بدون برنامه آن را راه‌اندازی مجدد نکنید.

۶. نتیجه و راه بازگشت

در بازه‌ای که مشکل معمولاً رخ می‌دهد مصرف، رویدادها، لاگ سرویس و پاسخ برنامه را دوباره بررسی کنید. اگر max یا oom_kill همچنان افزایش می‌یابد، سقف والد و محدودیت بیرونی، نشت حافظه و بار واقعی را دوباره تحلیل کنید؛ افزایش پیاپی سقف بدون محاسبه می‌تواند کل سرور را گرفتار کند. اگر تغییر باعث اختلال شد، با sudo systemctl edit myapp.service همان drop-in را به مقدار قبلی برگردانید، daemon را reload کنید و در پنجره مجاز سرویس را بازراه‌اندازی و آزمایش کنید. برای تشخیص سایر علت‌های توقف، راهنمای بررسی OOM در لینوکس را هم ببینید.
اشتراک‌گذاری:
لینکدینتلگرامایکس

دیدگاه‌ها (۰)

دیدگاه‌های تاییدشده و پاسخ‌های تیم نت‌آرام