SSD Nodes Learn 🎉 VPS من $5.50/شهر
الأدلة Matt Connorبقلم Matt Connor

Ansible: تجاهل المضيفين غير القابلين للوصول

تعرّف على الفرق بين المضيف غير القابل للوصول والمهمة الفاشلة في Ansible، واستخدم ignore_unreachable وserial وmax_fail_percentage مع معرفة ما فاتك.

المضيف غير القابل للوصول ليس مهمة فاشلة

لتجاهل المضيفين غير القابلين للوصول في Ansible، اضبط ignore_unreachable: true، وسيعمل هذا الخيار. المهم هو معرفة متى تستخدمه، لأن Ansible يتعامل مع مشكلتين مختلفتين بطريقتين مختلفتين. المهمة التي نُفِّذت على المضيف وأعادت خطأً تُعد فاشلة. أما المضيف الذي تعذّر على Ansible الاتصال به إطلاقاً فيُعد غير قابل للوصول. يغطي ignore_errors الحالة الأولى فقط. ويغطي ignore_unreachable الحالة الثانية فقط.

إليك الفرق في ملخص التنفيذ.

PLAY RECAP *********************************************************************
web1  : ok=7  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0
web2  : ok=0  changed=0  unreachable=1  failed=0  skipped=0  rescued=0  ignored=0

اتصل Ansible بـweb1 ونفّذ سبع مهام. يعرض web2 كلاً من unreachable=1 وfailed=0، ما يعني أن أياً من المهام لم يُنفَّذ عليه. لم ينجح Ansible في إنشاء اتصال، لذلك أزال المضيف من التنفيذ وتابع العمل مع بقية المضيفين. إذا كان ذلك التنفيذ يثبّت تحديثاً أمنياً، فهذا يعني أن أحد خوادمك لا يحتوي عليه.

ما الذي يجعل المضيف غير قابل للوصول

يعني تعذّر الوصول أن الاتصال فشل قبل وصول أي وحدة إلى المضيف. لا توجد مخرجات لوحدة لقراءتها، بل يظهر خطأ اتصال فقط في أول مهمة تتصل بالجهاز.

fatal: [web2]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 203.0.113.20 port 22: Connection refused", "unreachable": true}

يحمل الحقل msg السبب الفعلي. ستواجه الحالات التالية:

  • Connection refused: رُفض اتصال TCP، لذلك لا توجد خدمة تستمع على ذلك المنفذ. خدمة sshd متوقفة، أو نُقلت SSH إلى منفذ آخر بينما لا يزال ملف الجرد يحدد المنفذ 22.
  • Connection timed out: لم يستجب أي طرف. قد يكون جدار ناري يسقط الحزم، أو قد يكون الخادم متوقفاً. تستغرق كل محاولة مهلة الاتصال كاملة، وتبلغ 10 ثوانٍ افتراضياً.
  • Host key verification failed.: لا يطابق المفتاح الموجود في ~/.ssh/known_hosts المفتاح الذي عرضه الخادم. يحتفظ VPS المُعاد بناؤه بعنوان IP نفسه ويحصل على مفتاح مضيف جديد، لذلك يُتوقع ظهور هذا الخطأ بعد إعادة التثبيت، لكنه خطير في أي وقت آخر.
  • Permission denied (publickey): استجاب SSH ورفض مفتاحك. المنفذ يعمل، لذلك تتعلق المشكلة بالمصادقة، وعادةً يكون السبب هو ansible_user غير الصحيح أو عدم تحميل مفتاح.
  • Timeout (12s) waiting for privilege escalation prompt: نجح الاتصال، لكن become لم ينجح. ينتظر sudo كلمة مرور لا تصل إليه.

يتوقع البعض أن يكون مفسر Python المفقود ضمن هذه القائمة، لكنه ليس كذلك. يتصل SSH بنجاح، لذلك يكون المضيف قابلاً للوصول. بعد ذلك لا تجد الوحدة ما تشغّله عليه:

fatal: [db1]: FAILED! => {"changed": false, "module_stdout": "/bin/sh: 1: /usr/bin/python3: not found\r\n", "msg": "The module failed to execute correctly, you probably need to set the interpreter", "rc": 127}

يوضح هذا السطر أن FAILED!، ويعدّه الملخص ضمن failed، لذلك لن يتصل به ignore_unreachable مطلقاً. عيّن ansible_python_interpreter لهذا المضيف، أو ثبّت python3 عليه.

كيفية تجاهل المضيفين غير القابلين للوصول في مهمة

على مستوى المهمة، تأتي الكلمة المفتاحية بجانب الوحدة:

- name: Read the package list, and do not stop if the host is down
  ansible.builtin.command: dpkg -l
  register: packages
  changed_when: false
  ignore_unreachable: true

على مستوى التشغيل، تحدد الكلمة المفتاحية الإعداد الافتراضي لكل مهمة في التشغيل، ويمكن لمهمة واحدة إعادته إلى الإعداد السابق:

- name: Opportunistic fleet maintenance
  hosts: all
  ignore_unreachable: true
  tasks:
    - name: This runs, cannot connect, and the play carries on
      ansible.builtin.ping:

    - name: This one still ends the play for a host that is down
      ansible.builtin.ping:
      ignore_unreachable: false

من المهم فهم ما يتغير في الخلفية. عند ضبط ignore_unreachable، لا يُزال المضيف من التشغيل، لذلك تحاول كل مهمة لاحقة الاتصال به مرة أخرى، وتفشل بالطريقة نفسها. تنتظر كل محاولة انتهاء مهلة الاتصال، وهي 10 ثوانٍ ما لم تغيّر timeout في ansible.cfg. ويضيف تشغيل يتضمن عشرين مهمة مقابل خادم متوقف نحو 200 ثانية إلى مدة التنفيذ، ويضيف عشرين سطراً أحمر إلى السجل.

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

- name: Opportunistic fleet maintenance
  hosts: all
  gather_facts: false
  tasks:
    - name: Check that the host answers before doing any work
      ansible.builtin.ping:
      register: reachable
      ignore_unreachable: true

    - name: End the play for this host if it never answered
      ansible.builtin.meta: end_host
      when: reachable.unreachable | default(false)

    - name: Gather facts now that the connection is known good
      ansible.builtin.setup:

    - name: Refresh the package index
      ansible.builtin.apt:
        update_cache: true
      become: true

بهذا تصبح هناك محاولة اتصال واحدة لكل مضيف متوقف بدلاً من محاولة واحدة لكل مهمة. تنهي end_host، التي أُضيفت في Ansible 2.8، التشغيل للمضيف الحالي من دون وضع علامة فشل عليه. لا يظهر المفتاح unreachable في النتيجة المسجّلة إلا عند فشل الاتصال، لذلك تحافظ default(false) على صلاحية الشرط لكل مضيف استجاب. ويكون جمع الحقائق معطّلاً على مستوى التشغيل، لأن المهمة الضمنية Gathering Facts ستواجه الاتصال المعطّل بخلاف ذلك، بينما تريد أن تكون محاولة ping الخاصة بك هي التي تفعل ذلك.

ignore_unreachable هي كلمة مفتاحية للتشغيل وللمهمة. أبقِها في playbook حيث يمكن للقارئ رؤيتها، بدلاً من وضعها داخل role، لأنها تحدد المضيفين الذين يُسمح للتنفيذ بتجاوزهم. يوضّح الفصل بين playbooks وroles أي طبقة ينبغي أن تملك إعداداً كهذا.

لماذا تُعدّ ignore_errors الأداة الخاطئة هنا

توضح وثائق Ansible هذا القيد صراحةً. ignore_errors «تعمل فقط عندما يمكن تنفيذ المهمة وتُرجع قيمة هي 'failed'. ولا تجعل Ansible يتجاهل أخطاء المتغيرات غير المعرّفة، أو حالات فشل الاتصال، أو مشكلات التنفيذ، مثل الحزم المفقودة، أو أخطاء الصياغة».

لا تتحول حالة فشل الاتصال أبداً إلى نتيجة مهمة تحتوي على failed: true. بل تصل كعلامة منفصلة، وتعالج Ansible هذه العلامة أولاً: يضع المضيف في قائمة المضيفين غير القابلين للوصول ويخرجه من الـplay. إذا وضعت ignore_errors: true في المهام الاثنتي عشرة في play، فسيتوقف المضيف الذي يكون منفذ SSH لديه مغلقاً عند أول مهمة. هذا هو الالتباس الأكثر شيوعاً في هذا المجال، ومن المفيد البحث عنه باستخدام grep في ملفات playbook الأقدم، وخصوصاً تلك التي كُتبت أثناء تعلّم كتابة أول playbook على VPS.

صحّح الخلل قبل تعطيل الفحص

يؤدي تعطيل الفحص الذي يصبح دائماً إلى انحراف مجموعة الخوادم عن حالتها المطلوبة، لأن المضيف الذي لا يستطيع أحد الوصول إليه هو أيضاً المضيف الذي لا يقوم أحد بتثبيت التحديثات عليه. نفّذ الخطوات بالترتيب التالي أولاً. كل أمر هنا يقرأ المعلومات فقط.

  1. يشغّل ansible web2 -i inventory.ini -m ansible.builtin.ping -o وحدة واحدة على مضيف واحد ويطبع سطراً واحداً.
  2. أضف -vvvv إلى الأمر نفسه. يطبع Ansible أمر ssh الكامل الذي ينشئه، بما في ذلك المستخدم المستهدف والمنفذ والمفتاح الخاص والخيارات التي يمررها.
  3. شغّل أمر ssh بنفسك مع -v. إذا تعذّر على ssh العادي تسجيل الدخول، فالمشكلة أدنى من Ansible، ولن تصلحها أي كلمة مفتاحية في playbook.
  4. اقرأ السلسلة msg وطابقها مع القائمة أعلاه. يشير Connection refused وConnection timed out إلى موضعين مختلفين: الأول إلى خدمة SSH، والثاني إلى مسار الشبكة.
  5. بالنسبة إلى Host key verification failed.، افحص ما خزّنته باستخدام ssh-keygen -F web2.example.com. إذا أُعيد إنشاء الخادم، احذف الإدخال القديم باستخدام ssh-keygen -R web2.example.com، ثم اقبل المفتاح الجديد بعد التحقق منه مقابل وحدة تحكم موفّر الخدمة. يؤدي ضبط host_key_checking = False في ansible.cfg إلى إزالة الخطأ، كما يلغي الفحص الذي كان سيخبرك بأن جهازاً مختلفاً يجيب الآن على ذلك العنوان.
  6. بالنسبة إلى Permission denied (publickey)، تحقّق مما يعتقد Ansible أنه يجب استخدامه. يطبع ansible-inventory -i inventory.ini --host web2 المتغيرات الفعالة، بما في ذلك ansible_user وansible_port.
  7. إذا كان SSH يعمل لكن الوحدات لا تعمل، فتحقّق من المفسّر باستخدام ansible web2 -m ansible.builtin.raw -a 'command -v python3 || echo none'. تنفّذ وحدة raw أمراً عبر shell ولا تحتاج إلى python على المضيف الهدف.

بعد ذلك فقط يصبح تجاهل المضيف قراراً مدروساً بدلاً من عادة.

يحسب الملخص حالات عدم الوصول بشكل منفصل، وغالباً ما تفوّتها CI

يخرج ansible-playbook بالرمز 0 عند النجاح، وبالرمز 2 عندما يفشل مضيف واحد على الأقل، وبالرمز 4 عندما يتعذر الوصول إلى مضيف واحد على الأقل. هاتان القيمتان عبارة عن رايتي بتات في المصدر، لذلك يخرج التشغيل بالرمز 6 عند وجود مضيف فاشل ومضيف يتعذر الوصول إليه. يعيد الأمر ansible الرموز نفسها. تم التحقق من ذلك مقابل مصدر ansible-core في August 2026.

اضبط الآن ignore_unreachable: true وشغّل المهمة نفسها ذات المهام السبعة مقابل المضيف المتوقف نفسه:

PLAY RECAP *********************************************************************
web1  : ok=7  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0
web2  : ok=7  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=7

يعرض web2 القيمة unreachable=0، وتظهر سبع مهام ok، وينتهي التشغيل بالرمز 0. عند ضبط هذه الكلمة المفتاحية، يزيد Ansible عدّادي ok وignored لذلك المضيف بدلاً من العدّاد الذي يسميه dark، وهو العدّاد الذي يملأ العمود unreachable. وتظل أسطر UNREACHABLE! الحمراء مطبوعة، لذلك يكون السجل دقيقاً، بينما لا يكون الملخص ورمز الخروج كذلك.

تعتبر مهمة CI التي تشغّل ملف التشغيل وتتحقق من $? فقط أن التشغيل ناجح، ولا يوضح ملخصها أن جهازاً لم تتم محاولة الاتصال به قط. اجعل فحص إمكانية الوصول خطوة مستقلة قبل التشغيل:

ansible all -i inventory.ini -m ansible.builtin.ping -o

يطبع ذلك سطراً واحداً لكل مضيف، ويخرج بالرمز 4 إذا تعذر الوصول إلى أي مضيف. وبذلك تحصل pipeline على شرط تفشل عنده، وتحصل أنت على أسماء المضيفين في السجل. يتطلب ping مفسر Python عاملاً على الهدف، لذلك فهو يثبت أكثر قليلاً من مجرد الاتصال، وهذا هو المطلوب عادةً. ثم شغّل ملف التشغيل باستخدام ignore_unreachable حتى تحصل المضيفات المتاحة على التغيير أيضاً.

any_errors_fatal وmax_fail_percentage ضمن دفعة

تحدد كلمتا play هاتان ما يحدث بعد وقوع خطأ في جزء من مجموعة الخوادم، وتتعاملان مع الخوادم غير القابلة للوصول بطريقة مختلفة.

تستجيب any_errors_fatal: true للخادم غير القابل للوصول. تكمل Ansible المهمة الحالية على بقية الخوادم في الدفعة، ثم توقف play لجميع الخوادم الموجودة فيها. استخدمها عندما لا يكون للتنفيذ معنى إلا إذا اكتمل بالكامل أو لم يكتمل، مثل تغيير مخطط قاعدة البيانات بالتنسيق بين الخوادم.

لا تستجيب max_fail_percentage: 30 للخادم غير القابل للوصول. يتحقق هذا الخيار من قسمة عدد الخوادم الفاشلة على حجم الدفعة. وتُحفظ الخوادم غير القابلة للوصول في قائمة منفصلة، لذلك لا تغيّر هذا العدد مطلقاً. تستمر عشرة خوادم، منها أربعة غير قابلة للوصول، في التنفيذ باستخدام max_fail_percentage: 10، بينما يؤدي فشل مهمّة على خادمين إلى إيقاف play. وتضيف الوثائق تحذيراً آخر: "يجب تجاوز النسبة المئوية المحددة، لا مساواتها." باستخدام serial: 4، يتطلب إيقاف التنفيذ بعد فشل خادمين من أصل أربعة كتابة 49، لا 50.

توجد حالة واحدة توقف فيها الخوادم غير القابلة للوصول التنفيذ بمفردها. إذا كانت كل خوادم الدفعة فاشلة أو غير قابلة للوصول، فلن يتبقى لدى Ansible ما يمكنه العمل عليه، وينهي play باستخدام NO MORE HOSTS LEFT.

التدرّج: تطبيق تغيير على مجموعة الخوادم

- name: Rolling nginx config update
  hosts: webservers
  serial: 2
  max_fail_percentage: 25
  tasks:
    - name: Deploy the site config
      ansible.builtin.template:
        src: site.conf.j2
        dest: /etc/nginx/conf.d/site.conf
        owner: root
        mode: "0644"
      become: true
      notify: Reload nginx
  handlers:
    - name: Reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded
      become: true

ينفّذ serial: 2 المهمة كاملة على خادمين، ثم يبدأ بالخادمين التاليين. يتكيّف serial: "25%" مع حجم المجموعة. وتحدّد القائمة serial: [1, 5, 10] أسلوب النشر التدريجي: خادم واحد أولاً، ثم خمسة خوادم، ثم عشرة، مع تشغيل أي خوادم متبقية على دفعات بحجم الدفعة الأخيرة. يُقاس max_fail_percentage لكل دفعة، لذلك يعمل الخياران معاً. إذا عطّلت الجهاز الأول، يتوقف التنفيذ قبل تعطيل أربعين جهازاً. وهذا ما يجعل إدارة مجموعة من خوادم Linux من جهاز تحكم واحد آمنة باستخدام أمر واحد.

متى تتجاهل المضيفين غير القابلين للوصول، ومتى لا تفعل ذلك

تجاهلهم في المهام الانتهازية. لا تخسر عملية جمع الحقائق أو فحص الانحراف كل ساعة شيئاً عند تخطي مضيف متوقف، لأن الجولة التالية ستلتقطه. يكون ignore_unreachable: true على مستوى play هو الخيار الصحيح هنا، مع ربطه بخطوة ping حتى تصل أسماء المضيفين الذين جرى تخطيهم إلى مكان سيقرأه شخص ما.

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

تنطبق قاعدة واحدة في الحالتين: أوقف الإيقاف، ولا توقف التسجيل. إذا جرى تخطي مضيف، فيجب أن يوضح شيء ما ذلك، سواء في الملخص أو سجل CI أو تنبيه المراقبة. لا يعرف Ansible بوجود مضيف إلا خلال الثواني التي ينفذ فيها play عليه، لذلك فهو مكان سيئ لاكتشاف أن خادماً متوقف منذ يوم الثلاثاء. هذه مهمة المراقبة، ويمكن لـplaybook في Ansible يثبّت Zabbix أن يوفّر رؤية على مستوى الأسطول خلال فترة بعد الظهر.

FAQ

ما الفرق بين ignore_errors: true وignore_unreachable: true في Ansible؟

ينطبق ignore_errors: true على مهمة نُفِّذت على المضيف وأعادت حالة فشل، مثل خروج أمر برمز غير صفري. وينطبق ignore_unreachable: true على مضيف تعذّر على Ansible الاتصال به، ولم تُنفَّذ عليه أي وحدة. يقرأ كل منهما حقولاً مختلفة من نتيجة المهمة، ولا يغطي أي منهما الحالة الأخرى. توضّح وثائق Ansible أن ignore_errors «لا يجعل Ansible يتجاهل أخطاء المتغيرات غير المعرّفة، أو حالات فشل الاتصال، أو مشكلات التنفيذ، مثل الحزم المفقودة، أو أخطاء الصياغة». ويُعد منفذ SSH المغلق حالة فشل في الاتصال.

هل يخفي fatal: [host]: UNREACHABLE! المضيف من ملخص التشغيل؟

عملياً، نعم. عند ضبط هذه الكلمة المفتاحية، يتوقف Ansible عن احتساب ذلك المضيف ضمن unreachable، ويحتسبه ضمن ok وignored مرة واحدة لكل مهمة، ثم ينتهي التشغيل بالرمز 0. تظل أسطر fatal: [host]: UNREACHABLE! مطبوعة، لذلك يبقى السجل دقيقاً رغم أن الملخص ورمز الخروج لا يعكسان الحالة. راقب العمود ignored، أو شغّل ansible all -m ansible.builtin.ping -o في خطوة منفصلة حتى ينتج المضيف غير القابل للوصول رمز خروج غير صفري في موضع ما.

ما رمز الخروج الذي يعيده ansible-playbook عندما يتعذّر الوصول إلى مضيف؟

يعيد الرمز 4. يعيد التشغيل الذي يتضمن مضيفاً واحداً فاشلاً على الأقل الرمز 2، والقيمتان عبارة عن رايات بتّية، لذلك يعيد التشغيل الذي يتضمن فشلاً ومضيفاً غير قابل للوصول الرمز 6. يعيد التشغيل السليم الرمز 0. جرى التحقق من هذه الرموز في مصدر ansible-core في August 2026. يؤدي ضبط ignore_unreachable: true إلى إزالة القيمة 4، ولذلك لا يستطيع مسار معالجة يختبر رمز الخروج فقط اكتشاف جهاز تم تخطيه.

كيف أتخطى بقية play لمضيف لم يستجب مطلقاً؟

اجعل المهمة الأولى ansible.builtin.ping باستخدام ignore_unreachable: true وregister: reachable، ثم أتبعها بـansible.builtin.meta: end_host تحت الشرط when: reachable.unreachable | default(false). ينهي end_host الـplay لذلك المضيف من دون وضع علامة فشل عليه. اضبط gather_facts: false على مستوى الـplay حتى تكون مهمة ping هي التي تتعامل مع انقطاع الاتصال. من دون هذا النمط، يبقى المضيف المتوقف ضمن الـplay، وتنتظر كل مهمة لاحقة انتهاء مهلة الاتصال من جديد.

هل ينبغي أن أتجاهل المضيفين غير القابلين للوصول أثناء تشغيل تصحيحات الأمان؟

لا. تكمن فائدة تشغيل التصحيحات في أنه يمنحك ضماناً بأن كل مضيف تلقى التحديث، بينما يؤدي تجاهل المضيفين غير القابلين للوصول إلى استبدال هذا الضمان بملخص أخضر. دع التشغيل ينتهي بالرمز 4، واقرأ أسماء المضيفين الذين لم يستجيبوا، ثم أصلحهم. يقتصر الإخفاء على عمليات التشغيل الانتهازية المتكررة، حيث ستلتقط الجولة التالية ما فات.

#ansible#playbooks#error-handling#inventory#automation