تفاوت Ansible playbook و role؛ چه زمانی از کدام استفاده
تفاوت کاربردی Ansible playbook و role را بیاموزید. بفهمید چه زمانی یک فایل ساده کافی است و چه زمانی باید با ansible-galaxy init ساختار پروژه خود را به role تبدیل کنید.
تفاوت Ansible playbook و role چیست
یک Ansible playbook فایلی است که آن را با ansible-playbook اجرا میکنید. این فایل گروهی از میزبانها را به وظایفی که باید انجام دهند، نگاشت میکند. یک Ansible role دایرکتوری با ساختاری ثابت است که شامل taskها، templateها، handlerها و متغیرهای پیشفرض است و یک playbook آن را با نام فراخوانی میکند. نحو (syntax) وظایف در هر دو یکسان است، بنابراین این پرسش درباره آنچه میتوانید بیان کنید نیست؛ بلکه پرسشی درباره قابلیت استفاده مجدد است.
با یک playbook تخت (flat) شروع کنید. یک site.yml که شامل لیستی از tasks: باشد، شکل مناسبی برای اولین اتوماسیون شماست و برای مدتی طولانیتر از آنچه اکثر افراد تصور میکنند، مناسب باقی میماند. زمانی که همان بلوک از وظایف باید برای گروه دوم میزبانها اجرا شود، یا زمانی که فایل از حدود 100 خط فراتر رفت و دیگر نمیتوانید با اسکرول کردن یک وظیفه را پیدا کنید، آن را به یک role تبدیل کنید.
اگر هنوز اولین مورد خود را ننوشتهاید، با یک playbook اولیه روی یک VPS تکی شروع کنید و زمانی که شروع به بزرگ شدن کرد، بازگردید.
چه زمانی یک playbook تخت، انتخاب درستی است
یک playbook تخت زمانی مناسب است که کار فقط یکبار انجام شود، یا روی یک میزبان (host) خاص باشد، یا زمانی که قرار نیست شخص دیگری آن را بخواند. آمادهسازی (provisioning) یک سرور برنامه واحد، یا وصلهکردن (patching) یک سیستم پیش از بازه زمانی نگهداری: هیچکدام از این موارد نیازی به ساختار درختی دایرکتوری ندارند. یک role هفت دایرکتوری و یک لایه غیرمستقیم (indirection) اضافه میکند. اگر تنها فراخواننده، همان playbook کنار آن باشد، این لایه غیرمستقیم هیچ ارزشی ایجاد نمیکند و فقط باعث میشود هر بار که میخواهید بدانید چه چیزی واقعاً اجرا میشود، یک پرش اضافی انجام دهید.
یک playbook تخت در لحظه خاصی دیگر انتخاب درستی نخواهد بود و تشخیص آن لحظه آسان است. زمانی که یک بلوک از taskها را در یک playbook دوم کپی میکنید، آن کپی، همان نشانه است. از آن لحظه به بعد، هر اصلاح باید دو بار انجام شود و یک روز فرا میرسد که آن اصلاح فقط یکبار اعمال خواهد شد.
محتوای واقعی یک دایرکتوری نقش (role)
roles/common/
defaults/main.yml
vars/main.yml
tasks/main.yml
handlers/main.yml
templates/99-hardening.conf.j2
files/
meta/main.ymltasks/main.ymlنقطه ورود است. Ansible هنگام فراخوانی نقش، این فایل را اجرا میکند و سایر دایرکتوریها اختیاری هستند.defaults/main.ymlمتغیرهایی را در خود نگه میدارد که انتظار میرود فراخواننده آنها را بازنویسی (override) کند. این دایرکتوری پایینترین اولویت را در Ansible دارد، بنابراین تقریباً هر منبع دیگری بر آن ارجحیت دارد.vars/main.ymlمتغیرهایی را نگه میدارد که انتظار نمیرود فراخواننده آنها را بازنویسی کند. اولویت این دایرکتوری از inventory بالاتر است که جایگاه بسیار قدرتمندی محسوب میشود. از آن به ندرت استفاده کنید.handlers/main.ymlوظایفی (tasks) را نگه میدارد که توسطnotifyفراخوانی میشوند. یک handler در پایان play، تنها یکبار اجرا میشود، فارغ از اینکه چند وظیفه آن را فراخوانی کرده باشند.files/فایلهایی را نگه میدارد که عیناً توسط ماژولcopyکپی میشوند وtemplates/قالبهای Jinja2 را نگه میدارد که توسط ماژولtemplateپردازش میشوند. در داخل یک نقش، به هر دو فقط با نام فایل و بدون مسیر ارجاع میدهید، زیرا Ansible ابتدا دایرکتوریهای خودِ نقش را جستجو میکند.meta/main.ymlوابستگیهای نقش و متادیتایی که Ansible Galaxy میخواند را تعریف میکند.
این ساختار یک ترجیح سلیقهای نیست. Ansible دقیقاً در همین مسیرها جستجو میکند، بنابراین قالبی که در roles/common/template/ (به صورت مفرد) قرار دهید، هرگز پیدا نخواهد شد.
ساخت نقش مشترک با ansible-galaxy init
mkdir -p ~/infra/roles
cd ~/infra
ansible-galaxy init --init-path roles commonاین دستور کل اسکلتبندی را در مسیر roles/common ایجاد میکند، که شامل دایرکتوریهایی است که از آنها استفاده نخواهید کرد و فایلهای stub در main.yml که فقط حاوی --- هستند. مواردی را که خالی میگذارید، حذف کنید. یک فایل vars/main.yml خالی برای Ansible بیضرر است، اما باعث میشود مشخص نباشد کدام فایلها در نقش واقعاً اهمیت دارند.
اکنون فایلهایی را که وظایف اصلی را انجام میدهند، تکمیل کنید. ابتدا با defaults شروع کنید، زیرا آنها رابط عمومی نقش هستند.
# roles/common/defaults/main.yml
---
common_packages:
- ufw
- fail2ban
- unattended-upgrades
common_admin_group: admins
common_permit_root_login: "no"
common_password_authentication: "no"مقادیر "no" و "yes" را داخل کوتیشن قرار دهید. Ansible فایلهای YAML را با PyYAML تجزیه میکند که یک no بدون کوتیشن را به عنوان مقدار بولی false میخواند؛ در نتیجه خط پیکربندی نهایی به PermitRootLogin False تبدیل شده و sshd آن را رد میکند. کوتیشنها باعث میشوند مقدار به صورت رشته (string) باقی بماند.
# roles/common/tasks/main.yml
---
- name: Install the base packages
ansible.builtin.apt:
name: "{{ common_packages }}"
state: present
update_cache: true
cache_valid_time: 3600
- name: Create the admin group
ansible.builtin.group:
name: "{{ common_admin_group }}"
state: present
- name: Install the sshd hardening drop-in
ansible.builtin.template:
src: 99-hardening.conf.j2
dest: /etc/ssh/sshd_config.d/99-hardening.conf
owner: root
group: root
mode: "0644"
validate: /usr/sbin/sshd -t -f %s
notify: Restart sshd# roles/common/handlers/main.yml
---
- name: Restart sshd
ansible.builtin.service:
name: ssh
state: restarted# roles/common/templates/99-hardening.conf.j2
# Managed by Ansible. Local edits are overwritten on the next run.
PermitRootLogin {{ common_permit_root_login }}
PasswordAuthentication {{ common_password_authentication }}در توزیعهای Debian و Ubuntu، واحد systemd با نام ssh شناخته میشود و در سیستمهای خانواده RHEL، این نام sshd است. هندلری که نام اشتباه را فراخوانی کند، تنها زمانی با خطا مواجه میشود که تغییری در قالب (template) ایجاد شود؛ به همین دلیل است که این مشکل معمولاً هفتهها بعد نمایان میشود.
خط validate مفیدترین بخش در آن task است. Ansible قالب را به یک فایل موقت تبدیل میکند، مسیر آن فایل را جایگزین %s میکند و دستور را اجرا میکند. فایل مقصد تنها در صورتی جایگزین میشود که دستور با کد خروجی 0 پایان یابد. یک دستور نامعتبر در قالب قرار دهید و دوباره اجرا کنید: task با خطای failed to validate شکست میخورد، فایل اصلی /etc/ssh/sshd_config.d/99-hardening.conf دستنخورده باقی میماند و شما همچنان سروری دارید که میتوانید به آن وارد شوید. توجه داشته باشید که این بررسی چیزی فراتر از سینتکس شما را تست میکند. اگر sshd -t نتواند کلیدهای میزبان را بخواند، با کد sshd: no hostkeys available -- exiting. خارج میشود و Ansible همان خطای failed to validate را گزارش میدهد؛ بنابراین پیش از مقصر دانستن قالب، msg مربوط به ماژول را مطالعه کنید.
نحوه فراخوانی یک role توسط playbook
# site.yml
---
- name: Base configuration for every server
hosts: all
become: true
roles:
- common# inventory.ini
[local]
localhost ansible_connection=localansible-playbook -i inventory.ini site.ymlاجرای play باید در بخش recap با failed=0 پایان یابد. پارامترها را در محل فراخوانی با استفاده از فرم بسطیافته ارسال کنید؛ این همان روشی است که یک role را برای دو گروه از میزبانها قابل استفاده میکند:
roles:
- role: common
common_admin_group: ops
common_permit_root_login: prohibit-passwordیک قانون ترتیببندی وجود دارد که تقریباً همه را غافلگیر میکند. یک play میتواند شامل pre_tasks، roles، tasks و post_tasks باشد و Ansible آنها را بدون توجه به ترتیبی که در فایل نوشتهاید، با همین اولویت اجرا میکند. حتی اگر tasks: را بالاتر از roles: قرار دهید، باز هم roleها زودتر اجرا میشوند. بنابراین اگر کاری باید پیش از یک role انجام شود، آن کار باید در pre_tasks: قرار بگیرد، نه در ابتدای tasks:.
- name: Ordering demonstration
hosts: local
gather_facts: false
pre_tasks:
- name: Runs first
ansible.builtin.debug:
msg: pre
roles:
- common
tasks:
- name: Runs after the role
ansible.builtin.debug:
msg: task
post_tasks:
- name: Runs last
ansible.builtin.debug:
msg: postبرای فراخوانی یک role از داخل لیست taskها به جای استفاده از کلید roles:، از import_role یا include_role استفاده کنید.
tasks:
- name: Static, read when the playbook is parsed
ansible.builtin.import_role:
name: common
- name: Dynamic, resolved when the task runs
ansible.builtin.include_role:
name: postgres
when: "'db' in group_names"import_role ایستا (static) است. Ansible در زمان parse کردن، role را میخواند و taskهای آن بخشی از play میشوند؛ به همین دلیل ansible-playbook --list-tasks site.yml آنها را لیست میکند و تگ اعمالشده روی import، به تمام taskهای داخل آن تعلق میگیرد. include_role پویا (dynamic) است. تا زمانی که task اجرا نشود، هیچچیز خوانده نمیشود؛ این ویژگی به شما اجازه میدهد نام role را از طریق یک متغیر یا حلقه تعیین کنید. هزینه این کار این است که آن taskها برای --list-tasks و --start-at-task نامرئی هستند.
یک تله در اینجا وجود دارد. یک when: روی یک task از نوع include_role، پیش از آنکه defaults/main.ymlِ roleِ گنجاندهشده در scope قرار بگیرد، ارزیابی میشود. اگر when: common_packages | length > 0 را روی include بنویسید، اجرا با خطای 'common_packages' is undefined متوقف میشود، حتی اگر آن متغیر در همان roleای که در حال include کردن آن هستید تعریف شده باشد. راهحل این است که آن شرط را از داخل role خارج کنید: آن را در group_vars/all.yml قرار دهید که در همه جا در دسترس (in scope) است و مقادیر پیشفرض role را برای مواردی بگذارید که خودِ role از آنها استفاده میکند.
کدام متغیر اولویت دارد: defaults، group_vars، vars، extra vars
Ansible بیش از 20 سطح اولویت برای متغیرها تعریف کرده است. چهار مورد از آنها تقریباً تمام بحثهای عملی را حلوفصل میکنند که در اینجا از ضعیفترین به قویترین مرتب شدهاند.
roles/<name>/defaults/main.ymlدر پایینترین سطح قرار دارد. تقریباً هر مقداری که در جای دیگری تنظیم کنید بر آن غلبه میکند؛ به همین دلیل است که این بخش بهترین مکان برای تنظیمات قابلتغییر (tunable knobs) یک role است.group_vars/وhost_vars/در سطح میانی قرار دارند. اینجاست که پاسخهای اختصاصی سایت شما قرار میگیرند و بهطور تمیزی مقادیر پیشفرض role را بازنویسی (override) میکنند.roles/<name>/vars/main.ymlبالاتر ازhost_varsقرار دارد. مقداری که در اینجا قرار میدهید، توسط inventory قابل بازنویسی نیست. این بخش را برای مواردی رزرو کنید که role برای حفظ سازگاری داخلی به آن نیاز دارد، مانند نام بستهای که باید با نام سرویس مطابقت داشته باشد.- پارامتر role که در محل فراخوانی ارسال میشود، بر
vars/main.ymlغلبه میکند و-eدر خط فرمان، همه چیز، از جمله پارامترهای role را شکست میدهد.
شما میتوانید این روند اولویتبندی را در حدود یک دقیقه مشاهده کنید. به یک role کوچک، یک مقدار پیشفرض و یک متغیر role بدهید، سپس همان نامها را در host_vars تنظیم کنید.
# roles/prec/defaults/main.yml
---
prec_tunable: from-defaults
prec_internal: from-defaults# roles/prec/vars/main.yml
---
prec_internal: from-rolevars# host_vars/localhost.yml
---
prec_tunable: from-hostvars
prec_internal: from-hostvars# roles/prec/tasks/main.yml
---
- name: Show which value survived
ansible.builtin.debug:
msg: "tunable={{ prec_tunable }} internal={{ prec_internal }}"ansible-playbook -i inventory.ini prec.yml
ansible-playbook -i inventory.ini prec.yml -e prec_internal=from-cliاجرای اول مقدار tunable=from-hostvars internal=from-rolevars را چاپ میکند. Inventory بر مقدار پیشفرض role غلبه کرد اما در برابر متغیر role شکست خورد. اجرای دوم مقدار internal=from-cli را چاپ میکند، زیرا extra vars در بالاترین سطح قرار دارند و هیچچیز در سطوح پایینتر نمیتواند با آن مقابله کند. به همین دلیل است که -e برای یک اجرای تکمرحلهای مناسب است اما در اسکریپتی که نگهداری میکنید اشتباه است: این متغیر بهطور بیصدا بر تمام تصمیمات سنجیده شده در مخزن شما اولویت مییابد.
قانون کاری: اگر میخواهید مقداری قابلتغییر باشد، آن را در defaults/ قرار دهید. قرار دادن آن در vars/ به هر کاربر آیندهٔ آن role میگوید که inventory ممکن است نتواند آن را تغییر دهد. این گاهی همان چیزی است که مد نظر شماست، اما معمولاً یک اشتباه است.
اثبات همتوان (Idempotent) بودن نقش: اجرای دوبار
یک اجرای قابلاعتماد Ansible باید در مرتبهٔ دوم نتیجهٔ یکسانی تولید کند و گزارش دهد که هیچ تغییری ایجاد نشده است. Playbook را دو بار اجرا کنید و خلاصهٔ آن را بخوانید.
ansible-playbook -i inventory.ini site.yml
ansible-playbook -i inventory.ini site.ymlخلاصهٔ دوم باید به این شکل باشد:
PLAY RECAP *********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0changed=0 به این معناست که هر ماژول وضعیت فعلی را بررسی کرده و متوجه شده است که کار از قبل انجام شده است. وجود changed=2 در اجرای دوم به این معناست که دو تسک نمیتوانند تفاوت وضعیت را تشخیص دهند، بنابراین آنها برای همیشه به بازنویسی فایلها و راهاندازی مجدد سرویسها ادامه خواهند داد. مقصر معمول در این موارد command یا shell است، زیرا Ansible راهی برای دانستن اینکه یک دستور دلخواه چه کاری انجام داده است، ندارد.
# traps.yml
---
- name: Command modules do not know what they changed
hosts: local
gather_facts: false
tasks:
- name: This appends a line on every run
ansible.builtin.shell: "echo run >> /tmp/grow.txt"
- name: This appends a line only once
ansible.builtin.shell: "echo run >> /tmp/guarded.txt"
args:
creates: /tmp/guarded.txtآن Playbook را دو بار اجرا کنید، سپس خطوط دارای wc -l /tmp/grow.txt /tmp/guarded.txt را بشمارید. /tmp/grow.txt شامل دو خط و /tmp/guarded.txt شامل یک خط است. در اجرای دوم، تسک محافظتشده اصلاً اجرا نشد و نتیجهٔ آن حاوی پیام skipped, since /tmp/guarded.txt exists است، زیرا creates به ماژول یک محصول قابلمشاهده میدهد تا ابتدا آن را جستجو کند. هنگامی که یک دستور چنین محصولی باقی نمیگذارد، خروجی آن را ثبت کنید و خودتان با استفاده از changed_when تصمیم بگیرید.
ansible-playbook --check --diff site.yml تغییرات را بدون اعمال آنها پیشبینی میکند و --diff خطوط دقیقی که یک template بازنویسی میکند را چاپ میکند. خروجی را با در نظر گرفتن یک نکته بخوانید: تسکهای shell و command در حالت check mode نادیده گرفته میشوند، بنابراین برنامهای که تمیز به نظر میرسد ممکن است همچنان کاری را پنهان کرده باشد.
یک ستون دیگر در آن خلاصه نیز شایستهٔ همین دقت است: میزبانی که Ansible نتوانسته به آن متصل شود، تحت unreachable شمارش میشود نه failed و هیچیک از تسکهای آن اجرا نشدهاند، بنابراین پیش از آنکه این نقش را به بیش از چند ماشین اختصاص دهید، از قبل تصمیم بگیرید که آیا یک میزبان غیرقابلدسترس باید کل اجرا را متوقف کند یا خیر.
چرا Ansible میگوید که role پیدا نشد
Ansible به دنبال دایرکتوری roles/ در کنار فایل playbook و سپس در roles_path میگردد. این جستجو بر اساس مسیر playbook انجام میشود، نه مسیر فعلی shell شما.
ERROR! the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonelyاین پیام بدین معناست که site.yml و roles/ از هم فاصله گرفتهاند؛ Ansible برای راهنمایی، مسیرهایی که تلاش کرده در آنها جستجو کند را چاپ میکند. این دو را در یک دایرکتوری نگه دارید. اجرای دستور از یک دایرکتوری والد مشکلی ندارد، زیرا مسیر playbook ملاک اصلی است:
ansible-playbook -i infra/inventory.ini infra/site.ymlنسخهٔ بیسروصداتری از همین مشکل نیز وجود دارد. اگر دایرکتوری فعلی برای همه قابل نوشتن (world writable) باشد، Ansible فایل ansible.cfg موجود در آن را نادیده میگیرد؛ زیرا هر کاربری روی سیستم میتواند یک فایل پیکربندی در آنجا قرار دهد و رفتار اجرای شما را تغییر دهد.
[WARNING]: Ansible is being run in a world writable directory (/tmp/infra), ignoring it as an ansible.cfg source.در این حالت، تنظیمات roles_path و inventory شما بدون هیچ هشداری نادیده گرفته میشوند و جستجوی role به دلیلی که هیچ ارتباطی به خودِ roleها ندارد، با شکست مواجه میشود. دستور ansible --version فایلی که در واقع بارگذاری شده است (config file) را چاپ میکند و ansible-config dump --only-changed تمام تنظیماتی که با مقادیر پیشفرض داخلی تفاوت دارند را نمایش میدهد. هر زمان که اجرای دستور طوری رفتار کرد که انگار پیکربندی شما وجود ندارد، هر دو مورد را بررسی کنید.
اشتراکگذاری نقشها: requirements.yml و نسخه ثابت (pinned)
نقشی که توسط شخص دیگری نوشته شده است باید نصب شود، نه کپی. آن را یکبار تعریف کنید:
# requirements.yml
---
roles:
- name: postgres
src: https://github.com/example/ansible-role-postgres
scm: git
version: v1.4.0ansible-galaxy install -r requirements.yml -p galaxy_rolesهمیشه version را تنظیم کنید. بدون آن، شما هر نسخهای که در شاخه پیشفرض (default branch) در روز اجرای دستور وجود داشته باشد را دریافت میکنید؛ بنابراین استقراری که ماه گذشته کار میکرد، بدون هیچ تغییری در مخزن شما، از کار میافتد. مقدار roles_path را به دایرکتوری دانلود اشاره دهید و آن دایرکتوری را از git دور نگه دارید:
# ansible.cfg
[defaults]
inventory = inventory.ini
roles_path = ./galaxy_rolesنقشهای موجود در roles/ در کنار playbook همچنان پیدا میشوند، زیرا آن مسیر همیشه علاوه بر roles_path جستجو میشود. بنابراین نقشهای خودتان در مخزن commit و بازبینی میشوند، در حالی که نقشهای شخص ثالث، دانلودهای قابل بازتولید هستند که به یک تگ خاص محدود (pinned) شدهاند.
جایی که نقشها دیگر پاسخگو نیستند
یک role واحدی برای استفادهٔ مجدد در طول یک اجرای Ansible است. این ابزار سرورها یا رکوردهای DNS را در سرویسدهندهٔ شما ایجاد نمیکند و تلاش برای وادار کردن آن به انجام این کار، باعث میشود playbookها به چیزی تبدیل شوند که هیچکس تمایلی به نگهداری آن ندارد. مطالعهٔ تقسیم کار بین Ansible و Terraform پیش از شروع، ارزشمند است. همچنین یک role جایگزین طراحی inventory نمیشود: زمانی که تعداد ماشینها از چند عدد فراتر میرود، نحوهٔ گروهبندی و دسترسی به آن سرورها اهمیت بیشتری نسبت به نحوهٔ دستهبندی taskها پیدا میکند.
ایمنسازی (hardening) که این role common نصب میکند نیز نیازمند تصمیمگیریهای خاص خود است. قطعهکد بالا تنها دو دستورالعمل را تنظیم میکند و نه بیشتر؛ بنابراین پیش از آنکه تصمیم بگیرید چه چیزی باید در role برای هر میزبانی که در اختیار دارید قرار بگیرد، کدام تنظیمات SSH واقعاً ارزش تغییر دارند و چگونه Ubuntu را وادار کنیم بهطور خودکار بهروزرسانیهای امنیتی را اعمال کند را مطالعه کنید.
FAQ
چه زمانی باید یک Ansible playbook را به یک role تبدیل کنم؟
زمانی که همان بلوک از وظایف (tasks) باید در یک play دوم یا روی گروه دوم از میزبانها اجرا شود. کپی کردن وظایف بین playbookها یک هشدار است، زیرا از آن لحظه به بعد، هر اصلاح باید دو بار اعمال شود و یک روز بالاخره فراموش میکنید یکی از آنها را اعمال کنید. یک playbook واحد با کمتر از حدود 100 خط که همیشه فقط یک گروه را هدف قرار میدهد، از تبدیل شدن به role سودی نمیبرد و دایرکتوریهای اضافی فقط خواندن آن را دشوارتر میکنند.
آیا roleها قبل از وظایف موجود در همان play اجرا میشوند؟
بله. Ansible ابتدا pre_tasks، سپس تمام موارد لیستشده در roles:، سپس tasks: و در نهایت post_tasks: را اجرا میکند و به ترتیبی که این کلیدها در فایل شما ظاهر شدهاند توجهی ندارد. نوشتن tasks: بالاتر از roles: باعث نمیشود آن وظایف زودتر اجرا شوند. اگر کاری باید پیش از یک role انجام شود، آن را در pre_tasks: قرار دهید.
چرا مقدار group_vars من، role را override نمیکند؟
بررسی کنید که آیا متغیر در vars/main.yml تعریف شده است یا در defaults/main.yml. در ترتیب اولویت Ansible، vars/ بالاتر از group_vars و host_vars قرار دارد، بنابراین inventory نمیتواند آن را override کند. متغیر را به defaults/main.yml منتقل کنید که در انتهای لیست اولویت قرار دارد و جایگاه صحیح برای هر چیزی است که فراخواننده (caller) باید بتواند آن را تغییر دهد. برای اطمینان از اینکه علت، اولویت است و نه یک غلط تایپی، یک بار با -e name=value اجرا کنید که از تمام منابع دیگر اولویت بالاتری دارد.
چرا Ansible میگوید role پیدا نشد؟
جستجو از کنار فایل playbook شروع میشود، بنابراین site.yml و roles/ باید در یک دایرکتوری باشند. خطا مسیرهایی را که بررسی کرده است چاپ میکند، مانند the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely. اجرای playbook از یک دایرکتوری والد مشکلی ندارد، زیرا جستجو مسیر playbook را دنبال میکند و نه دایرکتوری کاری (working directory) شل شما. اگر به roles_path از ansible.cfg وابسته هستید، تأیید کنید که آن فایل با ansible --version بارگذاری شده است، زیرا اگر دایرکتوری کاری برای همه قابل نوشتن (world writable) باشد، Ansible آن را نادیده میگیرد.
آیا برای ایجاد یک role به ansible-galaxy init نیاز دارم؟
خیر. یک role فقط مجموعهای از دایرکتوریها با نامهای مشخص است، بنابراین mkdir -p roles/common/tasks به همراه یک tasks/main.yml از قبل یک role کاری محسوب میشود. ansible-galaxy init --init-path roles common باعث صرفهجویی در تایپ میشود و اسکلت کامل، شامل meta/main.yml و یک پیشنویس README را به شما میدهد. دایرکتوریهایی را که خالی میگذارید حذف کنید، زیرا یک vars/main.yml خالی باعث میشود مشخص نباشد کدام فایلها در role واقعاً کاری انجام میدهند.