Ansible Role Variables: defaults/main.yml, vars/main.yml อธิบายแตกต่างกัน

Ansible มีระบบ variable precedence (ลำดับความสำคัญของ variables) ที่ซับซ้อน — ใน Ansible Role มีไฟล์ variable หลัก 2 ไฟล์คือ defaults/main.yml และ vars/main.yml ซึ่งมีความแตกต่างกันอย่างชัดเจนทั้งในด้าน priority, วัตถุประสงค์ และวิธีใช้งาน การเข้าใจความแตกต่างนี้ทำให้ออกแบบ role ที่ยืดหยุ่นและนำกลับมาใช้ซ้ำได้อย่างถูกต้อง

บทความนี้ครอบคลุมลำดับ variable precedence ทั้งหมดของ Ansible, บทบาทของ defaults/main.yml และ vars/main.yml, ความแตกต่างและเมื่อควรใช้แต่ละไฟล์, การ override variables จาก inventory และ playbook และ pattern สำหรับออกแบบ variable architecture ที่ดีใน role

Ansible Variable Precedence — ลำดับความสำคัญ

Ansible มีแหล่ง variable มากกว่า 20 แหล่ง โดยมี precedence จากต่ำสุดไปสูงสุด — variable ที่มี priority สูงกว่าจะ override ค่าจากแหล่งที่มี priority ต่ำกว่าเสมอ ต่อไปนี้คือลำดับที่สำคัญที่สุดในการทำงานกับ roles:

# Variable Precedence (จากต่ำสุดไปสูงสุด)
# ต่ำสุด ─────────────────────────────────── สูงสุด

1.  role defaults          ← defaults/main.yml     (override ได้ง่ายที่สุด)
2.  inventory vars         ← [webservers] vars
3.  inventory group_vars   ← group_vars/all.yml
4.  inventory host_vars    ← host_vars/server1.yml
5.  playbook group_vars    ← group_vars/ ใน playbook dir
6.  playbook host_vars     ← host_vars/ ใน playbook dir
7.  host facts             ← ansible_os_family, ansible_hostname
8.  play vars              ← vars: section ใน playbook
9.  play vars_prompt       ← vars_prompt:
10. play vars_files        ← vars_files:
11. role vars              ← vars/main.yml           (override ยาก)
12. block vars             ← vars: ใน block
13. task vars              ← vars: ใน task
14. include_vars           ← include_vars module
15. set_facts / registered ← set_fact, register
16. role params            ← role: vars: ใน playbook
17. extra vars             ← -e "key=value"          (override ได้เสมอ)

จากลำดับข้างต้น defaults/main.yml อยู่ที่ลำดับ 1 (ต่ำสุด) ในขณะที่ vars/main.yml อยู่ที่ลำดับ 11 — ช่องว่างระหว่าง 1 ถึง 11 นี้คือ inventory, group_vars, host_vars และ play vars ทั้งหมดที่สามารถ override defaults ได้ แต่ไม่สามารถ override vars ได้

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

defaults/main.yml เก็บ default values ที่ผู้ใช้ role ควรปรับได้ตาม environment — เมื่อไม่มีการ override ค่าเหล่านี้จะถูกใช้เป็น fallback ที่สมเหตุสมผล ทำให้ role ทำงานได้ทันทีโดยไม่ต้อง configure ทุกอย่าง

# roles/webapp/defaults/main.yml
---
# === Application Identity ===
webapp_name: webapp
webapp_version: latest
webapp_user: webapp
webapp_group: webapp

# === Paths (ปรับตาม convention ของแต่ละทีม) ===
webapp_install_dir: "/opt/{{ webapp_name }}"
webapp_config_dir: "/etc/{{ webapp_name }}"
webapp_log_dir: "/var/log/{{ webapp_name }}"
webapp_data_dir: "/var/lib/{{ webapp_name }}"

# === Network ===
webapp_bind_address: "0.0.0.0"
webapp_port: 8080
webapp_workers: 4

# === Database (ค่า default สำหรับ development) ===
webapp_db_host: localhost
webapp_db_port: 5432
webapp_db_name: "{{ webapp_name }}_db"
webapp_db_user: "{{ webapp_name }}_user"
webapp_db_password: ""   # ต้อง set ใน vault — ไม่มี default ที่ปลอดภัย

# === Feature Flags (ปิดเป็น default — เปิดเมื่อต้องการ) ===
webapp_enable_ssl: false
webapp_enable_metrics: false
webapp_enable_cache: false
webapp_debug_mode: false

# === Resource Limits ===
webapp_max_connections: 100
webapp_request_timeout: 30
webapp_max_upload_size: "10M"

# === Logging ===
webapp_log_level: info
webapp_log_format: json
webapp_log_rotate_size: "100M"
webapp_log_rotate_count: 7

หลักการเลือก variable สำหรับ defaults/main.yml: ใส่ทุก variable ที่ต้องการให้ผู้ใช้ปรับได้ตาม environment โดยตั้งค่า default ที่ทำงานได้ใน development environment แต่อาจต้องปรับสำหรับ production

vars/main.yml — Internal Constants

vars/main.yml เก็บ constants ที่ role ต้องการภายในและไม่ควรถูก override จากภายนอก — เนื่องจาก priority สูงกว่า inventory และ group_vars จึงป้องกัน role logic จากการถูก override โดยไม่ตั้งใจ

# roles/webapp/vars/main.yml
---
# === OS-specific paths (ไม่ควร override — role ใช้ภายใน) ===
_webapp_systemd_dir: /etc/systemd/system
_webapp_pid_file: "/var/run/{{ webapp_name }}/{{ webapp_name }}.pid"
_webapp_socket: "/var/run/{{ webapp_name }}/{{ webapp_name }}.sock"
_webapp_service_file: "{{ _webapp_systemd_dir }}/{{ webapp_name }}.service"

# === Runtime constants ===
_webapp_required_dirs:
  - "{{ webapp_install_dir }}"
  - "{{ webapp_config_dir }}"
  - "{{ webapp_log_dir }}"
  - "{{ webapp_data_dir }}"
  - "/var/run/{{ webapp_name }}"

# === OS-specific package names ===
_webapp_packages_debian:
  - curl
  - libssl-dev
  - libffi-dev

_webapp_packages_redhat:
  - curl
  - openssl-devel
  - libffi-devel

# === Role metadata ===
_webapp_role_version: "2.1.0"
_webapp_config_checksum_file: "{{ webapp_config_dir }}/.config_checksum"

Convention ที่ควรทำ: ตั้งชื่อ variables ใน vars/main.yml ด้วย prefix underscore (_) เพื่อบ่งบอกว่าเป็น internal variable — ทำให้ผู้ใช้ role ทราบว่าไม่ควร override ค่าเหล่านี้

ความแตกต่างหลักระหว่าง defaults และ vars

สรุปความแตกต่างที่สำคัญเพื่อตัดสินใจว่า variable แต่ละตัวควรอยู่ใน defaults หรือ vars:

# เปรียบเทียบ defaults vs vars

# defaults/main.yml
webapp_port: 8080          # ผู้ใช้ override ได้ด้วย group_vars, host_vars, play vars
webapp_workers: 4          # ค่า default สมเหตุสมผล แต่ปรับได้ตาม server spec
webapp_log_level: info     # เปลี่ยนเป็น debug หรือ warning ตาม environment

# vars/main.yml
_webapp_socket: "/var/run/webapp/webapp.sock"   # path fixed — role logic ผูกกับ path นี้
_webapp_packages_debian:                         # package list สำหรับ Debian เท่านั้น
  - curl
  - libssl-dev

# ─────────────────────────────────────────────────────────
# กฎง่าย ๆ:
# 
# defaults ← "ผู้ใช้ควรปรับค่านี้ตาม environment ของตัวเอง"
# vars     ← "role ต้องการค่านี้ภายใน — อย่า override"
# ─────────────────────────────────────────────────────────

# ตัวอย่างที่ผิด: ใส่ config path ใน defaults
# webapp_socket: "/var/run/webapp.sock"    ← ผิด: ถ้า user override แล้ว
#                                             task ที่ hardcode path จะพัง

# ตัวอย่างที่ถูก: ใส่ config path ใน vars
# _webapp_socket: "/var/run/{{ webapp_name }}/{{ webapp_name }}.sock"  ← ถูก

Override defaults จาก Inventory และ Playbook

เนื่องจาก defaults/main.yml มี priority ต่ำสุด จึง override ได้จากทุกแหล่ง — ตัวอย่างการ override สำหรับ environments ต่างกัน:

# group_vars/production.yml — override สำหรับ production
---
webapp_workers: 16
webapp_log_level: warning
webapp_enable_ssl: true
webapp_enable_metrics: true
webapp_debug_mode: false
webapp_max_connections: 500
webapp_request_timeout: 60
# group_vars/staging.yml — override สำหรับ staging
---
webapp_workers: 4
webapp_log_level: debug
webapp_enable_ssl: false
webapp_enable_metrics: true
webapp_debug_mode: true
webapp_max_connections: 50
# host_vars/web01.example.com.yml — override เฉพาะ host นี้
---
webapp_port: 8081           # host นี้ใช้ port ต่างจากปกติ
webapp_workers: 8           # server spec ดีกว่า
webapp_db_host: db01.example.com
webapp_db_password: "{{ vault_web01_db_password }}"
# Playbook-level override (priority สูงกว่า group_vars และ host_vars)
---
- name: Deploy webapp
  hosts: webservers
  vars:
    webapp_version: "v2.5.0"      # กำหนด version ตรง ๆ ใน playbook
    webapp_log_level: warning

  roles:
    - role: webapp
      vars:                       # role-level vars (priority สูงมาก)
        webapp_port: 9090         # override เฉพาะ role instance นี้

OS-Specific Variables — ใช้ include_vars

สำหรับ role ที่ต้องรองรับหลาย OS ใช้ include_vars ใน tasks เพื่อโหลด variable ที่แตกต่างตาม OS แทนที่จะใส่ OS-specific logic ใน defaults/main.yml

# roles/webapp/vars/Debian.yml
---
_webapp_packages:
  - curl
  - libssl-dev
  - libffi-dev
  - python3-venv

_webapp_service_manager: systemd
_webapp_user_shell: /usr/sbin/nologin
# roles/webapp/vars/RedHat.yml
---
_webapp_packages:
  - curl
  - openssl-devel
  - libffi-devel
  - python3

_webapp_service_manager: systemd
_webapp_user_shell: /sbin/nologin
# roles/webapp/tasks/main.yml — โหลด OS vars ก่อน task อื่น
---
- name: Load OS-specific variables
  include_vars: "{{ ansible_os_family }}.yml"
  # โหลด vars/Debian.yml หรือ vars/RedHat.yml โดยอัตโนมัติ

- name: Install packages
  package:
    name: "{{ _webapp_packages }}"   # ใช้ OS-specific list
    state: present

- name: Create webapp user
  user:
    name: "{{ webapp_user }}"
    shell: "{{ _webapp_user_shell }}"  # OS-specific shell path
    system: true

Pattern: Variable Architecture ที่ดีใน Role

ตัวอย่าง role ที่มี variable architecture ถูกต้อง — แสดงการแบ่งแยกระหว่าง user-configurable defaults, internal constants และ OS-specific vars อย่างชัดเจน

# roles/nginx_proxy/defaults/main.yml
# ─── User-configurable variables ───
---
# Upstream application servers (ผู้ใช้ต้องกำหนด)
nginx_proxy_upstream_servers: []
  # ตัวอย่าง:
  # - host: 127.0.0.1
  #   port: 8080
  #   weight: 1

# Virtual host settings
nginx_proxy_server_name: "{{ inventory_hostname }}"
nginx_proxy_listen_port: 80
nginx_proxy_listen_ssl_port: 443
nginx_proxy_enable_ssl: false

# SSL certificate paths (ถ้า enable_ssl = true)
nginx_proxy_ssl_cert: /etc/nginx/ssl/server.crt
nginx_proxy_ssl_key: /etc/nginx/ssl/server.key

# Performance tuning
nginx_proxy_worker_processes: auto
nginx_proxy_worker_connections: 1024
nginx_proxy_keepalive_timeout: 65
nginx_proxy_client_max_body_size: "10m"

# Caching
nginx_proxy_enable_cache: false
nginx_proxy_cache_time: "10m"
nginx_proxy_cache_size: "100m"

# Logging
nginx_proxy_access_log: "/var/log/nginx/{{ nginx_proxy_server_name }}-access.log"
nginx_proxy_error_log: "/var/log/nginx/{{ nginx_proxy_server_name }}-error.log"
nginx_proxy_log_format: combined
# roles/nginx_proxy/vars/main.yml
# ─── Internal constants (ไม่ควร override) ───
---
_nginx_conf_dir: /etc/nginx
_nginx_sites_dir: /etc/nginx/conf.d
_nginx_vhost_file: "{{ _nginx_sites_dir }}/{{ nginx_proxy_server_name }}.conf"
_nginx_ssl_dir: /etc/nginx/ssl
_nginx_cache_dir: /var/cache/nginx
_nginx_pid_file: /var/run/nginx.pid

# Role compatibility
_nginx_proxy_role_version: "1.3.0"
_nginx_proxy_min_ansible: "2.12"
# roles/nginx_proxy/tasks/main.yml
---
- name: Validate required configuration
  assert:
    that:
      - nginx_proxy_upstream_servers | length > 0
    fail_msg: "nginx_proxy_upstream_servers must have at least one server"

- name: Validate SSL config
  assert:
    that:
      - nginx_proxy_ssl_cert is defined
      - nginx_proxy_ssl_key is defined
    fail_msg: "SSL cert and key must be defined when enable_ssl is true"
  when: nginx_proxy_enable_ssl | bool

- import_tasks: install.yml
- import_tasks: configure.yml
- import_tasks: service.yml
# การใช้งานใน Playbook — override เฉพาะที่จำเป็น
---
- name: Configure nginx reverse proxy
  hosts: webservers
  become: true

  vars:
    # Override defaults ตาม environment
    nginx_proxy_server_name: api.example.com
    nginx_proxy_enable_ssl: true
    nginx_proxy_ssl_cert: /etc/letsencrypt/live/api.example.com/fullchain.pem
    nginx_proxy_ssl_key: /etc/letsencrypt/live/api.example.com/privkey.pem
    nginx_proxy_upstream_servers:
      - host: 127.0.0.1
        port: 8080
        weight: 3
      - host: 127.0.0.1
        port: 8081
        weight: 1
    nginx_proxy_client_max_body_size: "50m"

  roles:
    - role: nginx_proxy

สรุป

defaults/main.yml และ vars/main.yml มีจุดประสงค์ต่างกันอย่างสิ้นเชิง — defaults คือ interface ของ role ที่ผู้ใช้ปรับได้จาก inventory, group_vars หรือ playbook vars ในขณะที่ vars คือ internal constants ที่ role ต้องการภายในและไม่ควรถูก override

Pattern ที่ควรจำ: ใส่ทุก variable ที่ user ควรปรับได้ใน defaults/main.yml พร้อม default ที่สมเหตุสมผล, ใส่ path, runtime constants และ OS-specific values ใน vars/main.yml พร้อม underscore prefix, ใช้ include_vars สำหรับ OS-specific packages และ settings, ใช้ assert ตรวจ required variables และ validate ค่าที่ user ต้อง set เองเช่น passwords