Ansible Roles ขั้นสูง: Dependencies, Defaults และ Galaxy Integration

Ansible Role เป็นวิธีจัดระเบียบ Playbook ให้ reusable และ shareable — แต่ Role ที่ดีจริง ๆ ต้องเข้าใจการจัดการ dependencies ระหว่าง roles, ความแตกต่างระหว่าง defaults และ vars และการใช้ประโยชน์จาก Ansible Galaxy เพื่อดึง community roles มาใช้แทนการเขียนเอง

บทความนี้ครอบคลุม role dependencies ใน meta/main.yml, การใช้ defaults/main.yml สำหรับ overridable variables, การติดตั้งและใช้ roles จาก Ansible Galaxy, การจัดการ requirements ด้วย requirements.yml และ pattern สำหรับ production-ready role

Role Dependencies — กำหนด roles ที่ต้องการก่อน

Role dependencies กำหนดใน meta/main.yml — เมื่อ role A มี dependency ต่อ role B, Ansible จะรัน role B อัตโนมัติก่อน role A ทุกครั้ง ทำให้ไม่ต้องระบุ role B ใน Playbook เอง

# roles/myapp/meta/main.yml
---
galaxy_info:
  author: myteam
  description: Deploy MyApp web application
  license: MIT
  min_ansible_version: "2.9"
  platforms:
    - name: Ubuntu
      versions:
        - focal
        - jammy
    - name: EL
      versions:
        - "8"
        - "9"

dependencies:
  # role นี้ต้องการ nginx role ก่อนเสมอ
  - role: geerlingguy.nginx
    vars:
      nginx_vhosts:
        - listen: "80"
          server_name: "{{ app_domain }}"
          root: "/opt/{{ app_name }}/current/public"

  # ต้องการ postgresql role
  - role: geerlingguy.postgresql
    vars:
      postgresql_databases:
        - name: "{{ app_db_name }}"
      postgresql_users:
        - name: "{{ app_db_user }}"
          password: "{{ app_db_password }}"

  # ต้องการ common role ของทีม (local role)
  - role: common
    vars:
      common_packages:
        - curl
        - git
        - unzip

defaults/main.yml — Variables ที่ Override ได้

defaults/main.yml เก็บ variables ที่มี priority ต่ำที่สุด — ผู้ใช้ role สามารถ override ค่าเหล่านี้ได้จาก inventory, group_vars, host_vars หรือ Playbook vars โดยตรง ทำให้ role ยืดหยุ่นและ reusable ข้าม project

# roles/myapp/defaults/main.yml
---
# Application settings (ทุกค่าที่ user ควรปรับได้ตาม environment)
app_name: myapp
app_port: 8080
app_user: appuser
app_group: appuser
app_install_dir: "/opt/{{ app_name }}"
app_log_dir: "/var/log/{{ app_name }}"
app_config_dir: "/etc/{{ app_name }}"

# Database defaults
app_db_host: localhost
app_db_port: 5432
app_db_name: "{{ app_name }}_db"
app_db_user: "{{ app_name }}_user"

# Feature flags (ปิดเป็น default — เปิดเมื่อต้องการ)
app_enable_ssl: false
app_enable_metrics: false
app_enable_cache: true

# Performance tuning defaults
app_workers: 4
app_max_connections: 100
app_timeout: 30

# Deployment settings
app_version: latest
app_repo: ""   # บังคับให้ user ตั้งค่า — ไม่มี default ที่สมเหตุสมผล

เปรียบเทียบกับ vars/main.yml ที่มี priority สูงและไม่ควร override — ใช้สำหรับ internal constants ที่ role ต้องการ:

# roles/myapp/vars/main.yml
---
# Internal constants — ไม่ควร override จากภายนอก
_app_service_name: "{{ app_name }}"
_app_pid_file: "/var/run/{{ app_name }}.pid"
_app_socket: "/var/run/{{ app_name }}.sock"

# OS-specific paths (ตั้งตาม ansible_os_family)
_app_config_paths:
  Debian: /etc/{{ app_name }}
  RedHat: /etc/{{ app_name }}

# Role version tracking
_role_version: "1.2.0"

Ansible Galaxy — ติดตั้งและใช้ Community Roles

Ansible Galaxy เป็น repository สาธารณะสำหรับ roles ที่ community เขียนไว้ — ใช้คำสั่ง ansible-galaxy install ติดตั้ง role ที่ต้องการ หรือใช้ requirements.yml เพื่อ declare dependencies ทั้งหมดในครั้งเดียว

# ค้นหาและติดตั้ง role เดี่ยว
ansible-galaxy install geerlingguy.nginx

# ติดตั้งหลาย roles พร้อมกัน
ansible-galaxy install geerlingguy.nginx geerlingguy.postgresql geerlingguy.redis

# ติดตั้ง version เฉพาะ
ansible-galaxy install geerlingguy.nginx,3.2.0

# ดู roles ที่ติดตั้งแล้ว
ansible-galaxy list

# ลบ role
ansible-galaxy remove geerlingguy.nginx

# ค้นหา role บน Galaxy
ansible-galaxy search nginx --author geerlingguy

requirements.yml — จัดการ Galaxy Dependencies

requirements.yml เก็บรายการ roles และ collections ที่ project ต้องการ — เหมาะสำหรับ version pinning และ reproducible deployments โดย commit ไฟล์นี้ไว้ใน git แล้วรัน ansible-galaxy install -r requirements.yml

# requirements.yml
---
roles:
  # ติดตั้งจาก Galaxy ด้วย version เฉพาะ
  - name: geerlingguy.nginx
    version: "3.2.0"

  - name: geerlingguy.postgresql
    version: "3.4.0"

  - name: geerlingguy.redis
    version: "1.9.0"

  # ติดตั้งจาก GitHub repo โดยตรง
  - name: myteam.common
    src: https://github.com/myteam/ansible-role-common
    version: main
    scm: git

  # ติดตั้งจาก local path (สำหรับ private roles)
  - name: internal_security
    src: /opt/ansible-roles/security

collections:
  # ติดตั้ง Ansible Collections
  - name: community.general
    version: ">=6.0.0"

  - name: ansible.posix
    version: "1.5.4"

  - name: community.postgresql
    version: "2.3.0"
# ติดตั้งทุก dependencies จาก requirements.yml
ansible-galaxy install -r requirements.yml

# ติดตั้งลงใน path เฉพาะ (สำหรับ project-level roles)
ansible-galaxy install -r requirements.yml -p roles/

# Force re-install แม้มีอยู่แล้ว
ansible-galaxy install -r requirements.yml --force

# ติดตั้ง collections แยก
ansible-galaxy collection install -r requirements.yml

ใช้ Galaxy Roles ใน Playbook

หลังติดตั้ง Galaxy roles แล้ว ใช้ได้เหมือน local roles — สามารถ override defaults ของ Galaxy role ด้วย vars ใน Playbook หรือ group_vars เพื่อปรับให้ตรงกับ environment ของตัวเอง

---
- name: Deploy web application stack
  hosts: appservers
  become: true
  vars:
    # Override defaults ของ geerlingguy.nginx
    nginx_worker_processes: "auto"
    nginx_worker_connections: "1024"
    nginx_vhosts:
      - listen: "80"
        server_name: "mysite.example.com"
        root: "/var/www/mysite"
        index: "index.php index.html"
        extra_parameters: |
          location ~ \.php$ \{
              fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
          \}

    # Override defaults ของ geerlingguy.postgresql
    postgresql_version: "14"
    postgresql_databases:
      - name: myapp_db
        encoding: UTF-8
    postgresql_users:
      - name: myapp_user
        password: "{{ vault_db_password }}"
        db: myapp_db
        priv: "ALL"

    # Override defaults ของ geerlingguy.redis
    redis_bind_interface: "127.0.0.1"
    redis_maxmemory: "512mb"
    redis_maxmemory_policy: "allkeys-lru"

  roles:
    - role: geerlingguy.postgresql
    - role: geerlingguy.redis
    - role: geerlingguy.nginx
    - role: myapp    # local role ที่มี dependencies ข้างต้น

Pattern: Production-Ready Role พร้อม Dependencies และ Galaxy

ตัวอย่าง role structure ที่พร้อมใช้ใน production — มี defaults ครบถ้วน, meta/main.yml ระบุ dependencies และ platform support, พร้อม requirements.yml สำหรับ Galaxy dependencies

# โครงสร้าง role สำหรับ Django application
roles/
  django_app/
    defaults/
      main.yml          # overridable settings ทั้งหมด
    vars/
      main.yml          # internal constants
    tasks/
      main.yml          # task entry point
      install.yml       # ติดตั้ง packages
      configure.yml     # deploy config files
      service.yml       # manage systemd service
    handlers/
      main.yml          # reload/restart handlers
    templates/
      gunicorn.service.j2
      django_settings.py.j2
      nginx_site.conf.j2
    meta/
      main.yml          # dependencies และ galaxy_info
    molecule/           # tests (ถ้าใช้ Molecule)
      default/
        converge.yml
        verify.yml
# roles/django_app/meta/main.yml
---
galaxy_info:
  author: myteam
  description: Deploy Django application with Gunicorn
  license: MIT
  min_ansible_version: "2.12"
  platforms:
    - name: Ubuntu
      versions: [focal, jammy]
    - name: EL
      versions: ["8", "9"]
  galaxy_tags:
    - django
    - python
    - web

dependencies:
  - role: geerlingguy.nginx
    vars:
      nginx_vhosts:
        - listen: "80"
          server_name: "{{ django_domain }}"
          extra_parameters: |
            location / \{
                proxy_pass http://127.0.0.1:{{ django_port }};
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
            \}
            location /static/ \{
                alias {{ django_static_root }}/;
            \}

  - role: geerlingguy.postgresql
    vars:
      postgresql_databases:
        - name: "{{ django_db_name }}"
      postgresql_users:
        - name: "{{ django_db_user }}"
          password: "{{ django_db_password }}"
          db: "{{ django_db_name }}"
          priv: ALL
# roles/django_app/defaults/main.yml
---
# Application identity
django_app_name: django_app
django_domain: localhost
django_port: 8000

# Paths
django_install_dir: "/opt/{{ django_app_name }}"
django_static_root: "{{ django_install_dir }}/staticfiles"
django_media_root: "{{ django_install_dir }}/media"
django_venv_path: "{{ django_install_dir }}/venv"

# Database
django_db_name: "{{ django_app_name }}_db"
django_db_user: "{{ django_app_name }}_user"
django_db_password: ""    # ต้อง set ใน vault

# Python / Gunicorn
django_python_version: "3.11"
django_gunicorn_workers: "{{ ansible_processor_vcpus * 2 + 1 }}"
django_gunicorn_timeout: 30

# Django settings
django_debug: false
django_allowed_hosts:
  - "{{ django_domain }}"
django_secret_key: ""     # ต้อง set ใน vault

สรุป

Role ที่ดีต้องมีทั้ง 3 องค์ประกอบ: meta/main.yml สำหรับ declare dependencies ที่ต้องรันก่อน, defaults/main.yml สำหรับ variables ที่ผู้ใช้ override ได้, และการใช้ Galaxy roles แทนการเขียนซ้ำในส่วนที่มี community role ที่ดีแล้ว

Pattern ที่ควรจำ: ใส่ทุก variable ที่ user ควรปรับได้ใน defaults/main.yml, ใช้ vars/main.yml สำหรับ internal constants เท่านั้น, pin version ของ Galaxy roles ใน requirements.yml เสมอเพื่อ reproducible deployments และ commit ไฟล์ requirements.yml ไว้ใน git ร่วมกับ Playbook