پر شدن دیسک سرور لینوکس: پیدا کردن علت و راه‌حل قدم‌به‌قدم

پر شدن دیسک از آن مشکلاتی است که بی‌صدا جلو می‌آید و ناگهان همه‌چیز را از کار می‌اندازد: MySQL نمی‌تواند بنویسد و متوقف می‌شود، سایت خطای 500 می‌دهد، ایمیل‌ها ارسال نمی‌شوند و گاهی حتی ورود با SSH هم با خطا همراه است. خبر خوب این است که پیدا کردن علت معمولاً چند دقیقه بیشتر طول نمی‌کشد، به شرطی که با روش درست جلو بروید و از سر عجله چیزی را پاک نکنید که بعداً لازمش دارید.

در این مقاله روی Ubuntu 24.04 کار می‌کنیم، اما دستورات در بیشتر توزیع‌های لینوکس یکسان‌اند.

قدم ۱: کدام پارتیشن پر شده و چه چیزی تمام شده است؟

df -h
df -i

دستور اول فضای مصرفی هر فایل‌سیستم را نشان می‌دهد و دستور دوم Inodeها را. هر فایل، هر قدر هم کوچک، یک Inode مصرف می‌کند. اگر df -h فضای آزاد نشان می‌دهد ولی باز هم خطای No space left on device می‌گیرید، به احتمال زیاد Inodeها تمام شده‌اند. مقصر رایج در این حالت میلیون‌ها فایل کوچک است: فایل‌های Session در PHP، کش برنامه‌ها یا صف ایمیل.

برای پیدا کردن پوشه‌هایی که بیشترین تعداد فایل را دارند:

find / -xdev -type f -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -20

قدم ۲: پیدا کردن پوشه‌های حجیم

با du از ریشه شروع کنید و قدم‌به‌قدم پایین بروید. گزینه‌ی -x باعث می‌شود فقط همان فایل‌سیستم بررسی شود و وارد پارتیشن‌های دیگر یا /proc نشود:

du -xh --max-depth=1 / 2>/dev/null | sort -rh | head -15
du -xh --max-depth=1 /var 2>/dev/null | sort -rh | head -15

ابزار تعاملی ncdu همین کار را راحت‌تر می‌کند و می‌توانید با کلیدهای جهت بین پوشه‌ها حرکت کنید:

apt install -y ncdu
ncdu -x /

اگر دیسک کاملاً پر است و نصب بسته ممکن نیست، اول از روش‌های بخش بعد کمی فضا آزاد کنید (مثلاً apt clean) و بعد ncdu را نصب کنید.

برای پیدا کردن فایل‌های تکی بزرگ:

find / -xdev -type f -size +1G -exec ls -lh {} \; 2>/dev/null

قدم ۳: مقصرهای همیشگی و پاک‌سازی امن هر کدام

لاگ‌ها در /var/log

لاگ وب‌سرور، لاگ اپلیکیشن یا لاگی که logrotate آن را پوشش نمی‌دهد، رایج‌ترین مقصر است. فایل لاگی را که یک سرویس در حال نوشتن در آن است با rm پاک نکنید (در بخش فایل‌های حذف‌شده‌ی باز توضیح می‌دهیم چرا). به‌جای آن محتوایش را خالی کنید:

truncate -s 0 /var/log/nginx/access.log

و البته قبلش مطمئن شوید این لاگ برای بررسی یک حادثه‌ی امنیتی یا الزام قانونی لازم نیست. اگر لازم است، اول فشرده و به جای دیگری منتقلش کنید.

ژورنال systemd

journalctl --disk-usage
journalctl --vacuum-size=500M
journalctl --vacuum-time=14d

برای اینکه دوباره بزرگ نشود، سقف دائمی تعیین کنید. فایل /etc/systemd/journald.conf.d/size.conf را بسازید:

[Journal]
SystemMaxUse=500M
systemctl restart systemd-journald

کش apt و کرنل‌های قدیمی

apt clean
apt autoremove --purge

دستور دوم کرنل‌های قدیمی و بسته‌های بی‌استفاده را حذف می‌کند. قبل از تأیید، فهرست بسته‌هایی را که حذف می‌شوند بخوانید. اگر سرور بعد از آخرین آپدیت کرنل ری‌بوت نشده است، کرنل در حال اجرا (uname -r) نباید در فهرست حذف باشد. پارتیشن /boot جداگانه و کوچک هم یکی از جاهایی است که با کرنل‌های قدیمی پر می‌شود و جلوی آپدیت‌های بعدی را می‌گیرد.

Docker: ایمیج‌ها، Volumeها و کش Build

روی سرورهایی که Docker دارند، /var/lib/docker معمولاً بزرگ‌ترین پوشه است. اول ببینید فضا دقیقاً کجا رفته:

docker system df
docker system df -v

بعد به ترتیب از کم‌خطر به پرخطر جلو بروید:

docker builder prune          # کش Build؛ معمولاً بی‌خطر
docker image prune            # ایمیج‌های بدون Tag (dangling)
docker image prune -a         # همه‌ی ایمیج‌هایی که کانتینری از آن‌ها استفاده نمی‌کند

درباره‌ی Volumeها بسیار محتاط باشید. docker volume prune و docker system prune --volumes Volumeهایی را که در آن لحظه به کانتینری وصل نیستند پاک می‌کنند. اگر دیتابیس شما در یک Volume است و کانتینرش به هر دلیلی متوقف یا حذف شده باشد، داده‌ی آن برای همیشه از بین می‌رود. قبل از هر prune روی Volumeها، فهرست را با docker volume ls بررسی کنید. همچنین image prune -a باعث می‌شود دفعه‌ی بعد ایمیج‌ها دوباره دانلود شوند، که روی سرورهای داخل ایران ممکن است به خاطر محدودیت Docker Hub مشکل‌ساز باشد (راه‌حل‌ها را در دسترسی به Docker Hub از ایران ببینید).

لاگ کانتینرها هم به‌طور پیش‌فرض محدودیت حجم ندارند. در /etc/docker/daemon.json سقف تعیین کنید:

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "50m", "max-file": "3" }
}

این تنظیم فقط روی کانتینرهایی اثر دارد که بعد از ری‌استارت Docker از نو ساخته شوند. اشتباهات مشابه را در مقاله‌ی اشتباهات رایج Docker Compose در Production مرور کرده‌ایم.

Binary Log در MySQL و MariaDB

فایل‌هایی مثل binlog.000123 یا mysql-bin.000123 در /var/lib/mysql می‌توانند ده‌ها گیگابایت شوند. هرگز آن‌ها را با rm پاک نکنید؛ از داخل دیتابیس حذفشان کنید:

mysql -e "SHOW BINARY LOGS;"
mysql -e "PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;"

اگر Replication دارید، اول مطمئن شوید Replicaها این فایل‌ها را خوانده‌اند. برای جلوگیری از تکرار، در MySQL 8 مقدار binlog_expire_logs_seconds و در MariaDB مقدار expire_logs_days (یا در نسخه‌های جدیدتر binlog_expire_logs_seconds) را تنظیم کنید.

بکاپ‌هایی که روی همان سرور مانده‌اند

بکاپ‌های کنترل‌پنل یا اسکریپت‌های بکاپ که روی خود سرور ذخیره می‌شوند، هم دیسک را پر می‌کنند و هم در صورت خرابی سرور به هیچ دردی نمی‌خورند. بکاپ باید خارج از سرور نگه‌داری شود. یک راه مناسب، انتقال آن به Object Storage است که در مقاله‌ی بکاپ با restic روی Object Storage توضیح داده‌ایم.

صف ایمیل

اگر سایتی هک شده یا فرمی مورد سوءاستفاده قرار گرفته باشد، صف Postfix ممکن است صدها هزار ایمیل اسپم داشته باشد (این حالت بیشتر Inode مصرف می‌کند تا فضا):

postqueue -p | tail -1
du -sh /var/spool/postfix

قبل از پاک کردن صف، منبع ارسال را پیدا و مسدود کنید؛ وگرنه صف دوباره پر می‌شود. پاک کردن کل صف (postsuper -d ALL) ایمیل‌های سالم در انتظار ارسال را هم حذف می‌کند.

قدم ۴: فضا را پاک کردید ولی آزاد نشد؟ فایل‌های حذف‌شده‌ی باز

این رایج‌ترین گیج‌کننده‌ی این ماجراست. وقتی فایلی را که یک پروسه هنوز باز نگه داشته پاک می‌کنید، نام فایل حذف می‌شود ولی فضای آن تا وقتی پروسه فایل را ببندد آزاد نمی‌شود. به همین دلیل du و df اعداد متفاوتی نشان می‌دهند. این فایل‌ها را پیدا کنید:

lsof +L1 2>/dev/null | sort -k7 -rn | head

راه‌حل تمیز، ری‌استارت یا reload سرویسی است که فایل را باز نگه داشته است. اگر ری‌استارت در آن لحظه ممکن نیست، با استفاده از PID و شماره‌ی File Descriptor که lsof نشان می‌دهد، محتوای فایل را از مسیر /proc خالی کنید:

# مثال: PID=1234 و FD=7
: > /proc/1234/fd/7

قدم ۵: اگر واقعاً به فضای بیشتری نیاز دارید (LVM)

گاهی مشکل پاک‌سازی نیست، بلکه داده واقعاً رشد کرده است. نصاب پیش‌فرض Ubuntu Server از LVM استفاده می‌کند و در بسیاری از نصب‌ها همه‌ی فضای دیسک را به پارتیشن ریشه نمی‌دهد. اول ببینید فضای تخصیص‌نیافته دارید یا نه:

vgs
lvs

اگر ستون VFree در خروجی vgs فضای آزاد نشان می‌دهد، می‌توانید Logical Volume و فایل‌سیستم را در حالت آنلاین بزرگ کنید:

lvextend -r -L +20G /dev/ubuntu-vg/ubuntu-lv
df -h /

گزینه‌ی -r فایل‌سیستم را هم هم‌زمان بزرگ می‌کند. نام VG و LV را از خروجی دستورات قبل بردارید. اگر دیسک جدیدی اضافه کرده‌اید یا دیسک مجازی را بزرگ کرده‌اید، مراحل بیشتری (مثل pvresize یا vgextend) لازم است. این عملیات را بدون بکاپ معتبر انجام ندهید.

قدم ۶: جلوگیری از تکرار

  • logrotate برای همه‌ی لاگ‌ها: هر اپلیکیشنی که در مسیری غیر از استاندارد لاگ می‌نویسد، باید فایل تنظیم خودش را در /etc/logrotate.d/ داشته باشد. با logrotate -d /etc/logrotate.conf تنظیمات را بدون اعمال تست کنید.
  • سقف برای journald و لاگ کانتینرها همان‌طور که بالاتر گفتیم.
  • سیاست نگه‌داری برای binlogها و بکاپ‌ها.
  • هشدار در ۸۰٪: مهم‌ترین قدم. هشدار باید زمانی برسد که هنوز وقت برای واکنش دارید، نه وقتی سایت از کار افتاده است. هشدار Inode را هم فراموش نکنید. راه‌اندازی آن را در مانیتورینگ سرور با Prometheus و Grafana توضیح داده‌ایم.
  • بررسی دوره‌ای: فضای دیسک یکی از بندهای چک‌لیست سلامت سرور لینوکس است.

جمع‌بندی

ترتیب کار همیشه یکی است: اول با df -h و df -i بفهمید چه چیزی تمام شده، بعد با du یا ncdu مقصر را پیدا کنید، و فقط چیزی را پاک کنید که می‌دانید چیست. فایل‌های دیتابیس، Volumeهای Docker و لاگ‌های مورد نیاز برای بررسی امنیتی را با عجله پاک نکنید؛ هزینه‌ی از دست رفتن آن‌ها خیلی بیشتر از چند دقیقه صبر است.

نمی‌خواهید این کار را خودتان انجام دهید؟ تیم مروانت علت پر شدن دیسک را پیدا می‌کند، فضا را بدون ریسک آزاد می‌کند و مانیتورینگ و هشدار را طوری تنظیم می‌کند که دفعه‌ی بعد قبل از بحران خبردار شوید. صفحه‌ی مدیریت و پشتیبانی سرور را ببینید.

دیدگاهتان را بنویسید

جستجو

فهرست مطالب