Ansible自动化(十一):Jinja2模板

一、Jinja2 核心介绍

1. 什么是Jinja2

Jinja2是一款基于Python的模板引擎,支持动态生成文本文件(如配置文件、报告、脚本),核心能力是将「静态模板」与「动态变量/逻辑」结合,输出个性化内容。在Ansible中,Jinja2是默认模板引擎,专门用于解决「批量配置差异化」问题(比如不同主机生成不同的Nginx/MySQL配置)。

2. Ansible中Jinja2的核心价值

  • 告别手动修改配置文件:通过变量自动填充主机IP、系统信息等差异化内容;
  • 支持逻辑控制:用条件/循环实现「不同主机/场景输出不同配置」;
  • 与Ansible深度集成:可直接调用Ansible收集的「事实变量」(如主机接口、内存、系统版本)。

3. 基础使用规范

  • 模板文件后缀:约定为 .j2(如 httpd.conf.j2hosts.j2);
  • 渲染方式:Ansible通过 template 模块读取 .j2 模板,结合变量渲染后生成最终文件;
  • 前置条件:需开启 gather_facts: yes(Ansible默认开启),才能收集主机事实变量(如IP、内存)。

二、Jinja2 核心语法

1. 语法符号速查表

符号格式 作用 示例 注意事项
{``{ 变量 }} 输出变量值(插值) {``{ ansible_ens160.ipv4.address }} 变量不存在会报错,建议先判断存在性
{% 控制语句 %} 逻辑控制(条件/循环) {% if 条件 %} {% for 变量 in 列表 %} 必须用对应关键字闭合(如 endif
{# 注释 #} 模板内注释,渲染后自动忽略 {# 优先使用bond0接口IP #} 区别于文件自身的注释(如#

2. 变量来源(Ansible场景)

Jinja2模板中的变量主要来自3类,可直接调用:

  • 事实变量(Facts) :Ansible自动收集的主机信息(如 ansible_hostname 主机名、ansible_memtotal_mb 总内存、ansible_distribution 系统发行版);
  • 自定义变量 :Playbook中 vars 定义的变量、清单(inventory)中定义的主机/组变量;
  • 特殊变量 :Ansible内置变量(如 groups.all 所有主机、hostvars[主机名] 其他主机的变量)。

三、条件语句(if/elif/else)

1. 核心语法(按场景分类)

(1)单分支:满足条件才输出
jinja2 复制代码
{# 语法结构 #}
{% if 条件表达式 %}
    满足条件时输出的内容
{% endif %}  {# 必须闭合,否则模板渲染失败 #}

{# 实战示例:仅CentOS系统输出yum源配置 #}
{% if ansible_distribution == "CentOS" %}
[base]
name=CentOS-Base
baseurl=https://mirrors.aliyun.com/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
{% endif %}
(2)双分支:二选一输出
jinja2 复制代码
{# 语法结构 #}
{% if 条件表达式 %}
    满足条件的内容
{% else %}
    不满足条件的默认内容
{% endif %}

{# 实战示例:根据是否有SSL证书决定监听端口 #}
{% if ansible_facts['files']['/etc/nginx/ssl/server.crt'] is defined %}
listen 443 ssl;
{% else %}
listen 80;
{% endif %}
(3)多分支:按优先级匹配条件
jinja2 复制代码
{# 语法结构 #}
{% if 条件1 %}
    满足条件1的内容
{% elif 条件2 %}
    满足条件2的内容
{% elif 条件3 %}
    满足条件3的内容
{% else %}
    所有条件都不满足的默认内容
{% endif %}

{# 实战示例:优先使用bond0,其次ens160,最后0.0.0.0 #}
{% if ansible_bond0 is defined %}
Listen {{ ansible_bond0.ipv4.address }}:80
{% elif ansible_ens160 is defined %}
Listen {{ ansible_ens160.ipv4.address }}:80
{% else %}
Listen 0.0.0.0:80
{% endif %}

2. 常用条件表达式

表达式 含义 示例
变量 is defined 判断变量是否存在 ansible_bond0 is defined
变量 == 值 等值判断(字符串/数字) ansible_hostname == "node01"
变量 != 值 不等值判断 ansible_os_family != "RedHat"
变量 in 列表 包含判断 ansible_hostname in ["node01","node02"]
变量 >/</>=/<= 值 数值比较 ansible_memtotal_mb > 8192

3. 实战案例:按系统生成差异化文件

需求

所有主机生成 /opt/os.txt:CentOS系统内容为 OS: CentOS,Ubuntu为 OS: Ubuntu,其他系统为 OS: Unknown

步骤1:编写Jinja2模板(os.txt.j2)
jinja2 复制代码
{# 基于系统发行版生成差异化内容 #}
{% if ansible_distribution == "CentOS" %}
OS: CentOS
{% elif ansible_distribution == "Ubuntu" %}
OS: Ubuntu
{% else %}
OS: Unknown
{% endif %}
步骤2:编写Ansible Playbook(create_os_file.yml)
yaml 复制代码
- hosts: all
  gather_facts: yes  # 必须开启,才能获取ansible_distribution变量
  tasks:
    - name: 生成系统标识文件
      template:
        src: os.txt.j2       # 本地模板文件路径
        dest: /opt/os.txt    # 目标主机生成的文件路径
        mode: 0644           # 文件权限
步骤3:执行与验证
bash 复制代码
# 执行Playbook
ansible-playbook create_os_file.yml

# 验证node01的文件内容
ansible node01 -m command -a "cat /opt/os.txt"
# 预期输出(CentOS主机):OS: CentOS

四、循环语句(for)

1. 核心语法

Jinja2 for循环仅支持遍历「列表/可迭代对象」,用于批量生成重复格式的内容,语法如下:

jinja2 复制代码
{# 基础语法:遍历可迭代对象,每次将元素赋值给变量 #}
{% for 变量名 in 可迭代对象 %}
    循环体内容(可调用变量属性)
{% endfor %}  {# 必须闭合,否则报错 #}

{# 简化示例:遍历自定义端口列表 #}
{% for port in [80,443,8080] %}
port {{ port }}
{% endfor %}

2. Ansible常用可迭代对象

可迭代对象 含义 适用场景
groups.all Ansible清单中所有主机 生成全局hosts文件
groups['组名'] 清单中指定组的主机(如webservers) 生成Nginx upstream节点列表
ansible_mounts 主机挂载点列表 生成挂载点巡检报告
自定义列表 [80,443,8080] 批量生成端口配置
ansible_interfaces 主机网络接口列表 批量输出接口IP

3. 实战案例1:批量生成端口配置文件

需求

所有主机生成 /opt/ports.txt,内容为80、443、8080、9090四个端口,每行一个(用for循环实现)。

步骤1:编写模板(ports.txt.j2)
jinja2 复制代码
{# 遍历自定义端口列表,批量生成每行一个端口 #}
{% for port in [80,443,8080,9090] %}
{{ port }}
{% endfor %}
步骤2:编写Playbook(create_ports_file.yml)
yaml 复制代码
- hosts: all
  gather_facts: no  # 无需收集事实变量,可关闭提升速度
  tasks:
    - name: 生成端口文件
      template:
        src: ports.txt.j2
        dest: /opt/ports.txt
        mode: 0644
步骤3:验证
bash 复制代码
ansible-playbook create_ports_file.yml
# 查看node01的ports.txt
ansible node01 -m command -a "cat /opt/ports.txt"
# 预期输出:
# 80
# 443
# 8080
# 9090

4. 实战案例2:批量生成hosts文件

需求

所有主机生成 /opt/hosts,包含Ansible清单内所有主机的「IP + FQDN + 主机名」。

步骤1:编写模板(hosts.j2)
jinja2 复制代码
{# 批量生成hosts文件,遍历所有主机 #}
# Ansible自动生成 {{ ansible_date_time.date }}
{% for host in groups.all %}
{{ hostvars[host].ansible_ens160.ipv4.address }} {{ hostvars[host].ansible_fqdn }} {{ hostvars[host].ansible_hostname }}
{% endfor %}
  • 关键说明:hostvars[host] 是Ansible特殊变量,用于获取「指定主机」的事实变量(比如获取node02的IP,而不是当前主机)。
步骤2:编写Playbook(generate_hosts.yml)
yaml 复制代码
- hosts: all
  gather_facts: yes  # 需获取IP/FQDN,必须开启
  tasks:
    - name: 生成hosts文件
      template:
        src: hosts.j2
        dest: /opt/hosts
        mode: 0644
步骤3:验证
bash 复制代码
ansible-playbook generate_hosts.yml
# 查看node01的hosts文件
ansible node01 -m command -a "cat /opt/hosts"
# 预期输出:
# Ansible自动生成 2026-01-06
# 172.17.0.146 node01.example.com node01
# 172.17.0.149 node02.example.com node02

五、条件+循环 综合实战

需求

dbservers 组主机生成 /opt/mysql.cnf

  1. 循环生成MySQL从库列表(排除本机);
  2. 根据主机内存调整参数:内存>8192MB时,innodb_buffer_pool_size=6G,否则为2G

步骤1:编写模板(mysql.cnf.j2)

jinja2 复制代码
{# MySQL配置模板 - 综合条件+循环 #}
[mysqld]
# 基础配置
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
# 根据内存调整缓存池大小
{% if ansible_memtotal_mb > 8192 %}
innodb_buffer_pool_size=6G
{% else %}
innodb_buffer_pool_size=2G
{% endif %}

# 从库列表(排除本机)
{% for host in groups['dbservers'] %}
{% if host != ansible_hostname %}
slave_host={{ hostvars[host].ansible_ens160.ipv4.address }}
{% endif %}
{% endfor %}

步骤2:编写Playbook(config_mysql.yml)

yaml 复制代码
- hosts: dbservers  # 仅针对dbservers组
  gather_facts: yes  # 需获取内存、IP、主机名
  tasks:
    - name: 生成MySQL配置文件
      template:
        src: mysql.cnf.j2
        dest: /opt/mysql.cnf
        mode: 0644

步骤3:验证

假设 dbservers 组包含 node03(内存16G)、node04(内存4G):

bash 复制代码
ansible-playbook config_mysql.yml

# 查看node03的配置(内存>8G)
ansible node03 -m command -a "cat /opt/mysql.cnf"
# 关键输出:
# innodb_buffer_pool_size=6G
# slave_host=172.17.0.150(node04的IP)

# 查看node04的配置(内存<8G)
ansible node04 -m command -a "cat /opt/mysql.cnf"
# 关键输出:
# innodb_buffer_pool_size=2G
# slave_host=172.17.0.148(node03的IP)

六、避坑指南(高频问题)

  1. 语法闭合问题 :条件/循环语句必须用 {% endif %}/{% endfor %} 闭合,少写会导致模板渲染失败;
  2. 变量不存在报错 :使用变量前建议用 is defined 判断(如 {% if ansible_bond0 is defined %});
  3. 事实变量为空 :忘记开启 gather_facts: yes,导致无法获取IP、内存等变量;
  4. 注释被渲染 :模板内注释必须用 {# 内容 #},若用 # 会被保留到最终文件中;
  5. 循环中引用其他主机变量 :需用 hostvars[host],直接写 ansible_ens160 只会取当前主机的值。

七、典型使用场景总结

场景 核心语法 示例
差异化配置文件 if/elif/else 不同系统生成不同yum/apt源
批量生成节点列表 for循环 Nginx upstream、Keepalived节点
硬件适配参数 条件+数值比较 根据内存调整MySQL/Redis参数
批量输出资源报告 for循环 磁盘/挂载点/端口巡检报告
混合场景(排除/筛选) 条件+循环 生成从库列表(排除本机)

关键点回顾

  1. Jinja2在Ansible中核心用于「动态生成差异化配置」,模板后缀为.j2,依赖template模块渲染;
  2. 核心语法分三类:{``{ 变量 }} 插值、{% 控制语句 %} 逻辑、{# 注释 #} 模板注释;
  3. 条件语句解决「不同场景输出不同内容」,循环语句解决「批量生成重复内容」,两者结合可覆盖绝大多数运维配置场景;
  4. 使用前需确保开启gather_facts: yes,变量使用前建议用is defined判断存在性,避免报错。
相关推荐
23zhgjx-zgx18 小时前
HTTP网络攻击分析
网络·ctf
cly118 小时前
Ansible自动化(八):条件语句
运维·自动化·ansible
China_Yanhy18 小时前
Ansible 工业级项目标准化架构指南 (V1.0)
架构·ansible
TOPGUS18 小时前
谷歌Chrome浏览器即将对HTTP网站设卡:突出展示“始终使用安全连接”功能
前端·网络·chrome·http·搜索引擎·seo·数字营销
韶关亿宏科技-光纤通信小易19 小时前
光模块-数字时代的算力传输纽带
大数据·网络
Wadli19 小时前
项目5 |HTTP服务框架
网络·网络协议·http
oMcLin19 小时前
如何在RHEL 9上配置并优化Kubernetes 1.23高可用集群,提升大规模容器化应用的自动化部署与管理?
kubernetes·自动化·php
fy zs19 小时前
网络编程套接字
linux·服务器·网络·c++
yuanmenghao19 小时前
CAN系列 — (8) 为什么 Radar Object List 不适合“直接走 CAN 信号”
网络·数据结构·单片机·嵌入式硬件·自动驾驶·信息与通信