在playbook中jinja2模板的使用

本章主要介绍在playbook中如何使用jinja2模板。
  • 什么是jinja2模板
  • 在jinja2模板文件中写if判断语句
  • 在jinja2模板文件中写for循环语句
  • handlers的使用
可以使用copy模块把本地的一个文件拷贝到远端机器,下面再次复习一下。
本章实验都在/home/lduan/demo4下操作,先把demo4目录创建出来并把ansible.cfg
和hosts拷贝进去,命令如下。
[root@pp ~]# mkdir demo4
[root@pp ~]# cp ansible.cfg hosts demo4/
[root@pp ~]# cd demo4/
[root@pp demo4]# 
练习1:用copy拷贝一个文件到db主机组。
有一个文件aa.txt,内容如下。
[root@pp demo4]# cat aa.txt 
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| 我的 IP 地址是: {{ansible_default_ipv4.address}}
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
[root@pp demo4]# 
这个文件中包含一个 fact变量 ansible_default_ipv4.address。
写一个 playbook,内容如下。
[root@pp demo4]# cat 1.yaml 
---
- hosts: db
  tasks:
  - name: 拷贝一个文件到远端主机
    copy: src=aa.txt dest=/opt/aa.txt
[root@pp demo4]# 
运行此playbook,命令如下。
[root@pp demo4]# ansible-playbook 1.yaml 

PLAY [db] ********************************************************************************

TASK [Gathering Facts] *******************************************************************
ok: [up]
ok: [lp]

TASK [拷贝一个文件到远端主机] ***********************************************************************
ok: [lp]
ok: [up]

PLAY RECAP *******************************************************************************
lp                         : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
up                         : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@pp demo4]# 
现在已经把本地的aa.,txt拷贝到server2和 server3的/opt目录中了。下面查看这两台主机
上 /opt/aa.txt的内容,命令如下。
[root@pp demo4]# ansible db -m shell -a "cat /opt/aa.txt"
lp | CHANGED | rc=0 >>
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| 我的 IP 地址是: {{ansible_default_ipv4.address}}
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
up | CHANGED | rc=0 >>
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| 我的 IP 地址是: {{ansible_default_ipv4.address}}
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
[root@pp demo4]# 
可以看到,当用copy铂贝一个文件到远端机器时,如果这个文件中有变量,铂贝过去的
文件中的变量并不会变成具体的值。
如果希望文件考别过去之后,文件中的变量变成具体的值,那么就不能使用copy模块,而
是要使用template模块了。
练习2:修改1.yaml的内容如下。
[root@pp demo4]# mv aa.txt aa.j2
[root@pp demo4]# 
同时修改1.yaml中对应的内容,如下所示。
[root@pp demo4]# cat 1.yaml 
---
- hosts: db
  tasks:
  - name: 拷贝一个文件到远端主机
    template: src=aa.j2 dest=/opt/aa.txt
[root@pp demo4]# 
这里如果jinja2模板文件没有写路径,例如,例子中 src=aa.j2的aa.j2没有写路径,则优
先到当前目录的templates 中找aa.j2,如果没有,则到当前目录中找aa.j2.
练习3:验证,命令如下。
root@pp demo4]# mkdir templates
[root@pp demo4]# 
在templates目录中创建aa.j2,内容如下。
[root@pp demo4]# cat templates/aa.j2
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| 我的主机名是: {{ansible_fqdn}}
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
[root@pp demo4]# 
这样我们就有两个aa.j2了,还有一个是当前目录下的aa.j2
再次运行此playbook,命令如下。
[root@pp demo4]# ansible-playbook 1.yaml 
查看两台主机上/opt/aa.txt 的内容,命令如下。
[root@pp demo4]# ansible db -m shell -a "cat /opt/aa.txt"
up | CHANGED | rc=0 >>
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| 我的主机名是: up
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
lp | CHANGED | rc=0 >>
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
| 我的主机名是: lp
+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
[root@pp demo4]# 
这里可以看到显示的主机名,所以是 templates目录中的aa.j2生效了。
33.1 if 判断
在jinja2模板文件中,我们也是可以使用if判断语句的,语法格式如下。
1 {% if 判断1 %}
2 内容1
3 {% elif 判断2 %}
4 内容2
5 ...多个elif...
6 {% else %}
7 内容3
8 {% endif %}
注意
(1)“%”两边有没有空格都可以,不过所有的“%”前后空格要保持一致,即要有都
有,要没有都没有。
(2)if和elif中的内容如果太长了,可以另起一行写。
如果判断1成立,则打印内容1,后面的条件不再判断,直接跳转到endif后面的内容;如果
判断1不成立,则执行elif后面的判断2,如果成立则打印内容2,后面的条件不再判断,直接
跳转到endif后面的内容。以此类推,如果所有的f和elif都不成立,则打印else中的内容。
(3)elif和 else不是必需的。
练习:写一个 jinja2模板文件,内容如下。
[root@pp demo4]# cat templates/bb.j2 
1111
{% if ansible_fqdn=="server2.rhce.cc" %}
{{ansible_fqdn}}
{% else %}
aaaa
{% endif %}
a3333
[root@pp demo4]# 
这里jinja2模板所生成的文件一共会产生3行内容,第一行的1111和第三行的3333是必打
印出来的,第二行的内容具体是什么要看情况。如果在server2上执行则显示主机名,如果
在其他机器上执行则显示aaaa。
写一个playbook,内容如下。
[root@pp demo4]# cat 2.yaml 
---
- hosts: db
  tasks:
  - name: 拷贝一个文件过去
    template: src=bb.j2 dest=/opt/bb.conf
[root@pp demo4]# 
这里是把templates/bb.j2拷贝到两台机器的/opt中并命名为bb.conf。运行此
playbook,命令如下。
[root@pp demo4]# ansible-playbook 2.yaml 

PLAY [db] ********************************************************************************

TASK [Gathering Facts] *******************************************************************
ok: [lp]
ok: [up]

TASK [拷贝一个文件过去] **************************************************************************
changed: [lp]
changed: [up]

PLAY RECAP *******************************************************************************
lp                         : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
up                         : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@pp demo4]# 
查看两台机器上/opt/bb.conf的内容,命令如下。
[root@pp demo4]# ansible db -m shell -a "cat /opt/bb.conf"
up | CHANGED | rc=0 >>
1111
aaaa
a3333
lp | CHANGED | rc=0 >>
1111
aaaa
a3333
[root@pp demo4]# 
可以看到,server2的/opt/bb.conf的第二行显示的是主机名,server3的/opt/bb.conf
的第二行显示的是 aaaa。
在if和 elif后面是可以写多个判断的,用or或and作为连接符,语法如下。
判断1 or 判断11:判断1和判断11只要有一个成立就算成立,只有全部不成立才算不成
立。
判断1 and 判断11:判断1和判断11只有全部成立才算成立,只要有一个不成立就算不成
立。
查看下面的 jinja2模板文件。
[root@pp demo4]# vim templates/cc.j2
[root@pp demo4]# cat templates/cc.j2 
1111
{% if ansible_fqdn=="server2.rhce.cc"
and
ansible_distribution_major_version=="7" %}
{{ansible_fqdn}}
{% else %}
aaaa
{% endif %}
3333
[root@pp demo4]# 
这里jinja2模板会打印3行内容,第一行和第三行的内容是固定的,为1111和3333。第二
行的内容是什么,要看是否满足条件,这里判断被管理主机名为server2.rhce.cc及系统主版
本号为7,二者都要满足,第二行才会显示主机名,否则显示 aaaa。需要注意的是,这里if
判断语句太长,特意写成了3行也是没问题的。 写一个playbook,内容如下。
[root@pp demo4]# cat 3.yaml 
---
- hosts: db
  tasks:
  - name: 我要拷贝一个文件过去
    template: src=cc.j2 dest=/opt/cc.conf
[root@pp demo4]# 
运行此playbook。命令如下。
[root@pp demo4]# ansible-playbook 3.yaml 

PLAY [db] ********************************************************************************

TASK [Gathering Facts] *******************************************************************
ok: [lp]
ok: [up]

TASK [我要拷贝一个文件过去] ************************************************************************
changed: [up]
changed: [lp]

PLAY RECAP *******************************************************************************
lp                         : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
up                         : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@pp demo4]# 
查看两台机器上/opt/cc.conf的内容,命令如下。
[root@pp demo4]# ansible db -m shell -a "cat /opt/cc.conf"
up | CHANGED | rc=0 >>
1111
aaaa
3333
lp | CHANGED | rc=0 >>
1111
aaaa
3333
[root@pp demo4]# 
33.2 for循环
一个列表中有多个元素,如果需要依次对列表中的每个元素操作,则可以使用for循环来
实现,for 循环的语法如下。
1 {% for i in 列表名 %}
2 {{i}}
3 {% endfor %}
这里首先把列表中的第一个元素赋值给i,执行中间的操作;然后把第二个元素赋值给i执行
中间的操作,以此类推,直到把最后一个元素赋值给i。看下面的例子。
[root@pp demo4]# cat templates/dd.conf.j2 
{% set list1=['aa','bb','cc'] %}
1111
{% for i in list1 %}
{{i}}
{% endfor %}
5555
[root@pp demo4]# 
这里手动在jinja2模板中定义了一个列表(注意定义列表的方式)list1,里面有3个元素,分
别为aa、bb、cc。然后对这个列表的内容进行循环。
这里jinja2模板生成的文件有5行内容,第1行和第5行的内容是固定的,为1111和5555。
第2~4行是循环列表list1 中的值,为aa、bb、cc.
写一个 playbook,内容如下。
[root@pp demo4]# cat 4.yaml 
---
- hosts: up
  tasks: 
  - name: 拷贝-个文件到远端主机
    template: src=dd.conf.j2 dest=/opt/dd.conf
[root@pp demo4]# 
运行此playbook,内容如下。
[root@pp demo4]# ansible-playbook 4.yaml 

PLAY [up] ********************************************************************************

TASK [Gathering Facts] *******************************************************************
ok: [up]

TASK [拷贝-个文件到远端主机] ***********************************************************************
changed: [up]

PLAY RECAP *******************************************************************************
up                         : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@pp demo4]# 
查看server2 上/opt/dd.conf的内容,命令如下。
[root@pp demo4]# ansible up -m shell -a "cat /opt/dd.conf"
up | CHANGED | rc=0 >>
1111
aa
bb
cc
5555
[root@pp demo4]# 
除了jinja2模板中手动定义的列表,一般情况下,我们会在playbook中定义列表,然后对
列表中的元素进行循环。
33.3 handlers
前面讲了模板的使用,但是后期我们可能需要修改模板的内容,然后重新拷贝到各个机
器,此时需要重启httpd服务才会生效,先看下面的例子。
先获取 httpd.conf的配置文件,获取的httpd.conf中没有任何空白行和注释行。
[root@pp demo4]# egrep -v '#/^#' /etc/httpd/conf/httpd.conf > httpd.conf.j2
[root@pp demo4]# 
修改此httpd.conf.j2的第三行,把原来Listen后面的端口80换成{{myport}},让
httpd.confj2引用myport变量,内容如下。
[root@pp demo4]# head -3 httpd.conf.j2 
为了不让例子变得太复杂,先临时关闭server2上的 SELinux,命令如下。
[root@pp demo4]# setenforce 0
[root@pp demo4]# getenforce 0
Permissive
[root@pp demo4]# 
写一个名称为hand-1.yaml的playbook,命令如下。
[root@pp demo4]# cat hand-1.yaml 
---
- hosts: up
  vars:
    myport: 80
  tasks:
  - name: tasks1安装httpd
    yum: name=httpd state=installed
  - name: tasks拷贝备注文件
    template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
  - name: tasks3启动httpd服务
    service: name=httpd state=started
[root@pp demo4]# 
第一个task用于安装 htpd,第二个task用于把模板httpd.conf.j2拷贝到被管理机器,第
三个 task用于启动httpd服务,第一次是可以正常运行的。
下面修改myport的值为808,内容如下。
[root@pp demo4]# ansible-playbook hand-1.yaml 

PLAY [up] ********************************************************************************

TASK [Gathering Facts] *******************************************************************
ok: [up]

TASK [tasks1安装httpd] *********************************************************************
changed: [up]

TASK [tasks拷贝备注文件] ***********************************************************************
changed: [up]

TASK [tasks3启动httpd服务] *******************************************************************
changed: [up]

PLAY RECAP *******************************************************************************
up                         : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

第一个task用于安装httpd,第二个task用于把模板httpd.conf.2拷贝到被管理机器,第
三个 task用于启动httpd服务,第一次是可以正常运行的。
下面修改myport 的值为808,内容如下。
[root@pp demo4]# cat hand-1.yaml 
---
- hosts: up
  vars:
    myport: 808
  tasks:
  - name: tasks1安装httpd
    yum: name=httpd state=installed
  - name: tasks拷贝备注文件
    template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
  - name: tasks3启动httpd服务
    service: name=httpd state=started
[root@pp demo4]# 
再次运行此playbook,因为 httpd已经安装过了,状态并没有发生任何改变,所以这次
第一个task不会执行。因为改变了myport的值,httpd.conf.j2中的端口也就发生了变化,
第二个task再次把模板文件拷贝到被管理机器。因为httpd已经处于启动状态,所以第三个
task也不会执行,从而导致第二个task铂贝过去的新模板文件并不会生效,因为httpd并没有
重启。
这里把第三个task中的 started改成了restarted,如果我们修改了myport的值之后运行
此playbook,第二个task 会正常执行,因为 httpd.conf,j2中的端口发生了变化。第三个
task总是会执行,因为第三个task中state 的值为restarted,这样所做的修改会生效。
但是如果什么都不修改,只是重复运行playbook,第三个task也会重启。所以,这里就
有一个问题了,第三个task中的state不论写started还是 restarted,都不正确。
写started,配置文件修改了不会重启,所以不会生效。
写restarted,配置文件不修改也会重启。
但我们想要的是,只有第二个 task 发生了变化才会重启 httpd服务,否则是
不需要重启的,这种情况下,就需要使用handler了。
handler的用法如图33-1所示。
注意,这里handlers和 tasks是同级的。
在 tasks中定义了3个 task,在 handlers中定义了2个handler。当运行
playbook时,handlers中定义的handler并不会运行,它们只是在被触发时才
会运行。在某个 task中写了一个notify,其后面的值要与handlers中的某个
handler 的name进行匹配。例如,图33-1中,编号2中 notify后面的值是
aaa,这个值与第一个 handler(编号4)的name值一样,这样当运行playbook时,如果编
号2这个task执行了,则会触发编号4这个 handler。
修改hand-1.yaml 的内容如下。
[root@pp demo4]# cat hand-1.yaml 
---
- hosts: up
  vars:
    myport: 808
  tasks:
  - name: tasks1安装httpd
    yum: name=httpd state=installed
  - name: tasks拷贝备注文件
    template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
  - name: tasks3启动httpd服务
    service: name=httpd state=started
  handlers:
  ‐ name: restart httpd1
     service: name=httpd state=restarted
  ‐ name: restart httpd2
    service: name=httpd state=restarted

第三个task的state仍然设置为started,只要 httpd是启动的,第三个task就不会执行。 这里定义了两个handler,名称分别为restart httpd1和restart httpd2。在第二个task中, 通过notify指定了第一个 handler,即 restart httpd1。 如果不修改myport 的值,则第二个task不会执行,从而不会触发restart httpdl。如果 myport 的值发生了变化,则第二个task 会执行,从而会触发restart httpdl,重启 httpd服 务使得我们所做的修改生效。

你可能感兴趣的:(linux,python,linux,开发语言)