playbook的语法∶YAML


ansible的playbook采用yaml语法,它以非常简洁的方式实现了json格式的事件插述。y aml之于json就像markdown之于html一样,极度简化了json的书写。

YAML文件后缀通常为.yaml或.yml。

YAML在不少工具里都使用,学习它是"一次学习、终生受益"的,所以很有必要把yaml 的语法格式做个梳理,系统性地去学—学。

YAML的基本语法规则如下∶

(1)使用缩进表示层级关系

(2)缩进时不允许使用Tab键,只允许使用空格

(3)缩进的空格数目不重要,只要相同层级的元素左对齐即可

(4)yaml文件以"---"作为文档的开始,以表明这是一个yaml文件(即使没有使用"---"开头,也不会有什么影响)

(5)# 表示主释,从这个字符一直到行尾,都会被解析器忽略

(6)字符串不用加引号,但在可能产生歧义时,需加引号(单双引号皆可),比如引用变量时

(7)布尔值非常灵活,不分区大小写的truefalse、yes/no、on/off、y/n、0和1都允许

YAML支持三种数据结构∶

(1)对象∶key/value格式,也称为哈希结构、字典结构或关联数组

(2)数组∶也称为列表

(3)标量(scalars)∶单个值

可以去找一些在线YAML转换JSON网站,比如 http//:yaml-online-parser.appspot.com 通过在线转换可以验证或查看自己所写的YAML是否出错以及哪里出错。

 对象

一组键值对,使用冒号隔开key和value。注意,冒号后必须至少一个空格。

name: "lulei"

等价于json∶

{
"name": "lulei"
}

数组

---
- Shell
- Perl
- Python

等价于json∶

["Shel","Perl",Python]

也可以使用行内数组(内联语法)的写法∶

---
["Shell","Perl","Python"]

再例如∶

---
- langl:Shell
- lang2:Perl
- lang3:Python

等价于json∶

[
{"lang1": "Shel"),
{"lang2": "Per"},
{"lang3": "Python"}
]

将对象和数组混合∶

---
languages:
- Shell
- Perl
- Python

等价于json∶

{
"languages":["Shel",Per","Python"]
}

字典

---
person1:
name: xiaohong
age: 18
gender: male

person2:
name: xiaofang
age: 19
gender: female

等价于json∶

{
"person2":{
"gender: "female",
"age": 19,
"name": "xiaofanggao"
},

"person1":{
"gender": "male",
"age": 18,
"name": "junmajinlong"
}
}

也可以使用行内对象的写法∶

---
person1:{name: junmajinlong,age: 18,gender: male}

复合结构

---
- person1:
name:junmajinlong
age: 18
langs:
-Perl
- Ruby
- person2:
name: xiaofanggao
age: 19
langs:
- Python
- Javascript

等价于json∶

[
{
"langs:[
"Perl",
"Ruby",
"Shell"
],

"person: null,
"age": 18,
"name":"junmainlong
},

{
"person2": null,
"age": 19,
"langs": [
"Python",
"Javascript
],

"name":"xiaofanggao"
}
]

 

playbook的写法


了解YAML写法之后,就可以来写Ansible的playbook了。

playbook可以包含一个或多个play,每个play可以包含一个或多个任务,且每个play都需要指定要执行该play的目标主机。

于是,将下面这个ad-hoc模式的ansible任务改成等价的playbook模式∶

$ ansible nginx-m copy-a'src=/etc/passwd dest=/tmp'

假设这个playbook的文件名为copyyml,其内容如下∶

---
- name: first play
hosts: all
gather_facts: false

tasks:
- name: copy /etc/passwd to /tmp
copy: src=/etc/passwd dest=/tmp
- name: register vars
shell: hostname
register: system_status
- name: display vars
debug: msg={{system_status.stdout}}

再来解释一下这个playbook文件的含义。

playbook中,每个play都需要放在数组中,所以在playbook的顶层使用列表的方式- xxx∶来表示这是一个play(此处是 - name :

也可以使用- hosts : 开头)每个play都必须包含 hosts和 tasks指令。

hosts指令用来指定要执行该play的目标主机,可以是主机名,也可以是主机组,还支持其它方式来更灵活的指定目标主机。

tasks指令用来指定这个play中包含的任务,可以是一个或多个任务,任务也需要放在play的数组中,所以tasks指令内使用-xx∶的方式来表示每一个任务(此处是copy :)

gather_facts是一个play级别的指令设置,它是一个负责收集目标主机信息的任务,由setup模块提供。默认情况下,每个play都会先执行这个特殊的任务,收集完信息之后才开始执行其它任务。但是,收集目标主机信息的效率很低,如果能够确保playbo0k 中不会使用到所收集的信息,可以显式指定 gather_facts;no来禁止这个默认执行的收集任务,这对效率的提升是非常可观的。

此外每个play和每个task都可以使用name指令来命名,也建议尽量为每个play和每个task都命名,且名称具有唯一性。

 

默认的任务执行策略


最后,再来简单探究一下默认情况下Ansble是以什么样的策略去控制多个节点执行多个任务的。

假设有10个目标节点要执行某个play中的个任务∶tA、IB、tC。

默认情况下,会从10个目标节点中选择5个节点作为第一批次的节点执行任务A,第一批次的5个节点都执行tA完成后,将选择剩下的5个节点作为第二批次执行任务A。所有节点都执行完任务A后,第一批次的5节点开始执行任务B,然后第二批次的5个节点执行任务B。所有节点都执行完任务B后,第一批次的5节点开始执行任务C,然后第二批次的5个节点执行任务C。

这里提到的5个节点的数量5,是由配置文件中fork指令的值决定的,默认值为5。

$ grep 'fork'/etc/ansiblel/ansible.cfg 
forks=5

fork指令用来指定Ansible要创建几个子进程来执行任务,每个节点默认对应一个ansibl e-playbook进程和ssh进程,指定fork=5,表示创建5个ansible-playbook子进程。所以,fork的值也代表了一次选中多少个节点执行任务。例如,将hosts指令指定为all,并将gather_facts指令取消注释,因为这个任务执行比较慢,方便我们去观察进程列表。

- name:first play 
hosts: all
#gather_facts:false

执行该playbook。

$ ansible-playbook test.yaml

然后再另外一个终满上去查看进程列表∶

$ pstree -c | grep 'ansible'
│'ansible-playboo-+-ansible-playboo---ssh
|-ansible-playboo---ssh
|-ans1ble-playboo---ssh
|-ansible-playboo---ssh
|-ansible-playboo---ssh
| `-(ansible-playboo}

如果某个节点连接失败或执行某个任务失败,则该节点将不再执行该play中的后续任务(但会执行后续的play)。