一、playbook简介
前文中,我们介绍了一些ansible的常用模块,聪明如你,一定已经掌握了这些模块的使用方法。
那么现在,我们来想象一个工作场景,看看怎样把之前的知识点应用到这个工作场景中。
假设,我们想要在testB(server3)主机上安装httpd并启动,我们可以在ansible主机中执行如下3条命令
ansible testB -m yum_repository -a 'name=rhel7.3 description="ansible rhel7.3" baseurl=http://172.25.63.250/iso gpgcheck=no enabled=yes'
ansible testB -m yum -a 'name=httpd disable_gpg_check=yes'
ansible testB -m service -a "name=httpd state=started"
- 1
- 2
- 3
我们通过上述3条命令,先确定配置了对应的yum源,然后使用yum模块安装了httpd,最后使用service模块启动了httpd,最终达到了我们的目的。
但是在实际的工作环境中,我们可能需要经常在新主机上安装httpd,难道每次有新的服务器加入工作环境,我们都要修改上述3条命令中的主机名并且重新将每一条命令执行一遍吗?这样似乎有些麻烦,肯定有更好的办法,没错,我们可以将上述命令写成脚本,每次修改一些变量,然后执行脚本就行了,这样似乎方便了不少,而ansible天生就提供了这种类似"脚本"的功能, 在ansible中,类似"脚本"的文件被称作"剧本",‘剧本’的英文名称为’playbook’ ,我们只需要将要做的事情编写成playbook,把不同的模块按照顺序编排在剧本中,ansible就会按照剧本一步一步的执行,最终达到我们的目的
虽然playbook的功能与脚本类似,但是剧本并不是简单的将ad-hoc命令按照顺序堆砌在一个可执行文件中,编写剧本需要遵循YAML语法,如果你没有接触过YAML语法,不用害怕,坚持看完后面的示例,熟悉一些固定套路以后,你也可以自己编写playbook。
二、编写playbook
那么怎样编写playbook呢?我们先从一个简单的示例开始吧~
首先,我们需要创建一个YAML格式的playbook文件。
playbook文件以".yaml"或者".yml"作为文件名后缀,此处我们创建一个名为"ping.yml"的剧本文件。
在编写剧本之前,我们先来回顾两个简单的ad-hoc命令,比如如下两条命令
ansible testB -m ping
ansible testB-m file -a "path=/testdir/test state=directory"
- 1
- 2
上述命令表示使用ping模块去ping主机testB(server3),然后再用file模块在testB(server3)主机上创建目录,那么,如果把上述命令转换成playbook的表现形式,该如何书写呢?示例如下
文件内容:
---
- hosts: testA
remote_user: root
tasks:
- name: Ping the host
ping:
- name: make directory test
file:
path: /testdir/ddpp
state: directory
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如上所示,第一行使用三个横杠作为开始,在YAML语法中,---
表示文档开始。
第二行使用-
作为开头(注意:横杠后面有空格),如果你了解过YAML语法,那么你一定知道YAML使用"- “表示一个块序列的节点,如果你不了解YAML语法,不用在此处纠结,先这么写,写的多了,自然会理解,从上例可以看出,”- "后面使用hosts关键字指定了要操作的主机,hosts关键字对应的值为testB(server3),表示我们要在testB(server3)主机上进行操作,hosts: testB
是一个键值对,注意,在YAML语法中使用冒号映射键值对时,‘冒号’后面必须有’空格’,这也是语法,没有为什么,记住就好,如果你想要一次性在多台主机上进行操作,可以同时写多个主机,每台主机使用逗号隔开,比如hosts: testB,testA
,如果你在清单中对主机进行了分组,也可以使用组名。
第三行,使用remote_user
关键字可以指定在进行远程操作时使用哪个用户进行操作,remote_user: root
表示test70的root用户进行操作,上图中,remote_user
关键字与hosts
关键字对齐,表示它们是平级的,之前的文章中提到过,在YAML语法中进行缩进时,不能使用tab键进行缩进,必须使用空格,所以,为了兼容使用tab键进行缩进的使用习惯,可以将vim编辑器设置为自动将tab转成空格。
第四行,使用tasks
关键字指明要进行操作的任务列表,之后的行都属于tasks键值对中的值。
之后的行都属于tasks任务列表中的任务,可以看出,整个任务列表一共有两个任务组成,每个任务都以-
开头,每个任务都有自己的名字,任务名使用name
关键字进行指定,第一个任务使用ping
模块,使用ping模块时没有指定任何参数。第二个任务使用file模块,使用file模块时,指定了path参数与state参数的值。
三、运行playbook
好了,ping.yml已经编写完成了,但是,我们还没有运行这个剧本,运行剧本需要使用ansible-playbook
命令,示例如下
[root@server4 ~]# ansible-playbook ping.yml
- 1
如上图所示,playbook执行后返回了一些信息,这些信息是这次剧本运行的概况。
PLAY [test70]
表示这次运行的playbook中有一个play
是针对testB(server3)这台主机运行的,一个’playbook’是由一个或多个’play’组成的,这样说可能不太容易理解,那么我们打个比方,一个’剧本’是由一个或多个’桥段’组成的,每个桥段都有不同的场景、人物、故事,所有的桥段组合在一起,组成一个完整的剧本,剧本就是playbook,桥段就是play,而上例中,整个剧本中只有一个桥段,也就是说,上例的playbook中,我们只写了一个play,在下文中我们会举例说明怎样书写多个play,到时候你会更加明白到底什么是所谓的’play’,当然,'桥段’只是我自己为了方便理解给’play’起的中文名,官方名称只叫"play"。
从上述信息可以看出,仅有的这个play是针对testB(server3)运行的,这个play一共包含三个任务,第一个任务的名字叫做Gathering Facts
,第二个任务的名字叫做’Ping the host
’,第三个任务的名字叫做’make directory test
’,看到此处你会发现,我们在playbook中明明只写了两个任务,为什么最后执行时却有三个任务呢?这是因为,每个play在执行时,都会先执行一个默认任务,这个默认任务就是’Gathering Facts
’,’Gathering Facts
'任务会收集当前play对应的目标主机的相关信息,收集完这些基础信息后,才会执行我们指定的任务,由于上例中,hosts的值只testB(server3)一个主机,所以这个play只针对testB(server3)运行,所以’Gathering Facts
‘这个任务只收集了testB(server3)的相关信息,执行完默认任务后,开始执行’Ping the host
‘任务和make directory test
任务,在执行playbook之前,由于对应的目录并不存在,’make directory test
‘任务返回的信息应该是黄色的。如果在执行playbook之前,/testdir/test目录已经存在于ttestB(server3)主机中,那么’make directory test
'任务返回的信息是绿色的,这是因为幂等性的缘故,前文已经解释过,此处不再赘述。
当playbook中的所有play执行完毕后,在返回信息的’PLAY RECAP
‘中可以对所有目标主机的执行情况进行’回顾’,由于我们的目标主机只有testB(server3)一台,所以只看到了testB(server3)的执行概括信息。如果指定多台主机(server5、server6),那么就会返回多台主机的信息,如:
四、多个play编写及运行
我们已经执行了一个playbook,这个playbook中只有一个play,我们也可以在这个playbook中多写几个play,示例如下
[root@server4 ~]# vim ping1.yml
[root@server4 ~]# cat ping1.yml
---
- hosts: testA
remote_user: root
tasks:
- name: Ping the host
ping:
- name: make directory test
file:
path: /testdir/ddpp
state: directory
- hosts:
testA
testB
remote_user: root
tasks:
- name: create user ddapp
user:
name: ddapp
- hosts:
testA,testB
remote_user: root
tasks:
- name: touch file
file:
path: /mnt/ddpp
state: touch
- 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
如上图所示,上例中有多个play,第一个play针对testB(server3)执行,这个play会执行两个任务。
第二个play针对testA(server5、server6)和testB(server3)执行,第二个play只包含一个任务,即在对应的目标主机上创建/tfile文件
第三个play针对testA(server5、server6)和testB(server3)执行,细心如你一定发现了,当你需要在play中指定多个主机时,有两种语法可以使用,第二个play和第三个play中的语法都可以指定多个主机,第二个play也只包含一个任务,即在testA(server5、server6)和testB(server3)主机上创建ddapp用户。
多个play写完以后,我们来运行一下剧本试试。
[root@server4 ~]# ansible-playbook ping1.yml
- 1
如下图所示,每个play执行时都会显示当前play对应的目标主机,并且每个play执行时都会先执行默认的facts任务,收集对应目标主机的信息,当所有play都执行完毕后,会出现’PLAY RECP
’,相当于一个小的报告信息,看到这里,你应该已经理解了到底什么是所谓的’play’了吧。
再次强调一遍,如果你并不是很熟悉YAML语法,你可以不用过于深究YAML语法,只需要死记硬背,记住某些"固定套路"即可编写playbook,写的多了,自然会对这些语法有所理解的。
五、检查playbook语法
如果你的playbook写完了,但是你不能确定playbook文件中是否存在语法错误,那么你可以使用如下命令对playbook进行语法检查。
[root@server4 ~]# ansible-playbook --syntax-check ping1.yml
- 1
执行上述命令后,如果只返回了playbook的名称,就表示没有语法错误:
除了对playbook进行语法测试,我们还能够’模拟执行’playbook,'模拟执行’并不是真正的执行,只是’假装’执行一下,playbook中的任务并不会真正在目标主机中运行,所以你可以放心大胆的进行模拟,使用如下命令即可模拟运行playbook,模拟运行功能可以帮助我们’预估’playbook是否能够正常执行。
[root@server4 ~]# ansible-playbook --check ping1.yml
- 1
注意:使用上述命令进行’模拟’时,一些任务可能会报错,这可能是因为报错的任务在执行时需要依赖之前的其他任务的完成结果,但是因为是’模拟’执行,所以之前的任务并不会真正的执行,既然之前的任务没有真正的执行,自然不会产生对应的结果,所以后面的任务就报错了,也就是说,我们并不能完全以’模拟’的反馈结果作为playbook是否能够正常运行的判断依据,只能通过’模拟’大概的’预估’一下而已。
六、playbook的一些其他写法
有前文作为基础,如下示例是非常容易理解的:
---
- hosts: testB
remote_user: root
tasks:
- name: make testfile
file:
path: /testdir/testfile
state: touch
mode: 0700
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
上例中有一个play,这个play针对testB主机运行,这个play的任务列表中只有一个任务,这个任务就是调用file模块,确保/testdir/testfile文件存在并且testfile文件的权限为0700,把上例中的任务列表部分单独截取出来,如下所示
tasks:
- name: make testfile
file:
path: /testdir/testfile
state: touch
mode: 0700
- 1
- 2
- 3
- 4
- 5
- 6
正如你所看到的,path: /testdir/testfile
表示为file模块的path参数赋值,我们使用”冒号”(冒号后有空格)对参数赋值。
其实,除了这种使用冒号的方式,我们还可以使用如下格式为模块的参数赋值
tasks:
- name: make testfile
file: path=/testdir/testfile state=touch mode=0700
- 1
- 2
- 3
如上所示,我们调用file模块时,设置了三个参数,path参数、state参数、mode参数,为参数赋值时,使用了”等号”,每个参数之间使用空格隔开,这种格式也是完全正确的,如果你在使用一个模块时设置的参数比较多,那么使用上述格式设置参数时,这些参数可能会”挤在一行”里面,你也可以把它们分成多行去写,如下例所示
tasks:
- name: make testfile
file: path=/testdir/testfile
state=touch mode=0700
- 1
- 2
- 3
- 4
即使把多个参数分行写,也需要注意缩进。
上述书写格式都是0.8版本以后的ansible推荐的书写格式,在0.8版本之前,使用action关键字调用模块,示例如下:
tasks:
- name: make testfile
action: file path=/testdir/testfile state=touch mode=0700
- 1
- 2
- 3
如上例所示,使用action关键字调用对应的模块,在当前版本中(博客中的ansible版本为2.4)仍然兼容这种语法
在之前的示例中,我们对每个任务都指定了对应的名称,即每个task都有对应的name,当我们省略name时,默认以当前任务调用的模块的名称作为任务的名称,不过建议不要省略name,因为当任务存在name时,可读性比较高。
在编写任务时,我习惯将name属性写在任务的开头,当然,任务的各个属性并没有严格的顺序要求,如下三种写法的效果是相同的。
写法一:
tasks:
- name: make testfile
file: path=/testdir/testfile
state=touch
mode=0700
写法二:
tasks:
- file: path=/testdir/testfile
state=touch
mode=0700
name: make testfile
写法三:
tasks:
- file: path=/testdir/testfile
state=touch
mode=0700
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
各属性顺序虽然没有要求,但是仍然需要严格按照缩进进行对齐。