SSD Nodes Learn 🎉 VPS من $5.50/شهر
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-21

شرح تبعيات systemd وشروطها: Requires لا يعني After

افهم الفرق بين Requires وWants وAfter وBefore وExecStartPre وCondition، ولماذا يفشل بدء الوحدة عند الإقلاع رغم نجاحها يدوياً، وكيف تشخّص السبب.

لا يعني Requires الترتيب After

توجد أربع آليات منفصلة للتبعيات والشروط في systemd، لكن معظم ملفات الوحدات تستخدمها كما لو كانت آلية واحدة. يحدد Requires= وWants= الوحدات الأخرى التي تُضاف إلى معاملة التشغيل. ويحدد After= وBefore= ترتيب بدء الوحدات. وينفّذ ExecStartPre= فحصاً يمكن أن يؤدي فشله إلى فشل الوحدة. أما عائلتا Condition وAssert فتحددان ما إذا كانت الوحدة ستعمل أصلاً. كل آلية مستقلة عن الآليات الأخرى، لذلك يمكن لوحدة أن تتطلب وحدة أخرى، ومع ذلك تبدأ الوحدتان في اللحظة نفسها.

الجملة الأخيرة هي سبب الخلل في معظم البلاغات من نوع: «تعمل عند تشغيلها يدوياً، لكنها تفشل عند الإقلاع».

[Unit]
Description=Inventory API
Requires=postgresql.service

[Service]
ExecStartPre=/usr/bin/pg_isready -h 127.0.0.1 -t 5
ExecStart=/usr/local/bin/inventory-api

تضيف Requires=postgresql.service PostgreSQL إلى معاملة البدء نفسها. لكنها لا تنتظرها. يشغّل systemd المهمتين بالتوازي، لذلك تعمل pg_isready بينما لا يزال PostgreSQL يفتح دليل البيانات. وتنتهي بحالة 2 لأن لا شيء يستمع بعد، فتفشل الوحدة قبل الوصول إلى ExecStart. ويعمل تنفيذ sudo systemctl start inventory-api بعد ساعة، لأن PostgreSQL يكون قد بدأ بحلول ذلك الوقت. لم يتغير شيء في ملف الوحدة، ولذلك يبدو الملف سليماً.

الإصلاح يتطلب سطراً واحداً.

[Unit]
Requires=postgresql.service
After=postgresql.service

توجد نقطة أدق في الموضع نفسه. لا تؤدي تبعية Requires= الفاشلة إلى إيقاف بدء وحدتك إلا إذا ضبطت عليها أيضاً After=. من دون تحديد الترتيب، يكون systemd قد بدأ وحدتك عند فشل الوحدة الأخرى، ولذلك لا يبقى شيء لإلغائه. لا يوفر Requires= وحده الحماية التي يظن البعض أنه يوفرها. اكتب After= بجانب كل Requires= وكل Wants=، ما لم يكن لديك سبب محدد لعدم فعل ذلك.

ما الذي يضمنه Requires وWants وRequisite وBindsTo

كل هذه إعدادات تبعية. ولا يفرض أيٌّ منها ترتيباً.

  • Wants=: يضم الوحدة الأخرى. إذا فشلت أو لم تكن موجودة، تبدأ هذه الوحدة على أي حال. وهذا ما ينشئه systemctl enable باعتباره رابطاً رمزياً داخل دليل .wants/.
  • Requires=: يضم الوحدة الأخرى. إذا فشلت، وكنت قد رتّبتها أيضاً باستخدام After=، فلا تبدأ هذه الوحدة. وإذا أوقفت الوحدة الأخرى صراحةً لاحقاً، تتوقف هذه الوحدة معها.
  • Requisite=: لا يضم الوحدة الأخرى. إذا لم تكن نشطة مسبقاً، تفشل هذه الوحدة فوراً.
  • BindsTo=: مثل Requires=، وتتوقف هذه الوحدة أيضاً كلما توقفت الوحدة الأخرى لأي سبب، بما في ذلك اختفاء عتاد.
  • PartOf=: ينتقل الإيقاف وإعادة التشغيل من الوحدة الأخرى إلى هذه الوحدة. لا ينتقل البدء.
  • Conflicts=: يؤدي بدء هذه الوحدة إلى إيقاف الوحدة الأخرى.

بالنسبة إلى daemon يتصل بـdaemon آخر، يكون الجمع بين Wants= وAfter= هو الزوج المناسب عادةً. يربط Requires= دورة حياة الوحدتين: إذا أوقفت قاعدة البيانات للصيانة، تتوقف معها application، ولا تعود application عند عودة قاعدة البيانات. ويوفر الجمع بين Wants= وAfter= ترتيب الإقلاع من دون هذا الربط، بينما تتولى سياسة إعادة التشغيل معالجة حالة زوال التبعية لاحقاً.

كما أنك ترث تبعيات لم تكتبها بنفسك. مع DefaultDependencies=yes، وهو الإعداد الافتراضي، تحصل service عادية تلقائياً على Requires=sysinit.target وAfter=sysinit.target basic.target وConflicts=shutdown.target. لذلك تبدأ service التي يحتوي قسم [Unit] فيها على إعدادات قليلة جداً في مرحلة متأخرة من الإقلاع، وتتوقف أيضاً بطريقة سليمة عند إيقاف التشغيل.

After وBefore يرتبان المعاملة، ولا يفعلان شيئاً آخر

After= وBefore= مخصصان للترتيب فقط. ولا يفرضان أي متطلب على الإطلاق. إن وجود After=redis.service في وحدة لا تستدعي Redis إليها أي وحدة أخرى لا يفعل شيئاً: إذا لم تكن redis.service جزءاً من المعاملة، فلا توجد وحدة تنتظرها، ولذلك تبدأ وحدتك فوراً.

من المهم توضيح ذلك مرتين، لأن هذه هي بالضبط صورة خطأ network-online.target الذي سيظهر لاحقاً. ينتظر الترتيب الوحدات التي بدأ تشغيلها مسبقاً في المعاملة نفسها فقط.

العلاقة بين الخيارين متناظرة. إن كتابة After=b.service في a.service تعني الشيء نفسه الذي تعنيه كتابة Before=a.service في b.service، لذلك استخدم أحدهما وضعه في الوحدة التي تملكها. ينعكس الترتيب تلقائياً عند إيقاف التشغيل، ولذلك يعني After=b.service أيضاً إيقاف وحدتك قبل b.service.

تنتظر ‎After=‎ حالة «بدء التشغيل»، ويحدّد ‎Type=‎ معنى ذلك

ينتظر After= حتى تنتهي الوحدة الأخرى من بدء التشغيل. ويحدّد Type= لهذه الوحدة بالكامل معنى «انتهت من بدء التشغيل».

  • Type=simple: بمجرد أن ينشئ systemd عمليةً فرعية للعملية. قد لا يكون البرنامج قد حلّل إعداداته بعد، ناهيك عن فتح socket.
  • Type=exec: بمجرد نجاح execve(). هذا أقوى قليلاً، لكنه لا يثبت أن الخدمة جاهزة.
  • Type=forking: عند خروج العملية الأب الأصلية.
  • Type=oneshot: عند خروج العملية. هنا يعني «بدأ التشغيل» فعلاً أن العمل اكتمل.
  • Type=notify: عندما ترسل الخدمة READY=1 عبر socket الإشعارات الخاص بها. هذا هو النوع الوحيد الذي يبلّغ عن الجاهزية الفعلية.

لذلك فإن After= على daemon من نوع Type=simple ليس ضماناً قوياً، وهذا هو الجزء الثاني من حالة السباق في المثال الأول. إذا كانت الوحدة التي تعتمد عليها تُصدَر بصيغة Type=simple، فإن ترتيب التشغيل بعدها لا يعني أنها تقبل الاتصالات. توجد إجابتان عمليتان. رتّب التشغيل بعد وحدة socket الخاصة بها، بحيث يضع kernel الاتصالات الواردة في قائمة انتظار بينما يواصل daemon بدء تشغيله. أو اجعل خدمتك تعيد المحاولة، ودَع سياسة إعادة التشغيل تتولى ذلك. يظهر النوع الذي تستخدمه الوحدة في systemctl cat، ويستحق إعداد Type= وما يوضحه كل خيار لـsystemd القراءة قبل الاعتماد على ترتيب التشغيل.

ExecStartPre هو حاجز يمكن أن يفشل الوحدة

يُنفَّذ ExecStartPre= قبل ExecStart=. إذا انتهى برمز خروج غير صفري، يُلغى التفعيل وتنتقل الوحدة إلى failed. ولا يُنفَّذ ExecStart= مطلقاً. هذه هي الآلية التي تقف وراء عدد كبير من الوحدات التي تفشل من دون ظهور رسالة من البرنامج الفعلي، لأن البرنامج لم يبدأ أصلاً.

حقائق مهمة:

  • ليس هذا مُفسِّراً للأوامر. لا توجد أنابيب، أو إعادة توجيه، أو أنماط glob، أو &&. يجب أن يكون الرمز الأول مساراً مطلقاً. غلّف السطر في /bin/sh -c '...' عندما تحتاج إلى صياغة shell.
  • تجعل البادئة - الخروج برمز غير صفري غير قاتل: ExecStartPre=-/usr/bin/optional-check.
  • يجب أن ينتهي كل ExecStartPre= قبل تشغيل التالي. ولا يمكنه بدء عملية طويلة التشغيل.
  • تشترك جميع أسطر ExecStartPre= في TimeoutStartSec= مع ExecStart=. يؤدي فحص مسبق ينتظر قاعدة بيانات في حلقة إلى استهلاك مهلة البدء، ثم تفشل الوحدة مع Result: timeout بعد ظهور start operation timed out. Terminating. في السجل.

يسمّي سطر الفشل عملية التحكم، وليس العملية الرئيسية:

inventory-api.service: Control process exited, code=exited, status=2/INVALIDARGUMENT
inventory-api.service: Failed with result 'exit-code'.

اقرأ هذا الاسم الرمزي بعناية. يربط systemd رموز الخروج الصغيرة بجدول ثابت، لذلك يطبع 2 دائماً القيمة INVALIDARGUMENT، بصرف النظر عما قصده البرنامج بها. أما status=203/EXEC فهو الذي يحمل المعلومات الفعلية: لم يتمكن systemd من تنفيذ الملف الثنائي إطلاقاً، لأن المسار خاطئ أو لأن الملف غير قابل للتنفيذ.

لا تستخدم ExecStartPre= لإنشاء المجلدات. تنشئ RuntimeDirectory= وStateDirectory= وLogsDirectory= وCacheDirectory= المجلدات بالمالك والوضع الصحيحين، ويُحذف RuntimeDirectory= عند توقف الخدمة. كما تعمل هذه العناصر بصورة صحيحة تحت DynamicUser=، بخلاف mkdir المكتوب يدوياً.

يفشل Condition بصمت. أما Assert فيفشل بصوت واضح.

تشغّل عائلتا Condition وAssert الاختبارات نفسها. ويكمن الاختلاف فقط في ما يحدث عند فشل الاختبار.

يتسبب فشل Condition...= في تخطي الوحدة. وتُبلَّغ مهمة البدء على أنها ناجحة. وتبقى الوحدة في الحالة inactive (dead)، ولا تُعلَّم أي وحدة على أنها فاشلة، ولا يُطلَق أي تنبيه، ويسجل journal سطراً واحداً:

Condition check resulted in Inventory API being skipped.

في systemd 250 والإصدارات الأحدث، يعرض systemctl status السبب مباشرة:

     Active: inactive (dead)
  Condition: start condition unmet at Thu 2026-08-20 09:14:02 UTC; 2min ago

يسمي السطر ذو المسافة البادئة تحته التوجيه الدقيق الذي فشل، مثل ConditionPathExists=/etc/inventory/api.conf was not met.

يتسبب فشل Assert...= في فشل الوحدة. ويذكر journal أن Assertion failed for Inventory API.، وتنتهي الوحدة في الحالة failed (Result: assert)، وهذا واضح بما يكفي لكي تلاحظه أنظمة المراقبة.

اختر بينهما بسؤال ما الذي يعنيه فشل الاختبار. تعني Condition أن «هذه الوحدة لا تنطبق على هذا الجهاز». وتعني Assert أن «هذا الأمر يجب أن يكون صحيحاً، وإذا لم يكن كذلك فأبلغ شخصاً». تستخدم معظم الوحدات Condition. ولا تستخدم Assert إلا عندما يكون عدم تنفيذ أي إجراء بصمت أسوأ من وجود وحدة فاشلة.

ترتبط بعائلة Condition مشكلتان شائعتان.

أولاً، لا يؤدي فشل الشرط إلى فشل الوحدات التي تعتمد على الوحدة المعنية. إذا كانت a.service تتضمن Requires=b.service، وكانت b.service تتخطى التشغيل بسبب شرط، فستظل مهمة بدء b.service محسوبة على أنها مكتملة، ولذلك تبدأ a.service بشكل طبيعي في حالة لا تكون فيها b قيد التشغيل. يحمي الشرط الوحدة التي كُتب فيها فقط.

ثانياً، تُقيَّم الشروط في كل مرة تبدأ فيها الوحدة، في اللحظة التي تُنفَّذ فيها المهمة. يمكن لوحدة يشغّلها مؤقت systemd على VPS أن تُتخطى مئة مرة متتالية من دون أن تبدو فاشلة ولو مرة واحدة. وهذا من الفئة نفسها لعدم التنفيذ الصامت الذي يحدث مع مهمة cron تعمل لكنها لا تنفذ شيئاً، وتكتشفه بالطريقة نفسها: اقرأ journal الخاص بالوحدة بدلاً من الاعتماد على حالة الخروج.

الشروط التي تستحق معرفتها على الخادم:

  • ConditionPathExists=/etc/inventory/api.conf، ونفيه ConditionPathExists=!/etc/inventory/api.conf.
  • ConditionFileNotEmpty= وConditionDirectoryNotEmpty=، لملف إعداد أو دليل بيانات أنشأته حزمة لكنه بقي فارغاً.
  • ConditionVirtualization=، لكي تتمكن وحدة تحتاج إلى واجهة kernel فعلية من تضمين ConditionVirtualization=!container. تحقق مما يعرضه جهازك باستخدام systemd-detect-virt.
  • يطابق ConditionHost= اسم المضيف أو معرّف الجهاز، وبذلك يتصرف ملف وحدة مشترك بطريقة مختلفة على خادمين.
  • ConditionKernelCommandLine= وConditionKernelVersion=، للوحدات المرتبطة بمعامل إقلاع أو بإصدار kernel أدنى.

يؤدي تعيين فارغ إلى مسح القائمة، وهذه هي الطريقة التي يزيل بها drop-in شرطاً أرسلته الحزمة:

[Unit]
ConditionPathExists=
ConditionPathExists=/srv/inventory/api.conf

لماذا لا يعني network.target أن الشبكة أصبحت جاهزة

network.target هو نقطة مزامنة، وليس حالة. عند الإقلاع، يعني ترتيب الخدمة بعده أن برنامج إدارة الشبكة بدأ تشغيله. لكنه لا يعني أن واجهة شبكية تملك عنواناً، أو أن مساراً إلى الإنترنت موجود. يوجد هذا الهدف أساساً للاتجاه الآخر: تُوقَف الوحدة المرتبة After=network.target قبل تفكيك الشبكة عند إيقاف التشغيل.

network-online.target هو الذي ينتظر. وتدعمه خدمة wait-online التابعة لمدير الشبكة الذي تستخدمه:

  • systemd-networkd-wait-online.service عندما يدير systemd-networkd الوصلات، وهذا هو الإعداد المعتاد على خادم Ubuntu المُعدّ عبر netplan.
  • NetworkManager-wait-online.service مع NetworkManager.

تحصل إعدادات ifupdown الأقدم على النتيجة نفسها من networking.service بدلاً من ذلك. أياً كان الهدف الموجود لديك، فإن استخدامه بطريقة صحيحة يتطلب سطرين، لا سطراً واحداً.

[Unit]
Wants=network-online.target
After=network-online.target

لا يكون network-online.target جزءاً من معاملة الإقلاع الافتراضية، ولا تُدخله أي جهة تلقائياً. إذا كتبت After= فقط، فأنت ترتب الوحدة بالنسبة إلى وحدة لم تُدرج في قائمة الانتظار أصلاً، ولذلك لا يفعل الترتيب شيئاً على الإطلاق. هذه هي حالة عدم التنفيذ المذكورة سابقاً، ولكن بأعلى كلفة. سطر Wants= هو الذي يُدخل الهدف في المعاملة، بحيث يجد سطر After= ما ينتظره.

النقطة الثانية هي أن تعريف «online» تحدده آلية wait-online، وليس systemd. تعيد systemd-networkd-wait-online التحكم عندما تصل الوصلات التي تديرها إلى حالة مهيأة. لكنها لا تتحقق من نجاح DNS في تحليل الأسماء، ولا تتحقق من إمكانية الوصول إلى أي مضيف بعيد.

ينتج عن هذا التعريف عطل شائع في VPS. فإذا كان للجهاز واجهة ثانية لشبكة خاصة، وكانت معلنة في netplan من دون أن يُمنح لها عنوان، فقد تظل خدمة الانتظار معلقة إلى أن تنتهي مهلتها:

systemd-networkd-wait-online[612]: Timeout occurred while waiting for network connectivity.
systemd-networkd-wait-online.service: Failed with result 'exit-code'.

يستغرق الإقلاع دقيقتين إضافيتين لأن المهلة الافتراضية هي 120 ثانية. يوجد إصلاحان. علِّم الواجهة غير المستخدمة optional: true في ملف netplan، لكي يتوقف networkd عن انتظارها. أو أضف drop-in إلى خدمة الانتظار، واذكر الوصلة المطلوبة باستخدام --interface=، أو مرّر --any لكي تعود الخدمة فوراً عند جاهزية وصلة واحدة.

والأفضل من ذلك هو تجنب الحاجة إلى الهدف. تُرتَّب خدمات كثيرة بعد network-online.target فقط لأنها تربط عنواناً محدداً، فتفشل عند الإقلاع برسالة مثل الآتية:

nginx: [emerg] bind() to 203.0.113.10:443 failed (99: Cannot assign requested address)

يرفض kernel عملية الربط لأن ذلك العنوان لم يصبح جاهزاً بعد. يتيح ضبط net.ipv4.ip_nonlocal_bind=1 للعملية ربط عنوان لا يملكه الجهاز بعد، كما تغطي سياسة إعادة التشغيل ما تبقى. إن تأخير الإقلاع بالكامل إلى أن تصبح الشبكة جاهزة إجراء ثقيل لمشكلة تكون عادةً محصورة في socket واحد.

كيفية قراءة تبعيات systemd الفعلية على خادم قيد التشغيل

لا تستنتج التبعيات من ملف الوحدة وحده. تضيف ملفات drop-in وارتباطات .wants/ الرمزية والتبعيات الافتراضية الضمنية حوافاً لا يعرضها الملف.

systemctl cat inventory-api.service

يطبع هذا ملف الوحدة وكل ملفات drop-in، بالترتيب الذي تُطبَّق به، مع عرض مسار المصدر فوق كل كتلة. شغّله أولاً. يتغلب override من خمسة أسطر في /etc/systemd/system/inventory-api.service.d/ على الملف المضمّن مع الحزمة، وإلا فلن يظهر هذا التغيير.

systemctl show inventory-api.service -p Requires -p Wants -p After -p Before -p ConditionResult -p AssertResult

يطبع هذا القيم المحلولة بعد تطبيق ملفات drop-in وبعد أن يضيف systemd تبعياته الضمنية. يقدّم ConditionResult=no إجابة مباشرة عن حالة «أبلغت الوحدة عن النجاح ولم تفعل شيئاً».

systemctl list-dependencies inventory-api.service
systemctl list-dependencies --reverse inventory-api.service
systemctl list-dependencies --after inventory-api.service
systemctl list-dependencies --before inventory-api.service

يتتبع الشكل العادي Requires= وWants= نزولاً. يوضّح --reverse الوحدات التي تستدعي وحدتك، وبذلك تعرف الهدف الذي يشغّلها عند الإقلاع. يوضّح --after و--before ترتيب التشغيل، وهما ما يجب قراءته عند السؤال عمّا إذا كانت أي جهة قد انتظرت فعلاً.

journalctl -b -u inventory-api.service --no-pager
journalctl -b -o short-precise -u inventory-api.service -u postgresql.service

يدمج الأمر الثاني وحدتين في عرض واحد مع طوابع زمنية بدقة المللي ثانية. بهذه الطريقة تثبت وجود تعارض في الترتيب بدلاً من التخمين. يحدث فشل pg_isready قبل أن يسجّل PostgreSQL database system is ready to accept connections، وتظهر الفجوة الزمنية بينهما مباشرة في الناتج.

systemd-analyze verify /etc/systemd/system/inventory-api.service
systemd-analyze critical-chain inventory-api.service

يحمّل verify الوحدة بالطريقة التي سيستخدمها systemd، ويعرض التوجيهات غير المعروفة، والتبعيات على وحدات غير موجودة، ودورات الترتيب، وبنية الصياغة التي يتعذر تحليلها. لا يغيّر شيئاً في النظام. يطبع critical-chain سلسلة الترتيب التي أخّرت الوحدة، مع وقت تفعيل كل خطوة، ولا يعمل إلا مع وحدة بدأت أثناء الإقلاع الحالي.

بعد تعديل أي ملف وحدة، شغّل sudo systemctl daemon-reload. لتغيير وحدة مضمّنة مع حزمة، استخدم sudo systemctl edit inventory-api.service، إذ ينشئ لك ملف drop-in. يعمل تعديل ملف المورّد ضمن /usr/lib/systemd/system/ إلى أن يستبدله تحديث الحزمة التالي. وتستخدم الآلية نفسها لإضافة حدود الذاكرة ووحدة المعالجة المركزية إلى خدمة من دون تعديل ملف تملكه الحزمة.

دورات الترتيب والسطر الذي تتركه في journal

أضف الترتيب في الاتجاهين، وسيكسر systemd الحلقة بحذف إحدى المهام:

systemd[1]: Found ordering cycle on inventory-api.service/start
systemd[1]: Job postgresql.service/start deleted to break ordering cycle starting with inventory-api.service/start

يحدد systemd المهمة التي سيحذفها، وقد لا يختار المهمة التي تتوقعها. وتظهر النتيجة على شكل خدمة مفقودة بعد بعض عمليات إعادة التشغيل، وموجودة بعد عمليات أخرى. وهذا يجعل تصحيح المشكلة من الخارج صعباً للغاية. تنشأ معظم الدورات من وحدات تضبط DefaultDependencies=no ثم ترتب نفسها مقابل basic.target رغم ذلك، أو من إضافة Before= إلى وحدة كانت تحتوي بالفعل على After= يشير إليك مجدداً. يعثر systemd-analyze verify عليها من دون إعادة تشغيل.

الوحدة الثابتة

[Unit]
Description=Inventory API
Wants=postgresql.service network-online.target
After=postgresql.service network-online.target
ConditionPathExists=/etc/inventory/api.conf

[Service]
Type=notify
StateDirectory=inventory
ExecStart=/usr/local/bin/inventory-api
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

تؤدي كل سطر مهمة واحدة. يجلب Wants= التبعيتين إلى المعاملة من دون ربط دورة حياة هذه الوحدة بهما. ينفّذ After= الانتظار، ويجب أن يكرر الاسمين لأن التبعية والترتيب إعدادان منفصلان. يعني ConditionPathExists= أن الجهاز الذي يحتوي على الحزمة ولا يحتوي على الإعدادات يتجاوز الوحدة بهدوء بدلاً من إصدار تنبيه، وهذا هو السلوك الصحيح لخدمة تعتمد على الإعدادات. يعني Type=notify أن أي شيء مرتب بعد هذه الوحدة ينتظر الجاهزية الفعلية بدلاً من انتظار عملية fork. يغطي Restart=on-failure حالة توقف قاعدة البيانات عن العمل بعد الإقلاع بوقت طويل، لأن الترتيب ينطبق على التشغيل الأول فقط. تحدد إعدادات Restart= وRestartSec= مدى شدة إعادة المحاولة.

تحقق منها قبل أن تعتمد عليها:

sudo systemctl daemon-reload
systemd-analyze verify /etc/systemd/system/inventory-api.service
systemctl list-dependencies --after inventory-api.service
sudo systemctl start inventory-api.service
systemctl show inventory-api.service -p ConditionResult -p ActiveState -p Result

تقرأ الوحدة السليمة ConditionResult=yes باستخدام ActiveState=active، ويؤكد Result=success عدم حدوث أي فشل في آخر تشغيل. يعني وجود ConditionResult=no إلى جانب ActiveState=inactive أن الوحدة جرى تجاوزها، ويخبرك سطر السجل الذي يذكر الشرط بالاختبار الذي فشل.

FAQ

هل تنتظر Requires= حتى تبدأ الوحدة الأخرى؟

لا. Requires= وAfter= إعدادان منفصلان. يضيف Requires= الوحدة الأخرى إلى المعاملة نفسها، ثم يبدأ systemd المهمتين بالتوازي. للانتظار، أضف After= مع تسمية الوحدة نفسها. هناك سبب ثانٍ لإضافته: لا يمنع اعتماد Requires= الذي يفشل وحدتك من البدء إلا عند ضبط After= أيضاً، لأن وحدتك تكون قد بدأت بالفعل عند فشل الوحدة الأخرى إذا لم تحدد الترتيب.

هل ينبغي أن أحدد الترتيب بعد network.target أم network-online.target؟

عند الإقلاع، يعني network.target فقط أن برنامج إدارة الشبكة قد بدأ، ولذلك لا يضمن شيئاً بشأن العناوين أو المسارات. استخدم network-online.target عندما تحتاج خدمتك إلى عنوان عامل عند البدء، واكتب كلاً من Wants=network-online.target وAfter=network-online.target، لأن الهدف ليس ضمن معاملة الإقلاع الافتراضية، وAfter= وحده ينتظر وحدة لم تتم إضافتها إلى قائمة الانتظار. إذا فشلت الخدمة فقط لأنها تربط عنوان IP محدداً، فإن net.ipv4.ip_nonlocal_bind=1 مع Restart=on-failure يكون أخف من تأخير الإقلاع.

لماذا تبلغ وحدتي عن النجاح لكنها لا تعمل مطلقاً؟

يتخطى اختبار Condition...= الفاشل الوحدة ويبلغ عن نجاح مهمة البدء، لذلك لا تُسجَّل أي حالة فشل. شغّل systemctl show <unit> -p ConditionResult، ويؤكد ConditionResult=no ذلك. ثم اقرأ journalctl -b -u <unit> بحثاً عن السطر Condition check resulted in <description> being skipped. في systemd 250 والإصدارات الأحدث، يسمّي systemctl status <unit> أيضاً التوجيه المحدد الذي لم يتحقق.

ما الفرق بين Condition وAssert؟

ينفذان الاختبارات نفسها. يؤدي فشل Condition إلى تخطي الوحدة بهدوء، وتظل المهمة ناجحة. أما فشل Assert فيؤدي إلى فشل الوحدة، ويسجل Assertion failed for <description>.، ويتركها في الحالة failed (Result: assert). استخدم Condition عندما يكون المعنى هو «هذه الوحدة لا تنطبق على هذا الجهاز»، وهذا يغطي كل الحالات العملية تقريباً. استخدم Assert فقط عندما يجب أن يكون غياب شرط مسبق ظاهراً لمن يراقب الوحدات الفاشلة.

لماذا يفشل ExecStartPre بالحالة status=203/EXEC؟

يعني 203/EXEC أن systemd لم يتمكن من تنفيذ الأمر إطلاقاً. تشمل الأسباب المعتادة أن المسار ليس مطلقاً، أو أن الملف التنفيذي غير موجود على ذلك الجهاز، أو أن الملف لا يملك بت التنفيذ، أو أن سطر #! في البرنامج النصي يشير إلى مفسر مفقود. تأتي رموز systemd الصغيرة الأخرى من جدول ثابت، لذلك يعني status=2/INVALIDARGUMENT فقط أن الأمر خرج بالحالة 2، ولا يوضح شيئاً عن الوسائط. تذكر أن ExecStartPre= لا يُشغَّل عبر shell، لذلك تحتاج الأنابيب وعمليات glob إلى /bin/sh -c '...'.

#systemd#units#dependencies#ordering#troubleshooting