jinjia2模板的使用和管理大项目
1.使用jinja2模板部署自定义文件
1.1jinja2简介
Ansible将jinja2模板系统用于模板文件。Ansible还使用jinja2
语法来引用playbook
中的变量。
变量和逻辑表达式置于标记或分隔符之间。例如,jinja2模板将{% EXPR %}
用于表达式或逻辑(如循环),而{{ EXPR }}
则用于向最终用户输出表达式或变量的结果。后一标记在呈现时将被替换为一个或多个值,对最终用户可见。使用{# COMMENT #}
语法括起不应出现在最终文件中的注释。
在下例中,第一行中含有不会包含于最终文件中的注释。第二行中引用的变量被替换为所引用的系统事实的值。
下面演示使用copy和template模块及jinja2模板效果
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{# 这是一个注释 #}
{{ ansible_facts['default_ipv4']['address'] }} {{ansible_facts['hostname'] }}
---
- hosts: '192.168.8.132'
tasks:
- name: test
copy:
src: files/hosts
dest: /etc/hosts
[root@localhost ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{# 这是一个注释 #}
{{ ansible_facts['default_ipv4']['address]'}} {{ ansible_facts['hostname'] }}
使用jinja2模板变量
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{# 这是一个注释 #}
{{ ansible_facts['default_ipv4']['address'] }} {{ansible_facts['hostname'] }}
---
- hosts: 192.168.8.132
tasks:
- name: test
template:
src: files/hosts
dest: /etc/hosts
[root@localhost ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.8.132 localhost
[root@localhost ~]#
1.2构建jinja2模板
jinja2
模板由多个元素组成:数据、变量和表达式。在呈现jinja2
b模板时,这些变量和表达式被替换为对应的值。模板中使用的变量可以在playbook
的vars
部分中指定。可以将受管主机的事实用作模板中的变量。
请记住,可以使用ansible 192.168.8.132 -m setup ansible 192.168.8.132 -m setup | less命令来获取与受管主机相关的事实。
[root@master project]# ansible 192.168.8.132 -m setup //受控机的IP
192.168.8.132 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.122.1",
"192.168.8.132"
],
"ansible_all_ipv6_addresses": [
"fe80::94e:4f1:f9fe:b847",
"fe80::7f79:420c:8955:2ca7"
],
"ansible_apparmor": {
"status": "disabled"
},
......
注意:包含jinja2模板的文件不需要有任何特定的文件扩展名(如.j2)。但是,提供此类文件扩展名会让你更容易记住它是模板文件。
[root@master files]# mv hosts{,.j2}
[root@master files]# ls
hosts.j2
---
- hosts: 192.168.8.132
tasks:
- name: test
template:
src: files/hosts.j2
dest: /etc/hosts
1.3部署jinja2模板
jinja2
模板是功能强大的工具,可用于自定义要在受管主机上部署的配置文件。创建了适用于配置文件的jinja2
模板后,它可以通过template
模板部署到受管主机上,该模块支持将控制节点中的本地文件转移到受管主机。
若要使用template
模块,请使用下列语法。与src
键关联的值指定来源jinja2
模板,而与dest
键关联的值指定要在目标主机上创建的文件。
---
- hosts: 192.168.8.132
tasks:
- name: test
template:
src: files/hosts.j2
dest: /etc/hosts
template
模块还允许指定已部署文件的所有者、组、权限和SELINUX
上下文,就像file
模块一样。它也可以取用validate
选项运行任意命令(如visudo -c)
,在将文件复制到位之前检查该文件的语法是否正确。
有关更多详细信息,请参阅ansible-doc template
[root@master project]# ansible-doc template
1.4管理模板文件
为避免系统管理员修改Ansible
部署的文件,最好在模板顶部包含注释,以指示不应手动编辑该文件。
可使用ansible_managed
指令中设置的"Ansible managed"
字符串来执行此操作。这不是正常变量,但可以在模板中用作一个变量。ansible_managed
指令在ansible.cfg
文件中设置:
ansible_managed = Ansible managed
149 #ansible_managed = Ansible managed
150 ansible_managed = 这是一个测试
151 # by default, ansible-playbook will display "Skipping [host]" if it determines a task
要将ansible_managed字符串包含在jinja2模板内,请使用下列语法:
{{ ansible_managed }}
[root@master project]# cat playbook/files/hosts.j2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{# 这是一个注释 #}
{{ ansible_managed }}
{{ ansible_facts['default_ipv4']['address'] }} {{ansible_facts['hostname'] }}
[root@master project]#
---
- hosts: 192.168.8.132
tasks:
- name: test
template:
src: files/hosts.j2
dest: /etc/hosts
[root@localhost ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
这是一个测试
192.168.8.132 localhost
[root@localhost ~]#
1.5控制结构
用户可以在模板文件中使用jinja2控制结构,以减少重复输入,为play中的每个主机动态输入条目,或者有条件地将文本插入到文件中。
1.6使用循环
jinja2
使用for
语句来提供循环功能。在下例中,user
变量替换为users
变量中包含的所有值,一行一个值
{% for user in users %}
{{ user }}
{% endfor %}
以下示例模板使用for语句逐一运行users变量中的所有值,将myuser替换为各个值,但值为root时除外。
[root@master project]# cat playbook/vars/users.yml
users: //需要创建的四个用户做为变量
- pyd
- tom
- alice
[root@master project]# cat playbook/files/test.j2
{% for user in users %} //使用jinjia2循环
{{ user }}
========== //为了验证效果额外加的管道符
{% endfor %}
[root@master project]# cat playbook/test.yml
---
- hosts: 192.168.8.132
gather_facts: no //这么没有用到事实,关闭提升运行速度
vars_files: //引用用户变量
- vars/users.yml //变量位置
tasks:
- name: test
template:
src: files/test.j2 //使用jinjia2循坏模板
dest: /tmp/abc //目的地
[root@master project]#
[root@localhost ~]# cat /tmp/abc //验证效果
pyd
==========
tom
==========
alice
==========
[root@localhost ~]#
以下示例模板使用for语句逐一运行users变量中的所有值,将users替换为各个值,但值为pyd时除外。
[root@master project]# cat playbook/files/test.j2
{% for user in users if not user == "pyd" %}
{{ user }}
==========
{% endfor %}
[root@master project]#
[root@localhost ~]# cat /tmp/abc
tom
==========
alice
==========
[root@localhost ~]#
同理除了pyd都不替换
[root@master project]# cat playbook/files/test.j2
{% for user in users if user == "pyd" %}
{{ user }}
==========
{% endfor %}
[root@master project]#
[root@localhost ~]# cat /tmp/abc
pyd
==========
[root@localhost ~]#
loop.index变量扩展至循环当前所处的索引号。它在循环第一次执行时值为1,每一次迭代递增1。
[root@master project]# cat playbook/files/test.j2
{% for user in users %}
user number {{ loop.index }} - {{ user }} //循环的次数
{% endfor %}
[root@master project]#
[root@localhost ~]# cat /tmp/abc
user number 1 - pyd
user number 2 - tom
user number 3 - alice
[root@localhost ~]#
1.7使用条件句
jinja2
使用if语句来提供条件控制。如果满足某些条件,这允许用户在已部署的文件中放置一行。
在以下示例中,仅当finished
变量的值为True
时,才可将result
变量的值放入已部署的文件。
{% if finished %}
{{ result }}
{% endif %}
下面举例说明
[root@master project]# cat playbook/files/test.j2
{% if users %} //如果users存在的话我就显示它,如果不存在我就不显示它
{{ users }}
{% endif %}
[root@master project]#
1.8变量过滤器
jinja2
提供了过滤器,更改模板表达式的输出格式(例如,输出到果JSON
)。有适用于YAML
和JSON
等语言的过滤器。to_json
过滤器使用JSON
格式化表达式输出,to_yaml
过滤器则使用YAML
格式化表达式输出。
{{ output | to_json }}
{{ output | to_yaml }}
也有其他过滤器,如to_nice_json
和to_nice_yaml
过滤器,它们将表达式输出格式化为JSON
或YAML
等人类可读格式。
{{ output | to_nice_json }}
{{ output | to_nice_yaml }}
示例:
[root@master project]# cat playbook/files/test.j2
{{ users | to_nice_json }}
[root@master project]#
[root@localhost ~]# cat /tmp/abc
[
"pyd",
"tom",
"alice"
]
from_json
和from_yaml
过滤器相应要求JSON或YAML
格式的字符串,并对它们进行解析。
{{ output | from_json }}
{{ output | from_yaml }}
示例:
[root@master project]# cat playbook/files/test.j2
{{ users | from_yaml }}
[root@master project]#
[root@localhost ~]# cat /tmp/abc
['pyd', 'tom', 'alice']
[root@localhost ~]#
1.9变量测试
在Ansible Playbook中与when
子句一同使用的表达式是jinja2
表达式。用于测试返回值的内置Ansible
测试包括failed、changed、successded和skipped。
以下任务演示了如何在条件表达式内使用测试。
tasks:
...output omitted...
- debug: msg="the execution was aborted"
when: returnvalue is failed
2.管理大项目
2.1引用清单主机
主机模式用于指定要作为play
或临时命令的目标的主机。在最简单的形式中,清单中受管主机或主机组的名称就是指定该主机或主机组的主机模式。
在play
中,hosts
指定要针对其运行play
的受管主机。对于临时命令,以命令行参数形式将主机模式提供给ansible
命令。
本节中将通篇使用以下示例清单来演示主机模式。
[root@master project]# cat inventory
[httpd]
192.168.8.132 ansiable_user=root ansible_password=1
[mysql]
192.168.8.133 ansiable_user=root ansible_password=1
[php]
192.168.8.134 ansiable_user=root ansible_password=1
[root@master project]#
2.2受管主机
最基本的主机模式是单一受管主机名称列在清单中。这将指定该主机是清单中ansible
命令要执行操作的唯一主机。
在该playbook
运行时,第一个Gathering Facts
任务应在与主机模式匹配的所有受管主机上运行。此任务期间的故障可能导致受管主机从play中移除。
如果清单中明确列出了IP地址,而不是主机名,则可以将其用作主机模式。如果IP地址未列在清单中,我们就无法用它来指定主机,即使该IP地址会在DNS中解析到这个主机名。
以下示例演示了如何使用主机模式来引用清单中包含的IP地址。
[root@master project]# cat inventory
[httpd]
192.168.8.132 ansiable_user=root ansible_password=1
[mysql]
192.168.8.133 ansiable_user=root ansible_password=1
[php]
192.168.8.134 ansiable_user=root ansible_password=1
[root@master project]#
[root@master project]# ansible-playbook playbook/test.yml
PLAY [192.168.8.132] ***************************************************************************
TASK [test] ************************************************************************************
ok: [192.168.8.132]
PLAY RECAP *************************************************************************************
192.168.8.132 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
2.3 使用组指定主机
当组名称用作主机模式时,它指定Ansible将对属于该组的成员的主机执行操作。
---
- hosts: lab
记住,有一个名为all的特别组,它匹配清单中的所有受管主机。
---
- hosts: all
还有一个名为ungrouped的特别组,它包括清单中不属于任何其他组的所有受管主机:
---
- hosts: ungrouped
2.4使用通配符匹配多个主机
若要达成与all主机模式相同的目标,另一种方法是使用*
通配符,它将匹配任意字符串。如果主机模式只是带引号的星号,则清单中的所有主机都将匹配。
---
- hosts: '*'
---
- hosts: '!test1.example.com,development' //取反除了这个主机外所有的主机
也可使用*
字符匹配包含特定子字符串的受管主机或组。
例如,以下通配符主机模式匹配以.example.com
结尾的所有清单名称:
---
- hosts: '*.example.com'
以下示例使用通配符主机模式来匹配开头为192.168.8.
的主机或主机组的名称:
---
- hosts: '192.168.8.*'
以下示例使用通配符主机模式来匹配开头为datacenter
的主机或主机组的名称:
---
- hosts: 'data*'
2.5列表
可以通过逻辑列表来引用清单中的多个条目。主机模式的逗号分隔列表匹配符合任何这些主机模式的所有主机。
如果提供受管主机的逗号分隔列表,则所有这些受管主机都将是目标:
---
- hosts: labhost1.example.com,test2.example.com,192.168.8.132
如果提供组的逗号分隔列表,则属于任何这些组的所有主机都将是目标:
---
- hosts: lab,datacenter1
也可以混合使用受管主机、主机组和通配符,如下所示:
---
- hosts: 'lab,data*,192.168.8.132'
3.配置并行
3.1 使用分叉在ansible中配置并行
当Ansible处理playbook时,会按顺序运行每个play。确定play的主机列表之后,Ansible将按顺序运行每个任务。通常,所有主机必须在任何主机在play中启动下一个任务之前成功完成任务。
理论上,Ansible可以同时连接到play中的所有主机以执行每项任务。这非常适用于小型主机列表。但如果该play以数百台主机为目标,则可能会给控制节点带来沉重负担。
Ansible所进行的最大同时连接数由Ansible配置文件中的forks参数控制。默认情况下设为5,这可通过以下方式之一来验证。
[root@master project]# grep forks /etc/ansible/ansible.cfg
#forks = 5
[root@master project]#
[root@master project]# ansible-config dump|grep -i forks
DEFAULT_FORKS(default) = 5
[root@master project]#
例如,假设Ansible控制节点配置了5个forks的默认值,并且play具有10个受管主机。Ansible将在前5个受管主机上执行play中的第一个任务,然后在其他5个受管主机上对第一个任务执行第二轮。在所有受管主机上执行第一个任务后,Ansible将继续一次在5受管主机的组中的所有受管主机上执行下一个任务。Ansible将依次对每个任务执行此操作,直到play结束。
forks的默认值设置得非常保守。如果你的控制节点正在管理Linux主机,则大多数任务将在受管主机上运行,并且控制节点的负载较少。在这种情况下,通常可以将forks的值设置得更高,可能接近100,然后性能就会提高。
如果playbook在控制节点上运行很多代码,则应明智地提高forks限值。如果使用Ansible管理网络路由器和交换机,则大多数模块在控制节点上运行而不是在网络设备上运行。由于这会增加控制节点上的负载,因此其支持forks数量增加的能力将显著低于仅管理Linux主机的控制节点。
可以从命令行覆盖Ansible配置文件中forks的默认设置。ansible和ansible-playbook命令均提供-f或–forks选项以指定要使用的forks数量。
3.2滚动更新
通常,当Ansible运行play时,它会确保所有受管主机在启动任何主机进行下一个任务之前已完成每个任务。在所有受管主机完成所有任务后,将运行任何通知的处理程序。
但是,在所有主机上运行所有任务可能会导致意外行为。例如,如果play更新负载均衡Web服务器集群,则可能需要在进行更新时让每个Web服务器停止服务。如果所有服务器都在同一个play中更新,则它们可能全部同时停止服务。
避免此问题的一种方法是使用serial关键字,通过play批量运行主机。在下一批次启动之前,每批主机将在整个play中运行。
在下面的示例中,Ansible一次在两个受管主机上执行play,直至所有受管主机都已更新。Ansible首先在前两个受管主机上执行play中的任务。如果这两个主机中的任何一个或两个都通知了处理程序,则Ansible将根据这两个主机的需要运行处理程序。在这两个受管主机上执行完play时,Ansible会在接下来的两个受管主机上重复该过程。Ansible继续以这种方式运行play,直到所有受管主机都已更新。
---
- name: Rolling update
hosts: webservers
serial: 2
tasks:
- name: latest apache httpd package is installed
yum:
name: httpd
state: latest
notify: restart apache
handlers:
- name: restart apache
service:
name: httpd
state: restarted
假设上一示例中的webservers组包含5个Web服务器,它们位于负载均衡器后面。将serial参数设置为2后,play一次将运行两台Web服务器。因此,5台Web服务器中的大多数服务器将始终可用。
相反,如果不使用serial关键字,将同时在5台Web服务器上执行play和生成的处理程序。这可能会导致服务中断,因为Web服务将在所有Web服务器上同时重新启动。
重要
出于某些目的,每批主机算作在主机子集上运行的完整play。这意味着,如果整个批处理失败,play就会失败,这将导致整个playbook运行失败。
在设置了serial: 2的上一个场景中,如果出现问题并且处理的前2个主机的play失败,则playbook将中止,其余3个主机将不会通过play运行。这是一个有用的功能,因为只有一部分服务器会不可用,使服务降级而不是中断。
serial关键字也可以指定为百分比。此百分比应用于play中的主机总数,以确定滚动更新批处理大小。无论百分比为何,每一工序的主机数始终为1或以上。