نحوه نادیده گرفتن میزبانهای غیرقابل دسترس در Ansible
برای مدیریت خطای unreachable در Ansible از ignore_unreachable استفاده کنید. این راهنما تفاوت آن با ignore_errors را بررسی کرده و نحوه کار با serial و max_fail_percentage را شرح میدهد.
یک میزبان غیرقابلدسترس به معنای شکست خوردن تسک نیست
برای نادیده گرفتن میزبانهای غیرقابلدسترس در Ansible، باید ignore_unreachable: true را تنظیم کنید تا این سوئیچ عمل کند. نکتهٔ مهم این است که بدانید چه زمانی از آن استفاده کنید، زیرا Ansible دو مشکل متفاوت را به دو روش مختلف مدیریت میکند. تسکی که روی میزبان اجرا شده و خطا برگردانده است، یک شکست (failure) محسوب میشود. میزبانی که Ansible اصلاً نتوانسته به آن متصل شود، غیرقابلدسترس (unreachable) است. ignore_errors فقط حالت اول را پوشش میدهد. ignore_unreachable فقط حالت دوم را پوشش میدهد.
در اینجا تفاوت این دو در خلاصهٔ اجرا (play recap) آمده است.
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=0Ansible به web1 متصل شد و هفت تسک را اجرا کرد. web2 مقدار unreachable=1 و failed=0 را نشان میدهد، که به این معنی است که هیچچیز روی آن اجرا نشده است. Ansible هرگز موفق به برقراری اتصال نشد، بنابراین میزبان را از play حذف کرد و به کار خود با بقیه ادامه داد. اگر آن play در حال نصب یک بهروزرسانی امنیتی بود، یکی از سرورهای شما آن را دریافت نکرده است.
چه چیزی باعث غیرقابلدسترس شدن یک میزبان میشود
غیرقابلدسترس (Unreachable) به این معناست که اتصال پیش از آنکه هیچ ماژولی به میزبان برسد، با شکست مواجه شده است. هیچ خروجی ماژولی برای خواندن وجود ندارد، تنها یک خطای اتصال دیده میشود که در اولین تسکی که با ماشین تعامل دارد، ظاهر میگردد.
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 به پورت دیگری منتقل شده و موجودی (inventory) شما همچنان پورت 22 را نشان میدهد.Connection timed out: هیچ پاسخی دریافت نشده است. یک فایروال در حال drop کردن بستههاست یا سرور خاموش است. هر تلاش، زمان کامل timeout اتصال را مصرف میکند که بهصورت پیشفرض 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 را روی آن نصب نمایید.
نحوه نادیده گرفتن میزبانهای غیرقابلدسترس در یک play
در سطح task، این کلمه کلیدی در کنار ماژول قرار میگیرد:
- 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در سطح play، این گزینه مقدار پیشفرض را برای تمام taskهای موجود در آن play تعیین میکند و یک task تکی میتواند آن را تغییر دهد:
- 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، میزبان دیگر از play حذف نمیشود، بنابراین هر task بعدی دوباره تلاش میکند متصل شود و به همان شکل شکست میخورد. هر یک از این تلاشها به اندازه timeout اتصال منتظر میماند که اگر timeout را در ansible.cfg تغییر نداده باشید، 10 ثانیه است. یک play با بیست task روی یک سرور ازکارافتاده، حدود 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این کار باعث میشود به جای یک تلاش برای هر task، تنها یک تلاش برای هر میزبان ازکارافتاده صورت گیرد. end_host که در Ansible 2.8 اضافه شد، play را برای میزبان فعلی بدون علامتگذاری آن به عنوان شکستخورده، پایان میدهد. کلید unreachable تنها زمانی در نتیجه ثبتشده (registered result) وجود دارد که اتصال شکست خورده باشد، بنابراین default(false) شرط را برای هر میزبانی که پاسخ داده است، معتبر نگه میدارد. جمعآوری فکتها (Fact gathering) در سطح play غیرفعال است، زیرا در غیر این صورت task ضمنی Gathering Facts همان taskی خواهد بود که با اتصال قطعشده مواجه میشود و شما میخواهید این کار توسط ping خودتان انجام شود.
ignore_unreachable یک کلمه کلیدی در سطح play و task است. آن را در playbook نگه دارید تا برای خواننده قابل مشاهده باشد، نه داخل یک role؛ زیرا این گزینه تعیین میکند که اجرای یک دستور اجازه دارد کدام میزبانها را نادیده بگیرد. تفکیک بین playbookها و roleها توضیح میدهد که کدام لایه باید مسئولیت تنظیماتی از این دست را بر عهده داشته باشد.
چرا ignore_errors ابزار اشتباهی برای این کار است
مستندات Ansible در مورد محدودیت این دستور صریح است. ignore_errors «این دستور تنها زمانی کار میکند که تسک اجرا شود و مقدار failed را برگرداند. این دستور باعث نمیشود Ansible خطاهای مربوط به متغیرهای تعریفنشده، شکست در اتصال، مشکلات اجرایی (مثلاً بستههای گمشده) یا خطاهای نحوی (syntax) را نادیده بگیرد.»
شکست در اتصال هرگز به نتیجهٔ یک تسک با failed: true تبدیل نمیشود. این مورد به عنوان یک پرچم (flag) جداگانه دریافت میشود و Ansible ابتدا بر اساس آن پرچم عمل میکند: میزبان به لیست unreachable منتقل شده و از play خارج میشود. اگر ignore_errors: true را روی هر 12 تسک یک play قرار دهید، میزبانی که پورت SSH آن بسته است، همچنان در همان تسک اول متوقف میشود. این رایجترین سردرگمی در این حوزه است و ارزش آن را دارد که در playbookهای قدیمی خود، بهویژه مواردی که هنگام یادگیری نوشتن اولین playbook برای یک VPS نوشتهاید، با دستور grep جستجو کنید.
پیش از نادیده گرفتن، عیبیابی کنید
نادیده گرفتن دائمی خطاها باعث میشود ناوگان سرورها از کنترل خارج شود، زیرا میزبانی که کسی به آن دسترسی ندارد، همان میزبانی است که کسی آن را وصله (patch) نمیکند. ابتدا این مراحل را به ترتیب انجام دهید. تمام دستورات زیر فقط عملیات خواندن انجام میدهند.
ansible web2 -i inventory.ini -m ansible.builtin.ping -oیک ماژول را روی یک میزبان اجرا کرده و یک خط خروجی چاپ میکند.- عبارت
-vvvvرا به همان دستور اضافه کنید. Ansible دستور کامل ssh که میسازد، شامل کاربر مقصد، پورت، کلید خصوصی و گزینههای ارسالی را نمایش میدهد. - آن دستور ssh را شخصاً با
-vاجرا کنید. اگر ssh معمولی نمیتواند وارد شود، مشکل در لایهای پایینتر از Ansible است و هیچ کلمه کلیدی در playbook آن را حل نخواهد کرد. - رشته
msgرا بخوانید و با لیست بالا تطبیق دهید.Connection refusedوConnection timed outبه دو مکان متفاوت اشاره دارند؛ یکی به سرویس SSH و دیگری به مسیر شبکه. - برای
Host key verification failed.، آنچه را که باssh-keygen -F web2.example.comذخیره کردهاید بررسی کنید. اگر سرور بازسازی شده است، ورودی قدیمی را باssh-keygen -R web2.example.comحذف کنید و پس از تطبیق کلید با کنسول ارائهدهنده، کلید جدید را بپذیرید. تنظیمhost_key_checking = Falseدرansible.cfgخطا را برطرف میکند، اما بررسی امنیتی که به شما هشدار میدهد ماشین متفاوتی در آن آدرس پاسخ میدهد را نیز غیرفعال میکند. - برای
Permission denied (publickey)، تأیید کنید که Ansible از چه تنظیماتی استفاده میکند.ansible-inventory -i inventory.ini --host web2متغیرهای فعال، از جملهansible_userوansible_portرا چاپ میکند. - اگر SSH کار میکند اما ماژولها نه، مفسر را با
ansible web2 -m ansible.builtin.raw -a 'command -v python3 || echo none'بررسی کنید. ماژولrawدستور را از طریق shell اجرا میکند و نیازی به وجود python در مقصد ندارد.
تنها پس از انجام این مراحل است که نادیده گرفتن میزبان به یک تصمیم آگاهانه تبدیل میشود، نه یک عادت.
خلاصه وضعیت، میزبانهای غیرقابلدسترس را جداگانه محاسبه میکند و CI معمولاً این مورد را نادیده میگیرد
ansible-playbook در صورت موفقیت کد 0، در صورت شکست حداقل یک میزبان کد 2 و در صورت غیرقابلدسترس بودن حداقل یک میزبان کد 4 را برمیگرداند. این دو مقدار در سورسکد به صورت bit flag هستند، بنابراین اجرایی که در آن یک میزبان شکست خورده و یک میزبان غیرقابلدسترس باشد، کد 6 را برمیگرداند. دستور ansible نیز همین کدها را بازمیگرداند. این موارد در آگوست 2026 بر اساس سورسکد ansible-core بررسی شدهاند.
اکنون 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=7web2 گزارش میدهد که unreachable=0 و هفت تسک ok انجام شدهاند و اجرا با کد 0 خاتمه مییابد. هنگامی که این کلمه کلیدی تنظیم میشود، Ansible به جای شمارندهای که آن را dark مینامد (همان شمارندهای که ستون unreachable را پر میکند)، شمارندههای ok و ignored را برای آن میزبان افزایش میدهد. خطوط قرمز UNREACHABLE! همچنان چاپ میشوند، بنابراین لاگ صادقانه عمل میکند در حالی که خلاصه وضعیت و کد خروجی اینطور نیستند.
یک job در CI که پلیبوک را اجرا میکند و فقط $? را بررسی میکند، این اجرا را موفق تلقی میکند و هیچچیز در خلاصه آن نمیگوید که یک ماشین هرگز در دسترس نبوده است. بررسی قابلیت دسترسی را به یک مرحله مجزا قبل از اجرای پلیبوک تبدیل کنید:
ansible all -i inventory.ini -m ansible.builtin.ping -oاین دستور برای هر میزبان یک خط چاپ میکند و اگر هر میزبانی غیرقابلدسترس باشد، کد 4 را برمیگرداند که باعث میشود pipeline متوقف شود و نام میزبانها در لاگ مشخص گردد. ping به یک مفسر Python فعال روی مقصد نیاز دارد، بنابراین این دستور چیزی فراتر از یک اتصال ساده را اثبات میکند که معمولاً همان چیزی است که شما به آن نیاز دارید. سپس پلیبوک را با ignore_unreachable اجرا کنید تا میزبانهایی که در دسترس هستند، همچنان تغییرات مورد نظر را دریافت کنند.
پارامترهای any_errors_fatal و max_fail_percentage در یک batch
این دو کلمه کلیدی تعیین میکنند که پس از بروز خطا در بخشی از ناوگان سرورها چه اتفاقی بیفتد؛ آنها با میزبانهای غیرقابلدسترس (unreachable) رفتارهای متفاوتی دارند.
any_errors_fatal: true به میزبان غیرقابلدسترس واکنش نشان میدهد. Ansible تسک جاری را روی بقیه اعضای batch به پایان میرساند و سپس اجرای play را برای تمام میزبانهای آن batch متوقف میکند. زمانی از این گزینه استفاده کنید که اجرای عملیات فقط در صورت موفقیت کامل معنا دارد، مانند تغییرات هماهنگ در schema دیتابیس.
max_fail_percentage: 30 به میزبان غیرقابلدسترس واکنش نشان نمیدهد. این بررسی، تعداد میزبانهای شکستخورده (failed) را بر اندازه batch تقسیم میکند؛ میزبانهای غیرقابلدسترس در لیست جداگانهای نگهداری میشوند و هرگز در این محاسبه لحاظ نمیشوند. ده میزبان که چهار مورد آن غیرقابلدسترس هستند، تحت max_fail_percentage: 10 به کار خود ادامه میدهند، در حالی که شکست دو میزبان در یک تسک، اجرای play را متوقف میکند. مستندات یک نکته انحرافی دیگر را نیز اضافه میکنند: «درصد تعیینشده باید رد شود، نه اینکه با آن برابر باشد.» با serial: 4، برای توقف پس از دو شکست از چهار میزبان، باید عدد 49 را بنویسید، نه 50.
تنها یک حالت وجود دارد که در آن میزبانهای غیرقابلدسترس بهتنهایی باعث توقف اجرا میشوند. اگر تمام میزبانهای موجود در یک batch شکست بخورند یا غیرقابلدسترس باشند، Ansible دیگر هدفی برای کار کردن ندارد و play را با NO MORE HOSTS LEFT به پایان میرساند.
serial: اعمال تغییرات بهصورت مرحلهای در کل ناوگان
- 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: trueserial: 2 کل دستور را روی دو میزبان اجرا میکند، آن را به پایان میرساند و سپس به سراغ دو میزبان بعدی میرود. serial: "25%" با اندازه گروه مقیاسپذیر است. یک لیست، یعنی serial: [1, 5, 10]، الگوی canary را تشکیل میدهد: ابتدا یک میزبان، سپس پنج میزبان، سپس ده میزبان، و میزبانهای باقیمانده در دستههایی به اندازه آخرین گروه اجرا میشوند. max_fail_percentage برای هر دسته محاسبه میشود، بنابراین این دو مورد با هم کار میکنند. اگر ماشین اول دچار مشکل شود، اجرا قبل از اینکه چهل ماشین دیگر را تحت تأثیر قرار دهد، متوقف میشود. این همان چیزی است که مدیریت ناوگان سرورهای لینوکس از یک ماشین کنترل را با یک دستور واحد، ایمن میسازد.
چه زمانی میزبانهای غیرقابلدسترس را نادیده بگیریم و چه زمانی نه
برای کارهای فرصتطلبانه (opportunistic) آنها را نادیده بگیرید. در یک اجرای جمعآوری اطلاعات (fact collection) یا بررسی ساعتی انحراف پیکربندی (drift check)، نادیده گرفتن میزبانی که در دسترس نیست مشکلی ایجاد نمیکند، زیرا در اجرای بعدی وضعیت آن بررسی خواهد شد. در این موارد، سطح play برابر با ignore_unreachable: true پاسخ مناسبی است که همراه با مرحله ping استفاده میشود تا نامهای نادیدهگرفتهشده در جایی ثبت شوند که توسط کاربر قابل مشاهده باشد.
برای اجرای وصلههای امنیتی (security patch)، هرگز آنها را نادیده نگیرید. ارزش این عملیات در تضمین این است که هر میزبان وصله را دریافت کرده باشد؛ نادیده گرفتن وضعیت غیرقابلدسترس باعث میشود که «یک سرور همچنان آسیبپذیر است» به یک گزارش نهایی سبز و بینقص تبدیل شود. میزبانی که دو هفته غیرقابلدسترس بوده، همان میزبانی است که به احتمال زیاد بیشترین عقبماندگی را دارد. اجازه دهید آن اجرا با کد خروجی 4 متوقف شود تا یک نفر وضعیت آن را بررسی کند.
یک قانون در هر دو حالت صادق است: توقف عملیات را سرکوب کنید، اما ثبت گزارش را هرگز. اگر میزبانی نادیده گرفته شد، باید جایی در گزارش نهایی، لاگ CI یا هشدارهای مانیتورینگ به آن اشاره شود. Ansible فقط در ثانیههایی که یک play روی میزبان اجرا میشود از وجود آن آگاه است، بنابراین مکان مناسبی برای فهمیدن اینکه سروری از سهشنبه قطع بوده نیست. این وظیفه بر عهده سیستم مانیتورینگ است و یک Ansible playbook برای نصب Zabbix میتواند دید کاملی از کل ناوگان سرورها را در یک بعدازظهر فراهم کند.
FAQ
تفاوت بین ignore_errors و ignore_unreachable در Ansible چیست؟
ignore_errors: true برای وظیفهای (task) اعمال میشود که روی میزبان اجرا شده و با خطا مواجه شده است، مانند دستوری که با کد خروجی غیر صفر پایان مییابد. ignore_unreachable: true برای میزبانی اعمال میشود که Ansible نتوانسته به آن متصل شود و هیچ ماژولی روی آن اجرا نشده است. این دو فیلدهای متفاوتی را در نتیجهٔ وظیفه بررسی میکنند و هیچکدام دیگری را پوشش نمیدهد. مستندات Ansible بیان میکند که ignore_errors «باعث نمیشود Ansible خطاهای متغیرهای تعریفنشده، شکستهای اتصال، مشکلات اجرایی (مثلاً بستههای گمشده) یا خطاهای نحوی را نادیده بگیرد» و بسته بودن پورت SSH یک شکست اتصال محسوب میشود.
آیا ignore_unreachable میزبان را از گزارش نهایی (play recap) پنهان میکند؟
در عمل، بله. با تنظیم این کلیدواژه، Ansible دیگر آن میزبان را در بخش unreachable نمیشمارد و آن را به عنوان ok و ignored برای هر وظیفه در نظر میگیرد و اجرای کلی با کد 0 پایان مییابد. خطوط fatal: [host]: UNREACHABLE! همچنان چاپ میشوند، بنابراین لاگ دقیق است، اگرچه گزارش نهایی و کد خروجی چنین نیستند. ستون ignored را زیر نظر بگیرید یا ansible all -m ansible.builtin.ping -o را به عنوان یک مرحلهٔ جداگانه اجرا کنید تا میزبان غیرقابلدسترس همچنان یک کد خروجی غیر صفر ایجاد کند.
وقتی میزبانی غیرقابلدسترس است، ansible-playbook چه کد خروجی برمیگرداند؟
این دستور کد 4 را برمیگرداند. اجرای یک play با حداقل یک میزبان شکستخورده کد 2 برمیگرداند و چون این دو مقدار پرچمهای بیتی (bit flags) هستند، اجرای یک play که هم شکست و هم میزبان غیرقابلدسترس داشته باشد، کد 6 برمیگرداند. اجرای موفق کد 0 برمیگرداند. این کدها در اوت 2026 بر اساس سورسکد ansible-core بررسی شدهاند. تنظیم ignore_unreachable: true کد 4 را حذف میکند، به همین دلیل است که یک pipeline که فقط کد خروجی را تست میکند، نمیتواند ماشین نادیدهگرفتهشده را تشخیص دهد.
چگونه میتوانم بقیهٔ یک 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 باقی میماند و هر وظیفهٔ بعدی دوباره منتظر پایان زمان اتصال (timeout) میماند.
آیا باید میزبانهای غیرقابلدسترس را در طول اجرای وصلههای امنیتی نادیده بگیرم؟
خیر. اجرای وصله (patch) به این دلیل ارزشمند است که به شما تضمین میدهد هر میزبان بهروزرسانی را دریافت کرده است؛ نادیده گرفتن میزبانهای غیرقابلدسترس، این تضمین را با یک گزارش نهایی سبز رنگ جایگزین میکند. اجازه دهید اجرا با کد 4 پایان یابد، نام میزبانهایی که پاسخ ندادهاند را بخوانید و مشکل آنها را رفع کنید. سرکوب خطاها (Suppression) فقط برای اجراهای مکرر و فرصتطلبانه مناسب است که در آن، اجرای بعدی هر آنچه را که از دست رفته است، پوشش میدهد.