Linux من الصفر إلى السيرفر الأدلة التقنية

الأقراص في Linux: التركيب Mount والملف fstab وSwap

نضيف قرص بيانات إلى سيرفر Linux من التقسيم إلى التركيب الدائم، ونرى بأعيننا لماذا تختفي الملفات ويتوقف الإقلاع ويمتلئ القرص، ثم نضيف Swap ونوسع القرص دون إيقاف.

الأقراص في Linux: التركيب Mount والملف fstab وSwap

لنفرض أنك في شركة ناشئة تبيع منتجاتها عبر الإنترنت، وكل طلب يولد فاتورة PDF يحفظها التطبيق على السيرفر، إضافة إلى قاعدة البيانات وصور المنتجات. وبعد سنة وصلت رسالة من نظام المراقبة تقول إن القرص الرئيسي ممتلئ بنسبة 91%، فطلب الفريق من مزود الاستضافة قرصاً إضافياً بحجم 100 GB، وظهر في لوحة التحكم مكتوباً عليه «مرفق Attached». ثم دخل أحد المطورين إلى السيرفر فلم يجد أي مجلد جديد، ولا أي مساحة إضافية في df -h، وكأن شيئاً لم يحدث. وبعد بحث سريع في الإنترنت كتب ثلاثة أوامر نسخها من إجابة قديمة، فظهر المجلد وظهرت المساحة، ثم أعاد تشغيل السيرفر في نهاية اليوم ليتأكد أن كل شيء بخير، فلم يرجع السيرفر أصلاً.

وكل خطوة في هذه القصة لها سبب واضح عندما تعرف كيف يرى Linux الأقراص: القرص الجديد جهاز خام لا يحمل أي شيء، ويحتاج جدول أقسام Partition Table، ثم نظام ملفات File System، ثم تركيباً Mount في مجلد، ثم سطراً في الملف /etc/fstab حتى يعود بعد الإقلاع، وسطر واحد خاطئ في هذا الملف يكفي لإيقاف الإقلاع كله. وفي هذا الدليل سوف نمر على كل هذه الخطوات على قرص حقيقي بالنسبة للنظام، ونرى أخطاءها الشائعة بأعيننا، ثم نضيف مساحة تبديل Swap، ونوسع القرص بعد ذلك دون إيقاف. وإذا لم تقرأ بعد عن الـ Inode وفكرة نظام الملفات فابدأ بقسم نظام الملفات في مقال نظام التشغيل Operating System.

وسوف نناقش في هذا المقال ما يلي:

  • الطبقات الأربع بين القرص والمجلد، وكيف تراها بالأوامر lsblk وblkid وfindmnt.
  • تقسيم القرص الجديد بجدول GPT وتهيئته بنظام ext4، ومشكلة واجهتنا مع ملف الجهاز المفقود.
  • التركيب Mount، ولماذا اختفت ملفات كتبها التطبيق قبل التركيب.
  • الملف fstab حقلاً حقلاً، ولماذا نستخدم الـ UUID بدلاً من اسم الجهاز، وكيف تكتشف السطر الخاطئ قبل أن يكتشفه الإقلاع.
  • القرص الممتلئ والكتل المحجوزة Reserved Blocks، ولماذا يكتب root عندما يعجز التطبيق.
  • مساحة التبديل Swap: ملف أم قسم، وكم تحتاج، ولماذا رفضها النظام على نوع معين من الأنظمة.
  • توسيع القرص دون إيقاف، وخيارات التركيب مثل ro وnoexec، ثم الأخطاء الشائعة وقائمة تحقق.

تجهيز المختبر: قرص من ملف Loop Device

لكي نجرب كل شيء دون قرص حقيقي ودون أن نخاطر بأقراص الجهاز، نستخدم ميزة في النواة اسمها جهاز الحلقة Loop Device: ملف عادي على القرص تعرضه النواة كأنه قرص كامل، فتقسمه وتهيئه وتركبه بنفس الأوامر التي تستخدمها مع قرص حقيقي في السيرفر، كما تشرح صفحة losetup(8). والأوامر تعمل داخل Container من Debian 13 فيه أدوات الأقراص فقط:

FROM debian:trixie-slim
RUN apt-get update && apt-get install -y --no-install-recommends util-linux fdisk parted e2fsprogs procps && rm -rf /var/lib/apt/lists/*
WORKDIR /lab
docker build -t sbl-disks:1 .
docker run -d --name sbl-dk --privileged sbl-disks:1 sleep infinity

والخيار --privileged ضروري هنا، لأن ربط ملف بجهاز حلقة وتركيب أنظمة الملفات عمليات تحتاج صلاحيات كاملة على النواة، وDocker يمنعها عن الـ Containers العادية لسبب وجيه، فلا تستخدم هذا الخيار أبداً مع Container يشغل تطبيقاً. ولاحظ أيضاً أن أجهزة الحلقة تخص نواة الجهاز كله، فالمختبر يجب أن ينتهي بفصلها كما سنفعل في آخره. وكل خطوة في سكربت مستقل في مجلد المختبر، يعمل بالأمر docker exec sbl-dk bash /lab/01-new-disk.sh وما يليه.

من القرص Disk إلى المجلد: أربع طبقات

قبل أي أمر لنرسم الصورة التي غابت عن المطور في القصة. فالقرص الذي أضافه المزود يظهر للنظام كجهاز كتل Block Device له اسم مثل /dev/sdb أو /dev/vdb أو /dev/nvme1n1، وهو في هذه اللحظة سلسلة من البايتات الفارغة لا يعرف عنها النظام شيئاً. وحتى تكتب فيه ملفاً يجب أن تمر بأربع طبقات بالترتيب:

أربع طبقات من اليمين إلى اليسار: القرص /dev/loop2 وهو في السيرفر /dev/sdb أو /dev/nvme1n1 وفي المختبر ملف data.img، ثم جدول الأقسام GPT فيه القسم الأول loop2p1 من 1MiB إلى 100%، ثم نظام الملفات ext4 بالاسم data ورقم UUID، ثم التركيب في المجلد /srv/data من شجرة النظام
كل طبقة لها أمرها، ولا تظهر المساحة حتى تكتمل الأربع
  • جدول الأقسام Partition Table: يقسم القرص إلى أجزاء مستقلة اسمها الأقسام Partitions، ونوعه اليوم GPT في الغالب، والنوع القديم MBR لا يدعم أقراصاً أكبر من 2 TiB. وقد يتساءل البعض: لماذا القسم إذا كنت سأستخدم القرص كله؟ والإجابة أن النظام يستطيع فعلاً أن يضع نظام الملفات على القرص كله دون أقسام، ولكن القسم يجعل القرص مفهوماً لأي أداة تراه، فلا تظن أداة أخرى أنه قرص فارغ فتكتب عليه، ولذلك هو العرف السائد.
  • نظام الملفات File System: يحول القسم إلى Inodes ومجلدات وسجل Journal، وأشهره على السيرفرات ext4 وXFS، وقد شرحنا ما بداخل ext4 في مقال نظام الملفات من الداخل.
  • التركيب Mount: يربط نظام الملفات بمجلد في الشجرة الواحدة التي تبدأ من /، فلا توجد في Linux حروف أقراص مثل D: وE:، وإنما مجلدات تقود إلى أقراص مختلفة.
  • الملف fstab: يحفظ التركيب حتى يتكرر عند كل إقلاع، فالأمر mount وحده يختفي أثره عند إعادة التشغيل.

والمطور في القصة نفذ الطبقات الثلاث الأولى بالأوامر المنسوخة، وأخطأ في الرابعة، وهذا ما سوف نصل إليه.

ربط القرص الجديد وتقسيمه بجدول GPT

السكربت الأول ينشئ ملفاً بحجم 1 GiB ليلعب دور القرص الجديد، ويربطه بجهاز حلقة، ثم يقسمه بالأداة parted بجدول GPT وقسم واحد يأخذ القرص كله:

mkdir -p /lab/disks
truncate -s 1G /lab/disks/data.img
ls -lhs /lab/disks/data.img
DEV=$(losetup -f --show -P /lab/disks/data.img)
echo "$DEV" > /lab/disks/dev
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS "$DEV"
parted -s "$DEV" mklabel gpt mkpart data ext4 1MiB 100%
parted -s "$DEV" unit MiB print
lsblk -o NAME,SIZE,TYPE,PARTLABEL "$DEV"
ls -l "${DEV}p1"
cat /sys/block/$(basename $DEV)/$(basename $DEV)p1/dev

والمخرج سوف يكون كما يلي (حذفنا سطور التتبع التي يطبعها set -x):

0 -rw-r--r-- 1 root root 1.0G Oct 30 18:36 /lab/disks/data.img
NAME  SIZE TYPE FSTYPE MOUNTPOINTS
loop2   1G loop
sh: 1: udevadm: not found
sh: 1: udevadm: not found
sh: 1: udevadm: not found
sh: 1: udevadm: not found
Model: Loopback device (loopback)
Disk /dev/loop2: 1024MiB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number  Start    End      Size     File system  Name  Flags
 1      1.00MiB  1023MiB  1022MiB               data

NAME       SIZE TYPE PARTLABEL
loop2        1G loop
`-loop2p1 1022M part
ls: cannot access '/dev/loop2p1': No such file or directory
259:0

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

  • الرقم 0 في أول السطر هو المساحة الحقيقية التي يشغلها الملف، بينما حجمه الظاهر 1.0G. فالأمر truncate ينشئ ملفاً متفرقاً Sparse File لا يأخذ مساحة إلا عندما نكتب فيه، وهذا يجعل المختبر خفيفاً.
  • الأمر lsblk يعرض القرص loop2 بحجم 1G دون نظام ملفات ودون نقطة تركيب، وهذا بالضبط ما رآه المطور في القصة عندما كتب lsblk فوجد sdb بلا شيء تحته. أما df -h فلا يعرض إلا أنظمة الملفات المركبة، ولهذا لم تظهر المساحة فيه.
  • بدأنا القسم من 1MiB وليس من أول القرص، والسبب أن الجدول GPT نفسه يحتاج المساحة الأولى، وأن البدء من حد 1MiB يجعل القسم متوافقاً مع حجم الكتل الداخلية لأي قرص SSD، ولهذا تجد نفس الرقم في أغلب أدوات التقسيم.
  • الجدول يقول إن القسم من 1.00MiB إلى 1023MiB، وآخر 1MiB محجوز للنسخة الاحتياطية من الجدول، فأداة GPT تحفظ نسختين في بداية القرص ونهايته.

مشكلة واجهتنا: القسم موجود وملف الجهاز Device Node غير موجود

آخر سطرين في المخرج هما أول مشكلة واجهتنا: lsblk يرى القسم loop2p1 بحجم 1022M، أي أن النواة تعرفه، ولكن الملف /dev/loop2p1 الذي نحتاجه للتهيئة غير موجود. والسطور udevadm: not found تشرح السبب: في أي نظام عادي توجد خدمة اسمها udev تراقب الأجهزة الجديدة وتنشئ لها ملفات في /dev، وأداة parted طلبتها بعد تعديل الجدول فلم تجدها، لأن الـ Container لا يشغل udev. والنواة نفسها تنشر رقمي الجهاز في الملف /sys/block/loop2/loop2p1/dev، وهما 259:0، أي الرقم الرئيسي Major والرقم الفرعي Minor اللذان يعرف بهما النظام أي جهاز، فأنشأنا الملف بأنفسنا بالأمر mknod. وعلى أي سيرفر حقيقي لن تحتاج هذه الخطوة، لأن udev يعمل دائماً، ولكنها تريك أن ملفات /dev ليست الأجهزة نفسها، وإنما أبواب إليها بأرقام.

تهيئة القسم Format بنظام ext4

السكربت الثاني ينشئ ملف الجهاز، ثم يهيئ القسم بالأمر mkfs.ext4 مع اسم data، ثم يركبه ويقرأ نتيجته:

DEV=$(cat /lab/disks/dev); P=${DEV}p1
mknod "$P" b $(cat /sys/block/$(basename $DEV)/$(basename $P)/dev | tr ':' ' ')
ls -l "$P"
mkfs.ext4 -L data "$P"
blkid "$P"
mkdir -p /srv/data
mount "$P" /srv/data
findmnt /srv/data
df -hT /srv/data
df -i /srv/data
ls -la /srv/data
brw-r--r-- 1 root root 259, 0 Oct 30 18:37 /dev/loop2p1
mke2fs 1.47.2 (1-Jan-2025)
Discarding device blocks: done
Creating filesystem with 261632 4k blocks and 65408 inodes
Filesystem UUID: 4875ede9-71d9-4370-8967-4e2bcdb5cd33
Superblock backups stored on blocks:
	32768, 98304, 163840, 229376

Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done

/dev/loop2p1: LABEL="data" UUID="4875ede9-71d9-4370-8967-4e2bcdb5cd33" BLOCK_SIZE="4096" TYPE="ext4" PARTLABEL="data" PARTUUID="3d5f4d4b-74df-4c4f-af6a-568301e00391"
TARGET    SOURCE       FSTYPE OPTIONS
/srv/data /dev/loop2p1 ext4   rw,relatime
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/loop2p1   ext4  988M  276K  921M   1% /srv/data
Filesystem     Inodes IUsed IFree IUse% Mounted on
/dev/loop2p1    65408    12 65396    1% /srv/data
total 24
drwxr-xr-x 3 root root  4096 Oct 30 18:37 .
drwxr-xr-x 1 root root  4096 Oct 30 18:37 ..
drwx------ 2 root root 16384 Oct 30 18:37 lost+found

وهذا المخرج فيه أغلب ما تحتاج أن تعرفه عن نظام الملفات الجديد، فلنقرأه:

  • الحرف b في أول سطر ls -l يعني جهاز كتل Block Device، والرقمان 259 و0 هما رقما الجهاز اللذان قرأناهما من /sys.
  • 261632 4k blocks and 65408 inodes: القسم صار 261632 كتلة من 4 KB، ومعها 65408 Inode، أي Inode واحد لكل 16 KB من المساحة، وهذا هو الحد الأقصى لعدد الملفات على هذا القرص مهما كانت صغيرة. وإذا كنت ستحفظ ملايين الملفات الصغيرة فاطلب Inodes أكثر عند التهيئة بالخيار -i أو -N كما في صفحة mke2fs(8)، لأن عددها لا يتغير بعد ذلك إلا بالتوسيع.
  • Filesystem UUID: رقم فريد يولده mkfs لنظام الملفات، ويبقى معه مهما تغير اسم الجهاز، وهو الرقم الذي سنكتبه في fstab.
  • Superblock backups: نسخ احتياطية من الكتلة التي تصف نظام الملفات، موزعة على القرص، حتى يستطيع fsck الإصلاح إذا تلفت النسخة الأولى.
  • Creating journal: السجل Journal الذي يحمي نظام الملفات من التلف عند انقطاع الكهرباء.
  • الأمر blkid يعرض كل الأسماء التي يمكن أن تشير بها إلى هذا القسم: LABEL وUUID لنظام الملفات، وPARTLABEL وPARTUUID للقسم نفسه في جدول GPT.
  • الأمر df -hT يقول إن الحجم 988M والمستخدم 276K والمتاح 921M فقط، والفرق ليس خطأ، وسوف نعرف سببه في قسم القرص الممتلئ.
  • المجلد lost+found ينشئه mkfs ليضع فيه fsck الملفات التي يجدها بلا اسم عند الإصلاح، فلا تحذفه.

ext4 أم XFS: أي نظام ملفات File System تختار؟

اخترنا ext4 لأنه الافتراضي في Debian وUbuntu، وهو ما ستجده على أغلب السيرفرات التي تديرها، ولكنه ليس الخيار الوحيد. فالتوزيعات من عائلة Red Hat مثل RHEL وRocky وAlma تستخدم XFS افتراضياً، وستقابله حتماً إذا عملت عليها. والفرق بينهما لا يظهر في الاستخدام اليومي، فكلاهما نظام ملفات بسجل Journal، وكلاهما يتحمل أحجاماً أكبر بكثير مما ستحتاجه، وكلاهما يتوسع وهو مركب. وتظهر الفروق في الحالات الخاصة: ext4 يصغر إذا فككت تركيبه، وXFS لا يصغر عملياً، فإذا كنت تتوقع أن تعيد تقسيم القرص يوماً فهذه نقطة لصالح ext4. وفي المقابل يقسم XFS القرص إلى مجموعات تخصيص Allocation Groups يعمل كل منها باستقلال، فيتعامل جيداً مع الكتابة المتوازية من خيوط كثيرة على ملفات كبيرة، ولهذا يفضل في بعض قواعد البيانات وخوادم الملفات الكبيرة، كما يشرح توثيق XFS في النواة. وXFS ينشئ الـ Inodes عند الحاجة، فلا ينفد عددها بنفس الطريقة التي رأيناها في ext4. أما أنظمة مثل Btrfs وZFS فتقدم أشياء أخرى تماماً، مثل اللقطات Snapshots وفحص سلامة البيانات، وقد شرحنا الثاني في مقال ZFS للمبتدئين. والقاعدة العملية: استخدم الافتراضي في توزيعتك ما لم يكن عندك سبب محدد، لأن فريقك وأدواته ووثائقه مبنية عليه، والتنقل بين أنظمة الملفات على نفس السيرفرات يضاعف ما يجب أن يعرفه من يستلم المناوبة ليلاً.

التركيب Mount: ولماذا اختفت الفواتير؟

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

umount /srv/data
echo "uploaded before the disk was mounted" > /srv/data/invoice-0001.pdf
du -sh /srv/data
mount /dev/loop2p1 /srv/data
ls /srv/data
umount /srv/data
ls /srv/data
8.0K	/srv/data
lost+found
invoice-0001.pdf

بعد التركيب لم يظهر في المجلد إلا lost+found، وبعد فك التركيب رجعت الفاتورة كما هي. والتفسير أن التركيب لا ينسخ شيئاً ولا يحذف شيئاً، وإنما يجعل المجلد باباً إلى نظام ملفات آخر، فيصبح ما كان في المجلد على القرص الرئيسي مخفياً تحت الباب، ويبقى يشغل مساحة القرص الرئيسي دون أن يراه أحد:

قبل التركيب: المجلد /srv/data على القرص الرئيسي فيه الملف invoice-0001.pdf وحجمه 8.0K. بعد التركيب: المجلد يعرض محتوى القرص الجديد loop2p1 أي lost+found فقط، والملف القديم مخفي تحت نقطة التركيب ويعود بعد umount
التركيب يخفي محتوى المجلد ولا يحذفه

وهذا يسبب مشكلتين على السيرفر: بيانات «ضائعة» كتبت في الوقت الخطأ، ومساحة على القرص الرئيسي لا يفسرها du لأنه لا يرى ما تحت نقطة التركيب. والحل في هذه الحالة أن تفك التركيب، وتنقل الملفات إلى مكان مؤقت، ثم تركب القرص وتعيدها إليه. والوقاية أبسط: ركب القرص قبل أن تشغل التطبيق الذي يكتب فيه، وإذا كان التطبيق خدمة systemd فاجعلها تنتظر التركيب بالخيار RequiresMountsFor=/srv/data كما في صفحة systemd.unit(5)، فلا تبدأ الخدمة إذا لم يكن القرص مركباً، بدلاً من أن تكتب بصمت في القرص الرئيسي.

لماذا لا يتفق df مع du؟

هذا السؤال يأتي دائماً بعد مشكلة مثل الفواتير المخفية: df يقول إن القرص الرئيسي مستخدم بنسبة 91%، ومجموع ما يعرضه du لكل المجلدات أقل من ذلك بكثير. والسبب أن الأمرين يجيبان عن سؤالين مختلفين، كما في صفحتي df(1) وdu(1): الأمر df يسأل نظام الملفات نفسه عن عدد الكتل المستخدمة والحرة، أما du فيمشي في شجرة المجلدات ويجمع أحجام الملفات التي يراها. فكل ما لا يراه du في الشجرة يظهر في df فقط، وأشهره أربعة أشياء: ملفات مخفية تحت نقطة تركيب كما رأينا، وملفات محذوفة ما زال برنامج يفتحها، والكتل المحجوزة لـ root التي تحسب عند df ضمن غير المتاح، والملفات المتفرقة Sparse Files التي يختلف حجمها الظاهر عن المساحة التي تشغلها فعلاً. وقد رأينا الأخيرة في أول سطر من المختبر، فالملف data.img حجمه في ls 1.0G بينما المساحة التي يشغلها صفر، والأمر du بدون خيارات يعرض المساحة الفعلية، والأمر du --apparent-size يعرض الحجم الظاهر. ولكي تجد المساحة المخفية تحت نقطة تركيب على سيرفر يعمل، دون أن تفك تركيب أي شيء، ركب القسم الرئيسي نفسه مرة ثانية في مجلد مؤقت بالأمر mount --bind / /mnt/rootview، فترى فيه المجلدات كما هي على القرص دون الأقراص المركبة فوقها، ثم استخدم du هناك.

الملف fstab: التركيب الذي يبقى بعد الإقلاع

الأمر mount يركب الآن فقط، ومع أول إعادة تشغيل يرجع المجلد فارغاً ويعود التطبيق للكتابة في القرص الرئيسي، وهي نفس المشكلة السابقة مرة أخرى. ولكي يتكرر التركيب عند كل إقلاع نكتب سطراً في الملف /etc/fstab، الذي يقرؤه النظام عند الإقلاع ويركب كل ما فيه. والسطر الذي أضفناه:

UUID=$(blkid -s UUID -o value /dev/loop2p1)
echo "UUID=$UUID  /srv/data  ext4  defaults,noatime,nofail  0  2" >> /etc/fstab
cat /etc/fstab
# UNCONFIGURED FSTAB FOR BASE SYSTEM
UUID=4875ede9-71d9-4370-8967-4e2bcdb5cd33  /srv/data  ext4  defaults,noatime,nofail  0  2

والسطر ستة حقول تفصلها مسافات، كما في صفحة fstab(5):

سطر fstab من المختبر مقسم إلى حقوله الستة: المصدر UUID=4875ede9 لأن الاسم يتغير والرقم يبقى، ونقطة التركيب /srv/data، والنوع ext4، والخيارات defaults وnoatime وnofail، والحقل dump دائماً 0، وترتيب الفحص 1 للقسم الرئيسي و2 للبقية و0 بلا فحص
ماذا تركب، وأين، وبأي نوع، وبأي خيارات، وهل تفحصه
  • المصدر: كتبناه بالصيغة UUID= وليس /dev/loop2p1، والسبب في القسم التالي.
  • الخيار noatime: يمنع نظام الملفات من كتابة وقت آخر قراءة في كل مرة يقرأ فيها أحد ملفاً، فيوفر كتابات لا يحتاجها أغلب الناس. والوضع الافتراضي relatime الذي رأيناه في findmnt حل وسط لا يحدث هذا الوقت إلا إذا كان أقدم من وقت آخر تعديل أو مضى عليه يوم.
  • الخيار nofail: وهو أهم ما في السطر، فبدونه يكون هذا التركيب شرطاً لإكمال الإقلاع، فإذا غاب القرص لأي سبب، مثل فصله من لوحة المزود أو خطأ في الرقم، توقف النظام عن الإقلاع ودخل وضع الطوارئ Emergency Mode وانتظر من يدخل إليه من شاشة المزود. ومع nofail يقول توثيق systemd.mount(5) إن الإقلاع يكمل دون انتظار هذا التركيب ومهما كانت نتيجته. واستخدمه لكل قرص بيانات، ولا تستخدمه للقسم الرئيسي طبعاً.
  • الرقم الأخير 2: يعني أن fsck يفحص نظام الملفات عند الإقلاع إذا احتاج ذلك، بعد القسم الرئيسي الذي يأخذ الرقم 1.

وقبل أي إعادة تشغيل يوجد أمران يغنيانك عن الاكتشاف المؤلم، وهما ما نسيه المطور في القصة: findmnt --verify الذي يفحص الملف كله سطراً سطراً، ثم mount -a الذي يركب كل ما في الملف ولم يركب بعد، فإذا نجح الآن نجح عند الإقلاع غالباً:

findmnt --verify --verbose 2>&1 | tail -8
mount -a
findmnt /srv/data
/srv/data
   [ ] target exists
   [ ] VFS options: noatime
   [ ] userspace options: nofail
   [ ] UUID=4875ede9-71d9-4370-8967-4e2bcdb5cd33 translated to /dev/loop2p1
   [ ] source /dev/loop2p1 exists
   [ ] FS type is ext4
Success, no errors or warnings detected
TARGET    SOURCE       FSTYPE OPTIONS
/srv/data /dev/loop2p1 ext4   rw,noatime

ولاحظ أن الأداة تحققت من كل شيء: المجلد موجود، والخيارات مفهومة، والـ UUID يقود إلى جهاز موجود، والنوع صحيح. وبعد mount -a صار التركيب بالخيار noatime كما كتبنا.

ماذا لو كان في سطر fstab خطأ واحد؟ وضع الطوارئ Emergency Mode

لنكرر الآن ما حدث في القصة: سطر لقرص نسخ احتياطي بحرف واحد خاطئ في آخر الـ UUID، وبدون nofail، ولمجلد غير موجود. والسكربت الرابع يضيفه ثم يفحص:

cp /etc/fstab /etc/fstab.bak
echo "UUID=4875ede9-71d9-4370-8967-4e2bcdb5cd34  /srv/backup  ext4  defaults  0  2" >> /etc/fstab
findmnt --verify
mount -a; echo "mount -a exit: $?"
cp /etc/fstab.bak /etc/fstab
0 parse errors, 2 errors, 0 warnings
/srv/backup
   [E] unreachable on boot required target: No such file or directory
   [E] unreachable on boot required source: UUID=4875ede9-71d9-4370-8967-4e2bcdb5cd34
mount: /srv/backup: can't find UUID=4875ede9-71d9-4370-8967-4e2bcdb5cd34.
mount -a exit: 32

والعبارة المهمة هنا unreachable on boot required، أي أن هذا التركيب مطلوب للإقلاع ولن يتحقق، وهذا بالضبط ما أوقف سيرفر القصة. ولو كتب المطور هذا الأمر قبل إعادة التشغيل لرأى الخطأين في ثانية. ولاحظ أننا نسخنا الملف قبل التعديل وأعدناه بعده، وهذه عادة تستحق أن تلتزم بها مع fstab تحديداً. وإذا وجدت نفسك في وضع الطوارئ بعد إعادة التشغيل فالحل من شاشة المزود: ادخل بكلمة مرور root، وأعد تركيب القسم الرئيسي للكتابة بالأمر mount -o remount,rw /، وأصلح السطر أو علق عليه بعلامة #، ثم أعد الإقلاع.

⚠️
لم نستطع تكرار وضع الطوارئ نفسه، فقد أضفنا نفس السطر الخاطئ إلى fstab في Container يعمل فيه systemd كالعملية رقم 1 وأعدنا تشغيله، فلم يحاول تركيب السطر أصلاً ووصل النظام إلى حالة running، لأن تركيب الأقراص عند الإقلاع مهمة السيرفر الحقيقي. ولهذا فإن findmnt --verify وmount -a هما الاختبار الذي تملكه قبل الإقلاع، فلا تتجاوزهما.

لماذا الـ UUID وليس اسم الجهاز؟

أسماء مثل /dev/sdb تعطيها النواة للأقراص بترتيب اكتشافها عند الإقلاع، والترتيب ليس ثابتاً: إضافة قرص جديد، أو تغيير منفذ، أو قرص يتأخر في الاستجابة، كل ذلك قد يجعل sdb اسماً لقرص آخر في الإقلاع التالي، فيركب النظام القرص الخطأ في المجلد الخطأ، أو يفشل في التركيب. وفي المختبر جربنا ذلك: فصلنا قرص البيانات، وربطنا قرصاً آخر صغيراً فأخذ الاسم loop2، ثم أعدنا ربط قرص البيانات:

umount /srv/data
losetup -d /dev/loop2
truncate -s 64M /lab/disks/other.img
losetup -f --show /lab/disks/other.img
DEV=$(losetup -f --show -P /lab/disks/data.img); echo "data disk is now $DEV"
blkid /dev/loop3p1
mount -a
findmnt /srv/data
/dev/loop2
data disk is now /dev/loop3
/dev/loop3p1: LABEL="data" UUID="4875ede9-71d9-4370-8967-4e2bcdb5cd33" BLOCK_SIZE="4096" TYPE="ext4" PARTLABEL="data" PARTUUID="3d5f4d4b-74df-4c4f-af6a-568301e00391"
TARGET    SOURCE       FSTYPE OPTIONS
/srv/data /dev/loop3p1 ext4   rw,noatime

صار قرص البيانات loop3، والقسم loop3p1، ومع ذلك ركبه mount -a في /srv/data دون أي تعديل في fstab، لأن الـ UUID بقي كما هو. ولو كتبنا في fstab /dev/loop2p1 لحاول النظام تركيب القرص الصغير الآخر في مكان الفواتير:

أول مرة كان القرص data.img باسم /dev/loop2p1، وبعد فصله أخذ القرص other.img الاسم loop2 فعاد data.img باسم /dev/loop3p1، والسطر في fstab بالـ UUID ركبه في /srv/data في الحالتين
الاسم يتبع الترتيب، والـ UUID يتبع نظام الملفات نفسه

وفي الحقيقة ظهر أول مرة في هذه الخطوة المصدر /dev/loop2p1 بدلاً من loop3p1، والسبب ملف الجهاز القديم الذي أنشأناه يدوياً وبقي في /dev برقمي 259:0، وهما نفس رقمي القسم الجديد، فوجد النظام القسم عبر الاسم القديم. فالجهاز كان صحيحاً والاسم مضللاً، وبعد حذف الملف القديم ظهر الاسم الصحيح. وهذا تفصيل خاص بغياب udev في المختبر، ولكنه يؤكد نفس الدرس: الاسم في /dev مجرد باب، والذي يحدد القرص هو رقماه وما عليه. وفي السحابة المشكلة أوضح، فتوثيق Amazon عن أسماء أقراص EBS من نوع NVMe يقول إن تعريفات النظام تكتشف الأقراص المرفقة عند الإقلاع وتعطيها أسماء بحسب ترتيب استجابتها، لا بحسب الاسم الذي اخترته في لوحة التحكم، فالقرص الذي سميته عند الإرفاق /dev/sdf قد يظهر داخل السيرفر باسم /dev/nvme1n1 أو /dev/nvme2n1. ولهذا السبب نفسه تنشئ مزودات مثل Google Cloud روابط ثابتة للأقراص في /dev/disk/by-id/ كما في توثيقها عن روابط الأقراص. والبدائل المقبولة للـ UUID في fstab هي LABEL=data إذا كنت متأكداً أن الاسم فريد على السيرفر، وPARTUUID عندما تريد أن تشير إلى القسم لا إلى نظام الملفات، والمسارات الثابتة في /dev/disk/by-id/ التي تبنى من الرقم التسلسلي للقرص.

القرص الممتلئ والكتل المحجوزة Reserved Blocks

رأينا أن df عرض الحجم 988M والمتاح 921M على قرص فارغ تقريباً. ولكي نفهم هذا الفرق ملأنا القرص كما يملؤه التطبيق، أي بمستخدم عادي اسمه app وليس root، حتى لم يبق شيء:

tune2fs -l /dev/loop3p1 | grep -E "Block count|Reserved block count|Block size"
su -s /bin/bash app -c 'dd if=/dev/zero of=/srv/data/uploads/big.bin bs=1M status=none; echo "dd exit: $?"; ls -lh /srv/data/uploads/big.bin; echo "one more line" >> /srv/data/uploads/log.txt; echo "append exit: $?"'
df -h /srv/data
echo "root can still write" > /srv/data/root-note.txt && echo "root write ok"
Block count:              261632
Reserved block count:     13081
Block size:               4096
dd: error writing '/srv/data/uploads/big.bin': No space left on device
dd exit: 1
-rw-rw-r-- 1 app app 921M Oct 30 18:39 /srv/data/uploads/big.bin
append exit: 1
bash: line 1: echo: write error: No space left on device
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop3p1    988M  921M     0 100% /srv/data
root write ok

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

  • نظام الملفات يحجز 13081 كتلة من 261632، أي 5% بالضبط، وهي 51 MiB تقريباً، وهذه هي القيمة الافتراضية لـ ext4.
  • المستخدم app كتب 921M ثم توقف بالخطأ No space left on device، واسمه في البرمجة ENOSPC، وأي محاولة كتابة بعدها فشلت، حتى إضافة سطر واحد إلى ملف سجل. والسطر append exit: 1 ظهر قبل رسالة الخطأ نفسها لأن المخرج العادي ومخرج الأخطاء يصلان إلى الشاشة في طريقين مختلفين، وهذا شكل ستراه كثيراً.
  • df يقول 100% والمتاح صفر، بينما root كتب ملفاً بعد ذلك دون مشكلة.

والسبب كما تقول صفحة tune2fs(8) أن ext4 يحجز نسبة من الكتل لا يستخدمها إلا root، حتى تستمر خدمات النظام التي تعمل بصلاحياته في العمل بعد أن يملأ المستخدمون القرص، وحتى يقلل التجزئة. وهذا في القسم الرئيسي حماية حقيقية: عندما يملأ تطبيق القرص يستطيع مدير النظام أن يدخل ويحذف ويصلح. أما في قرص بيانات كبير لا يكتب فيه إلا التطبيق، فإن 5% من 2 TB هي 100 GB معطلة، ولذلك يمكنك تقليلها:

tune2fs -m 1 /dev/loop3p1
df -h /srv/data
su -s /bin/bash app -c 'echo "one more line" >> /srv/data/uploads/log.txt; echo "append exit: $?"'
tune2fs 1.47.2 (1-Jan-2025)
Setting reserved blocks percentage to 1% (2616 blocks)
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop3p1    988M  921M   41M  96% /srv/data
append exit: 0

صار المحجوز 1%، وظهر 41M متاحة للتطبيق دون أن نحذف شيئاً، ونجحت الكتابة. وهذا تغيير يعمل فوراً على نظام ملفات مركب. ولاحظ أن الحجم نفسه 988M وليس 1022M لأن جداول الـ Inodes والسجل أخذت مكانها من القسم عند التهيئة، أما الفرق بين الحجم والمتاح فمعظمه الكتل المحجوزة لـ root، وجزء صغير منه احتياطي داخلي يحتفظ به ext4 لعملياته، ولهذا ظهر 41M وليس 51M بعد التغيير:

شريط يمثل قرص البيانات بـ 261632 كتلة، منها 13081 كتلة محجوزة لـ root، و921M يستطيع التطبيق استخدامها. عند 100% فشل المستخدم app في الكتابة بالخطأ No space left on device بينما كتب root، وبعد tune2fs -m 1 صار المحجوز 2616 كتلة وظهرت 41M متاحة بنسبة استخدام 96%
100% عند التطبيق لا تعني 100% عند root

ولا تقلل هذه النسبة في القسم الرئيسي، فهي التي تنقذك عندما تملأ السجلات القرص في منتصف الليل. ويوجد نوعان آخران من «القرص الممتلئ» يربكان الناس: نفاد الـ Inodes بينما المساحة فارغة، وملف محذوف ما زال برنامج يكتب فيه فلا تعود مساحته، وقد شرحنا الاثنين مع طريقة كشفهما في قسم القرص من مقال تحليل أداء سيرفر Linux في 60 ثانية، فلن نكررهما هنا.

مساحة التبديل Swap: ملف أم قسم؟

مساحة التبديل Swap مكان على القرص تنقل إليه النواة صفحات الذاكرة التي لم تستخدم منذ مدة، لتفرغ مكانها في RAM لمن يحتاجه الآن. وهي ليست ذاكرة إضافية بالمعنى الحقيقي، لأن القرص أبطأ من الذاكرة بآلاف المرات، وإنما مساحة أمان: تبقي السيرفر يعمل ببطء بدلاً من أن يبدأ قاتل العمليات OOM Killer في إنهاء الخدمات عند أول ضغط على الذاكرة. وقديماً كان الناس يخصصون لها قسماً كاملاً عند تثبيت النظام، أما اليوم فالشائع ملف عادي، لأنك تنشئه وتكبره وتحذفه دون إعادة تقسيم القرص. والسكربت السادس ينشئ ملف Swap بحجم 256 MiB على قرص البيانات:

swapon --show
free -m
cat /proc/sys/vm/swappiness
fallocate -l 256M /srv/data/swapfile
chmod 600 /srv/data/swapfile
mkswap /srv/data/swapfile
swapon /srv/data/swapfile
swapon --show
free -m
NAME     TYPE      SIZE USED PRIO
/dev/sdb partition   8G   0B   -2
               total        used        free      shared  buff/cache   available
Mem:           32036        2034       27719          41        2774       30001
Swap:           8192           0        8192
60
Setting up swapspace version 1, size = 256 MiB (268431360 bytes)
no label, UUID=a09113c0-ee4c-491e-8466-6af8dbfb0784
NAME               TYPE      SIZE USED PRIO
/dev/sdb           partition   8G   0B   -2
/srv/data/swapfile file      256M   0B   -3
               total        used        free      shared  buff/cache   available
Mem:           32036        2033       27719          41        2774       30002
Swap:           8447           0        8447

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

  • الجهاز الذي يشغل Docker عنده قسم Swap بحجم 8G هو /dev/sdb، وبعد التفعيل ظهر ملفنا تحته بنوع file وأولوية أقل، فالنواة تستخدم الأعلى أولوية أولاً.
  • الصلاحية 600 ضرورية، لأن ملف Swap يحمل نسخاً من ذاكرة البرامج، وفيها أحياناً كلمات مرور ومفاتيح، فلا يجب أن يقرأه غير root، والأمر swapon يحذرك إذا كانت الصلاحيات أوسع.
  • السطر Swap في free زاد من 8192 إلى 8447 وليس 8448، والفرق صفحة واحدة يحجزها mkswap لرأس الملف.
  • القيمة swappiness هي 60، وهي تقول للنواة كم تميل إلى نقل صفحات البرامج إلى Swap بدلاً من التخلص من نسخ الملفات المحفوظة في الذاكرة، والمدى من 0 إلى 200 كما في توثيق إعدادات الذاكرة في النواة.

ولكي يبقى الملف بعد الإقلاع يضاف إلى fstab بسطر نوعه swap ونقطة تركيبه none:

/srv/data/swapfile  none  swap  defaults,nofail  0  0

وبعد التجربة ألغينا التفعيل بالأمر swapoff وحذفنا الملف، لأن ملف Swap المفعل من داخل الـ Container يخص نواة الجهاز كله وليس الـ Container. وكم تحتاج من Swap؟ القاعدة القديمة «ضعف الذاكرة» وضعت لأجهزة فيها 256 MB من RAM، ولا معنى لها على سيرفر فيه 64 GB. والمعقول لأغلب السيرفرات مساحة صغيرة من 1 إلى 4 GB تمتص الضغط المفاجئ وتعطيك وقتاً لترى المشكلة في المراقبة، أما إذا وجدت السيرفر يستخدم Swap باستمرار فالحل ذاكرة أكثر وليس Swap أكبر. وتشرح أقسام الذاكرة في مقال تحليل الأداء متى يصبح Swap مشكلة وكيف تقيسه.

ماذا لو وضعنا ملف Swap على نظام ملفات لا يناسبه؟

قبل أن نضع الملف على قرص البيانات جربنا أن نضعه على نظام ملفات الـ Container نفسه، وهو من نوع overlay:

overlay        overlay 1055762868 156584856 845474540  16% /
swapon: /swap-on-overlay: swapon failed: Invalid argument
swapon exit: 255

رفضت النواة الملف برسالة Invalid argument رغم أن mkswap نجح. والسبب أن النواة تكتب في ملف Swap مباشرة إلى كتل القرص دون المرور بنظام الملفات، فتحتاج أن تعرف مكان كل كتلة مسبقاً وأن يبقى ثابتاً، وأنظمة مثل overlay لا تعطيها ذلك. وتذكر صفحة swapon(8) حالات مشابهة: الملفات المتفرقة التي ينشئها truncate أو cp مرفوضة، وعلى Btrfs يحتاج الملف خاصية عدم النسخ عند الكتابة، وقد يعامل fallocate على بعض أنظمة الملفات كملف متفرق، ولذلك تنصح الصفحة بالأمر dd من /dev/zero كأكثر طريقة تعمل في كل مكان. أما على ext4 فقد عمل fallocate كما رأينا.

توسيع القرص Resize دون إيقاف

بعد أشهر امتلأ قرص البيانات أيضاً، فزاد الفريق حجمه من لوحة المزود. وهنا يتكرر ما حدث في البداية: الحجم الجديد لا يظهر في df، لأن الزيادة وصلت إلى طبقة القرص فقط، والقسم ونظام الملفات ما زالا بالحجم القديم. ومثلنا ذلك في المختبر بزيادة حجم الملف إلى 1.5 GiB، ثم أخبرنا جهاز الحلقة بالحجم الجديد بالأمر losetup -c، وهو ما يحدث تلقائياً في أغلب السحابات أو بإعادة فحص القرص:

NAME       SIZE
loop3      1.5G
`-loop3p1 1022M
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop3p1    988M  284K  961M   1% /srv/data

القرص 1.5G، والقسم 1022M، ونظام الملفات 988M. وأول محاولة كانت توسيع القسم بالأداة parted وهو مركب، فتوقفت عند سؤالين لا يقبل الوضع غير التفاعلي الإجابة عنهما:

Warning: Not all of the space available to /dev/loop3 appears to be used, you can fix the GPT to use all of the space (an extra 1048576 blocks) or continue with the current setting?
Warning: Partition /dev/loop3p1 is being used. Are you sure you want to continue?

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

sfdisk --relocate gpt-bak-std /dev/loop3
echo ", +" | sfdisk --no-reread -N 1 /dev/loop3 2>&1 | tail -6
lsblk -o NAME,SIZE /dev/loop3
partx -u /dev/loop3
lsblk -o NAME,SIZE /dev/loop3
resize2fs /dev/loop3p1
df -h /srv/data
cat /srv/data/orders.csv
The partition table has been altered.
Calling ioctl() to re-read partition table.
Re-reading the partition table failed.: Device or resource busy
The kernel still uses the old table. The new table will be used at the next reboot or after you run partprobe(8) or partx(8).
Syncing disks.
NAME       SIZE
loop3      1.5G
`-loop3p1 1022M
NAME       SIZE
loop3      1.5G
`-loop3p1  1.5G
resize2fs 1.47.2 (1-Jan-2025)
Filesystem at /dev/loop3p1 is mounted on /srv/data; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 1
The filesystem on /dev/loop3p1 is now 392955 (4k) blocks long.

Filesystem      Size  Used Avail Use% Mounted on
/dev/loop3p1    1.5G  288K  1.5G   1% /srv/data
order 1001

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

  • الأمر الأول نقل نسخة الجدول الاحتياطية إلى آخر القرص، والثاني جعل القسم الأول يأخذ كل المساحة، والرمز , + يعني «نفس البداية، وأكبر نهاية ممكنة».
  • Re-reading the partition table failed.: Device or resource busy: الجدول على القرص تغير، ولكن النواة رفضت قراءته من جديد لأن القسم مركب ومستخدم، فبقي lsblk يعرض 1022M. وهذه هي المشكلة الثانية التي واجهتنا، وحلها في الرسالة نفسها: الأمر partx -u يخبر النواة بالحجم الجديد للقسم دون أن يطلب إعادة قراءة الجدول كله، فصار القسم 1.5G.
  • الأمر resize2fs وسع ext4 وهو مركب، وهذا ما تعنيه عبارة on-line resizing، فصار 392955 كتلة بدلاً من 261632.
  • الملف orders.csv الذي كتبناه قبل التوسيع بقي كما هو، والتطبيق لم يتوقف لحظة.
ثلاث طبقات للتوسيع بالترتيب: القرص من 1G إلى 1.5G بالأمرين truncate وlosetup -c أو من لوحة المزود، ثم القسم loop3p1 من 1022M إلى 1.5G بالأمرين sfdisk -N 1 وpartx -u لأن النواة تبقي الجدول القديم للقسم المستخدم، ثم نظام الملفات من 261632 إلى 392955 كتلة بالأمر resize2fs، ومع LVM طبقتان إضافيتان pvresize ثم lvextend
كل طبقة تتوسع بأمرها، من الأسفل إلى الأعلى

وعلى السيرفر الحقيقي تختصر الأداة growpart الخطوتين الأوليين في أمر واحد، وإذا كان القرص مبنياً على LVM كما في التثبيت الافتراضي لـ Ubuntu فهناك طبقتان إضافيتان، وقد شرحنا الطريق كاملاً لقرص النظام في دليل توسيع قرص النظام في Ubuntu عند استخدام LVM. ولاحظ أن التوسيع ممكن وهو مركب، أما التصغير فقصة أخرى: ext4 لا يصغر إلا وهو غير مركب، وXFS لا يدعم التصغير إلا في حالة محدودة جداً كما تقول صفحة xfs_growfs(8)، ولذلك ابدأ بحجم معقول ووسع عند الحاجة، ولا تفعل العكس. وقبل أي تعديل على جدول الأقسام خذ Snapshot أو نسخة احتياطية، فخطأ في رقم بداية القسم يجعل نظام الملفات غير قابل للقراءة، وقد شرحنا ذلك في دليل النسخ الاحتياطي بقاعدة 3-2-1.

خيارات التركيب Mount Options: للقراءة فقط ومنع التنفيذ

الخيارات في الحقل الرابع من fstab تغير سلوك نظام الملفات كله، وأغلبها يمكن تغييره دون فك التركيب بالخيار remount. وجربنا اثنين منها على قرص البيانات:

mount -o remount,ro /srv/data
touch /srv/data/new.txt
mount -o remount,rw,noexec /srv/data
printf '#!/bin/sh\necho hello from the data disk\n' > /srv/data/run.sh && chmod +x /srv/data/run.sh
/srv/data/run.sh; echo "exec exit: $?"
sh /srv/data/run.sh
mount -o remount,exec /srv/data
touch: cannot touch '/srv/data/new.txt': Read-only file system
/lab/07-grow-and-options.sh: line 22: /srv/data/run.sh: Permission denied
exec exit: 126
hello from the data disk
  • ro: للقراءة فقط، ومفيد عندما تريد نسخ قرص دون أن يتغير، أو عندما تحمي قرص أرشيف من خطأ. ولاحظ أن النواة نفسها قد تعيد تركيب نظام الملفات بهذا الوضع إذا اكتشفت فيه خطأ، عندما يكون الخيار errors=remount-ro مضبوطاً، وهو الشائع للقسم الرئيسي في Debian وUbuntu، فإذا رأيت فجأة Read-only file system على قرص يعمل فاقرأ سجل النواة بالأمر dmesg قبل أي شيء.
  • noexec: يمنع تشغيل أي برنامج من هذا القرص، فرفضت النواة تشغيل السكربت برمز 126 رغم صلاحية التنفيذ. وهو مناسب لمجلدات الرفع مثل الفواتير والصور، فإذا رفع مخترق سكربتاً عبر ثغرة لا يستطيع تشغيله مباشرة. ولكن انظر إلى السطر الأخير: sh /srv/data/run.sh عمل، لأن البرنامج الذي ينفذ هنا هو sh من القرص الرئيسي، والسكربت مجرد ملف يقرؤه. فالخيار noexec طبقة حماية مفيدة وليس جداراً كاملاً.
  • ومن الخيارات الشائعة أيضاً nosuid الذي يتجاهل صلاحية SUID على هذا القرص، وnodev الذي يمنع ملفات الأجهزة عليه، وكلاهما معقول لأي قرص بيانات، وتجد القائمة الكاملة في صفحة mount(8).

الأوامر Commands التي تحتاجها مع الأقراص Disks

السؤالالأمرما تبحث عنه
ما الأقراص والأقسام الموجودة؟lsblk -fقرص بلا أقسام أو قسم بلا نظام ملفات
ما أسماء هذا القسم الثابتة؟blkid /dev/sdb1UUID وLABEL وPARTUUID
ما المركب الآن وبأي خيارات؟findmnt أو findmnt /srv/dataالمصدر والخيارات الفعلية
هل الملف fstab سليم؟findmnt --verify ثم mount -aأي سطر بالحرف [E]
كم بقي من المساحة والـ Inodes؟df -hT وdf -iالاستخدام ونوع نظام الملفات
من يأخذ المساحة؟du -xh --max-depth=1 /srvالخيار -x يبقى في نفس نظام الملفات
كم يحجز ext4 لـ root؟tune2fs -l /dev/sdb1Reserved block count
ما مساحة التبديل المفعلة؟swapon --show وfree -mالحجم والاستخدام والأولوية
هل وصل الحجم الجديد؟lsblk ثم df -hأي طبقة ما زالت بالحجم القديم

أخطاء شائعة

  • البحث عن القرص الجديد في df -h: لا يظهر فيه حتى يقسم ويهيأ ويركب، والأمر الصحيح lsblk.
  • تصديق du وحده عند البحث عن المساحة: لا يرى ما تحت نقاط التركيب ولا الملفات المحذوفة المفتوحة، فقارنه دائماً بما يقوله df.
  • تهيئة القرص الخطأ: mkfs لا يسأل، فتأكد من الاسم بالحجم في lsblk وبأنه بلا نظام ملفات قبل أي تهيئة.
  • تشغيل التطبيق قبل التركيب: يكتب في المجلد على القرص الرئيسي، ثم يخفي التركيب ما كتبه.
  • الاكتفاء بالأمر mount: يختفي أثره عند إعادة التشغيل، ويعود التطبيق للكتابة في القرص الرئيسي دون أن يلاحظ أحد.
  • كتابة /dev/sdb1 في fstab: الأسماء تتغير بترتيب الاكتشاف، والصحيح UUID=.
  • نسيان nofail لقرص بيانات: غيابه يوقف الإقلاع كله في وضع الطوارئ.
  • إعادة التشغيل دون findmnt --verify وmount -a: هذه هي الطريقة التي توقف بها سيرفر القصة.
  • تقليل الكتل المحجوزة في القسم الرئيسي: تفقد المساحة التي تنقذك عندما تملأ السجلات القرص.
  • ملف Swap بصلاحيات مفتوحة، أو على نظام ملفات لا يناسبه، أو مصنوع بالأمر truncate.
  • توسيع القرص من لوحة المزود والاكتفاء بذلك: يجب توسيع القسم ثم نظام الملفات، وقد يحتاج القسم المستخدم partx -u حتى تراه النواة.

قائمة تحقق Checklist لإضافة قرص بيانات

  • Snapshot أو نسخة احتياطية قبل أي تعديل على الأقسام.
  • lsblk -f: القرص الجديد بالحجم المتوقع ودون نظام ملفات.
  • جدول GPT وقسم يبدأ من 1MiB.
  • mkfs.ext4 -L باسم واضح، أو XFS إذا كان هذا معيار فريقك، مع عدد Inodes مناسب إذا كانت الملفات صغيرة جداً.
  • المجلد فارغ قبل التركيب، والتطبيق متوقف أو لا يكتب فيه بعد.
  • سطر fstab بالـ UUID، مع nofail وnoatime، وnosuid,nodev وربما noexec لمجلدات الرفع.
  • findmnt --verify بلا أخطاء، ثم mount -a بلا أخطاء، ثم إعادة تشغيل واحدة مخططة للتأكد.
  • الصلاحيات والمالك على المجلد للمستخدم الذي يعمل به التطبيق، وRequiresMountsFor في خدمته.
  • مراقبة المساحة والـ Inodes على القرص الجديد بتنبيه قبل 85% وليس عند 100%.
  • ملف Swap صغير بصلاحية 600 وسطر في fstab إذا كان السيرفر بلا Swap.

حذف المختبر Cleanup

أجهزة الحلقة تخص نواة الجهاز، فيجب فك التركيب وفصلها قبل حذف الـ Container، وإلا بقيت مربوطة بملفات لم تعد موجودة:

docker exec sbl-dk sh -c 'umount /srv/data; losetup -d /dev/loop3 /dev/loop2; losetup -l | grep lab || echo "no lab loops"'
docker rm -f sbl-dk
docker rmi sbl-disks:1

الخلاصة

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

  • بين القرص والمجلد أربع طبقات: جدول الأقسام، ونظام الملفات، والتركيب، والسطر في fstab، والمساحة لا تظهر في df حتى تكتمل الثلاث الأولى.
  • التركيب يخفي محتوى المجلد ولا يحذفه، فالفاتورة التي كتبت قبل التركيب رجعت بعد umount.
  • الأمر df يسأل نظام الملفات والأمر du يجمع ما يراه في الشجرة، ولهذا لا يرى du الملفات المخفية تحت نقاط التركيب ولا الملفات المحذوفة المفتوحة ولا الكتل المحجوزة، والملف المتفرق data.img كان حجمه الظاهر 1.0G ومساحته الفعلية صفراً.
  • الـ UUID يبقى مع نظام الملفات، بينما تغير اسم القرص في المختبر من loop2 إلى loop3 بمجرد أن سبقه قرص آخر.
  • nofail يمنع قرص البيانات الغائب من إيقاف الإقلاع، وfindmnt --verify أظهر السطر الخاطئ بخطأين قبل أي إعادة تشغيل.
  • ext4 يحجز 5% لـ root، فتوقف التطبيق عند 100% بينما كتب root، وtune2fs -m 1 أعاد 41M على قرص بيانات.
  • ملف Swap يحتاج صلاحية 600 ونظام ملفات يعطي النواة كتلاً ثابتة، فرفضته النواة على overlay وقبلته على ext4.
  • التوسيع من الأسفل إلى الأعلى: القرص ثم القسم ثم نظام الملفات، والقسم المستخدم احتاج partx -u حتى تراه النواة، وresize2fs عمل والقرص مركب.

وفي المقال القادم بإذن الله سوف نستخدم كل هذا في تشخيص سيرفر معطل، ومنه القرص الممتلئ الذي يوقف الخدمات. وهذه مقالات مرتبطة:

دليلنظام التشغيل Operating System: ماذا يفعل بين برامجك والجهاز؟خريطة للمبتدئين تريك بأوامر حقيقية كيف يدير نظام التشغيل العمليات والذاكرة والملفات، ولماذا لا يسقط جهازك كله عندما يسقط برنامج واحد.دليلنظام الملفات File System: Inodes والسجل Journal وfsyncنتابع الكتابة من برنامجك حتى القرص داخل ext4، ونقطع الكهرباء عن نظام ملفات يعمل لنرى ماذا يبقى فعلاً، ومتى تحتاج fsync وكم يكلف.دليلتوسيع قرص النظام في Ubuntu عند استخدام LVMعندما تزيد حجم القرص من Proxmox أو من لوحة المزود ويبقى الحجم القديم، هكذا توسع قرص النظام في Ubuntu دون إيقاف السيرفر.دليلأنواع التخزين وواجهة S3: أين تضع بيانات تطبيقك؟أين تضع قاعدة البيانات وملفات المستخدمين والنسخ الاحتياطية، ولماذا يناسب كلاً منها نوع مختلف من التخزين.دليلتحليل أداء سيرفر Linux في 60 ثانية: ماذا تكتب أولاً؟ماذا تكتب أول ما تدخل إلى سيرفر بطيء، وكيف تعرف خلال دقيقة هل المشكلة في المعالج أم الذاكرة أم القرص أم الشبكة.

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

  • أكتوبر 2026: كتابة الدليل، وتنفيذ التجارب على أجهزة حلقة Loop Devices داخل Container من Debian 13 (trixie) مع util-linux 2.41.5 وe2fsprogs 1.47.2 وparted 3.6، على نواة Linux 5.15 (WSL2) وDocker Engine 29.8.0.
Could not load content
نشرة عرب رووت | ArabRoot

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

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

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

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