我们之前的例子都是使用ansible <host-pattern> [-m module_name] [-a args]这种语法来完成的。对于这种每次使用一个模块,只能执行一个任务的方式,称为ad-hoc(点对点模式),相当与被控节点在bash中执行一句shell命令。如果想要简单使用ansible的话,ad-hoc配合shell脚本可以满足大部分情况了。
ansible之所以能成为当今自动化运维的一杆大旗是基于它提供了另一种任务方式——playbook。
playbook是剧本的意思,而之前提到的inventory就像是演员表,ansible的程序执行可以形象的看成拍电影,其中playbook中的每一个play就相当于是电影的每个片段,每一个play都可以有多个任务(tasks),相当于电影片段中的每一幕。每个play中可以定义专属的变量,对应电影片段中的场景布置,每个play中都需要指定执行该play的主机,即当期上场的演员名单。等等的这些组织多个任务多种行为的方式,正是ansible强大的地方——“编排”,而编写这些playbook的我们,即是整个电影的导演。
playbook示例 环境清单
主机名
IP地址
操作系统版本
内核版本
角色
slions_pc1
192.168.100.10
CentOS 7.6.1810
3.10.0-957.el7.x86_64
控制节点
slions_pc2
192.168.100.11
CentOS 7.6.1810
3.10.0-957.el7.x86_64
被控节点
slions_pc3
192.168.100.12
CentOS 7.6.1810
3.10.0-957.el7.x86_64
被控节点
所有的主机上都已启动sshd服务并保持默认配置(监听22端口)。
为了后续控制目标节点方便些,事先在控制节点将所有节点的DNS解析配置好了。并且配置了控制节点到被控节点间的免密。
1 2 3 4 5 6 [root@slions_pc1 ~]# cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.100.10 slions_pc1 192.168.100.11 slions_pc2 192.168.100.12 slions_pc3
inventory文件 1 2 3 4 5 6 7 [root@slions_pc1 ansible]# cat /etc/ansible/hosts [leader] slions_pc1 slions_pc2 [worker] slions_pc3
playbook文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [root@slions_pc1 ansible]# cat demo.yml --- - name: play1 hosts: leader gather_facts: false tasks: - name: task1 in play1 debug: msg: "output task1 in play1" - name: task2 in play1 debug: msg: "output task2 in play1" - name: play2 hosts: worker gather_facts: false tasks: - name: task1 in play2 debug: msg: "output task1 in play2" - name: task2 in play2 debug: msg: "output task2 in play2"
使用ansible-playbook命令执行这个playbook:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 [root@slions_pc1 ansible]# ansible-playbook demo.yml PLAY [play1] **************************************************************************************************************************************************************************** TASK [task1 in play1] ******************************************************************************************************************************************************************* ok: [slions_pc1] => { "msg": "output task1 in play1" } ok: [slions_pc2] => { "msg": "output task1 in play1" } TASK [task2 in play1] ******************************************************************************************************************************************************************* ok: [slions_pc1] => { "msg": "output task2 in play1" } ok: [slions_pc2] => { "msg": "output task2 in play1" } PLAY [play2] **************************************************************************************************************************************************************************** TASK [task1 in play2] ******************************************************************************************************************************************************************* ok: [slions_pc3] => { "msg": "output task1 in play2" } TASK [task2 in play2] ******************************************************************************************************************************************************************* ok: [slions_pc3] => { "msg": "output task2 in play2" } PLAY RECAP ****************************************************************************************************************************************************************************** slions_pc1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 slions_pc2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 slions_pc3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
从输出可以直观的看到,执行完”play 1”之后,执行”play 2”,在一个 play 之中,所有 hosts 会获取相同的任务指令,且PLAY和TASK后面都指明了play的名称、task的名称。
最后输出的是每个主机执行任务的状态统计,比如某个主机节点执行成功的任务有几个,失败的有几个。
playbook语法:yaml Playbooks 的格式是YAML,它以非常简洁的方式实现了json格式的事件描述,如果之前接触过kubernetes,对yaml应该就非常熟悉了。具体的语法可以百度查看,以下列举一些常用的规则。
使用缩进表示层级关系
缩进不允许使用tab建,只能使用空格键
缩进空格数目不重要,只要相同层级的元素左对齐即可,程序判别配置的级别是通过缩进结合换行实现的
在单一一个playbook文件中,可以连续三个连子号(---)区分多个play
使用#号注释代码
YAML文件内容和Linux系统大小写判断方式保持一致,是区分大小写的
YAML支持三种数据结构:
对象:key/value格式,也称为哈希结构、字典结构或关联数组
数组:也称为列表
标量(scalars):单个值
对象 一组键值对,使用冒号隔开key和value。注意,冒号后必须至少一个空格。
等价于json:
数组
等价于json:
也可以使用行内数组(内联语法)的写法:
再例如:
1 2 3 --- - animal1: cat - animal2: pig
等价于json:
1 2 3 4 [ {"animal1" : "cat" }, {"animal2" : "pig" } ]
将对象和数组混合:
1 2 3 4 --- animal: - cat - pig
等价于json:
1 2 3 { "animal" : ["cat" ,"pig" ] }
字典 1 2 3 4 5 6 --- animal: name: wangcai kind: dog age: 3 color: black
等价于json:
1 2 3 4 5 6 7 8 { "animal" : { "name" : "wangcai" , "kind" : "dog" , "age" : 3 , "color" : "black" } }
也可以使用行内对象的写法:
1 2 --- animal: {name: wangcai, kind: dog, age: 3 , color: black}
字符串续行 字符串可以写成多行,从第二行开始,必须至少有一个单空格缩进。换行符会被转为空格。
等价于json:
1 2 3 { "str" : "hello world" }
也可以使用>换行,它类似于上面的多层缩进写法。此外,还可以使用|在换行时保留换行符。
1 2 3 4 5 6 7 --- str1: | hello world str2: > hello world
等价于json:
1 {'str1': 'hello world', 'str2': 'hello\nworld\n'}
空值 YAML中某个key有时候不想为其赋值,可以直接写key但不写value,另一种方式是直接写null,还有一种比较少为人知的方式:波浪号~。
下面几种方式全是等价的:
1 2 3 4 5 key1: key2: null key3: Null key4: NULL key5: ~
YAML中的单双引号和转义 YAML中的字符串是可以不用使用引号包围的,但是如果包含了特殊符号,则需要使用引号包围。
单引号包围字符串时,会将特殊符号保留。
双引号包围字符串时,反斜线需要额外进行转义。
例如,下面几对书写方式是等价的:
1 2 3 - key1: '\.yml' - key2: "\\.yml" - key3: \.yml
等价于json:
1 2 3 4 5 [ { "key1" : "\\.yml" }, { "key2" : "\\.yml" }, { "key3" : "\\.yml" } ]
playbook写法 将下面这个ad-hoc模式的ansible任务改成等价的playbook模式:
1 $ ansible leader -m copy -a 'src=/etc/passwd dest=/tmp'
playbook:
1 2 3 4 5 6 --- - hosts: leader gather_facts: false tasks: - copy: src=/etc/passwd dest=/tmp
playbook中,每个play都需要放在数组中,所以在playbook的顶层使用列表的方式- xxx:来表示这是一个play(此处是- hosts:)。
每个play都必须包含 hosts和 tasks指令 。
hosts指令用来指定要执行该play的目标主机,可以是主机名,也可以是主机组,还支持正则表达式或者是变量的形式来更灵活的指定目标主机。
tasks指令用来指定这个play中包含的任务,可以是一个或多个任务,任务也需要放在play的数组中,所以tasks指令内使用- xxx:的方式来表示每一个任务(此处是- copy:)。
gather_facts是一个play级别的指令设置,它是一个负责收集目标主机信息的任务,由setup模块提供。默认情况下,每个play都会先执行这个特殊的任务,收集完信息之后才开始执行其它任务。但是,收集目标主机信息的效率很低,如果能够确保playbook中不会使用到所收集的信息,可以显式指定gather_facts: false来禁止这个默认执行的收集任务,这对效率的提升是非常可观的。
此外每个play和每个task都可以使用name指令来命名,也建议尽量为每个play和每个task都命名,且名称具有唯一性。
所以上面的playbook可以改写为:
1 2 3 4 5 6 7 8 --- - name: this is a play hosts: leader gather_facts: false tasks: - name: copy /etc/passwd to /tmp copy: src=/etc/passwd dest=/tmp
playbook中的主机 play中的hosts指令通过pattern的方式来筛选节点,pattern的指定方式有以下几种规则:
直接指定inventory中定义的主机名或者是主机组名,如hosts: slions_pc1、hosts: leader
指定主机组名时,可使用索引的方式表示组中的第几个主机,如hosts: leader[0]
可以使用冒号或者逗号来分开多个pattern,如hosts: slions_pc3:leader
支持范围表示,如:hosts: slions_pc[1:3]
支持通配符,如:hosts: *
支持正则表达式,需使用~开头,如:hosts: ~slions_pc(1|2)
pattern前面加一个&符号表示取交集,如leader:&worker会匹配同时存在于leader和worker中的主机
pattern前面加一个!符号表示取差集,如leader:!worker匹配存在与leader组但不在worker组中的主机
playbook模块参数的传递方式 tasks中的模块参数有几种写法,都是等价的,保持整体统一就好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 - name: this is a play hosts: leader gather_facts: false tasks: - copy: src=/etc/passwd dest=/tmp - copy: src=/etc/passwd dest=/tmp - copy: > src=/etc/passwd dest=/tmp - copy: | src=/etc/passwd dest=/tmp - copy: src: /etc/passwd dest: /tmp - copy: args: src: /etc/passwd dest: /tmp