Template module ใน Ansible ใช้ Jinja2 engine สร้าง configuration files แบบ dynamic โดยแทรกค่าจาก variables และ facts ลงในไฟล์ต้นแบบ (.j2) ก่อน deploy ไปยัง remote host ทำให้ config file ชุดเดียวรองรับหลาย environment ได้โดยไม่ต้องดูแลหลายไฟล์แยกกัน
บทความนี้อธิบายการใช้ template module ตั้งแต่โครงสร้างไฟล์ .j2 พื้นฐาน, การใช้ conditionals และ loops ใน template, การจัดการ whitespace, ไปจนถึง pattern ที่ใช้บ่อยในทางปฏิบัติสำหรับ nginx, systemd และ app config
ใช้งาน Template Module
Module template ทำงานคล้าย copy แต่ประมวลผล Jinja2 expressions ในไฟล์ต้นแบบก่อนส่งไปยัง host
---
- name: Deploy config with template
hosts: webservers
vars:
server_name: "example.com"
app_port: 8080
tasks:
- name: Deploy nginx config
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/conf.d/app.conf
owner: root
group: root
mode: '0644'
notify: reload nginx
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
ไฟล์ต้นแบบ (.j2) วางไว้ในไดเรกทอรี templates/ ภายใน playbook หรือ role เสมอ Ansible จะค้นหา path นี้โดยอัตโนมัติ
โครงสร้างไฟล์ .j2 พื้นฐาน
Jinja2 ใช้ delimiters 3 แบบ: {{ }} สำหรับ expressions (แทรกค่า), {% %} สำหรับ statements (control flow), และ {# #} สำหรับ comments
{# templates/nginx.conf.j2 #}
server {
listen {{ app_port | default(80) }};
server_name {{ server_name }};
root {{ web_root | default('/var/www/html') }};
index index.html index.php;
location / {
try_files $uri $uri/ =404;
}
{% if enable_ssl is defined and enable_ssl %}
listen 443 ssl;
ssl_certificate {{ ssl_cert_path }};
ssl_certificate_key {{ ssl_key_path }};
{% endif %}
access_log /var/log/nginx/{{ server_name }}_access.log;
error_log /var/log/nginx/{{ server_name }}_error.log;
}
Conditionals ใน Template
ใช้ {% if %} เพื่อเปิด/ปิด section ของ config ตามค่า variable ทำให้ template เดียวรองรับหลาย configuration
{# templates/app.conf.j2 #}
[app]
name = {{ app_name }}
port = {{ app_port }}
{% if db_host is defined %}
[database]
host = {{ db_host }}
port = {{ db_port | default(5432) }}
name = {{ db_name }}
user = {{ db_user }}
password = {{ db_password }}
{% endif %}
{% if cache_enabled | default(false) %}
[cache]
backend = {{ cache_backend | default('redis') }}
host = {{ cache_host | default('localhost') }}
port = {{ cache_port | default(6379) }}
{% endif %}
[logging]
level = {{ log_level | default('info') }}
{% if log_level == 'debug' %}
verbose = true
{% else %}
verbose = false
{% endif %}
Loops ใน Template
{% for %} ใช้สร้าง config ซ้ำหลาย block จาก list variable เหมาะสำหรับ upstream server, allowed IPs, หรือ vhost หลายตัว
{# templates/haproxy.cfg.j2 #}
frontend web_frontend
bind *:80
default_backend web_servers
backend web_servers
balance roundrobin
{% for server in backend_servers %}
server {{ server.name }} {{ server.ip }}:{{ server.port | default(80) }} check
{% endfor %}
{# ตัวอย่างการใช้ loop กับ dict items #}
[allowed_hosts]
{% for host in allowed_hosts %}
{{ loop.index }}. {{ host }}
{% endfor %}
ตัวแปรพิเศษที่ใช้ได้ใน for loop: loop.index (เริ่มจาก 1), loop.index0 (เริ่มจาก 0), loop.first, loop.last, และ loop.length ช่วยให้ควบคุม output ได้ละเอียด
Whitespace Control
Jinja2 จะ render newlines รอบ {% %} blocks ออกมาด้วยซึ่งอาจทำให้ config มีบรรทัดว่างเกิน ใช้ - เพื่อตัด whitespace
{# ปัญหา: มีบรรทัดว่างเกิน #}
server_list:
{% for s in servers %}
- {{ s }}
{% endfor %}
{# แก้ด้วย whitespace control #}
server_list:
{%- for s in servers %}
- {{ s }}
{%- endfor %}
{# ตัด whitespace ทั้งก่อนและหลัง block #}
{% if feature_enabled -%}
feature = on
{%- endif %}
ใช้ Facts ใน Template
Template เข้าถึง facts และ variables ทั้งหมดที่มีใน play ได้โดยตรง ทำให้ปรับ config ตาม hardware จริงได้อัตโนมัติ
{# templates/nginx.conf.j2 — ปรับตาม CPU และ RAM จริง #}
user nginx;
worker_processes {{ ansible_processor_vcpus }};
worker_rlimit_nofile {{ [ansible_processor_vcpus * 1024, 8192] | max }};
events {
worker_connections {{ [ansible_memtotal_mb // 4, 1024] | max }};
multi_accept on;
}
http {
server_tokens off;
{% if ansible_memtotal_mb >= 4096 %}
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
{% else %}
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
{% endif %}
}
Template สำหรับ systemd Service
Pattern ที่ใช้บ่อยคือสร้าง systemd unit file จาก template เพื่อ deploy application service พร้อม configuration ที่ถูกต้องในทีเดียว
{# templates/myapp.service.j2 #}
[Unit]
Description={{ app_name }} Service
After=network.target
{% if db_host is defined %}
After=mysql.service
Requires=mysql.service
{% endif %}
[Service]
Type=simple
User={{ app_user | default('www-data') }}
WorkingDirectory={{ app_dir }}
ExecStart={{ app_dir }}/bin/{{ app_name }} --port {{ app_port }}
Restart=on-failure
RestartSec=5
{% if app_env_vars is defined %}
Environment={% for key, value in app_env_vars.items() %}"{{ key }}={{ value }}" {% endfor %}
{% endif %}
[Install]
WantedBy=multi-user.target
---
- name: Deploy app service
hosts: appservers
vars:
app_name: myservice
app_dir: /opt/myservice
app_port: 8080
app_env_vars:
NODE_ENV: production
LOG_LEVEL: info
tasks:
- name: Deploy systemd unit
template:
src: templates/myapp.service.j2
dest: /etc/systemd/system/{{ app_name }}.service
mode: '0644'
notify:
- reload systemd
- restart service
handlers:
- name: reload systemd
systemd:
daemon_reload: yes
- name: restart service
service:
name: "{{ app_name }}"
state: restarted
enabled: yes
validate: ตรวจ Config ก่อน Deploy
Parameter validate ของ template module รัน command เพื่อตรวจสอบความถูกต้องของ config ก่อนเขียนลง destination จริง ป้องกัน config พังทำ service ล่ม
---
- name: Deploy and validate nginx config
hosts: webservers
tasks:
- name: Deploy nginx config with validation
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: nginx -t -c %s
notify: reload nginx
- name: Deploy sshd config with validation
template:
src: templates/sshd_config.j2
dest: /etc/ssh/sshd_config
validate: sshd -t -f %s
notify: restart sshd
Ansible จะเขียน template ลงไฟล์ชั่วคราวก่อน แล้วรัน validate command โดยแทนที่ %s ด้วย path ของไฟล์ชั่วคราว ถ้า command return non-zero exit code จะไม่เขียนทับ destination
สรุป
Template module เป็นเครื่องมือหลักสำหรับ configuration management ที่แท้จริง Pattern ที่ควรจำ: ใช้ | default() กับ variable ทุกตัวที่อาจไม่ได้กำหนด, ใช้ whitespace control (-) เมื่อ config format มีความสำคัญ, และใส่ validate parameter กับ config ของ service สำคัญเสมอเพื่อป้องกัน deploy config ที่พัง
Facts จาก remote host เช่น CPU count และ RAM ทำให้ template ปรับ config ตาม hardware จริงได้โดยอัตโนมัติ ไม่ต้องดูแล config file แยกสำหรับแต่ละ server spec

