第九章:Jinja2 模板引擎
掌握 Jinja2 模板语法,用于生成动态配置文件。
最后更新: 2024-01-23
页面目录
Jinja2 模板引擎
Jinja2 是 Python 的一个现代模板引擎,Ansible 使用它来生成动态配置文件。
模板基础
Ansible template 模块
tasks:
- name: Generate config from template
template:
src: app.conf.j2
dest: /etc/myapp/app.conf
owner: root
group: root
mode: '0644'
validate: "/usr/sbin/nginx -t -c %s"
notify: Restart app
Jinja2 语法
变量输出
{# 单行注释 #}
{# 多行
注释 #}
{# 变量 #}
{{ variable }}
{{ user.name }}
{{ user['name'] }}
{{ ansible_hostname }}
{# 过滤器 #}
{{ name | upper }}
{{ name | lower }}
{{ name | default('Anonymous') }}
{{ name | length }}
{# 表达式 #}
{{ 1 + 1 }}
{{ 'hello' ~ 'world' }}
控制结构
if 条件
{% if条件 %}
内容
{% elif 条件 %}
内容
{% else %}
内容
{% endif %}
示例:
{% if ansible_facts['os_family'] == 'RedHat' %}
yum install nginx
{% elif ansible_facts['os_family'] == 'Debian' %}
apt install nginx
{% else %}
echo "Unsupported OS"
{% endif %}
for 循环
{% for item in items %}
{{ item }}
{% endfor %}
示例:
# 循环列表
{% for user in users %}
User: {{ user.name }} - {{ user.email }}
{% endfor %}
# 循环字典
{% for key, value in config.items() %}
{{ key }} = {{ value }}
{% endfor %}
# 带索引
{% for item in items %}
{{ loop.index }}. {{ item }}
{% endfor %}
循环变量
| 变量 | 说明 |
|---|---|
loop.index |
当前循环索引(从1开始) |
loop.index0 |
当前循环索引(从0开始) |
loop.first |
是否为第一次循环 |
loop.last |
是否为最后一次循环 |
loop.length |
循环总数 |
loop.depth |
当前递归深度 |
Jinja2 过滤器
字符串过滤器
{{ name | upper }} {# 大写 #}
{{ name | lower }} {# 小写 #}
{{ name | capitalize }} {# 首字母大写 #}
{{ name | title }} {# 每个单词首字母大写 #}
{{ name | reverse }} {# 反转字符串 #}
{{ name | length }} {# 字符串长度 #}
{{ name | trim }} {# 去除首尾空格 #}
{{ name | striptags }} {# 去除 HTML 标签 #}
{{ name | wordcount }} {# 单词数量 #}
{{ path | basename }} {# 获取文件名 #}
{{ path | dirname }} {# 获取目录名 #}
{{ path | expanduser }} {# 展开 ~ #}
{{ path | realpath }} {# 真实路径 #}
列表过滤器
{{ list | length }} {# 列表长度 #}
{{ list | first }} {# 第一个元素 #}
{{ list | last }} {# 最后一个元素 #}
{{ list | min }} {# 最小值 #}
{{ list | max }} {# 最大值 #}
{{ list | sum }} {# 求和 #}
{{ list | unique }} {# 去重 #}
{{ list | sort }} {# 排序 #}
{{ list | reverse }} {# 反转 #}
{{ list | join(',') }} {# 拼接 #}
{{ list | random }} {# 随机元素 #}
{{ list | slice(3) }} {# 分组 #}
字典过滤器
{{ dict | keys }} {# 获取所有键 #}
{{ dict | values }} {# 获取所有值 #}
{{ dict | items }} {# 键值对 #}
{{ dict | length }} {# 字典长度 #}
{{ dict | to_yaml }} {# 转换为 YAML #}
{{ dict | to_nice_yaml }} {# 美化 YAML #}
{{ dict | to_json }} {# 转换为 JSON #}
{{ dict | to_nice_json }} {# 美化 JSON #}
类型转换过滤器
{{ '123' | int }} {# 转换为整数 #}
{{ 123 | string }} {# 转换为字符串 #}
{{ value | bool }} {# 转换为布尔值 #}
{{ value | float }} {# 转换为浮点数 #}
{{ value | list }} {# 转换为列表 #}
{{ value | dict }} {# 转换为字典 #}
默认值过滤器
{{ value | default('N/A') }}
{{ value | default True }}
{{ value | default(False) }}
{{ value | d('default') }}
其他过滤器
{{ uuid | hash('sha256') }} {# 哈希计算 #}
{{ path | md5 }} {# MD5 #}
{{ path | sha1 }} {# SHA1 #}
{{ name | match('pattern') }} {# 正则匹配 #}
{{ text | regex_replace('old', 'new') }} {# 正则替换 #}
测试
{# 检查变量是否定义 #}
{% if value is defined %}
{{ value }}
{% endif %}
{# 检查变量未定义 #}
{% if value is not defined %}
default
{% endif %}
{# 检查值 #}
{% if value is truthy %}
{% if value is falsey %}
{% if value is none %}
{% if value is string %}
{% if value is number %}
{% if value is iterable %}
{% if value is mapping %}
完整模板示例
Nginx 配置模板
# nginx.conf.j2
# Ansible managed file
# Generated at: {{ ansible_date_time.iso8601 }}
user {{ nginx_user | default('www-data') }};
worker_processes {{ nginx_worker_processes | default(ansible_facts.cpu_cores) }};
pid {{ nginx_pid_file | default('/run/nginx.pid') }};
error_log {{ nginx_error_log | default('/var/log/nginx/error.log') }};
events {
worker_connections {{ nginx_worker_connections | default(1024) }};
{% if nginx_multi_accept %}
multi_accept on;
{% endif %}
}
http {
include {{ nginx_mime_types_file | default('/etc/nginx/mime.types') }};
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log {{ nginx_access_log | default('/var/log/nginx/access.log') }} main;
sendfile {{ nginx_sendfile | default('on') }};
tcp_nopush {{ nginx_tcp_nopush | default('on') }};
tcp_nodelay {{ nginx_tcp_nodelay | default('on') }};
keepalive_timeout {{ nginx_keepalive_timeout | default(65) }};
types_hash_max_size {{ nginx_types_hash_max_size | default(2048) }};
# Gzip compression
{% if nginx_gzip | default(True) %}
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss;
{% endif %}
include {{ nginx_conf_dir | default('/etc/nginx/conf.d') }}/*.conf;
}
多站点配置模板
# sites-enabled.conf.j2
{% for site in nginx_sites %}
server {
listen {{ site.port | default(80) }};
{% if site.ssl | default(False) %}
listen {{ site.port | default(443) }} ssl;
{% endif %}
server_name {{ site.server_name }};
root {{ site.document_root }};
index {{ site.index | default('index.html index.htm') }};
access_log /var/log/nginx/{{ site.server_name }}_access.log;
error_log /var/log/nginx/{{ site.server_name }}_error.log;
location / {
{% if site.try_files is defined %}
try_files {{ site.try_files }};
{% else %}
try_files $uri $uri/ =404;
{% endif %}
}
{% if site.php_enabled | default(False) %}
location ~ \.php$ {
include {{ nginx_php_config | default('/etc/nginx/fastcgi_params') }};
fastcgi_pass {{ site.php_socket | default('unix:/var/run/php/php-fpm.sock') }};
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
{% endif %}
{% if site.ssl | default(False) %}
ssl_certificate {{ site.ssl_cert }};
ssl_certificate_key {{ site.ssl_key }};
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
{% endif %}
}
{% endfor %}
应用配置文件模板
# app.config.j2
# Application Configuration
# Generated by Ansible
[application]
name = {{ app_name }}
version = {{ app_version }}
environment = {{ app_environment | default('production') }}
debug = {{ app_debug | default(False) | lower }}
[server]
host = {{ app_host | default('0.0.0.0') }}
port = {{ app_port }}
workers = {{ app_workers | default(ansible_facts.cpu_cores) }}
timeout = {{ app_timeout | default(30) }}
[database]
host = {{ db_host }}
port = {{ db_port | default(3306) }}
name = {{ db_name }}
user = {{ db_user }}
{% if db_password is defined %}
password = {{ db_password }}
{% endif %}
pool_size = {{ db_pool_size | default(10) }}
pool_recycle = {{ db_pool_recycle | default(3600) }}
[redis]
{% if redis_host is defined %}
host = {{ redis_host }}
port = {{ redis_port | default(6379) }}
{% if redis_password is defined %}
password = {{ redis_password }}
{% endif %}
db = {{ redis_db | default(0) }}
{% endif %}
[logging]
level = {{ app_log_level | default('INFO') }}
format = {{ app_log_format | default('%(asctime)s - %(name)s - %(levelname)s - %(message)s') }}
file = {{ app_log_file | default('/var/log/myapp/app.log') }}
{% if app_features is defined %}
[features]
{% for feature, enabled in app_features.items() %}
{{ feature }} = {{ enabled | lower }}
{% endfor %}
{% endif %}
负载均衡器模板
# haproxy.cfg.j2
# HAProxy Configuration
# Generated: {{ ansible_date_time.iso8601 }}
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /var/run/haproxy.sock mode 0600 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
{% if haproxy_maxconn is defined %}
maxconn {{ haproxy_maxconn }}
{% endif %}
defaults
log global
mode http
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout connect {{ haproxy_timeout_connect | default('5s') }}
timeout client {{ haproxy_timeout_client | default('30s') }}
timeout server {{ haproxy_timeout_server | default('30s') }}
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 503 /etc/haproxy/errors/503.http
# Stats page
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
stats auth admin:{{ haproxy_stats_password | default('admin') }}
{% for backend in haproxy_backends %}
# Backend: {{ backend.name }}
listen {{ backend.name }}
bind {{ backend.bind | default('*:80') }}
{% if backend.ssl | default(False) %}
bind {{ backend.bind_ssl | default('*:443') }} ssl crt {{ backend.ssl_cert }}
{% endif %}
mode {{ backend.mode | default('http') }}
balance {{ backend.balance | default('roundrobin') }}
{% for server in backend.servers %}
server {{ server.name }} {{ server.ip }}:{{ server.port }}
{% if server.check | default(True) %}
check inter 2000 rise 2 fall 3
{% endif %}
{% if server.cookie is defined %}
cookie {{ server.cookie }}
{% endif %}
{% endfor %}
{% if backend.stats is defined %}
stats enable
stats uri {{ backend.stats.uri | default('/stats') }}
{% endif %}
{% endfor %}
模板技巧
空白控制
{# 默认保留空白 #}
{% for item in items -%}
{{ item }}
{%- endfor %}
{# 去掉前后空白 #}
{{- value -}}
多行文本
{% set multiline = "line1
line2
line3" %}
{% raw %}
# 这部分不会被 Jinja 处理
{% endraw %}
宏定义
{% macro input(name, value='', type='text', size=20) %}
<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}">
{% endmacro %}
{# 使用宏 #}
{{ input('username') }}
{{ input('password', type='password') }}
下一步
现在你已经掌握了 Jinja2 模板。接下来让我们学习 Roles 角色管理。