Ansible Role Dependencies: ใช้ Role ซ้อน Role

Ansible Role Dependencies ช่วยให้ role หนึ่งสามารถกำหนดว่าต้องการ role อื่นทำงานก่อนโดยอัตโนมัติ — แทนที่จะ list roles ทุกตัวใน Playbook เอง role ที่มี dependency จะดึง role ที่จำเป็นมารันให้เองผ่าน meta/main.yml เทคนิคนี้ทำให้สร้าง role architecture แบบ layered ได้ โดยแต่ละ layer ทำหน้าที่เฉพาะและ reuse กันได้

บทความนี้ครอบคลุม role dependency ใน meta/main.yml, การส่ง variables ไปยัง dependency roles, allow_duplicates, การใช้ include_role สำหรับ dynamic dependencies, dependency chain หลายชั้น และ pattern สำหรับ layered role architecture

Role Dependency พื้นฐาน — meta/main.yml

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

# roles/webapp/meta/main.yml
---
galaxy_info:
  author: myteam
  description: Deploy web application
  license: MIT
  min_ansible_version: "2.12"
  platforms:
    - name: Ubuntu
      versions: [focal, jammy]

dependencies:
  # Ansible รัน dependency roles เหล่านี้ก่อน role webapp เสมอ
  - role: common          # local role — setup OS baseline
  - role: geerlingguy.nginx   # Galaxy role — install nginx
  - role: geerlingguy.postgresql  # Galaxy role — install postgresql
# Playbook ที่ใช้ role webapp — ไม่ต้องระบุ common, nginx, postgresql
---
- name: Deploy webapp
  hosts: appservers
  become: true
  roles:
    - role: webapp   # Ansible รัน common → nginx → postgresql → webapp อัตโนมัติ

ลำดับการรัน: Ansible จะ resolve dependency tree ก่อน แล้วรัน roles ตามลำดับ dependency ก่อนเสมอ — ถ้า role C depend on B และ B depend on A ลำดับจะเป็น A → B → C

ส่ง Variables ไปยัง Dependency Role

กำหนด vars ใน dependency entry เพื่อ override defaults ของ role นั้น — ทำให้ parent role ควบคุม configuration ของ dependency ได้โดยตรง

# roles/django_app/meta/main.yml
---
dependencies:
  # ส่ง nginx config สำหรับ Django reverse proxy
  - role: geerlingguy.nginx
    vars:
      nginx_worker_processes: auto
      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-Forwarded-For $proxy_add_x_forwarded_for;
            }
            location /static/ {
                alias {{ django_static_root }}/;
                expires 30d;
            }

  # ส่ง postgresql config สำหรับ Django database
  - role: geerlingguy.postgresql
    vars:
      postgresql_version: "15"
      postgresql_databases:
        - name: "{{ django_db_name }}"
          encoding: UTF-8
      postgresql_users:
        - name: "{{ django_db_user }}"
          password: "{{ django_db_password }}"
          db: "{{ django_db_name }}"
          priv: ALL
          role_attr_flags: "NOSUPERUSER,NOCREATEDB"

  # ส่ง redis config สำหรับ Django caching
  - role: geerlingguy.redis
    vars:
      redis_bind_interface: "127.0.0.1"
      redis_maxmemory: "{{ django_cache_maxmemory | default('256mb') }}"
      redis_maxmemory_policy: allkeys-lru

allow_duplicates — รัน Role เดิมหลายครั้ง

ตามค่า default Ansible จะรัน role เดิมเพียงครั้งเดียวต่อ play แม้จะมีหลาย roles depend on role เดียวกัน — ถ้าต้องการให้ role รันซ้ำด้วย parameters ต่างกัน ใช้ allow_duplicates: true ใน meta/main.yml ของ role นั้น

# roles/create_vhost/meta/main.yml — role ที่อนุญาตให้รันซ้ำ
---
galaxy_info:
  author: myteam
  description: Create a single nginx virtual host
allow_duplicates: true   # อนุญาตให้รันหลายครั้งด้วย params ต่างกัน
dependencies: []
# Playbook — รัน create_vhost หลายครั้งสำหรับ vhosts ต่างกัน
---
- name: Setup multiple virtual hosts
  hosts: webservers
  become: true
  roles:
    - role: create_vhost
      vars:
        vhost_name: api
        vhost_domain: api.example.com
        vhost_port: 8080

    - role: create_vhost    # รันซ้ำได้เพราะ allow_duplicates: true
      vars:
        vhost_name: admin
        vhost_domain: admin.example.com
        vhost_port: 8081

    - role: create_vhost
      vars:
        vhost_name: static
        vhost_domain: static.example.com
        vhost_port: 8082

include_role — Dynamic Dependency ใน Tasks

include_role ใน tasks ช่วยให้เรียก role แบบ dynamic ได้ตาม condition — ต่างจาก meta/main.yml dependencies ที่รันเสมอ, include_role รองรับ when, loop และ dynamic role names

# roles/webapp/tasks/configure.yml
---
# เรียก role ตาม condition
- name: Configure SSL if enabled
  include_role:
    name: ssl_cert
  vars:
    ssl_domain: "{{ webapp_domain }}"
    ssl_email: "{{ webapp_admin_email }}"
  when: webapp_enable_ssl | bool

# เรียก role สำหรับ monitoring ถ้า enabled
- name: Setup monitoring agent
  include_role:
    name: "{{ webapp_monitoring_agent }}"   # dynamic role name
  vars:
    monitoring_tags:
      - webapp
      - "{{ webapp_name }}"
  when: webapp_enable_monitoring | bool

# เรียก role หลายครั้งด้วย loop
- name: Create database schemas
  include_role:
    name: create_schema
  vars:
    schema_name: "{{ item.name }}"
    schema_owner: "{{ item.owner }}"
  loop: "{{ webapp_db_schemas }}"
  when: webapp_db_schemas is defined

import_role — Static Role ใน Tasks

import_role ต่างจาก include_role ตรงที่ Ansible parse ไว้ตั้งแต่ต้น (static) — ทำให้รองรับ tags และ handlers ของ role นั้น แต่ไม่รองรับ dynamic variables ใน role name

# roles/full_stack/tasks/main.yml
---
# import_role — static, รองรับ tags ของ role ที่เรียก
- name: Setup database layer
  import_role:
    name: postgresql_server
  vars:
    pg_version: "15"
    pg_data_dir: /var/lib/postgresql/15/main

- name: Setup cache layer
  import_role:
    name: redis_server
  vars:
    redis_port: 6379
    redis_maxmemory: 512mb

# include_role — dynamic, รองรับ when/loop
- name: Setup app layer
  include_role:
    name: "{{ item.role }}"
  vars:
    app_port: "{{ item.port }}"
  loop:
    - { role: api_service,    port: 8080 }
    - { role: worker_service, port: 8081 }
    - { role: scheduler,      port: 8082 }

Dependency Chain — Role ซ้อน Role หลายชั้น

Ansible resolve dependency chain โดยอัตโนมัติ — ถ้า role C depend on B และ B depend on A, Ansible รัน A → B → C เสมอ แม้จะเรียกแค่ role C ใน Playbook

# Layer 1: roles/base_os/meta/main.yml — ไม่มี dependencies
---
galaxy_info:
  description: OS baseline setup
dependencies: []
# Layer 2: roles/web_server/meta/main.yml — depend on base_os
---
galaxy_info:
  description: Web server setup
dependencies:
  - role: base_os
    vars:
      base_packages:
        - curl
        - openssl
        - ca-certificates
# Layer 3: roles/app_platform/meta/main.yml — depend on web_server
---
galaxy_info:
  description: Application platform (Python/Node runtime)
dependencies:
  - role: web_server
    vars:
      web_server_type: nginx
      web_server_version: "1.24"
# Layer 4: roles/my_app/meta/main.yml — depend on app_platform
---
galaxy_info:
  description: My specific application
dependencies:
  - role: app_platform
    vars:
      platform_runtime: python
      platform_version: "3.11"

# Playbook ที่ใช้แค่ my_app — Ansible รัน chain อัตโนมัติ:
# base_os → web_server → app_platform → my_app

Pattern: Layered Role Architecture สำหรับ Multi-Tier Application

ตัวอย่าง architecture ของ role ที่ออกแบบเป็น layers ชัดเจน — แต่ละ layer reusable ข้าม project และ override ได้ด้วย variables

# โครงสร้าง role layers สำหรับ Django application stack

roles/
  # Layer 1: Infrastructure baseline
  common/
    defaults/main.yml     # packages, users, timezone, etc.
    tasks/main.yml
    meta/main.yml         # dependencies: []

  # Layer 2: Database server
  postgresql/
    defaults/main.yml     # pg_version, pg_port, pg_data_dir
    tasks/main.yml
    meta/main.yml         # dependencies: [common]

  # Layer 3: Web server
  nginx/
    defaults/main.yml     # nginx_version, worker settings
    tasks/main.yml
    meta/main.yml         # dependencies: [common]

  # Layer 4: Application platform
  python_app/
    defaults/main.yml     # python_version, venv settings
    tasks/main.yml
    meta/main.yml         # dependencies: [common]

  # Layer 5: Specific application (depends on layers 2-4)
  django_blog/
    defaults/main.yml     # app-specific settings
    tasks/main.yml
    meta/main.yml         # dependencies: [postgresql, nginx, python_app]
# roles/django_blog/meta/main.yml — complete dependency definition
---
galaxy_info:
  author: myteam
  description: Deploy Django blog application
  min_ansible_version: "2.12"
  platforms:
    - name: Ubuntu
      versions: [focal, jammy]

dependencies:
  - role: postgresql
    vars:
      pg_version: "15"
      pg_databases:
        - name: "{{ blog_db_name }}"
      pg_users:
        - name: "{{ blog_db_user }}"
          password: "{{ blog_db_password }}"
          db: "{{ blog_db_name }}"
          priv: ALL

  - role: nginx
    vars:
      nginx_vhosts:
        - server_name: "{{ blog_domain }}"
          root: "{{ blog_static_root }}"
          extra_parameters: |
            location / {
                proxy_pass http://unix:{{ blog_socket }};
                proxy_set_header Host $host;
            }

  - role: python_app
    vars:
      python_version: "3.11"
      python_venv_path: "{{ blog_venv_path }}"
      python_requirements: "{{ blog_install_dir }}/requirements.txt"
# roles/django_blog/defaults/main.yml
---
blog_name: myblog
blog_domain: blog.example.com
blog_port: 8000

blog_install_dir: "/opt/{{ blog_name }}"
blog_static_root: "{{ blog_install_dir }}/staticfiles"
blog_venv_path: "{{ blog_install_dir }}/venv"
blog_socket: "/var/run/{{ blog_name }}/gunicorn.sock"

blog_db_name: "{{ blog_name }}_db"
blog_db_user: "{{ blog_name }}_user"
blog_db_password: ""   # set ใน vault

blog_gunicorn_workers: "{{ ansible_processor_vcpus * 2 + 1 }}"
blog_debug: false
# Playbook — เรียกแค่ django_blog, Ansible handle dependency chain เอง
---
- name: Deploy Django blog
  hosts: blogservers
  become: true
  vars:
    blog_domain: myblog.example.com
    blog_db_password: "{{ vault_blog_db_password }}"
    blog_debug: false

  roles:
    - role: django_blog
    # Ansible รัน: postgresql → nginx → python_app → django_blog
    # common รันก่อนทุก role เพราะ postgresql/nginx/python_app ต่างก็ depend on common

สรุป

Role dependencies ใน meta/main.yml ช่วยให้สร้าง self-contained roles ที่จัดการ dependencies เองได้ — Ansible resolve chain อัตโนมัติทำให้ Playbook กระชับและไม่ต้องระบุทุก role เอง การออกแบบเป็น layers ทำให้แต่ละ role reusable และ test แยกกันได้

Pattern ที่ควรจำ: ใช้ meta/main.yml dependencies สำหรับ static dependencies ที่รันทุกครั้ง, ใช้ include_role สำหรับ dynamic dependencies ที่รันตาม condition, ใช้ allow_duplicates: true เมื่อต้องการรัน role เดิมหลายครั้งด้วย parameters ต่างกัน และออกแบบ role layers จาก infrastructure baseline ขึ้นไปสู่ application layer เพื่อ reuse สูงสุด