1、什么是playbook
通过前面的几篇文章我们了解到,Ansible可以通过ad-hoc模式输入一条命令来执行任务,这对于一些简单的任务是非常方便的。但当我们执行的一个任务非常复杂需要进行大量的操作时,通过ad-hoc模式来执行就显得过于麻烦。
为此,Ansible为我们提供了playbook模式执行任务的功能。playbook的本意为剧本,我们可以通过编写playbook来将需要执行的任务交由Ansible一次性的执行,而不需要我们一条条命令的输入。
Ansible提供了ansible-playbook命令用于解析playbook文件。ansible-playbook命令将根据自上而下的顺序依次执行playbook文件中的内容。同时playbook允许传输某个命令的状态到后面的命令中,也可以从一台主机的文件中抓取内容并赋值给变量,然后在另一台主机中使用,这使得playbook可以实现一些复杂的部署机制。
playbook文件使用YAML语言书写。
2、YAML语言基础
想要编写playbook,必须先了解下YAML语言。
YAML是一个类似XML、JSON的标记性语言。YAML强调以数据为中心,并不是以标识语言为重点。YAML本身比较简单,是一种直观的能够被电脑识别的数据序列化格式,是一个可读性高并且容易被人类阅读,容易和脚本语言交互,用来表达资料序列的编程语言。它是类似于标准通用标记语言的子集XML的数据描述语言,语法比XML简单很多。
YAML的基本格式:
- 使用---做为一个YAML的开头,…做为结尾,因为一个YAML文件中可以保存多个YAML内容,必须使用符号区分这些YAML
- 大小写敏感
- 使用缩进代表层级,缩进时使用空格,不能使用tab键
- 缩进时不要求空格数量,只要求相同层级左对齐即可
- 注释使用#开头
- 使用key: value的格式表示一个对象,注意,:后要加一个空格。如
name: test
name:
name1: test1
name2: test2
- 使用-后加一个空格表示一个数组项,如
name:
- test1
- test2
3、playbook的构成
一个playbook文件主要由以下四部分构成:
- target:定义将要执行playbook的远程主机或主机组
- variable:定义playbook运行时需要使用到的变量
- task:定义将要在远程主机上执行的任务列表
- handler:定义task执行完成后需要调用的任务
4、playbook基础语法详解
4.1、target部分
target中使用以下两个关键字来定义将要执行playbook的主机/主机组以及用户:
- hosts:用于指定要执行playbook的主机/主机组。每个playbook都必须定义hosts,可以使用通配符,值可以为主机IP、主机组名或者是all(表示主机清单里面的所有主机);hosts写在YAML的第一层级;主机和主机组在主机清单中指定,默认为/etc/ansible/hosts,也可以自定义,在执行ansible-playbook时使用-i选项指定自定义的主机清单;在执行ansible-playbook时使用--list-hosts选项将显示哪些主机将会执行playbook
- remote_user:指定使用哪个用户身份在被管理主机上执行任务。在playbook中必须定义,写在YAML的第一层级
4.2、variable部分
该部分中使用以下格式定义参数(该部分可以定义在playbook全局中使用的变量,具体任务的变量可以在task部分中定义):
vars: # 表示接下来是定义的参数,写在YAML的第一层级
参数1: 值
参数2: 值
...
4.3、task部分
该部分是playbook的主体,在playbook中必须存在。Ansible按从上到下的顺序在指定的主机或主机组上执行任务(在所有主机上完成一个任务后再接下去执行第二个任务),在任务执行过程中如果某个任务发生错误,则所有已经执行的任务都将回滚,因此更正playbook之后必须重新执行一次所有任务。
该部分的格式如下:
tasks: # 定义task部分的开始,在YAML中的第一层级
- name: 自定义任务名 # 可选项,自定义该任务的名称,在playbook执行的时候输出在屏幕上
Ansible模块: 选项1=值 选项2=值 ...
- name: 自定义任务名
Ansible模块: 选项1=值 选项2=值 ...
...
忽略playbook中某个task的错误继续执行剩余task
上面我们提到,当playbook在执行任务过程中发生错误,那么已经执行的任务都将回滚。同时ansible-playbook命令将会退出,playbook中剩余的任务将不会继续执行。但是,如果出错的任务影响不大,或者我们需要继续执行playbook中剩余的任务,可以使用以下关键字忽略错误:
ignore_errors: true
# 在具体的任务中设置,默认值为false,发生错误时中断playbook的执行
仅运行或跳过某些task
在执行playbook时,也可以选择仅运行某个task或者跳过某个task。要实现这一功能可以在task部分中使用tags关键字:
tasks:
- name: 自定义任务名
Ansible模块: 选项1=值 选项2=值
tags: 自定义tags名称1 # 自定义的名称,用于在ansible-playbook命令中指定运行或跳过
- name: 自定义任务名
Ansible模块: 选项1=值 选项2=值
tags: 自定义tags名称2
在ansible-playbook命令中使用tags:
# 该playbook名称设置为test.yml
# 仅运行第一个task
ansible-playbook -t|--tags=自定义tags名称1 test.yml
# 不运行第一个task
ansible-playbook --skip-tags=自定义tags名称1 test.yml
注意:每个task除了可以设置自己的tags外,还可以设置相同的tags,以实现一次运行几个task或者一次跳过几个task的功能
tasks:
- name: test1
...
tags:
- test11
- test22
- name: test2
...
tags:
- test33
- test22
4.4、handler部分
该部分的格式如下:
tasks:
- name: xxx
Ansible模块: 选项1=值 选项2=值 ...
notify: # 在任务执行完调用handler
- handler name # 必须与下面handlers中name的值一样
handlers: # 定义handler,在YAML的第一层级
- name: handler name
Ansible模块: 选项1=值 选项2=值 ...
4.5、收集被管理主机信息
在playbook执行的时候,默认会有一个task,而且是playbook中的第一个task,那就是通过setup模块来收集被管理主机的facts信息,这些信息包括被管理主机的发行版、IP地址、CPU、主机名、内存等信息(具体可以查看上篇文章中的setup模块介绍)。Ansible使用JSON格式呈现这些信息。
如果在playbook中的task里没有用到这些facts信息,那么我们可以在target部分中使用以下关键字禁用收集信息的这个task:
gather_facts: false
# 写在YAML中的第一层级,该关键字的值默认为true
4.6、playbook中的条件和循环
在playbook中使用关键字when来进行条件判断,条件表达式使用Python语言编写,如只有在被管理主机的操作系统为CentOS 7时才执行这个task:
tasks:
- name: xxx
xxx: xxx=x
when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'
循环的格式如下:
tasks:
- name: 自定义任务名
Ansible模块: 选项={{item}}
with_items:
- 值1
- 值2
...
4.7、获取任务执行后的返回结果
在playbook中如果需要获取任务执行后返回的结果,可以使用以下关键字将返回结果赋值给一个自定义变量:
register: 自定义变量
任务执行的返回结果是一个字典。
4.8、playbook执行输出解析
playbook的执行结果使用以下三种颜色表示不同的执行结果:
- 绿色代表执行成功,但系统没有任何改变
- 黄色代表执行成功,且系统发生了改变,也就表明执行的操作已经生效
- 红色代表执行失败,将会输出错误信息
playbook只有在执行错误时才会输出错误信息,执行成功则无详细信息输出。如果需要查看执行成功的信息,可以使用ansible-playbook的-v选项输出更详细的信息。
5、一个例子
编写一个playbook,在centos6组的两台主机上判断是否安装了telnet,如果没有安装的话则使用YUM进行安装。
这里使用rpm –qa|grep telnet命令来判断telnet是否已经安装,如果telnet尚未安装,则该命令无返回值。我们在playbook中执行该命令来查看返回的是什么内容,以便在安装telnet任务中进行判断,playbook文件命名为telnet.yaml,内容如下:
---
- hosts: centos6
remote_user: root
gather_facts: false
tasks:
- name: judge telnet
shell: rpm -qa|grep telnet
我们先来执行这个playbook,看下返回的内容:
[root@localhost ~]# ansible-playbook telnet.yaml -k
执行返回:
通过返回的信息,我们发现可以通过msg这个键或者rc这个键的值判断命令的运行结果。rpm –qa|grep telnet这个命令如果没有查找的telnet,则返回值为1,Ansible通过返回值来判断命令执行成功与否。rc键即为命令返回值,而msg键则给出了non-zero return code的信息。我们通过这两个键的值就可以判断被管理主机是否安装了telnet。
所以,我们再给这个playbook添加一个任务以执行安装telnet的动作,完整的playbook内容如下:
---
- hosts: centos6
remote_user: root
gather_facts: false
tasks:
- name: judge telnet # 任务一:判断telnet是否已经安装
shell: rpm -qa|grep telnet
register: return_msg # 任务返回值赋值给自定义变量return_msg
ignore_errors: true # 忽略该任务的错误,以便下一个任务可以执行
- name: install telnet # 任务二:安装telnet
yum: name=telnet state=installed
when: return_msg["rc"] == 1 # 使用Python语法的表达式,此时return_msg的值为一个字典
执行这个playbook,我们需要任务执行成功的输出信息,使用以下命令:
[root@localhost ~]# ansible-playbook telnet.yaml -v -k
执行的返回:
可以看到centos6组的两台主机已经成功安装了telnet。此时再执行开头的那个playbook,看看返回的结果:
此时执行rpm –qa|grep telnet已经不会返回错误了。