1.ansible playbook初识
1.Ansible playbook 简介
playbook 是 ansible 用于配置,部署,和管理被控节点的剧本。
通过 playbook 的详细描述,执行其中的一系列 tasks ,可以让远端主机达到预期的状态。playbook 就像 Ansible 控制器给被控节点列出的的一系列 to-do-list ,而被控节点必须要完成。
也可以这么理解,playbook 字面意思,即剧本,现实中由演员按照剧本表演,在Ansible中,这次由计算机进行表演,由计算机安装,部署应用,提供对外服务,以及组织计算机处理各种各样的事情。
2.Ansible playbook使用场景
执行一些简单的任务,使用ad-hoc命令可以方便的解决问题,但是有时一个设施过于复杂,需要大量的操作时候,执行的ad-hoc命令是不适合的,这时最好使用playbook。
就像执行shell命令与写shell脚本一样,也可以理解为批处理任务,不过playbook有自己的语法格式。
使用playbook你可以方便的重用这些代码,可以移植到不同的机器上面,像函数一样,最大化的利用代码。在你使用Ansible的过程中,你也会发现,你所处理的大部分操作都是编写playbook。可以把常见的应用都编写成playbook,之后管理服务器会变得十分简单。
3.Ansible playbook和script模块应用起来的区别
这里有的同学可能会问上面讲Ansible playbook就像写shell脚本一样。那我不是可以通过写shell脚本,然后通过script模块来执行脚本,以此达到批量管理服务器的功能吗?为什么还要编写playbook呢?
这是因为shell脚本没有Ansible playbook的很多高级特性,比如幂等性之类的。还有就是playbook从通用性,可读性,易用性方面看比通过script模块执行shell脚本来管理主机更加方便和便捷。
4.Ansible playbook格式
playbook由YMAL语言编写。YAML( /ˈjæməl/ )参考了其他多种语言,包括:XML、C语言、Python、Perl以及电子邮件格式RFC2822,Clark Evans在2001年5月在首次发表了这种语言,另外Ingy döt Net与OrenBen-Kiki也是这语言的共同设计者。
YMAL格式是类似于JSON的文件格式,便于人理解和阅读,同时便于书写。首先学习了解一下YMAL的格式,对我们后面书写playbook很有帮助。以下为playbook常用到的YMAL格式:
1、文件的第一行应该以 "---" (三个连字符)开始,表明YMAL文件的开始。
2、在同一行中,#之后的内容表示注释,类似于shell,python和ruby。
3、YMAL中的列表元素以”-”开头然后紧跟着一个空格,后面为元素内容。
4、同一个列表中的元素应该保持相同的缩进。否则会被当做错误处理。
5、play中hosts,variables,roles,tasks等对象的表示方法都是键值中间以":"分隔表示,":"后面还要增加一个空格。
playbook文件以".yaml"或者".yml"作为文件名后缀,此处我们创建一个名为"test.yml"的剧本文件,来对playbook的语法进行举例
[root@linux-test-no test]# cat test.yaml
---
- hosts: testB
remote_user: root
tasks:
- name: Ping the host
ping:
- name: make directory test
file:
path: /testdir/test-play
state: directory
如上所示,为playbook的基本格式示例,相关关键字解释如下:
host部分:使用 hosts 指示使用哪个主机或主机组来运行下面的 tasks ,每个 playbook 都必须指定 hosts ,hosts也可以使用通配符格式。主机或主机组在 inventory 清单中指定,可以使用系统默认的/etc/ansible/hosts,也可以自己编辑,在运行的时候加上-i选项,指定清单的位置即可。在运行清单文件的时候,–list-hosts选项会显示那些主机将会参与执行 task 的过程中。如果你想要一次性在多台主机上进行操作,可以同时写多个主机,每台主机使用逗号隔开,比如'hosts: test70,test61',如果你在清单中对主机进行了分组,也可以使用组名。
remote_user:指定远端主机中的哪个用户来登录远端系统,在远端系统执行 task 的用户,可以任意指定,也可以使用 sudo,但是用户必须要有执行相应 task 的权限。
tasks:指定远端主机将要执行的一系列动作,相当于任务列表。tasks 的核心为 ansible 的模块,前面已经提到模块的用法。tasks 包含 name 和要执行的模块,name 是可选的,只是为了便于用户阅读,不过还是建议加上去,模块是必须的,同时也要给予模块相应的参数。
从上述示例剧本中,我们看出,整个任务列表一共有两个任务组成,每个任务都以"- "开头,每个任务都有自己的名字,任务名使用name关键字进行指定,第一个任务使用ping模块,使用ping模块时没有指定任何参数。第二个任务使用file模块,使用file模块时,指定了path参数与state参数的值。
5.剧本的执行结果解析
使用ansible-playbook运行playbook文件,得到如下输出信息,输出内容为JSON格式。并且由不同颜色组成,便于识别。一般而言
| 绿色代表执行成功,系统保持原样
| 黄色代表系统代表系统状态发生改变
| 红色代表执行失败,显示错误输出
执行过程有三个步骤:
1、收集facts (收集对应目标主机的信息)
2、执行tasks
3、报告结果
执行结果如下图。
6.剧本编写后的语法检查和调试执行
如果你的playbook写完了,但是你不能确定playbook文件中是否存在语法错误,那么你可以使用如下命令对playbook进行语法检查。
ansible-playbook --syntax-check test.yaml
执行上述命令后,如果只返回了playbook的名称,就表示没有语法错误。
除了对playbook进行语法测试,我们还能够'模拟执行'playbook,'模拟执行'并不是真正的执行,只是'假装'执行一下,playbook中的任务并不会真正在目标主机中运行,所以你可以放心大胆的进行模拟,使用如下命令即可模拟运行playbook,模拟运行功能可以帮助我们'预估'playbook是否能够正常执行。
ansible-playbook --check test.yaml
注意:使用上述命令进行'模拟'时,一些任务可能会报错,这可能是因为报错的任务在执行时需要依赖之前的其他任务的完成结果,但是因为是'模拟'执行,所以之前的任务并不会真正的执行,既然之前的任务没有真正的执行,自然不会产生对应的结果,所以后面的任务就报错了,也就是说,我们并不能完全以'模拟'的反馈结果作为playbook是否能够正常运行的判断依据,只能通过'模拟'大概的'预估'一下而已。
7.playbook语法格式拓展
这里我们先看一个默认任务列表的写法
tasks:
- name: make testfile
file:
path: /testdir/testfile
state: touch
mode: 0700
上述写法,是我们上面介绍的默认写法,除了这种默认写法,以下写法,同样正确,也能达到相同效果。
tasks:
- name: make testfile
file: path=/testdir/testfile state=touch mode=0700
tasks:
- name: make testfile
file: path=/testdir/testfile
state=touch mode=0700
即使把多个参数分行写,也需要注意缩进。
上述书写格式都是0.8版本以后的ansible推荐的书写格式,在0.8版本之前,使用action关键字调用模块,示例如下:
tasks:
- name: make testfile
action: file path=/testdir/testfile state=touch mode=0700
在之前的示例中,我们对每个任务都指定了对应的名称,即每个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
各属性顺序虽然没有要求,但是仍然需要严格按照缩进进行对齐。
对于在playbook中shell和command这类没有参数的模块,我们在编写的时候后面直接跟命令,而非key=value类的参数列表,如下
tasks:
- name: show ip addr to file
shell: echo $(hostname -i) >> /tmp/ip.txt
2.ansible playbook拓展
1.handlers的应用(任务列表的拓展)
handlers的介绍:可以理解成另一种任务列表,在特定条件下触发;接收到其它任务的通知时被触发;handlers中的任务会被tasks中的任务进行"调用",但是,被"调用"并不意味着一定会执行,只有当tasks中的任务"真正执行"以后(真正的进行实际操作,造成了实际的改变),handlers中被调用的任务才会执行,如果tasks中的任务并没有做出任何实际的操作,那么handlers中的任务即使被'调用',也并不会执行。
handlers出现的背景是:在我们通过playbook执行tasks下面的任务的时候,默认会依次执行tasks下面的任务列表,不管前面的任务是否真正执行了,都不影响后面任务的执行。这个某些背景下,是不适用的。比如当我们修改了某些程序的配置文件以后,有可能需要重启应用程序,以便能够使新的配置生效。那么当我们通过playbook来实现这个简单的功能的时候,如果我们如果通过默认tasks任务来进行剧本的编写的话,在没有修改配置的情况下,仍然会重启服务,这种重启是不需要的,我们想要达到的效果是,如果配置文件发生了改变,则重启服务,如果配置文件并没有被真正的修改,则不对服务进行任何操作,这种情况下,handlers出现了。handlers的出现就是为了解决这个问题。
handler的应用格式语法示例如下:
[root@linux-test-no test]# cat test-handlers.yaml
---
- hosts: testB
remote_user: root
tasks:
- name: make testfile1
file: path=/testdir/testfile1
state=directory
notify: ht2
- name: make testfile2
file: path=/testdir/testfile2
state=directory
notify: ht1
handlers:
- name: ht1
file: path=/testdir/ht1
state=touch
- name: ht2
file: path=/testdir/ht2
state=touch
如上例所示,我们使用handlers关键字,指明哪些任务可以被'调用',之前说过,handlers是另一种任务列表,你可以把handlers理解成另外一种tasks,你可以理解成它们是'平级'的,所以,handlers与tasks是'对齐'的(缩进相同),上例中的handlers中有两个任务,这个任务的名称为"ht1"和"ht2",之前也说明过,handlers中的任务需要被tasks中的任务调用,那么上例中,"ht1"被哪个任务调用了呢?很明显,"ht1"被"make testfile2"调用了,没错,如你所见,我们使用notify关键字'调用'handlers中的任务,或者说,通过notify关键字'通知'handlers中的任务(从上图我们也看出,具体格式就是,通过notify关键字后面的值,去指定调用handlers中的哪个任务,handlers任务通过-name后面的值进行区分,这个值需要和notify后面的值是对应的)所以,综上所述,上例中的play表示,如果"make testfile2"真正的创建了/testdir/testfile2(实际的操作),那么则执行"ht1"任务,如果"make testfile2"并没有进行目录的创建,则不执行"ht1" ,通常来说,任务执行后如果做出了实际的操作,任务执行后的状态为changed(前文中解释过changed状态,此处不再赘述),所以,任务执行后的状态为changed则会执行对应的handlers,这就是handlers的作用。
那么我们来执行一下上述playbook,执行结果如下图:
handler执行的顺序与handler在playbook中定义的顺序是相同的,与"handler被notify"的顺序无关。
如果你想要在执行完某些task以后立即执行对应的handler,则需要使用meta模块,示例如下
[root@linux-test-no test]# cat test-handlers2.yaml
---
- hosts: testB
remote_user: root
tasks:
- name: task1
file: path=/testdir/testfile
state=touch
notify: handler1
- name: task2
file: path=/testdir/testfile2
state=touch
notify: handler2
- meta: flush_handlers
- name: task3
file: path=/testdir/testfile3
state=touch
notify: handler3
handlers:
- name: handler1
file: path=/testdir/ht1
state=touch
- name: handler2
file: path=/testdir/ht2
state=touch
- name: handler3
file: path=/testdir/ht3
state=touch
如上例所示,我在task1与task2之后写入了一个任务,我并没有为这个任务指定name属性,这个任务使用meta模块,meta任务是一种特殊的任务,meta任务可以影响ansible的内部运行方式,上例中,meta任务的参数值为flush_handlers,"meta: flush_handlers"表示立即执行之前的task所对应handler,什么意思呢?意思就是,在当前meta任务之前,一共有两个任务,task1与task2,它们都有对应的handler,当执行完task1与task2以后,立即执行对应的handler,而不是像默认情况那样在所有任务都执行完毕以后才能执行各个handler,那么我们来实际运行一下上述剧本,运行结果如下
正如上图所示,meta任务之前的任务task1与task2在进行了实际操作以后,立即运行了对应的handler1与handler2,然后才运行了task3,在所有task都运行完毕后,又逐个将剩余的handler根据情况进行调用。
聪明如你一定想到了,如果想要每个task在实际操作后都立马执行对应handlers,则可以在每个任务之后都添加一个meta任务,并将其值设置为flush_handlers 所以,我们可以依靠meta任务,让handler的使用变得更加灵活
我们还可以在一个task中一次性notify多个handler,怎样才能一次性notify多个handler呢?你可能会尝试将多个handler使用相同的name,但是这样并不可行,因为当多个handler的name相同时,只有一个handler会被执行,所以,我们并不能通过这种方式notify多个handler,如果想要一次notify多个handler,则需要借助另一个关键字,它就是'listen',你可以把listen理解成"组名",我们可以把多个handler分成"组",当我们需要一次性notify多个handler时,只要将多个handler分为"一组",使用相同的"组名"即可,当notify对应的值为"组名"时,"组"内的所有handler都会被notify,这样说可能还是不容易理解,我们来看个小示例,示例如下
[root@linux-test-no test]# cat test-handlers3.yml
---
- hosts: testB
remote_user: root
tasks:
- name: task1
file: path=/testdir/testfile
state=touch
notify: handler group1
handlers:
- name: handler1
listen: handler group1
file: path=/testdir/ht1
state=touch
- name: handler2
listen: handler group1
file: path=/testdir/ht2
state=touch
如上例所示,handler1与handler2的listen的值都是handler group1,当task1中notify的值为handler group1时,handler1与handler2都会被notify,还是很方便的。
2.tags的用法(为任务列表中的指定任务打上标签)
你写了一个很长的playbook,其中有很多的任务,这并没有什么问题,不过在实际使用这个剧本时,你可能只是想要执行其中的一部分任务而已,或者,你只想要执行其中一类任务而已,而并非想要执行整个剧本中的全部任务,这个时候我们该怎么办呢?我们可以借助tags实现这个需求。
见名知义,tags可以帮助我们对任务进行'打标签'的操作,当任务存在标签以后,我们就可以在执行playbook时,借助标签,指定执行哪些任务,或者指定不执行哪些任务了,这样说可能不够直观,我们来看一个小示例(为了方便示例此处只写3个任务进行举例)。
tags在playbook中应用语法格式一:
[root@linux-test-no test]# cat test-tag.yml
---
- hosts: testB
remote_user: root
tasks:
- name: task1
file:
path: /testdir/t1
state: touch
tags: t1
- name: task2
file: path=/testdir/t2
state=touch
tags: t2
- name: task3
file: path=/testdir/t3
state=touch
tags: t3
如上例所示,上例的play中有3个task,每个task都有对应的tags,为了方便示例,我只是简单的把tags的值写成了t1、t2、t3,在实际的使用中,我们应该让tags的值能够见名知义,现在每个task都有了标签,假如在执行上述playbook时,我们只想执行task2,该怎样执行呢?我们可以使用如下命令
ansible-playbook --tags=t2 test-tag.yml
如你所见,可以使用--tags选项指定某个标签,当指定标签后,只有标签对应的任务会被执行,其他任务都不会被执行,执行上述命令后,只有task2会执行,因为task2的标签值为t2,task1和task3都不会执行,这样就达到了只执行playbook中部分任务的目的。
在调用标签时,也可以一次性指定多个标签,调用多个标签需要用逗号隔开,命令如下
ansible-playbook --tags package,service testhttpd.yml
借助标签,除了能够指定"需要执行的任务",还能够指定"不执行的任务",示例命令如下。
ansible-playbook --skip-tags='t2' test-tag.yml
我们可以使用 --skip-tags选项指定"不执行的任务",执行上述命令后,task1和task3会执行,task2不会执行,因为我们已经在命令中指定了'跳过'标签t2所对应的任务,相当于使用了'排除法',t2对应的任务被排除了,其他任务都会执行。
在调用标签之前,如果你想要概览一下playbook中都有哪些标签,可以使用 ' --list-tags' 选项,示例如下
ansible-playbook --list-tags testhttpd.yml
除了使用上例中的语法指定标签,还能够使用下例中的两种语法指定标签的值。
tags在playbook中应用语法格式二:
[root@linux-test-no test]# cat test-tag2.yml
---
- hosts: testB
remote_user: root
tasks:
- name: task1
file:
path: /testdir/t1
state: touch
tags:
- t1
- name: task2
file: path=/testdir/t2
state=touch
tags: ['t2']
之前描述的三种语法都可以指定标签,不过上例中,每个任务只有一个标签,其实,我们可以为每个任务添加多个标签,三种语法添加多个标签的示例如下
语法一:
tags:
- testtag
- t1
语法二:
tags: tag1,t1
语法三:
tags: ['tagtest','t2']
上述示例的语法一使用了YAML块序列的语法格式指定多个标签,语法二与语法三都是在原来语法的基础上,使用'逗号'隔开多个标签。
在playbook中,不同的任务可以使用相同的标签。示例如下,
---
- hosts: testB
remote_user: root
tasks:
- name: install httpd package
tags: httpd,package
yum:
name=httpd
state=latest
- name: start up httpd service
tags: httpd,service
service:
name: httpd
state: started
上例中每个任务都有多个标签,而且上例中两个任务都有一个共同的标签,就是httpd标签,所以,当我们执行'ansible-playbook --tags=httpd testhttpd.yml',上述两个任务都会执行。
上例的play中的所有任务都有共同的httpd标签,像这种情况,我们可以把httpd标签提取出来,写在play中,示例如下。(共同标签的语法格式二)
---
- hosts: testB
remote_user: root
tags: httpd
tasks:
- name: install httpd package
tags: ['package']
yum:
name=httpd
state=latest
- name: start up httpd service
tags:
- service
service:
name: httpd
state: started
当tags写在play中而非task中时,play中的所有task会继承当前play中的tags,而上例中,两个任务都会继承httpd标签,同时还有拥有自己的标签。
3.内置tag详解
ansible还预置了5个特殊tag,这5个特殊tag分别为
always
never(2.5版本中新加入的特殊tag)
tagged
untagged
all
当我们把任务的tags的值指定为always时,那么这个任务就总是会被执行,除非你使用'--skip-tags'选项明确指定不执行对应的任务,示例如下:
---
- hosts: testB
remote_user: root
tasks:
- name: task1
file:
path: /testdir/t1
state: touch
tags:
- t1
- name: task2
file: path=/testdir/t2
state=touch
tags: ['t2']
- name: task3
file: path=/testdir/t3
state=touch
tags: t3,always
上例中,task3的标签有两个,t3和always,那么我们来执行一下这个playbook,假设,我只想运行上述playbook中标签为t1的任务,执行如下命令,结果如下图
包含always关键字,所以即使task3对应的标签没有被调用,task3也会执行,这就是always的作用。
在2.5版本的ansible中,引入了新的特殊标签 'never',从字面上理解,never的作用应该与always正好相反。表示当我们执行所有任务的时候,加了never标签的任务不执行。这里我就不做过多演示了。
剩余的三个特殊标签分别为 tagged、untagged、all
这三个特殊标签并非像always和never一样,always和never作为标签值存在,而这三个特殊标签则是在调用标签时使用,示例如下
tagged用法如下:
下面命令表示只执行有标签的任务,没有任何标签的任务不会被执行。这里加了never标签的不会执行。
ansible-playbook --tags tagged testtag.yml
下面命令表示跳过包含标签的任务,即使对应的任务包含always标签,也会被跳过。
ansible-playbook --skip-tags tagged testtag.yml
untagged用法:
下面命令表示只执行没有标签的任务,但是如果某些任务包含always标签,那么这些任务也会被执行。
ansible-playbook --tags untagged testtag.yml
下面命令表示跳过没有标签的任务。意思是只执行有标签的任务。但是加了never标签的任务不会被执行。
ansible-playbook --skip-tags untagged testtag.yml
all用法:
特殊标签all表示所有任务会被执行,不用指定,默认情况下就是使用这个标签。
2 3 4 5 6 | tasks: - name: make testfile file: path: /testdir/testfile state: touch mode: 0700 |