Ansible Jinja2 模板 #
什么是 Jinja2? #
Jinja2 是 Python 的模板引擎,Ansible 使用它来生成动态配置文件。模板文件使用 .j2 扩展名,包含变量、条件、循环等逻辑。
text
┌─────────────────────────────────────────────────────────────┐
│ 模板工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 模板文件 (.j2) 变量数据 渲染引擎 │
│ ────────────── ────────── ────────── │
│ server { nginx_port: 80 Jinja2 │
│ listen {{ port }}; server_name: web Engine │
│ server_name {{ name }}; │
│ } │
│ │
│ ↓ │
│ │
│ 生成的配置文件 │
│ ──────────────── │
│ server { │
│ listen 80; │
│ server_name web; │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘
基本语法 #
变量 #
jinja2
{# templates/config.j2 #}
{# 输出变量 #}
ServerName {{ server_name }}
Port {{ port }}
{# 访问字典 #}
Database: {{ database.host }}:{{ database.port }}
{# 访问列表元素 #}
First server: {{ servers[0] }}
{# 属性访问 #}
{{ ansible_facts.hostname }}
注释 #
jinja2
{# 单行注释 - 不会出现在输出中 #}
{#
多行注释
这些内容不会出现在生成的文件中
#}
# 普通注释会出现在输出中
# 这是一个配置文件
template 模块 #
基本使用 #
yaml
tasks:
- name: Deploy configuration file
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes
notify: Restart Nginx
模板参数 #
yaml
tasks:
- name: Deploy with all options
template:
src: app.conf.j2
dest: /etc/app/app.conf
owner: appuser
group: appuser
mode: '0644'
backup: yes
validate: 'nginx -t -c %s'
variable_start_string: '{%'
variable_end_string: '%}'
block_start_string: '{%'
block_end_string: '%}'
条件判断 #
if 语句 #
jinja2
{# templates/nginx.conf.j2 #}
server {
listen {{ nginx_port }};
server_name {{ server_name }};
{% if ssl_enabled %}
listen 443 ssl;
ssl_certificate {{ ssl_cert_path }};
ssl_certificate_key {{ ssl_key_path }};
{% endif %}
{% if environment == 'production' %}
# Production settings
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
{% elif environment == 'staging' %}
# Staging settings
access_log /var/log/nginx/staging_access.log;
error_log /var/log/nginx/staging_error.log;
{% else %}
# Development settings
access_log off;
{% endif %}
}
条件表达式 #
jinja2
{# 使用内联条件 #}
worker_processes {{ ansible_facts.processor_vcpus if nginx_worker_processes == 'auto' else nginx_worker_processes }};
{# 使用过滤器条件 #}
debug_mode: {{ 'true' if debug_enabled else 'false' }}
{# 复杂条件 #}
{% if ansible_facts.memtotal_mb > 8192 and environment == 'production' %}
cache_size: 2g
{% else %}
cache_size: 512m
{% endif %}
if-elif-else #
jinja2
{% if ansible_facts.os_family == 'Debian' %}
# Debian/Ubuntu configuration
user www-data;
{% elif ansible_facts.os_family == 'RedHat' %}
# RHEL/CentOS configuration
user nginx;
{% else %}
# Default configuration
user nobody;
{% endif %}
循环 #
for 循环 #
jinja2
{# 遍历列表 #}
{% for server in upstream_servers %}
server {{ server }};
{% endfor %}
{# 遍历字典 #}
{% for key, value in config.items() %}
{{ key }} = {{ value }}
{% endfor %}
{# 遍历字典列表 #}
{% for vhost in vhosts %}
server {
listen {{ vhost.port }};
server_name {{ vhost.server_name }};
root {{ vhost.root }};
}
{% endfor %}
循环变量 #
jinja2
{% for item in items %}
Item {{ loop.index }}: {{ item }}
{# loop.index - 当前索引(从1开始) #}
{# loop.index0 - 当前索引(从0开始) #}
{# loop.first - 是否是第一个元素 #}
{# loop.last - 是否是最后一个元素 #}
{# loop.length - 列表长度 #}
{# loop.cycle - 循环值 #}
{% endfor %}
{# 示例 #}
{% for server in servers %}
Server {{ loop.index }}/{{ loop.length }}: {{ server }}
{% if not loop.last %}, {% endif %}
{% endfor %}
循环控制 #
jinja2
{# for-else #}
{% for user in users %}
User: {{ user.name }}
{% else %}
No users found
{% endfor %}
{# 跳过元素 #}
{% for item in items %}
{% if item.disabled %}
{% continue %}
{% endif %}
Item: {{ item.name }}
{% endfor %}
{# 退出循环 #}
{% for item in items %}
{% if item.critical %}
ERROR: Critical item found
{% break %}
{% endif %}
Item: {{ item.name }}
{% endfor %}
嵌套循环 #
jinja2
{% for group in groups %}
Group: {{ group.name }}
{% for user in group.users %}
- {{ user }}
{% endfor %}
{% endfor %}
过滤器 #
字符串过滤器 #
jinja2
{# 大小写转换 #}
{{ name | upper }} {# MYAPP #}
{{ name | lower }} {# myapp #}
{{ name | capitalize }} {# Myapp #}
{{ name | title }} {# My App #}
{# 替换 #}
{{ url | replace('http://', 'https://') }}
{# 截断 #}
{{ description | truncate(50) }}
{# 分割 #}
{{ csv_string | split(',') }}
{# 去除空白 #}
{{ value | trim }}
{{ value | strip }}
列表过滤器 #
jinja2
{# 排序 #}
{{ items | sort }}
{{ items | sort(reverse=true) }}
{{ users | sort(attribute='name') }}
{# 过滤 #}
{{ items | select('defined') }}
{{ numbers | select('odd') }}
{{ items | reject('none') }}
{# 映射 #}
{{ users | map(attribute='name') | list }}
{{ items | map('upper') | list }}
{# 合并 #}
{{ items | join(', ') }}
{# 统计 #}
{{ items | length }}
{{ items | min }}
{{ items | max }}
{{ numbers | sum }}
{# 去重 #}
{{ items | unique | list }}
{# 合并列表 #}
{{ list1 + list2 }}
{{ list1 | union(list2) }}
字典过滤器 #
jinja2
{# 转换为列表 #}
{{ my_dict | dict2items }}
{# [{'key': 'k1', 'value': 'v1'}, ...] #}
{# 列表转字典 #}
{{ items | items2dict }}
{# {'k1': 'v1', 'k2': 'v2'} #}
{# 合并字典 #}
{{ dict1 | combine(dict2) }}
{{ dict1 | combine(dict2, recursive=true) }}
{# 获取键值 #}
{{ my_dict | dict_keys | list }}
{{ my_dict | dict_values | list }}
数字过滤器 #
jinja2
{# 数学运算 #}
{{ value | int }}
{{ value | float }}
{{ value | abs }}
{{ value | round(2) }}
{{ value | round(2, 'floor') }}
{# 随机 #}
{{ items | random }}
{{ range(10) | random }}
默认值 #
jinja2
{# 默认值 #}
{{ undefined_var | default('default_value') }}
{{ empty_var | default('default', true) }}
{# 必须定义 #}
{{ required_var | mandatory }}
{# 省略 #}
{{ param | default(omit) }}
类型转换 #
jinja2
{{ value | string }}
{{ value | int }}
{{ value | float }}
{{ value | bool }}
{{ value | to_json }}
{{ value | to_yaml }}
{{ value | to_nice_json }}
{{ value | to_nice_yaml }}
空白控制 #
控制空白 #
jinja2
{# 默认 - 保留换行 #}
{% for item in items %}
Item: {{ item }}
{% endfor %}
{# 输出:
Item: item1
Item: item2
#}
{# 使用 - 去除空白 #}
{% for item in items -%}
Item: {{ item }}
{%- endfor %}
{# 输出:
Item: item1
Item: item2
#}
{# 完全去除换行 #}
{%- for item in items -%}
Item: {{ item }}
{%- endfor -%}
{# 输出: Item: item1Item: item2 #}
trim_blocks 和 lstrip_blocks #
yaml
# ansible.cfg
[defaults]
jinja2_trim_blocks = True
jinja2_lstrip_blocks = True
宏 #
定义宏 #
jinja2
{# 定义可重用的宏 #}
{% macro server_block(name, port, root) %}
server {
listen {{ port }};
server_name {{ name }};
root {{ root }};
location / {
try_files $uri $uri/ =404;
}
}
{% endmacro %}
{# 使用宏 #}
{{ server_block('example.com', 80, '/var/www/example') }}
{{ server_block('api.example.com', 80, '/var/www/api') }}
宏参数 #
jinja2
{% macro config_entry(key, value, comment='') %}
{# {{ key }}: {{ value }}{% if comment %} # {{ comment }}{% endif %} #}
{{ key }}: {{ value }}{% if comment %} # {{ comment }}{% endif %}
{% endmacro %}
{{ config_entry('port', 8080, 'Application port') }}
{{ config_entry('debug', false) }}
宏文件 #
jinja2
{# templates/macros.j2 #}
{% macro input(name, value='', type='text') %}
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}
{% macro textarea(name, value='', rows=4) %}
<textarea name="{{ name }}" rows="{{ rows }}">{{ value }}</textarea>
{% endmacro %}
{# 在其他模板中导入 #}
{% from 'macros.j2' import input, textarea %}
{{ input('username') }}
{{ textarea('description') }}
包含和继承 #
include #
jinja2
{# 包含其他模板 #}
{% include 'header.j2' %}
server {
listen {{ port }};
{% include 'server_common.j2' %}
}
{# 包含并传递变量 #}
{% include 'item.j2' with context %}
{% include 'item.j2' without context %}
extends 继承 #
jinja2
{# templates/base.conf.j2 #}
# Base Configuration
# Generated by Ansible
{% block header %}
# Header section
{% endblock %}
{% block main %}
# Main configuration
{% endblock %}
{% block footer %}
# Footer section
{% endblock %}
{# templates/app.conf.j2 #}
{% extends 'base.conf.j2' %}
{% block header %}
# Application Header
# App: {{ app_name }}
{% endblock %}
{% block main %}
# Application Settings
port: {{ app_port }}
debug: {{ debug_mode }}
{% endblock %}
实际示例 #
Nginx 配置模板 #
jinja2
{# templates/nginx.conf.j2 #}
# Nginx configuration
# Managed by Ansible - DO NOT EDIT MANUALLY
user {{ nginx_user }};
worker_processes {{ nginx_worker_processes | default('auto') }};
pid /run/nginx.pid;
error_log {{ nginx_log_dir }}/error.log;
events {
worker_connections {{ nginx_worker_connections | default(1024) }};
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
{% if nginx_gzip_enabled | default(true) %}
gzip on;
gzip_types text/plain text/css application/json application/javascript;
{% endif %}
{% for upstream in nginx_upstreams | default([]) %}
upstream {{ upstream.name }} {
{% for server in upstream.servers %}
server {{ server.host }}:{{ server.port }}{% if server.options %} {{ server.options }}{% endif %};
{% endfor %}
}
{% endfor %}
{% for vhost in nginx_vhosts %}
server {
listen {{ vhost.port | default(80) }};
server_name {{ vhost.server_name }};
root {{ vhost.root | default('/var/www/html') }};
{% if vhost.ssl | default(false) %}
listen {{ vhost.ssl_port | default(443) }} ssl;
ssl_certificate {{ vhost.ssl_cert }};
ssl_certificate_key {{ vhost.ssl_key }};
{% endif %}
{% for location in vhost.locations | default([]) %}
location {{ location.path }} {
{% if location.proxy_pass %}
proxy_pass {{ location.proxy_pass }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
{% elif location.try_files %}
try_files {{ location.try_files }};
{% endif %}
}
{% endfor %}
}
{% endfor %}
}
应用配置模板 #
jinja2
{# templates/app.env.j2 #}
# Application Environment Configuration
# Generated: {{ ansible_date_time.iso8601 }}
# Application
APP_NAME={{ app_name }}
APP_ENV={{ environment }}
APP_DEBUG={{ debug_mode | default(false) | lower }}
APP_URL={{ app_url | default('http://localhost') }}
# Database
DB_CONNECTION={{ db_connection | default('mysql') }}
DB_HOST={{ database.host }}
DB_PORT={{ database.port }}
DB_DATABASE={{ database.name }}
DB_USERNAME={{ database.user }}
DB_PASSWORD={{ database.password }}
# Cache
CACHE_DRIVER={{ cache_driver | default('redis') }}
REDIS_HOST={{ redis_host | default('127.0.0.1') }}
REDIS_PASSWORD={{ redis_password | default('null') }}
REDIS_PORT={{ redis_port | default(6379) }}
# Queue
QUEUE_CONNECTION={{ queue_driver | default('redis') }}
# Mail
MAIL_MAILER={{ mail_driver | default('smtp') }}
MAIL_HOST={{ mail_host | default('smtp.mailtrap.io') }}
MAIL_PORT={{ mail_port | default(587) }}
MAIL_USERNAME={{ mail_username }}
MAIL_PASSWORD={{ mail_password }}
MAIL_ENCRYPTION={{ mail_encryption | default('tls') }}
# Custom Variables
{% for key, value in custom_vars.items() | default({}) %}
{{ key | upper }}={{ value }}
{% endfor %}
YAML 配置模板 #
jinja2
{# templates/config.yml.j2 #}
# {{ app_name }} Configuration
# Generated by Ansible
app:
name: {{ app_name }}
version: {{ app_version }}
environment: {{ environment }}
server:
host: {{ server_host | default('0.0.0.0') }}
port: {{ server_port | default(8080) }}
{% if ssl_enabled %}
ssl:
enabled: true
cert: {{ ssl_cert_path }}
key: {{ ssl_key_path }}
{% endif %}
database:
{% for db in databases %}
- name: {{ db.name }}
host: {{ db.host }}
port: {{ db.port | default(3306) }}
user: {{ db.user }}
password: {{ db.password }}
{% if db.options %}
options:
{% for key, value in db.options.items() %}
{{ key }}: {{ value }}
{% endfor %}
{% endif %}
{% endfor %}
logging:
level: {{ log_level | default('info') }}
format: {{ log_format | default('json') }}
output: {{ log_output | default('/var/log/app/app.log') }}
features:
{% for feature, enabled in features.items() | default({}) %}
{{ feature }}: {{ enabled | lower }}
{% endfor %}
模板验证 #
验证语法 #
yaml
tasks:
- name: Validate template syntax
template:
src: templates/nginx.conf.j2
dest: /tmp/nginx.conf.test
validate: nginx -t -c %s
check_mode: yes
预览模板 #
yaml
tasks:
- name: Preview template
debug:
msg: "{{ lookup('template', 'templates/app.conf.j2') }}"
最佳实践 #
1. 使用注释 #
jinja2
{# 文件头注释 #}
# {{ app_name }} Configuration
# Managed by Ansible - DO NOT EDIT MANUALLY
# Template: {{ ansible_managed }}
# Generated: {{ ansible_date_time.iso8601 }}
2. 合理使用空白控制 #
jinja2
{# 使用 - 控制空白 #}
{% for item in items -%}
{{ item }}
{% endfor %}
3. 提供默认值 #
jinja2
{# 使用 default 过滤器 #}
port: {{ app_port | default(8080) }}
debug: {{ debug_mode | default(false) | lower }}
4. 组织模板文件 #
text
templates/
├── nginx/
│ ├── nginx.conf.j2
│ ├── site.conf.j2
│ └── ssl.conf.j2
├── app/
│ ├── config.yml.j2
│ ├── env.j2
│ └── logging.conf.j2
└── macros/
├── common.j2
└── nginx.j2
下一步 #
现在你已经掌握了 Jinja2 模板,接下来学习 Roles 角色 了解如何组织和管理复杂的 Ansible 项目!
最后更新:2026-03-29