由于playbook是使用yaml格式书写的,先来学习下。
yaml格式
以一个简单的playbook为例,说明yaml的基本语法。
1 |
|
yaml文件以
---开头,以表明这是一个yaml文件,就像xml文件在开头使用<?xml version="1.0" encoding="utf-8"?>宣称它是xml文件一样。但即使没有使用---开头,也不会有什么影响。yaml中使用”#”作为注释符,可以注释整行,也可以注释行内从”#”开始的内容。
yaml中的字符串通常不用加任何引号,即使它包含了某些特殊字符。但有些情况下,必须加引号,最常见的是在引用变量的时候。具体见后文。
关于布尔值的书写格式,即true/false的表达方式。其实playbook中的布尔值类型非常灵活,可分为两种情况:
模块的参数: 这时布尔值作为字符串被ansible解析。接受yes/on/1/true/no/off/0/false,这时被ansible解析。例如上面示例中的
update_cache=yes。非模块的参数: 这时布尔值被yaml解释器解析,完全遵循yaml语法。接受不区分大小写的true/yes/on/y/false/no/off/n。例如上面的
gpgcheck=no和enabled=True。建议遵循ansible的官方规范,模块的布尔参数采用yes/no,非模块的布尔参数采用True/False。
列表与字典
在python中,列表使用 [] 来表示,例如 [ "zhangsan","lisi","wangsu" ]。在yaml中,是使用 -来表示,如下:
1 | - zhangsan |
而在python中,字典是使用 {} 来表示的。例如:{name:"zhangsan", age:18},这就是一个字典,表示 key-value的关系。而在yaml就不需要加大括号,但每一项都需要分行写。
有如下例子:
1 | - name: "zhangsan" |
翻译成python的写法就是这个样子的:[ {name:"zhangsan", age:18} , {name:"lisi", age:88}] ,表示这个列表里面有2个元素,每个元素里面又都是一个字典。
具体对应在ansible playbook中,列表所描述的是局部环境,它不一定要有名称,不一定要从同一个属性开始,只要使用”- “,它就表示圈定一个范围,范围内的项都属于该列表。例如:
1 |
|
唯一要注意的是,每一个playbook中必须包含"hosts"和"tasks"项。更严格地说,是每个play的顶级列表必须包含这两项。就像上面的例子中,就表示该playbook中包含了两个play,每个play的顶级列表都包含了hosts和tasks。其实绝大多数情况下,一个playbook中都只定义一个play,所以只有一个顶级列表项。顶级列表的各项,其实可以将其看作是ansible-playbook运行时的选项。
playbook的内容
先来看一下简单实例。
1 | [root@localhost yaml]# cat test.yaml |
运行的过程如下:
1 | [root@localhost yaml]# ansible-playbook test.yaml |
hosts内容
对于playbook中的每一个play,使用hosts选项可以定义要执行这些任务的主机或主机组。具体可以设置为IP或者是/etc/ansible/hosts定义的分组,有以下几种指定主机和主机组的方式:
all或*:表示inventory中的所有主机。::取并集。例如”host1:host2:group1”表示2台主机加一个主机组。:&:取交集。例如”group1:&group2”表示两个主机组中都有的主机。:!:排除。例如”group1:!host1”表示group1中排除host1主机的剩余主机。通配符:例如”web*.baidu.com”。数字范围:例如”web[0-5].baidu.com”。字母范围:例如”web[a-d].baidu.com”。正则表达式:以”~”开头。例如”~web\d.baidu.com”。
tasks任务
每个play都包含一个hosts和一个tasks,hosts定义的是inventory中待控制的主机,tasks下定义的是一系列task任务列表,比如调用各个模块。这些task按顺序一次执行一个,直到所有被筛选出来的主机都执行了这个task之后才会移动到下一个task上进行同样的操作。
当某一台被控主机执行某个任务出错或失败时,它将会被移除出任务轮询列表。也就是说,对于某主机来说,某任务执行失败,后续的所有任务都不会再去执行。当然,这不会影响其他的主机执行任务(除非主机的任务之间有依赖关系)。
tasks的特性如下:
- 可以为每个task加上name项,但是也不是必须。
- 可以执行一个或者多个任务,每调用的一个模块都称为一个action。
由于一个task失败会影响到所有的tasks,所以在shell中,如果要忽略非0状态码继续执行任务,可以使用以下两种方式:
1 | tasks: |
或者
1 | tasks: |
notify和handler
ansible中几乎所有的模块都具有幂等性,这意味着被控主机的状态是否发生改变是能被捕捉的,即每个任务的changed=true或changed=false。ansible在捕捉到changed=true时,就可以触发notify组件(如果定义了该组件)。notify是一个组件,并非一个模块,它可以直接定义action,其主要目的是调用handler。
1 |
|
此实例来自 ansible笔记(12):handlers的用法 ,主要是用来说明handlers是需要等tasks全部运行完才会去运行handlers的,并且是根据handlers的顺序依次运行的。如果想提前不运行handlers的操作,可以加上- meta: flush_handlers 这个参数。
tag标签
可以为playbook中的每个任务都打上标签,标签的主要作用是可以在ansible-playbook中设置只执行哪些被打上tag的任务或忽略被打上tag的任务。
1 | --list-tags # list all available tags |
条件控制
在ansible中,只有when可以实现条件判断。
1 | tasks: |
注意两点:
when判断的对象是task,所以和task在同一列表层次。它的判断结果决定它所在task是否执行,而不是它下面的task是否执行。
when中引用变量的时候不需要加大括号符号。
when也可以应用在role上,如下:
1 | - hosts: webservers |
循环控制
ansible中的循环都是借助迭代来实现的。基本都是以”with_”开头。可以参考 https://ansible-tran.readthedocs.io/en/latest/docs/playbooks_loops.html 官方文档,由国文翻译的。
with_items迭代列表
ansibel支持迭代功能。例如,有一大堆要输出的命令、一大堆要安装的软件包、一大堆要copy的文件等等。如:
1 |
|
with_lines迭代行
with_lines很好用,可以将命令行的输出结果按行迭代。
例如,find一堆文件出来,copy走。
1 |
|
对文件列表使用循环
with_fileglob 可以以非递归的方式来模式匹配单个目录中的文件.如下面所示:
1 |
|
随机元素
random_choice功能可以用来随机获取一些值.它并不是负载均衡器(已经有相关的模块了).它有时可以用作一个简化版的负载均衡器,比如作为条件判断:
1 |
|
block特性
在ansible中,可以使用”block”关键字将多个任务整合成一个”块”,这个”块”将被当做一个整体,我们可以对这个”块”添加判断条件,当条件成立时,则执行这个块中的所有任务。rescue:只有脚本报错时才执行,always:无论结果如何都执行。
1 |
|
变量
ansible有很多类型的变量,需要掌握之。
变量优先级
官网给出来了具体的优先级
- command line values (for example,
-u my_user, these are not variables) - role defaults (defined in role/defaults/main.yml) 1
- inventory file or script group vars 2
- inventory group_vars/all 3
- playbook group_vars/all 3
- inventory group_vars/* 3
- playbook group_vars/* 3
- inventory file or script host vars 2
- inventory host_vars/* 3
- playbook host_vars/* 3
- host facts / cached set_facts 4
- play vars
- play vars_prompt
- play vars_files
- role vars (defined in role/vars/main.yml)
- block vars (only for tasks in block)
- task vars (only for the task)
- include_vars
- set_facts / registered vars
- role (and include_role) params
- include params
- extra vars (for example,
-e "user=my_user")(always win precedence)
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html
内置变量:https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
facts变量
ansible playbook是在运行时是可以主要可以各主机的基础信息的,即默认是会运行setup模块可以获取这些信息。可以使用gather_facts: no设置不去收集信息。
有如下例子:
1 | [root@localhost yaml]# cat setup.yaml |
作用是获取机器的IP信息,运行结果如下:
1 | [root@localhost yaml]# ansible-playbook setup.yaml -e "user=root hosts=localhost" |
register注册变量
使用register选项,可以将当前task的输出结果赋值给一个变量。例如,下面的示例中将echo的结果”haha”赋值给say_hi变量。注意,模块的输出结果是json格式的,所以,引用变量时要指定引用的对象。
1 |
|
set_fact定义变量
set_fact和register的功能很相似,也是将值赋值给变量。它更像shell中变量的赋值方式,可以将某个变量的值赋值给另一个变量,也可以将字符串赋值给变量。
1 |
|
vars定义变量
可以在play或task层次使用vars定义字典型变量。如果同名,则task层次的变量覆盖play层次的变量。
1 |
|
运行结果:
1 | [root@localhost yaml]# ansible-playbook test_var.yaml |
vars_files定义变量
和vars一样,只不过它是将变量以字典格式定义在独立的文件中,且vars_files不能定义在task层次,只能定义在play层次。
1 | [root@localhost yaml]# cat var_file.yaml |
上面var_file.yaml使用的是相对路径,基于playbook所在的路径。例如该playbook为/tmp/x.yml,则var_file.yaml也应该在/tmp下。当然,完全可以使用绝对路径。
命令行传递变量
ansible和ansible-playbook命令的”-e”选项都可以传递变量,传递的方式有两种:-e key=value和-e @var_file。注意,当key=value方式传递变量时,如果变量中包含特殊字符,必须防止其被shell解析。如下案例:
1 | [root@localhost yaml]# ansible-playbook test_var.yaml -e "key1=value111111" |
roles中的变量
由于role是整合playbook的,它有默认的文件组织结构。其中有一个目录vars,其内的main.yml用于定义变量。还有defaults目录内的main.yml则是定义role默认变量的,默认变量的优先级最低。
1 | shell> tree /yaml |
main.yml中变量定义方式也是字典格式。
主机与主机组变量
参考上一节内容。
实例
以下实例实现了一个halo搭建的blog系统。
1 | # cat halo.yaml |
- 安装halo:
ansible-playbook halo.yaml --skip-tags clean - 删除halo:
ansible-playbook halo.yaml -t clean
playbook和play的关系
一个playbook中可以包含多个play。每个play都至少包含有tasks和hosts这两项,还可以包含其他非必须项,如vars,vars_files,remote_user等。tasks中可以通过模块调用定义一系列的action。只不过,绝大多数时候,一个playbook都只定义一个play。
所以,大致关系为:
- playbook: [play1,play2,play3]
- play: [hosts,tasks,vars,remote_user…]
- tasks: [module1,module2,…]
模块的参数一般来说是key=value格式的,有3种传递的方式:
- 直接写在模块后,此时要求使用”key=value”格式。这是让ansible内部去解析字符串。因为可分行写,所以有多种写法。
- 写成字典型,即”key: value”。此时要求多层缩进。这是让yaml去解析字典。
- 使用内置属性args,然后多层缩进定义参数列表。这是让ansible明确指定用yaml来解析。