Ansible Playbook چیست؟ play، task و handler با مثال
پلیبوک Ansible یک فایل YAML است که سرورهای inventory را به فهرستی مرتب از taskها وصل میکند. ساختارش را با یک مثال کوچک باز میکنیم و فرقش را با اسکریپت bash و role میبینیم.
پلیبوک Ansible چیست؟
پلیبوک Ansible (Ansible playbook) یک فایل YAML است که با دستور ansible-playbook اجرا میشود و میزبانهای تعریفشده در inventory را به یک فهرست مرتب از taskها وصل میکند. هر task یک وضعیت را اعلام میکند، مثلاً «بسته nginx نصب باشد» یا «سرویس nginx در حال اجرا باشد». task دستوری برای اجرا نیست. Ansible خودش بررسی میکند که سرور در آن وضعیت هست یا نه، و فقط وقتی لازم باشد چیزی را تغییر میدهد.
YAML (مخفف YAML Ain't Markup Language) یک قالب متنی ساده برای داده است که ساختار را با تورفتگی نشان میدهد. inventory فایلی است که فهرست سرورها و گروههای آنها را نگه میدارد. Ansible از طریق SSH (Secure Shell) به این سرورها وصل میشود، پس روی سرورها به هیچ agent جداگانهای نیاز ندارد.
یک نکته درباره خود کلمه. در دنیای کسبوکار، و در تیمهای امنیت و پاسخ به رخداد که با پلتفرمهای SOAR (Security Orchestration, Automation and Response) کار میکنند، «playbook» معمولاً به یک روال مکتوب گفته میشود که آدمها قدمبهقدم دنبالش میکنند. پلیبوک Ansible نوع اجرایی همین ایده است: فایلی که ماشین آن را میخواند و روی سرورها اجرا میکند. اگر با جستوجوی کلی «playbook چیست» به اینجا رسیدهاید، اینجا درباره همین نوع اجرایی صحبت میکنیم.
چهار اصطلاح که باید بشناسید
در متنهای فارسی DevOps این اصطلاحها را معمولاً ترجمه نمیکنند، چون در خود فایلها و در خروجی Ansible به همین شکل لاتین دیده میشوند. ما هم همین کار را میکنیم:
- play (بخش اجرایی): یک بلوک در پلیبوک که یک گروه از میزبانها را هدف میگیرد و میگوید روی آنها چه کاری انجام شود. یک پلیبوک میتواند چند play پشت سر هم داشته باشد.
- task (وظیفه): یک قدم در play. هر task دقیقاً یک module را با چند آرگومان صدا میزند.
- module (ماژول): کدی که کار واقعی را انجام میدهد، مثلاً
ansible.builtin.aptبرای نصب بسته یاansible.builtin.serviceبرای مدیریت سرویس. - handler (هندلر، وظیفه واکنشی): یک task ویژه که خودبهخود اجرا نمیشود. فقط وقتی اجرا میشود که task دیگری با
notifyآن را خبر کند و آن task واقعاً چیزی را تغییر داده باشد.
یک مثال کوچک از پلیبوک Ansible
مثال زیر روی یک سرور Ubuntu وبسرور nginx را نصب میکند، یک تنظیم کوچک امنیتی به آن اضافه میکند و مطمئن میشود سرویس روشن است. اول فایل inventory، با نام inventory.ini:
[web]
203.0.113.10 ansible_user=deployاین فایل یک گروه به نام web میسازد که یک سرور دارد. Ansible با کاربر deploy به آن وصل میشود. حالا خود پلیبوک، با نام site.yml:
---
- name: Set up a basic nginx web server
hosts: web
become: true
vars:
web_package: nginx
tasks:
- name: Install nginx
ansible.builtin.apt:
name: "{{ web_package }}"
state: present
update_cache: true
- name: Hide the nginx version in responses
ansible.builtin.copy:
dest: /etc/nginx/conf.d/hide-version.conf
content: "server_tokens off;\n"
owner: root
group: root
mode: "0644"
notify: Reload nginx
- name: Make sure nginx is running and starts at boot
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloadedدر ادامه این فایل را تکهتکه باز میکنیم.
play: کجا و با چه دسترسیای؟
خط اول (---) فقط شروع یک سند YAML را نشان میدهد. خط بعد با یک خط تیره (-) شروع میشود، چون پلیبوک در اصل یک فهرست از playهاست. این مثال فقط یک play دارد.
name توضیحی است که هنگام اجرا روی صفحه چاپ میشود. آن را طوری بنویسید که ماه بعد خودتان بفهمید این play چه کاری میکند.
hosts: web میگوید این play روی کدام میزبانها اجرا شود. مقدار آن نام یک گروه در inventory است، یا نام یک سرور، یا all برای همه سرورها. خود پلیبوک آدرس IP هیچ سروری را نمیداند. این اطلاعات فقط در inventory است.
become: true به Ansible میگوید taskها را با دسترسی root اجرا کند. روی Ubuntu این کار معمولاً از طریق sudo انجام میشود. بدون این خط، نصب بسته شکست میخورد، چون کاربر deploy بهتنهایی اجازه نوشتن در مسیرهای سیستمی را ندارد.
vars متغیرهای این play را تعریف میکند. اینجا web_package مقدار nginx را دارد و task اول آن را با سینتکس Jinja2، یعنی دو آکولاد دور نام متغیر، میخواند. Jinja2 موتور قالبی است که Ansible برای جایگذاری متغیرها از آن استفاده میکند. وقتی مقداری با یک متغیر شروع میشود، باید آن را داخل کوتیشن بگذارید. دلیلش این است که YAML آکولاد اول را شروع یک دیکشنری میفهمد.
task و module: وضعیت را اعلام کنید، نه دستور را
هر task یک name دارد و یک module. در task اول، module همان ansible.builtin.apt است. پیشوند ansible.builtin نشان میدهد این module جزو مجموعه داخلی خود Ansible است. خطهای زیر نام module آرگومانهای آن هستند.
نکته اصلی در state: present است. این خط نمیگوید «nginx را نصب کن». میگوید «nginx باید نصب باشد». تفاوت کوچک به نظر میرسد، اما کل رفتار Ansible روی همین است. module اول وضعیت فعلی سرور را بررسی میکند. اگر nginx از قبل نصب باشد، کاری نمیکند و task را بدون تغییر گزارش میدهد. اگر نصب نباشد، آن را نصب میکند و task را تغییریافته گزارش میدهد.
task دوم با ansible.builtin.copy یک فایل تنظیم با محتوای مشخص میسازد. اگر فایل از قبل با همین محتوا و همین مالک و دسترسی وجود داشته باشد، دوباره نوشته نمیشود. task سوم با state: started و enabled: true مطمئن میشود nginx الان در حال اجراست و بعد از راهاندازی دوباره سرور هم خودش بالا میآید.
taskها به همان ترتیبی که نوشته شدهاند اجرا میشوند. اگر یک task روی یک سرور شکست بخورد، Ansible بقیه taskها را روی آن سرور اجرا نمیکند، اما روی سرورهای دیگر ادامه میدهد.
handler و notify: کاری که فقط بعد از تغییر انجام میشود
task دوم خط notify: Reload nginx را دارد. معنیاش این است: اگر این task چیزی را تغییر داد، handler با نام Reload nginx را خبر کن. اگر فایل از قبل درست بود و task تغییری نداد، handler هم اجرا نمیشود.
handlerها بلافاصله اجرا نمیشوند. Ansible آنها را در انتهای play اجرا میکند و هر handler را فقط یک بار، حتی اگر چند task آن را خبر کرده باشند. این رفتار برای کاری مثل reload یک سرویس دقیقاً همان چیزی است که میخواهید: پنج فایل تنظیم عوض شده، اما nginx فقط یک بار reload میشود. مقدار notify باید دقیقاً با name آن handler یکی باشد. وقتی بهجای copy از قالبها استفاده کنید، همین الگو کاربرد بیشتری پیدا میکند، و راهنمای قالبهای Jinja2 و handlerها در Ansible این ترکیب را کامل توضیح میدهد.
چطور یک پلیبوک را اجرا کنیم؟
این دستورها را روی ماشین خودتان اجرا میکنید، یعنی جایی که Ansible نصب است. روی سرور مقصد فقط به دسترسی SSH و Python نیاز است. اول سینتکس فایل را بررسی کنید:
ansible-playbook -i inventory.ini site.yml --syntax-checkبعد یک اجرای آزمایشی بدون تغییر واقعی:
ansible-playbook -i inventory.ini site.yml --check --diffدر حالت --check، Ansible فقط گزارش میدهد که چه چیزی را تغییر میداد. بعضی moduleها در این حالت کامل شبیهسازی نمیشوند، و جزئیاتش در اجرای آزمایشی پلیبوک با check mode آمده است. در آخر اجرای واقعی:
ansible-playbook -i inventory.ini site.ymlاگر کاربر deploy برای sudo رمز عبور میخواهد، گزینه --ask-become-pass را هم اضافه کنید تا Ansible رمز را از شما بپرسد.
در انتهای هر اجرا، Ansible بخشی به نام PLAY RECAP چاپ میکند که برای هر سرور یک خط دارد. چهار عدد اصلی این خط اینها هستند. ok تعداد taskهایی است که با موفقیت تمام شدند. changed نشان میدهد چند تا از همان taskها مجبور شدند چیزی را روی سرور عوض کنند. unreachable یعنی Ansible اصلاً نتوانست با SSH به سرور وصل شود، پس مشکل در اتصال یا inventory است، نه در taskها. failed یعنی یک task اجرا شد اما شکست خورد. ممکن است ok کمی بیشتر از تعداد taskهای شما باشد، چون Ansible قبل از taskها اطلاعات سرور را جمع میکند. این مرحله در facts و gather_facts در Ansible توضیح داده شده است.
پلیبوک یا دستور ad-hoc؟
Ansible دو راه برای اجرا دارد. دستور ansible یک module را یک بار روی یک گروه از سرورها اجرا میکند. به این دستور ad-hoc میگویند، یعنی یکباره و بدون فایل:
ansible web -i inventory.ini -m ansible.builtin.ping
ansible web -i inventory.ini -b -m ansible.builtin.apt -a "name=nginx state=present"دستور اول بررسی میکند Ansible میتواند به سرورها وصل شود و Python را روی آنها اجرا کند. دستور دوم همان کار task اول مثال را انجام میدهد. گزینه -b همان become است.
ad-hoc برای یک سؤال سریع یا یک کار یکباره مناسب است. اما هیچ چیزی از آن ثبت نمیشود. پلیبوک یک فایل است: میتوانید آن را در git نگه دارید، بازبینی کنید و ماه بعد دقیقاً همان را دوباره اجرا کنید. قاعده ساده این است که اگر کاری را دو بار انجام میدهید، آن را در پلیبوک بنویسید.
چرا اجرای دوم پلیبوک چیزی را تغییر نمیدهد؟
فرق اصلی پلیبوک با یک اسکریپت bash در یک ویژگی است به نام idempotence (خودتوانی): اگر یک کار را چند بار انجام دهید، نتیجه همان نتیجه بار اول است.
یک اسکریپت bash دستورها را اجرا میکند، بدون توجه به وضعیت سرور. فرض کنید اسکریپت شما با echo "server_tokens off;" >> some.conf یک خط به فایلی اضافه میکند. بار دوم که اسکریپت را اجرا کنید، همان خط دوباره اضافه میشود. بار سوم هم همینطور. برای جلوگیری از این، باید در اسکریپت برای هر قدم یک شرط بنویسید.
پلیبوک این شرطها را در خود moduleها دارد. چون هر task یک وضعیت را اعلام میکند، اجرای دوم مثال بالا روی همان سرور باید changed را صفر نشان دهد، چون همه taskها سرور را از قبل در وضعیت درست پیدا میکنند. task دوم تغییری نداده، پس handler هم اجرا نمیشود و nginx بیدلیل reload نمیشود. همین ویژگی است که اجرای منظم پلیبوک را امن میکند. اگر کسی دستی تنظیمی را عوض کرده باشد، اجرای بعدی آن را به وضعیت تعریفشده برمیگرداند.
یک استثنای مهم وجود دارد. moduleهای ansible.builtin.command و ansible.builtin.shell فقط یک دستور را اجرا میکنند و نمیدانند آن دستور چه چیزی را عوض کرده است. به همین دلیل این taskها در هر اجرا تغییریافته گزارش میشوند، مگر اینکه با آرگومانی مثل creates یا با changed_when به Ansible بگویید چه زمانی واقعاً تغییری رخ داده است. هر جا یک module اختصاصی برای کارتان وجود دارد، از آن استفاده کنید.
پلیبوک با role چه فرقی دارد؟
role یک ساختار پوشهای استاندارد است که taskها، handlerها، قالبها و متغیرهای یک کار مشخص، مثلاً «نصب و تنظیم nginx»، را کنار هم نگه میدارد تا در چند پروژه دوباره استفاده شود. role بهتنهایی اجرا نمیشود. همیشه یک پلیبوک آن را صدا میزند و مشخص میکند روی کدام میزبانها اجرا شود. پس پلیبوک نقطه ورود است و role بستهای قابلاستفادهمجدد داخل آن. اینکه کی باید از یک پلیبوک ساده به سمت role بروید، موضوع مقایسه پلیبوک و role در Ansible است.
inventory کجای این تصویر قرار میگیرد؟
پلیبوک میگوید «چه کاری» و inventory میگوید «روی کدام سرورها». این جداسازی عمدی است. همان site.yml را میتوانید یک بار با inventory سرورهای آزمایشی و یک بار با inventory سرورهای اصلی اجرا کنید، بدون اینکه حتی یک خط از پلیبوک عوض شود. inventory علاوه بر آدرس سرورها میتواند متغیرهای هر سرور یا هر گروه را هم نگه دارد، مثل ansible_user در مثال بالا. ساختار کامل آن، شامل قالب YAML و متغیرهای گروهی، در راهنمای فایل inventory در Ansible آمده است.
قدم بعدی
تا اینجا فقط ساختار یک پلیبوک را دیدیم. برای نصب Ansible، آماده کردن کلید SSH و اجرای واقعی اولین پلیبوک روی یک VPS (سرور مجازی اختصاصی)، راهنمای نوشتن و اجرای اولین پلیبوک Ansible روی VPS را دنبال کنید.
پرسشهای پرتکرار (FAQ)
پلیبوک Ansible به زبان ساده چیست؟
یک فایل YAML است که با دستور ansible-playbook اجرا میشود. این فایل مشخص میکند روی کدام سرورهای inventory چه taskهایی به چه ترتیبی اجرا شوند. هر task وضعیت مطلوب را اعلام میکند، مثلاً نصب بودن یک بسته، و Ansible فقط وقتی سرور در آن وضعیت نباشد چیزی را تغییر میدهد.
فرق play و task در Ansible چیست؟
play یک بلوک در پلیبوک است که با hosts یک گروه از سرورها را هدف میگیرد و تنظیماتی مثل become و vars را برای آن گروه تعیین میکند. task یک قدم داخل play است که یک module را با آرگومانهایش صدا میزند. یک پلیبوک میتواند چند play داشته باشد و هر play چند task.
چرا handler من اجرا نمیشود؟
handler فقط وقتی اجرا میشود که taskی که آن را با notify خبر کرده، واقعاً چیزی را تغییر داده باشد. اگر آن task بدون تغییر تمام شود، handler اجرا نمیشود. همچنین مقدار notify باید دقیقاً با name آن handler یکی باشد. handlerها در انتهای play اجرا میشوند، نه بلافاصله بعد از task.
آیا اجرای دوباره یک پلیبوک خطرناک است؟
برای پلیبوکی که با moduleهای اختصاصی مثل apt یا service نوشته شده، نه. این moduleها اول وضعیت سرور را بررسی میکنند، پس اجرای دوم باید در PLAY RECAP تعداد changed را صفر نشان دهد. taskهایی که از command یا shell استفاده میکنند استثنا هستند، چون هر بار دستور را دوباره اجرا میکنند و تغییر گزارش میدهند.