Ansible playbook กับ role ต่างกันอย่างไร เลือกใช้อย่างไรดี
เรียนรู้ความแตกต่างระหว่างการเขียน Ansible playbook แบบไฟล์เดียวกับการใช้ role ที่มีโครงสร้างไดเรกทอรีชัดเจน พร้อมเกณฑ์การตัดสินใจเมื่อโค้ดเกิน 100 บรรทัดและการจัดการตัวแปร
ความแตกต่างระหว่าง Ansible playbook และ role
Ansible playbook คือไฟล์ที่คุณรันด้วยคำสั่ง ansible-playbook โดยทำหน้าที่จับคู่กลุ่มโฮสต์เข้ากับงานที่ต้องดำเนินการ ส่วน Ansible role คือไดเรกทอรีที่มีโครงสร้างตายตัวซึ่งประกอบด้วย tasks, templates, handlers และ default variables โดยที่ playbook จะเรียกใช้งาน role ผ่านชื่อ ไวยากรณ์ของ task ในทั้งสองรูปแบบนั้นเหมือนกัน ดังนั้นจึงไม่ใช่คำถามว่าคุณสามารถเขียนอะไรได้บ้าง แต่เป็นคำถามเรื่องการนำกลับมาใช้ใหม่ (reuse)
ให้เริ่มต้นด้วย playbook แบบแบนราบ การใช้ไฟล์ site.yml เพียงไฟล์เดียวที่มีรายการ tasks: คือรูปแบบที่เหมาะสมสำหรับการทำระบบอัตโนมัติครั้งแรกของคุณ และมันยังคงใช้งานได้ดีนานกว่าที่คนส่วนใหญ่คาดคิด ให้เปลี่ยนไปใช้ role เมื่อต้องรันชุด task เดิมซ้ำสำหรับโฮสต์กลุ่มที่สอง หรือเมื่อไฟล์มีขนาดใหญ่เกินกว่า 100 บรรทัดจนคุณไม่สามารถหา task ที่ต้องการได้จากการเลื่อนหน้าจอ
หากคุณยังไม่เคยเขียน playbook มาก่อน ให้เริ่มต้นด้วย playbook แรกสำหรับ VPS เครื่องเดียว แล้วค่อยกลับมาอ่านหน้านี้เมื่อ playbook ของคุณเริ่มมีขนาดใหญ่ขึ้น
เมื่อใดที่ควรใช้ flat playbook
การใช้ flat playbook เป็นทางเลือกที่เหมาะสมเมื่อต้องทำงานเพียงครั้งเดียว บนโฮสต์เดียว หรือเมื่อไม่มีผู้อื่นต้องมาอ่านโค้ดดังกล่าว การจัดเตรียมเซิร์ฟเวอร์แอปพลิเคชันเพียงเครื่องเดียว หรือการแพตช์ระบบก่อนช่วงเวลาบำรุงรักษา ไม่จำเป็นต้องใช้โครงสร้างไดเรกทอรีที่ซับซ้อน การสร้าง role จะเพิ่มไดเรกทอรีขึ้นอีก 7 แห่งและเพิ่มชั้นการอ้างอิงขึ้นอีกหนึ่งระดับ หากผู้เรียกใช้มีเพียง playbook ที่วางอยู่ข้างๆ กัน การอ้างอิงนั้นก็ไม่ได้สร้างประโยชน์ใดๆ แต่กลับทำให้คุณต้องเสียเวลาคลิกข้ามไปมาทุกครั้งที่ต้องการตรวจสอบว่ามีการทำงานจริงอย่างไร
flat playbook จะเริ่มไม่เหมาะสม ณ จุดหนึ่ง ซึ่งเป็นจุดที่สังเกตได้ง่าย นั่นคือเมื่อคุณคัดลอกบล็อกของ tasks ไปไว้ใน playbook ที่สอง การคัดลอกนั้นคือสัญญาณเตือน นับจากนั้นเป็นต้นไป ทุกการแก้ไขจะต้องทำซ้ำสองครั้ง และวันหนึ่งคุณจะลืมแก้ไขในที่ใดที่หนึ่งจนได้
สิ่งที่ไดเรกทอรีบทบาท (role directory) จัดเก็บจริง
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 จะรันไฟล์นี้เมื่อมีการเรียกใช้ role และไดเรกทอรีอื่นๆ ทั้งหมดถือเป็นทางเลือกdefaults/main.ymlจัดเก็บตัวแปรที่คาดหวังให้ผู้เรียกทำการ override ซึ่งเป็นแหล่งข้อมูลที่มีลำดับความสำคัญต่ำที่สุดใน Ansible ดังนั้นเกือบทุกอย่างจึงมีความสำคัญเหนือกว่าvars/main.ymlจัดเก็บตัวแปรที่ไม่ได้คาดหวังให้ผู้เรียกทำการ override โดยมีลำดับความสำคัญสูงกว่า inventory ซึ่งถือเป็นข้อกำหนดที่เข้มงวด จึงควรใช้ด้วยความระมัดระวังhandlers/main.ymlจัดเก็บ task ที่ถูกกระตุ้นโดยnotifyโดย handler จะทำงานเมื่อจบ play เพียงครั้งเดียว ไม่ว่าจะมีกี่ task ที่แจ้งเตือนมันก็ตามfiles/จัดเก็บไฟล์ที่ถูกคัดลอกแบบตรงตัวโดยโมดูลcopyและtemplates/จัดเก็บ Jinja2 template ที่ถูกประมวลผลโดยโมดูลtemplateภายใน role คุณสามารถอ้างอิงทั้งสองอย่างได้ด้วยชื่อไฟล์เปล่าๆ โดยไม่ต้องระบุ path เพราะ Ansible จะค้นหาในไดเรกทอรีของ role นั้นๆ ก่อนเสมอmeta/main.ymlประกาศการพึ่งพาของ role และ metadata ที่ Ansible Galaxy อ่าน
โครงสร้างนี้ไม่ใช่เรื่องของความชอบส่วนบุคคล Ansible ค้นหาใน path เหล่านี้โดยเฉพาะ ดังนั้น template ที่คุณวางไว้ใน roles/common/template/ (รูปเอกพจน์) จะไม่ถูกพบอย่างแน่นอน
สร้าง role ทั่วไปด้วย 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 แต่จะทำให้แยกแยะได้ยากว่าไฟล์ใดใน role ที่มีความสำคัญจริง
จากนั้นให้เติมเนื้อหาลงในไฟล์ที่ทำหน้าที่ประมวลผล โดยเริ่มจากค่าเริ่มต้น (defaults) ก่อน เนื่องจากเป็นอินเทอร์เฟซสาธารณะของ role
# 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 เปล่าๆ ว่าเป็นค่า boolean false ส่งผลให้บรรทัด config ที่ถูกเรนเดอร์กลายเป็น PermitRootLogin False และ sshd จะปฏิเสธค่าดังกล่าว การใส่เครื่องหมายคำพูดจะช่วยรักษาค่าให้เป็นสตริง
# 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 unit จะใช้ชื่อว่า ssh ส่วนบนระบบตระกูล RHEL จะใช้ชื่อว่า sshd การตั้งชื่อ handler ผิดจะทำให้เกิดข้อผิดพลาดก็ต่อเมื่อมีการเปลี่ยนแปลงใน template เท่านั้น ซึ่งเป็นสาเหตุว่าทำไมปัญหาประเภทนี้มักจะปรากฏให้เห็นหลังจากผ่านไปหลายสัปดาห์
บรรทัด validate เป็นส่วนที่มีประโยชน์ที่สุดใน task นั้น Ansible จะเรนเดอร์ template ลงในไฟล์ชั่วคราว จากนั้นจะแทนที่ path ของไฟล์นั้นด้วย %s แล้วจึงรันคำสั่ง ปลายทางจะถูกแทนที่ก็ต่อเมื่อคำสั่งรันสำเร็จ (exit 0) เท่านั้น หากคุณใส่ directive ที่ผิดพลาดลงใน template แล้วรันใหม่อีกครั้ง task จะล้มเหลวพร้อมแจ้ง failed to validate โดยที่ไฟล์ /etc/ssh/sshd_config.d/99-hardening.conf จริงจะยังคงไม่ถูกแก้ไข และคุณจะยังคงสามารถล็อกอินเข้าเซิร์ฟเวอร์ได้ตามปกติ โปรดทราบว่าการตรวจสอบนี้ทดสอบมากกว่าแค่ไวยากรณ์ของคุณ หาก sshd -t ไม่สามารถอ่าน host keys ได้ มันจะ exit ด้วย sshd: no hostkeys available -- exiting. และ Ansible จะรายงาน failed to validate เช่นเดียวกัน ดังนั้นควรอ่าน msg ของโมดูลก่อนที่จะสรุปว่าปัญหาเกิดจาก template
วิธีการเรียกใช้ 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.ymlPlay ควรจบลงด้วย 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 จะอ่าน role ในช่วงเวลา parse และ task ต่างๆ จะกลายเป็นส่วนหนึ่งของ play ทำให้ ansible-playbook --list-tasks site.yml สามารถแสดงรายการ task เหล่านั้นได้ และ tag ที่กำหนดบน import จะมีผลกับทุก task ที่อยู่ภายใน ส่วน include_role เป็นแบบ dynamic ซึ่งจะไม่มีการอ่านข้อมูลจนกว่า task จะทำงานจริง วิธีนี้ช่วยให้คุณกำหนดชื่อ role จากตัวแปรหรือ loop ได้ แต่ข้อเสียคือ task เหล่านั้นจะไม่ปรากฏให้ --list-tasks และ --start-at-task เห็น
มีกับดักหนึ่งประการที่ต้องระวัง คือ when: บน task แบบ include_role จะถูกประเมินก่อนที่ defaults/main.yml ของ role ที่ถูก include จะอยู่ในขอบเขต (scope) หากคุณเขียน when: common_packages | length > 0 ไว้บน include การทำงานจะหยุดลงพร้อมกับข้อผิดพลาด 'common_packages' is undefined แม้ว่าตัวแปรนั้นจะถูกกำหนดไว้ใน role ที่คุณกำลัง include อยู่ก็ตาม วิธีแก้ไขคือให้ย้ายตัวแปรควบคุมนั้นออกมาจาก role โดยนำไปไว้ใน group_vars/all.yml ซึ่งจะทำให้ตัวแปรอยู่ในขอบเขตที่เข้าถึงได้จากทุกที่ และให้ใช้ค่า defaults ของ role สำหรับค่าที่ role นั้นจำเป็นต้องใช้เองเท่านั้น
Which variable wins: defaults, group_vars, vars, extra vars
Ansible documents more than twenty levels of variable precedence. Four of them settle nearly every real argument, and here they are from weakest to strongest.
roles/<name>/defaults/main.ymlsits near the bottom. Almost anything you set anywhere else beats it, which is exactly why it is the right home for a role's tunable knobs.group_vars/andhost_vars/sit in the middle. This is where your site's own answers belong, and they cleanly override role defaults.roles/<name>/vars/main.ymlsits abovehost_vars. A value you put here cannot be overridden from inventory. Reserve it for things the role needs to stay internally consistent, such as a package name that has to match a service name.- A role parameter passed at the call site beats
vars/main.yml, and-eon the command line beats everything, including role parameters.
You can watch this resolve in about a minute. Give a small role one default and one role var, then set the same names in 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-cliThe first run prints tunable=from-hostvars internal=from-rolevars. Inventory beat the role default and lost to the role var. The second run prints internal=from-cli, because extra vars sit at the very top and nothing below can push back. That is also why -e is fine for a one-off run and wrong in a script you keep: it silently outranks every considered decision in your repository.
The working rule: if you want a value to be settable, it goes in defaults/. Putting it in vars/ tells every future user of the role that inventory may not change it. That is occasionally what you meant, and usually an accident.
พิสูจน์ว่า role มีคุณสมบัติ 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 หมายความว่าทุก module ได้ตรวจสอบสถานะปัจจุบันแล้วพบว่างานเสร็จสิ้นไปแล้ว หากพบ changed=2 ในการรันครั้งที่สอง แสดงว่ามีสอง task ที่ไม่สามารถแยกแยะความแตกต่างได้ จึงพยายามเขียนไฟล์และรีสตาร์ท service ซ้ำไปเรื่อยๆ สาเหตุที่พบบ่อยคือการใช้ 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 จะมีหนึ่งบรรทัด ในการรันครั้งที่สอง task ที่มีการป้องกันไว้จะไม่ทำงานเลย และผลลัพธ์จะแสดงข้อความ skipped, since /tmp/guarded.txt exists เนื่องจาก creates ทำให้ module มีผลลัพธ์ที่มองเห็นได้ให้ตรวจสอบก่อน หากคำสั่งไม่มีผลลัพธ์ดังกล่าว ให้เก็บ output ไว้ใน register แล้วตัดสินใจด้วยตนเองโดยใช้ changed_when
ansible-playbook --check --diff site.yml ใช้คาดการณ์การเปลี่ยนแปลงโดยไม่ลงมือทำจริง และ --diff จะแสดงบรรทัดที่ template จะเขียนทับจริง ให้อ่านผลลัพธ์โดยคำนึงถึงข้อควรระวังหนึ่งประการคือ task ประเภท shell และ command จะถูกข้ามไปในโหมด check ดังนั้นแผนที่ดูสะอาดตาอาจยังมีการทำงานแฝงอยู่
อีกหนึ่งคอลัมน์ในสรุปผลที่ควรให้ความสำคัญไม่แพ้กันคือ โฮสต์ที่ Ansible ไม่สามารถเชื่อมต่อได้จะถูกนับรวมใน unreachable แทนที่จะเป็น failed และไม่มี task ใดของโฮสต์นั้นทำงานเลย ดังนั้น ตัดสินใจล่วงหน้าว่าโฮสต์ที่ไม่สามารถเข้าถึงได้ควรทำให้การรันทั้งหมดหยุดลงหรือไม่ ก่อนที่คุณจะนำ role นี้ไปใช้กับเครื่องจำนวนมาก
เหตุใด 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 จะแสดงรายการ path ที่ได้พยายามค้นหาให้ทราบ คุณควรเก็บไฟล์ทั้งสองไว้ในไดเรกทอรีเดียวกัน การสั่งรันจากไดเรกทอรีแม่สามารถทำได้เนื่องจาก Ansible ยึดตาม path ของ playbook เป็นหลัก:
ansible-playbook -i infra/inventory.ini infra/site.ymlปัญหาในลักษณะเดียวกันนี้อาจเกิดขึ้นโดยไม่มีข้อความแจ้งเตือนที่ชัดเจน Ansible จะเพิกเฉยต่อไฟล์ ansible.cfg ในไดเรกทอรีปัจจุบันหากไดเรกทอรีนั้นอนุญาตให้ผู้ใช้ทุกคนเขียนไฟล์ได้ (world writable) เนื่องจากผู้ใช้รายอื่นบนเซิร์ฟเวอร์อาจวางไฟล์ config ที่ไม่พึงประสงค์เพื่อเปลี่ยนพฤติกรรมการทำงานของคำสั่งคุณได้
[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 เพื่อตรวจสอบว่า Ansible โหลดไฟล์ config file ใดมาใช้งานจริง และใช้ ansible-config dump --only-changed เพื่อดูการตั้งค่าทั้งหมดที่แตกต่างไปจากค่าเริ่มต้นของระบบ ให้ตรวจสอบทั้งสองคำสั่งนี้ทุกครั้งที่การรันงานมีพฤติกรรมเสมือนว่าไม่มีการตั้งค่าของคุณอยู่
การแชร์บทบาท: requirements.yml และการระบุเวอร์ชัน
บทบาทที่ผู้อื่นเขียนขึ้นควรใช้วิธีการติดตั้ง ไม่ใช่การคัดลอกไฟล์ ให้ประกาศไว้ในไฟล์เดียว:
# 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 เสมอ หากไม่มีค่านี้ คุณจะได้รับเวอร์ชันล่าสุดจาก branch เริ่มต้น ณ วันที่รันคำสั่ง ซึ่งจะส่งผลให้การปรับใช้ (deployment) ที่เคยทำงานได้เมื่อเดือนก่อนเกิดข้อผิดพลาดขึ้นได้แม้คุณจะไม่ได้แก้ไขอะไรใน repository ของตนเองเลย ให้ชี้ roles_path ไปยังไดเรกทอรีสำหรับดาวน์โหลด และอย่ารวมไดเรกทอรีดังกล่าวไว้ใน git:
# ansible.cfg
[defaults]
inventory = inventory.ini
roles_path = ./galaxy_rolesบทบาทที่อยู่ใน roles/ ซึ่งอยู่ถัดจาก playbook จะยังคงถูกค้นพบ เนื่องจากเส้นทางดังกล่าวจะถูกค้นหาเพิ่มเติมจาก roles_path เสมอ ดังนั้นบทบาทที่คุณเขียนเองจะยังคงถูก commit และตรวจสอบได้ ในขณะที่บทบาทจากบุคคลที่สามจะเป็นการดาวน์โหลดที่ทำซ้ำได้และถูกตรึงไว้ที่ tag ที่กำหนด
จุดที่ Role ไม่ใช่คำตอบอีกต่อไป
Role คือหน่วยสำหรับการนำกลับมาใช้ใหม่ภายใน Ansible run หนึ่งครั้ง Role ไม่สามารถสร้างเซิร์ฟเวอร์หรือระเบียน DNS ที่ผู้ให้บริการของคุณได้ และการพยายามทำให้ Role ทำหน้าที่ดังกล่าวจะทำให้ Playbook กลายเป็นสิ่งที่ไม่มีใครอยากดูแลรักษา การอ่านเรื่อง การแบ่งงานระหว่าง Ansible และ Terraform เป็นสิ่งที่ควรทำก่อนเริ่มต้น นอกจากนี้ Role ยังไม่สามารถทดแทนการออกแบบ Inventory ได้ เมื่อคุณมีเครื่องจำนวนมาก วิธีการจัดกลุ่มและเข้าถึงเซิร์ฟเวอร์เหล่านั้น จะมีความสำคัญมากกว่าวิธีการจัดเก็บไฟล์ Task
การทำ Hardening ที่ Role common นี้ติดตั้งให้ ก็เป็นสิ่งที่ต้องตัดสินใจด้วยตนเองเช่นกัน การตั้งค่าข้างต้นกำหนด Directive ไว้เพียงสองรายการเท่านั้น ดังนั้นโปรดอ่าน การตั้งค่า SSH ใดที่ควรเปลี่ยนจริง และ วิธีการตั้งค่าให้ Ubuntu อัปเดตความปลอดภัยด้วยตนเอง ก่อนที่คุณจะตัดสินใจว่าสิ่งใดควรอยู่ใน Role สำหรับทุกโฮสต์ที่คุณเป็นเจ้าของ
FAQ
เมื่อใดที่ควรเปลี่ยน Ansible playbook ให้เป็น role?
เมื่อชุดของ task เดิมต้องถูกนำไปรันใน play ที่สอง หรือรันกับกลุ่ม host อื่น การคัดลอก task ไปมาระหว่าง playbook เป็นสัญญาณเตือน เพราะนับจากจุดนั้น ทุกการแก้ไขจะต้องทำซ้ำสองครั้ง และวันหนึ่งคุณจะลืมแก้ไขจุดใดจุดหนึ่งไป หาก playbook มีความยาวไม่เกิน 100 บรรทัดและรันกับกลุ่ม host เดียวเสมอ การทำเป็น role จะไม่ช่วยให้ได้ประโยชน์เพิ่มขึ้น แถมโครงสร้างไดเรกทอรีที่เพิ่มเข้ามายังทำให้การอ่านทำความเข้าใจยากขึ้นด้วย
role ทำงานก่อน task อื่นๆ ใน play เดียวกันหรือไม่?
ใช่ Ansible จะรัน pre_tasks ก่อน จากนั้นจึงรันทุกอย่างที่ระบุไว้ภายใต้ roles:, tasks: และ post_tasks: ตามลำดับ โดย Ansible จะไม่สนใจลำดับที่คีย์เหล่านี้ปรากฏในไฟล์ของคุณ การเขียน tasks: ไว้เหนือ roles: ไม่ได้ทำให้ task เหล่านั้นทำงานก่อน หากมีสิ่งใดที่จำเป็นต้องเกิดขึ้นก่อน role ให้ใส่ไว้ใน pre_tasks:
ทำไมค่าใน group_vars ถึงไม่ override ค่าใน role?
ให้ตรวจสอบว่าตัวแปรนั้นถูกตั้งค่าไว้ใน vars/main.yml ของ role แทนที่จะเป็น defaults/main.yml หรือไม่ เนื่องจาก vars/ มีลำดับความสำคัญสูงกว่า group_vars และ host_vars ในลำดับการประมวลผลของ Ansible ทำให้ inventory ไม่สามารถ override ค่าได้ ให้ย้ายตัวแปรไปไว้ที่ defaults/main.yml ซึ่งอยู่เกือบล่างสุดของลำดับความสำคัญ และเป็นตำแหน่งที่ถูกต้องสำหรับค่าที่ผู้เรียกใช้ควรจะสามารถปรับเปลี่ยนได้ หากต้องการยืนยันว่าปัญหาเกิดจากลำดับความสำคัญไม่ใช่การพิมพ์ผิด ให้รันด้วย -e name=value ซึ่งมีลำดับความสำคัญสูงสุดเหนือแหล่งข้อมูลอื่นทั้งหมด
ทำไม Ansible ถึงแจ้งว่าไม่พบ role?
Ansible จะเริ่มค้นหาจากตำแหน่งที่อยู่ข้างไฟล์ playbook ดังนั้น site.yml และ roles/ จะต้องอยู่ในไดเรกทอรีเดียวกัน ข้อความแสดงข้อผิดพลาดจะระบุ path ที่ได้พยายามค้นหาไว้ เช่น the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely การรัน playbook จากไดเรกทอรีแม่สามารถทำได้ปกติ เพราะการค้นหาจะอ้างอิงตาม path ของ playbook ไม่ใช่ working directory ของ shell หากคุณพึ่งพา roles_path จาก ansible.cfg ให้ตรวจสอบว่าไฟล์นั้นถูกโหลดด้วย ansible --version แล้ว เนื่องจาก Ansible จะเพิกเฉยต่อไฟล์หาก working directory นั้นอนุญาตให้ทุกคนเขียนได้ (world writable)
จำเป็นต้องใช้ ansible-galaxy init เพื่อสร้าง role หรือไม่?
ไม่จำเป็น 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 ที่มีการทำงานจริง