RHCE Ansible 系列 #7:Jinja2 模板

在上一篇关于 Ansible 决策的教程中,您学习了如何使用 块文件 或者 排队 Ansible 模块。

在本教程中,您将学习如何使用 神社2 模板引擎 进行更多涉及和动态的文件修改。

您将学习如何访问 Jinja2 模板中的变量和事实。 此外,您将学习如何在 Jinja2 中使用条件语句和循环结构。

要尝试本教程中的示例,您应该按照正确的顺序学习整个 RHCE Ansible 教程系列。

在 Jinja2 中访问变量

Ansible 将在您的项目目录或名为的目录中查找 jinja2 模板文件 模板 在您的项目目录下。

让我们创建一个模板目录来保持更干净和更有条理:

[[email protected] plays]$ mkdir templates
[[email protected] plays]$ cd templates/

现在创建您的第一个 Jinja2 模板,名称为 索引.j2

[[email protected] templates]$ cat index.j2 
A message from {{ inventory_hostname }}
{{ webserver_message }}

请注意,Jinja2 模板文件名必须以 .j2 扩展名结尾。

库存主机名 是另一个 Ansible 内置(又名特殊或魔术)变量,它引用在剧中迭代的“当前”主机。 这 webserver_message 是您将在剧本中定义的变量。

现在回到您的项目目录并创建以下内容 检查-apache.yml

[[email protected] plays]$ cat check-apache.yml 
---
- name: Check if Apache is Working
  hosts: webservers
  vars:
    webserver_message: "I am running to the finish line."
  tasks:
    - name: Start httpd
      service:
        name: httpd
        state: started

    - name: Create index.html using Jinja2
      template:
        src: index.j2
        dest: /var/www/html/index.html

请注意, httpd 软件包已在上一个教程中安装。

在本手册中,您首先要确保 Apache 在第一个任务中运行 Start httpd. 然后使用 模板 第二个任务中的模块 Create index.html 使用 Jinja2 处理和传输 索引.j2 您创建到目的地的 Jinja2 模板文件 /var/www/html/index.html.

继续运行剧本:

[[email protected] plays]$ ansible-playbook check-apache.yml 

PLAY [Check if Apache is Working] **********************************************

TASK [Gathering Facts] *********************************************************
ok: [node3]
ok: [node2]

TASK [Start httpd] *************************************************************
ok: [node2]
ok: [node3]

TASK [Create index.html using Jinja2] ******************************************
changed: [node3]
changed: [node2]

PLAY RECAP *********************************************************************
node2                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node3                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0

到目前为止一切看起来都不错; 让我们运行一个快速的临时 Ansible 命令来检查 索引.html 在网络服务器节点上:

[[email protected] plays]$ ansible webservers -m command -a "cat /var/www/html/index.html"
node3 | CHANGED | rc=0 >>
A message from node3
I am running to the finish line.
node2 | CHANGED | rc=0 >>
A message from node2
I am running to the finish line.

惊人的! 请注意 Jinja2 是如何获取 库存主机名 内置变量和 webserver_message 你的剧本中的变量。

您还可以使用 curl 命令查看是否从两个网络服务器获得响应:

[[email protected] plays]$ curl node2.linuxhandbook.local
A message from node2
I am running to the finish line.
[[email protected] plays]$ curl node3.linuxhandbook.local
A message from node3
I am running to the finish line.

访问 Jinja2 中的事实

您可以像访问 playbook 中的事实一样访问 Jinja2 模板中的事实。

为了演示,更改为您的 模板 目录并创建 信息.j2 Jinja2 文件,内容如下:

[[email protected] templates]$ cat info.j2 
Server Information Summary
--------------------------

hostname={{ ansible_facts['hostname'] }}
fqdn={{ ansible_facts['fqdn'] }}
ipaddr={{ ansible_facts['default_ipv4']['address'] }}
distro={{ ansible_facts['distribution'] }}
distro_version={{ ansible_facts['distribution_version'] }}
nameservers={{ ansible_facts['dns']['nameservers'] }}
totalmem={{ ansible_facts['memtotal_mb'] }}
freemem={{ ansible_facts['memfree_mb'] }}

请注意 信息.j2 访问八个不同的事实。 现在回到您的项目目录并创建以下内容 服务器信息.yml 剧本:

[[email protected] plays]$ cat server-info.yml 
---
- name: Server Information Summary
  hosts: all
  tasks:
   - name: Create server-info.txt using Jinja2
     template:
       src: info.j2
       dest: /tmp/server-info.txt

请注意,您正在创建 /tmp/server-info.txt 在所有主机上基于 信息.j2 模板文件。 继续运行剧本:

[[email protected] plays]$ ansible-playbook server-info.yml 

PLAY [Server Information Summary] *******************************************

TASK [Gathering Facts] **********************************
ok: [node4]
ok: [node1]
ok: [node3]
ok: [node2]

TASK [Create server-info.txt using Jinja2] ********
changed: [node4]
changed: [node1]
changed: [node3]
changed: [node2]

PLAY RECAP *************************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node2                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node3                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node4                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0

一切看起来都不错! 现在让我们运行一个快速的临时命令来检查 /tmp/server-info.txt 在其中一个节点上的文件:

[[email protected] plays]$ ansible node1 -m command -a "cat /tmp/server-info.txt"
node1 | CHANGED | rc=0 >>
Server Information Summary
--------------------------

hostname=node1
fqdn=node1.linuxhandbook.local
ipaddr=10.0.0.5
distro=CentOS
distro_version=8.2
nameservers=['168.63.129.16']
totalmem=1896
freemem=1087

如您所见,Jinja2 能够访问和处理所有事实。

Jinja2 中的条件语句

您可以使用 如果 Jinja2 中的条件语句,用于测试各种条件和比较变量。 这允许您根据您的测试条件确定您的文件模板执行流程。

要演示,请转到您的 模板 目录并创建以下内容 selinux.j2 模板:

[[email protected] templates]$ cat selinux.j2 
{% set selinux_status = ansible_facts['selinux']['status'] %}

{% if selinux_status == "enabled" %}
	"SELINUX IS ENABLED"
{% elif selinux_status == "disabled" %}
	"SELINUX IS DISABLED"
{% else %}
	"SELINUX IS NOT AVAILABLE"
{% endif %}

模板中的第一条语句创建一个新变量 selinux_statusand 将其值设置为 ansible_facts['selinux']['status'].

然后你使用 selinux_status 在你的 如果 测试条件判断是否 SELinux 已启用、禁用或未安装。 在这三种不同的情况下,您都会显示一条反映 Selinux 状态的消息。

注意如何 如果 Jinja2 中的语句模仿 Python 的 如果 陈述; 只是不要忘记使用 {% endif %}.

现在回到您的项目目录并创建以下内容 selinux-status.yml 剧本:

[[email protected] plays]$ cat selinux-status.yml 
---
- name: Check SELinux Status
  hosts: all
  tasks:
    - name: Display SELinux Status
      debug:
        msg: "{{ ansible_facts['selinux']['status'] }}"

    - name: Create selinux.out using Jinja2
      template:
        src: selinux.j2
        dest: /tmp/selinux.out

继续运行剧本:

[[email protected] plays]$ ansible-playbook selinux-status.yml 

PLAY [Check SELinux Status] ****************************************************

TASK [Gathering Facts] *********************************************************
ok: [node4]
ok: [node2]
ok: [node3]
ok: [node1]

TASK [Display SELinux Status] **************************************************
ok: [node1] => {
    "msg": "enabled"
}
ok: [node2] => {
    "msg": "disabled"
}
ok: [node3] => {
    "msg": "enabled"
}
ok: [node4] => {
    "msg": "Missing selinux Python library"
}

TASK [Create selinux.out using Jinja2] *****************************************
changed: [node4]
changed: [node1]
changed: [node3]
changed: [node2]

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node2                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node3                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    node4                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0  

从剧本输出; 你可以看到 SELinux 在两者上都启用了 节点1 节点3. 我禁用了 SELinux 节点2 在运行剧本之前 节点4 没有安装 SELinux,因为 Ubuntu 使用 AppArmor 而不是 SELinux。

最后,您可以运行以下临时命令来检查 selinux.out 在所有托管主机上:

[[email protected] plays]$ ansible all -m command -a "cat /tmp/selinux.out"
node4 | CHANGED | rc=0 >>

	"SELINUX IS NOT AVAILABLE"
 
node2 | CHANGED | rc=0 >>

	"SELINUX IS DISABLED"
 
node3 | CHANGED | rc=0 >>

	"SELINUX IS ENABLED"
 
node1 | CHANGED | rc=0 >>

	"SELINUX IS ENABLED"

在 Jinja2 中循环

您可以使用 为了 Jinja2 中的语句来循环列表、范围等中的项目。对于 example,下面的 for 循环将遍历 范围(1,11)因此将显示 1->10 的数字:

{% for i in range(1,11) %}
	Number {{ i }}
{% endfor %}

注意 Jinja2 中的 for 循环是如何模仿 Python 的 for 循环的语法的; 再次不要忘记结束循环 {% endfor %}.

现在让我们创建一个完整的 example 这展示了 Jinja2 中 for 循环的强大功能。 切换到您的模板目录并创建以下内容 主机.j2 模板文件:

[[email protected] templates]$ cat hosts.j2 
{% for host in groups['all'] %}
{{ hostvars[host].ansible_facts.default_ipv4.address }}  {{ hostvars[host].ansible_facts.fqdn }}  {{ hostvars[host].ansible_facts.hostname }}
{% endfor %}

注意这里你使用了一个新的内置特殊(魔术)变量 主机变量 这基本上是一个字典,其中包含清单中的所有主机和分配给它们的变量。

您遍历清单中的所有主机,然后遍历每个主机; 您显示了三个变量的值:

  1. {{主机变量[host].ansible_facts.default_ipv4.address }}
  2. {{主机变量[host].ansible_facts.fqdn }}
  3. {{主机变量[host].ansible_facts.hostname }}

另请注意,您必须在同一行并排包含这三个变量以匹配 /etc/hosts 文件。

现在回到您的项目目录并创建以下内容 本地 dns.yml 剧本:

[[email protected] plays]$ cat local-dns.yml 
---
- name: Dynamically Update /etc/hosts File
  hosts: all
  tasks:
    - name: Update /etc/hosts using Jinja2
      template:
        src: hosts.j2
        dest: /etc/hosts

然后继续运行剧本:

[[email protected] plays]$ ansible-playbook local-dns.yml 

PLAY [Dynamically Update /etc/hosts File] *********************************************

TASK [Gathering Facts] ***************************
ok: [node4]
ok: [node2]
ok: [node1]
ok: [node3]

TASK [Update /etc/hosts using Jinja2] ***********************************************
changed: [node4]
changed: [node3]
changed: [node1]
changed: [node2]

PLAY RECAP **********************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node2                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node3                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node4                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0 

到目前为止一切看起来都不错; 现在运行以下临时命令来验证 /etc/hosts 文件已正确更新 节点1

[[email protected] plays]$ ansible node1 -m command -a "cat /etc/hosts"
node1 | CHANGED | rc=0 >>
10.0.0.5  node1.linuxhandbook.local  node1
10.0.0.6  node2.linuxhandbook.local  node2
10.0.0.7  node3.linuxhandbook.local  node3
10.0.0.8  node4.linuxhandbook.local  node4

完美的! 看起来像您预期的那样正确格式化。

我希望您现在意识到 Ansible 中 Jinja2 模板的强大功能。 请继续关注下一个教程,您将在其中学习使用 Ansible Vault 保护敏感信息和文件。