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

