Ansible 中的 Templates
Ansible 中的模板
狭义来讲,就是一个特定后缀的文本文件,在使用时,可以根据此文件,将部份关健内容进行替换,生 成新的文件,以达到在不同主机中,使用不同配置的作用,其中的逻辑部份或动态代码,用 jinja2 来实 现。
广义来讲,要模板文件,变量,变量文件,参数,条件判断,流程控制,playbook 中的 template 模块 调用时相互配合,来实现预期的效果。
Jinjia2 语言
Jinja2 是 Python 下一个被广泛应用的模版引擎,他的设计思想来源于 Django 的模板引擎,并扩展了其 语法和一系列强大的功能。
Jinjia2 的特点
-
沙箱中执行
-
强大的 HTML 自动转义系统保护系统免受 XSS
-
模板继承
-
及时编译最优的 python 代码
-
可选提前编译模板的时间
-
易于调试,异常的行数直接指向模板中的对应行
-
可配置的语法
jinjia2 中的数据类型
数据类型 | 说明 |
---|---|
字符串 | 使用单引号或双引号引起来的 |
数值型 | 包括整数和浮点数,有小数点表示浮点数,在 Python中,12 和 12.0 的含义是不一样的 |
列表 | [a,b,c,d] |
元组 | (a,b,c,d) |
字典 | {key1:val1,key2:val2,key3:val3} |
布尔 | true/false |
jinjia2 中的运算符
bash
#算术运算符
+ #加法
- #减法
* #乘法,两个数值相乘,也可用于字符串重复,{{ 'a'*10 }} 表示10个a
/ #除法,会保留小数
// #除法,只保留整数 {{ 20 // 7 }} 的值为2
% #求余
** #次方运算 {{ 2**3 }}表示 2的3次方,值为8
#比较运算符
== #等于
!= #不等于
> #大于
>= #大于或等于
< #小于
<= #小于或等于
#逻辑运算符
or #或运算
and #且运算
not #取反
true #真
false #假
jinjia2 中的条件判断
If 语句
bash
{% if EXPR %}
...
{% endif %}
{% if EXPR %}
...
{% else %}
...
{% endif %}
{% if EXPR %}
...
{% elif EXPR %}
...
{% else %}
...
{% endif %}
jinjia2 中的流程控制
For 循环
bash
{% for i in EXPR %}
{% endfor %}
Template 的使用规范
template 文件建议存放在和 playbook 文件同级目录的 templates 目录下,且以 .j2 结尾,这样在 playbook 中使用模板文件时,就不需要指定模板文件的路径。
bash
.
├── templates
│ └── test.j2 #模板文件
└── test.yaml #playbook文件
Template 的基本替换
bash
.
├── templates
│ └── test-1.j2
└── test-1.yaml
cat test-1.yaml
- hosts: group1
gather_facts: yes
vars:
var1: 'abcd'
var2: 3
var3: 3.14
tasks:
- name: template-task-1
template: src=test-1.j2 dest=/tmp/ansible-test-1.txt
cat templates/test-1.j2
string------{{ var1 }}---{{ var1*3 }}
int---------{{ var2 }}---{{ var2+10 }}---{{ var2-10 }}---{{ var2*10 }}---{{ var2/10 }}---{{ var2//10 }}---{{ var2**2 }}
float-------{{ var3 }}---{{ var3+10 }}---{{ var3-10 }}---{{ var3*10 }}---{{ var3/10 }}---{{ var3//10 }}---{{ var3**2 }}
facts-------{{ ansible_default_ipv4.address }}
ansible-playbook test-1.yaml
#查看远程主机
cat /tmp/ansible-test-1.txt
string------abcd---abcdabcdabcd
int---------3---13----7---30---0.3---0---9
float-------3.14---13.14----6.859999999999999---31.400000000000002---0.314---0.0---9.8596
facts-------10.0.0.213
Template 的 for 循环和 if 判断
bash
cat test-2.yaml
- hosts: rocky
gather_facts: no
vars:
var1: 'abcd'
var2: 3
var3: 3.14
var4:
- tom
- jerry
var5: [ {name: tom, age: 123},{name: jerry, age: 456} ]
var6:
- {id: 1, ip: 1.1.1.1, port: 11, master: 1}
- {id: 2, ip: 2.2.2.2, port: 22, master: 0, node: slave-2}
- {id: 3, ip: 3.3.3.3, port: 33, master: 0, node: slave-3}
tasks:
- name: template-task-2
template: src=test-2.j2 dest=/tmp/ansible-test-2.txt
cat templates/test-2.j2
{% if var1 == 'abcd' %}
var1 is abcd
{% endif %}
===========================
{% if var1 != 'abcd' %}
var1 is not abcd
{% else %}
var1 is abcd
{% endif %}
===========================
{% if var2 > 3 %}
var2 > 3
{% elif var2 == 3 %}
var2 == 3
{% else %}
var2 < 3
{% endif %}
============================
{% for i in range(1,4) %}
---{{ i }}---{{ i+10 }}
{% endfor %}
============================
{% for i in var4 %}
---{{ i }}
{% endfor %}
===========================
{% for i in var5 %}
---{{ i.name }}---{{ i.age }}
{% endfor %}
============================
{% for i in var6 %}
---{{ i.ip }}:{{ i.port }}----{% if i.master == 1 %}master{% else %}slave{% endif %}{% if i.node is defined %}----node-{{ i.node }}{% endif %}
{% endfor %}
ansible-playbook test-2.yaml
#查看远程主机
cat /tmp/ansible-test-2.txt
var1 is abcd
===========================
var1 is abcd
===========================
var2 == 3
============================
---1---11
---2---12
---3---13
============================
---tom
---jerry
===========================
---tom---123
---jerry---456
============================
---1.1.1.1:11----master
---2.2.2.2:22----slave----node-slave-2
---1.1.1.1:11----slave----node-slave-3
Ansible 中的流程控制
在 ansible 的 task 中,如果要重复执行相同的模块,则可以使用循环的方式来实现
loop (with_items) 迭代
对于迭代项的引用,要使用内置变量 item 来引用,这是固定写法。
迭代元素使用 with_items 来锚定列表,列表中可以是单项元素,也可以是字典。
从 ansible2.5 以后的版本中,使用 loop 来代替 with_items。
bash
cat loop-1.yaml
- hosts: 10.0.0.166
gather_facts: no
tasks:
- name: with_items-task
debug: msg={{ item }}
with_items:
- tom
- jerry
- spike
- name: loop-task
debug: msg={{ item.name }}--{{ item.age }}
loop: [ {name: tom, age: 10},{name: jerry, age: 20},{name: spike, age: 30}]
ansible-playbook loop-1.yaml
PLAY [10.0.0.166]
********************************************************************************
*
TASK [with_items-task]
****************************************************************************
ok: [10.0.0.166] => (item=tom) => {
"msg": "tom"
}
ok: [10.0.0.166] => (item=jerry) => {
"msg": "jerry"
}
ok: [10.0.0.166] => (item=msg-3) => {
"msg": "spike"
}
TASK [loop-task]
********************************************************************************
**
ok: [10.0.0.166] => (item={'name': 'tom', 'age': 10}) => {
"msg": "tom--10"
}
ok: [10.0.0.166] => (item={'name': 'jerry', 'age': 20}) => {
"msg": "jerry--20"
}
ok: [10.0.0.166] => (item={'name': 'spike', 'age': 30}) => {
"msg": "spike--30"
}
PLAY RECAP
********************************************************************************
********
10.0.0.166 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
until 循环
使用 until 也可以控制一个 task 重复执行,until 后面的值或表达式为 true 的时候,才退出重试,即在 task 没有获得预期值的情况下,会一直重复执行,直到得到预期结果。
until 默认重试三次,每次重试之间间隔 5S,可自定义修改。
bash
cat until-2.yaml
- hosts: 10.0.0.166
gather_facts: no
tasks:
- shell: cat /tmp/ansible-until
register: rs #注册变量rs
until: rs.stdout=="123" #rs.stdout就是cat命令的标准输出
retries: 5 #重试 5次,默认值为 3
delay: 2 #重试间隔时间,默认 5S
with_lines 逐行处理
with_lines 可以将一条命令的执行结果逐行调用同一个 task 进行处理
bash
cat with_line.yaml
- hosts: localhost
gather_facts: no
tasks:
- name: with_lines-1
debug: msg={{ item }}
with_lines: cat /etc/fstab
[root@ubuntu24 ~]# ansible-playbook with_line.yaml
PLAY [localhost] ***********************************************************************************************************************************************************************
TASK [with_lines-1] ********************************************************************************************************************************************************************
ok: [localhost] => (item=# /etc/fstab: static file system information.) => {
"msg": "# /etc/fstab: static file system information."
}
ok: [localhost] => (item=#) => {
"msg": "#"
}
ok: [localhost] => (item=# Use 'blkid' to print the universally unique identifier for a) => {
"msg": "# Use 'blkid' to print the universally unique identifier for a"
}
ok: [localhost] => (item=# device; this may be used with UUID= as a more robust way to name devices) => {
"msg": "# device; this may be used with UUID= as a more robust way to name devices"
}
ok: [localhost] => (item=# that works even if disks are added and removed. See fstab(5).) => {
"msg": "# that works even if disks are added and removed. See fstab(5)."
}
ok: [localhost] => (item=#) => {
"msg": "#"
}
ok: [localhost] => (item=# <file system> <mount point> <type> <options> <dump> <pass>) => {
"msg": "# <file system> <mount point> <type> <options> <dump> <pass>"
}
ok: [localhost] => (item=# / was on /dev/ubuntu-vg/ubuntu-lv during curtin installation) => {
"msg": "# / was on /dev/ubuntu-vg/ubuntu-lv during curtin installation"
}
ok: [localhost] => (item=/dev/disk/by-id/dm-uuid-LVM-H5gHRIOORHBs4Od7xVmGBfQE2ZJSd1kxqhie9niuE1re5q1XVMJmVvoQ7stlUKe2 / ext4 defaults 0 1) => {
"msg": "/dev/disk/by-id/dm-uuid-LVM-H5gHRIOORHBs4Od7xVmGBfQE2ZJSd1kxqhie9niuE1re5q1XVMJmVvoQ7stlUKe2 / ext4 defaults 0 1"
}
ok: [localhost] => (item=# /boot was on /dev/sda2 during curtin installation) => {
"msg": "# /boot was on /dev/sda2 during curtin installation"
}
ok: [localhost] => (item=/dev/disk/by-uuid/8a0cc0fa-cc21-4fe4-ab25-4a43540d9f02 /boot ext4 defaults 0 1) => {
"msg": "/dev/disk/by-uuid/8a0cc0fa-cc21-4fe4-ab25-4a43540d9f02 /boot ext4 defaults 0 1"
}
ok: [localhost] => (item=/swap.img none swap sw 0 0) => {
"msg": "/swap.img\tnone\tswap\tsw\t0\t0"
}
PLAY RECAP *****************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
条件判断 when
when 语句可以实现选择执行,即根据条件判断的结果决定是否执行 task,条件判断的数据来源可以是 变量,前面的 task 的执行结果等
bash
#判断变量是否被定义
- hosts: localhost
gather_facts: no
tasks:
- debug: msg="undefined"
when: tom is undefined
#循环判断
- hosts: localhost
gather_facts: no
tasks:
- debug: msg={{ item }}
with_items: [1,2,3,4,5]
when: item > 3
#根据不同的系统安装不同的软件
- hosts: 10.0.0.166
#gather_facts: yes
tasks:
- name: redhat-yum-task
yum: name=httpd state=present
when: ansible_distribution_file_variety == "RedHat"
- name: debian-apt-task
apt: name=apache2 state=present update_cache=yes
when: ansible_distribution_file_variety=="Debian"
fail_when 取反
使用 when 是保证让条件成立时执行 task,使用 fail_when 表示满足时 task 是执行失败状态
block 分组
使用 block 可以对 task 任务进行分组,将多个 task 任务放到一个 block 下,可以在写一个 when 判断 的情况下调用多个 task 任务
bash
- hosts: localhost
tasks:
- name: task-1
debug: msg=task-1
when: ansible_distribution_file_variety == 'RedHat'
- name: task-2
debug: msg=task-2
when: ansible_distribution_file_variety == 'RedHat'
#使用分组写法,一个block 中可以有多个task
- hosts: localhost
tasks:
- block:
- debug: msg=task-1
- debug: msg=task-2
when: ansible_distribution_file_variety == 'RedHat'
changed_when
只有在 task 的执行结果返回状态为 changed 的时候,我们才认为该 task 是真实执行了,在远程主机上 产生了数据变化,但是在 ansible 中,不是所有模块都具有幂等性,对于某些不会产生数据变化的 task ,ansible 也会给出 changed 输出,我们可以使用 changed_when 来避免这一情况
bash
#对于此 playbook,task-1 不会在远程主机上产生任何变化,task-2 总会产生变化,但每次执行,都会产生 changed 的提示
- hosts: localhost
tasks:
- name: task-1
shell: id
- name: task-2
file: path=/tmp/changed-when state=touch
#对于确定不会发生 change 的 task,可以使用 changed_when 来关闭changed 提示
- hosts: localhost
tasks:
- name: task-1
shell: id
changed_when: false
- name: task-2
file: path=/tmp/changed-when state=touch
滚动执行
默认情况下, ansible 从上到下执行,如果一个 playbook 中有多个 task,在有多台远程主机的情况 下,需要在所有远程主机上执行完当前的 task 之后才执行下一个 task,如果主机过多,或者需要执行的 task 比较消耗时间,则会导致所有主机都处于一个执行中状态
滚动执行,深度优先
bash
- hosts: group1
serial: 1 #每次在一台机上执行完所有task,可以写成百分比,如 "20%" 先执行 20% 的主机
tasks:
- name: task-1
debug: msg=task-1
- name: task-2
debug: msg=task-2
委派执行
利用委派执行可以在非指定的主机上执行 task
bash
- hosts: rocky
tasks:
- name: task-1
shell: hostname -I
delegate_to: localhost #委派给当前主机执行
register: rs
- name: task-2
debug: msg={{ rs.stdout }}
run_once 只执行一次
利用 run_once 指令可以让 task 只执行一次,而非在所有被控主机都执行。
bash
- hosts: rocky
tasks:
- name: task-1
debug: msg=task-1
- name: task-2
debug: msg=task-2
run_once: true
修改环境变量
使用 environment 选项可以修改目标主机的环境变量,environment 配置的环境变量只在当前的 task 中有效
bash
- hosts: "10.0.0.150"
gather_facts: yes
tasks:
- name: task-1
shell: echo $PATH
register: rs
environment:
PATH: /usr/test:{{ ansible_env.PATH }} #ansible_env 从 gather_facts 中收集而来
- name: task-2
debug: msg={{ rs.stdout }}
- name: task-3
shell: echo $PATH
register: rs2
- name: task-4
debug: msg={{ rs2.stdout }}
[root@ubuntu ~]# ansible-playbook env.yaml
PLAY [10.0.0.150]
********************************************************************************
*******************
TASK [Gathering Facts]
********************************************************************************
*******************
ok: [10.0.0.150]
TASK [task-1]
********************************************************************************
*******************
changed: [10.0.0.150]
TASK [task-2]
********************************************************************************
*******************
ok: [10.0.0.150] => {
"msg": "/usr/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
}
TASK [task-3]
********************************************************************************
*******************
changed: [10.0.0.150]
TASK [task-4]
********************************************************************************
*******************
ok: [10.0.0.150] => {
"msg": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
}
PLAY RECAP
********************************************************************************
*********************
10.0.0.150 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Yaml 文件互相调用
include_tasks 包含
利用 include 或 include_tasks 可以在某个 task 中调用其它的只有 task 内容的 yaml 文件,include 在 2.16 版本之后被弃用,建议使用 include_tasks 来实现包含。include_tasks 一次只能引用一个 yaml 文 件
bash
#a.yaml
- hosts: localhost
tasks:
- name: task-1
debug: msg=task-1
- name: task-2
include_tasks: b.yaml
- name: task-3
include_tasks: c.yaml
#b.yaml
- name: "b.yaml-task-1"
debug: msg="b.yaml-task-1"
- name: "b.yaml-task-2"
debug: msg="b.yaml-task-2"
#c.yaml
- name: "c.yaml-task-1"
debug: msg="c.yaml-task-1"
注意:一个task 只有一个 include_tasks 生效
import_playbook 合并多个 playbook 文件
import_playbook 可以将多个包含完整内容的 yaml 文件交由一个 yaml 统一调用
bash
cat main.yaml
- import_playbook: task1.yaml
- import_playbook: task2.yaml