一、角色的定义
角色(roles)是ansible自1.2版本开始引入的新特性,用于层次性,结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单的说,roles就是通过分别将变量、文件、任务、模块及处理器放置于单独的目录中、并可以便捷地include他们的一种机制。角色一般用于基于主机构建服务的场景中、但也可以是用于构建守护进程等场景中。
二、角色示例
为了让你更直观的认识角色的标准结构,我提前创建了一个示例角色,示例角色的目录结构如下:
在testredhat目录下文件的结构:
如你所见,我创建的示例角色的角色名为testredhat,testredhat目录就代表了这个角色,此目录中包含了defaults 、files 、handlers 、meta 、tasks 、templates 、vars等子目录,而且在defaults 、handlers 、meta 、tasks 、vars等目录中,还都有一个名为"main.yml"的文件,那么这样的目录结构代表了什么含义呢?我们一起来了解一下,在角色中,上述目录结构的作用如下:
注:此处先进行大致介绍,以便你有一个大概的印象,之后会有对应的示例,所以,如果有疑问请先保留。
目录 | 作用 |
tasks目录 | 角色需要执行的主任务文件放置在此目录中,默认的主任务文件名为main.yml,当调用角色时,默认会执行main.yml文件中的任务,你也可以将其他需要执行的任务文件通过include的方式包含在tasks/main.yml文件中。 |
handlers目录 | 当角色需要调用handlers时,默认会在此目录中的main.yml文件中查找对应的handler |
defaults目录 | 角色会使用到的变量可以写入到此目录中的main.yml文件中,通常,defaults/main.yml文件中的变量都用于设置默认值,以便在你没有设置对应变量值时,变量有默认的值可以使用,定义在defaults/main.yml文件中的变量的优先级是最低的。 |
vars目录 | 角色会使用到的变量可以写入到此目录中的main.yml文件中,看到这里你肯定会有疑问,vars/main.yml文件和defaults/main.yml文件的区别在哪里呢?区别就是,defaults/main.yml文件中的变量的优先级是最低的,而vars/main.yml文件中的变量的优先级非常高,如果你只是想提供一个默认的配置,那么你可以把对应的变量定义在defaults/main.yml中,如果你想要确保别人在调用角色时,使用的值就是你指定的值,则可以将变量定义在vars/main.yml中,因为定义在vars/main.yml文件中的变量的优先级非常高,所以其值比较难以覆盖。 |
meta目录 | 如果你想要赋予这个角色一些元数据,则可以将元数据写入到meta/main.yml文件中,这些元数据用于描述角色的相关属性,比如 作者信息、角色主要作用等等,你也可以在meta/main.yml文件中定义这个角色依赖于哪些其他角色,或者改变角色的默认调用设定,在之后会有一些实际的示例,此处不用纠结。 |
templates目录 | 角色相关的模板文件可以放置在此目录中,当使用角色相关的模板时,如果没有指定路径,会默认从此目录中查找对应名称的模板文件。 |
files目录 | 角色可能会用到的一些其他文件可以放置在此目录中,比如,当你定义nginx角色时,需要配置https,那么相关的证书文件即可放置在此目录中。 |
当然,上述目录并不全是必须的,也就是说,如果你的角色并没有相关的模板文件,那么角色目录中并不用包含templates目录,同理,其他目录也一样,一般情况下,都至少会有一个tasks目录。
看完上述描述,你可能还是有一些小疑惑,不如我们来动手写一个简单的角色,这样就比较容易理解了。
注:当前用于测试的ansible版本为2.9.0,以下所有示例基于此版本进行。
为了熟悉角色的使用,我们一起来纯手动的编写一个用于测试的角色吧,这个角色的名字就叫"testredhat"。
首先,我们创建一个名为"testredhat"的目录,这个目录就代表了"testredhat"角色,执行如下命令即可:
# mkdir testredhat
角色目录创建完毕,我决定先赋予testredhat角色一个简单的功能,即输出"hello role"这句话,没错,通过debug模块可以输出信息,那么我们需要编写一个debug任务,之前提到过,调用角色时,角色会默认执行tasks/main.yml中的任务,那么我们就把debug任务写在tasks/main.yml文件中吧,首先,在testrole目录中创建tasks子目录,在tasks子目录中创建一个名为main.yml的文件。
# mkdir tasks
# touch tasks/main.yml
在tasks/main.yml文件中写入如下内容
- debug:
msg: "hello role!"
如你所见,直接将我们需要执行的任务写在tasks/main.yml文件中即可。
我们并不需要创建handlers、defaults等目录,因为目前我们编写的角色非常简单,用不到这些目录结构。
我们的第一个测试角色编写完毕了~,就是这么简单,角色编写完毕后,就需要调用对应的角色了,那么怎样才能调用角色呢?其实也很简单,我们只需要在testredhat的同级目录中编写一个简单的剧本即可,此例中,调用角色的剧本文件名为test.yml,它与testredhat目录处于同级目录中,如下:
# ls
testredhat test.yml
我们需要使用test.yml文件来调用testrole角色,test.yml文件的内容如下:
[root@server4 ~]# cat test.yml
- hosts: testB
roles:
- testredhat
如上例所示,调用对应的角色时,需要使用roles关键字进行调用,使用"- hosts"指定目标主机,上例表示,在目标主机testB上执行testredhat角色对应的任务,但是由于testredhat并没有什么其他操作,只是输出了一句话,所以并不会对目标主机有什么实际动作。
那么我们执行test.yml,看看这个测试角色能不能正常被调用,执行后结果如下:
如我们所愿,角色被正常调用了,"hello role!"输出了,没错,我们已经学会使用角色了。
看到此处,你会不会觉得我把简单的问题复杂化了,如果你只是单纯的调用一个debug任务输出一句话,那么是有点复杂化了,但是如果你的配置过程慢慢变得丰富,文件越来越多,结构越来越复杂,那么使用角色会是更好的选择,它能让你的文件结构符合统一的标准,让任何一个懂得这个标准的人快速的阅读你的代码,并且为以后的扩展留出很大的空间,而且,通过刚才的调用过程,你应该已经明白了,我可以将testredhat目录移至到任何ansible主机中进行调用,testredhat目录中包含了这个角色所需的所有文件,它是一个独立的的结构,说到独立,它在逻辑上也是独立的,因为这个角色的配置过程与目标主机是分开的,虽然我们调用角色时,需要编写一个playbook指定对应的目标主机,但是我们并没有修改角色目录中的任何文件,这正好解决了本文开头提出的问题。
刚才在调用角色时,我刻意的将test.yml文件写在了testrole目录的同级目录中,也就是说,调用testrole角色时,test.yml会从同级目录中查找与testrole角色同名的目录,其实,不仅仅是同级目录,还有一些其他的目录,在调用角色时,test.yml也会去查找,这些目录就是:
- 同级目录中的roles目录中。
- 当前系统用户的家目录中的.ansible/roles目录,即 ~/.ansible/roles目录中。
也就是说,只要testrole目录处于上述三个目录中的任何一个目录中,都可以使用上述方法正常的调用。
你也可修改ansible的配置文件,设置自己的角色搜索目录,编辑/etc/ansible/ansible.cfg配置文件,设置roles_path选项,此项默认是注释掉的,将注释符去掉,当你想要设置多个路径时,多个路径之间用冒号隔开,示例如下
roles_path = /etc/ansible/roles:/opt:/testdir
即使你的角色目录不处于上述目录中的任何一个,也可以使用绝对路径的方式,调用对应的角色,示例如下:
- hosts: testB
roles:
- "/testdir/ansible/testredhat/"
如上例所示,我直接使用了testrole的绝对路径,调用了testrole角色,其实,上述写法不是特别正规,标准的语法应该是如下模样:
- hosts: testB
roles:
- role: "/testdir/ansible/testredhat/"
没错,在roles关键字中使用role关键字指定角色对应的绝对路径,也可以直接调用角色,即使不使用绝对路径,也可以使用同样的语法指定角色名,如下:
- hosts: testB
roles:
- role: testredhat
除了上述调用角色的语法,还有一些其他的语法也可以调用角色。
上述示例中,我们没有使用任何变量,那么我们来尝试一下在角色中使用变量。
我们将tasks/main.yml中的内容改为如下内容:
[root@server4 tasks]# vim main.yml
[root@server4 tasks]# cat main.yml
- debug:
msg: "hello {{ testvar }}!"
如上例所示,我们在输出的信息中使用了testvar变量,那么,我们在调用这个角色时,则需要传入对应的变量,否则就会报错,调用上例角色的示例如下:
[root@server4 ~]# vim test.yml
[root@server4 ~]# cat test.yml
- hosts: testB
roles:
- role: testrole
vars:
testvar: "112233"
如上例所示,我们在调用角色时,传入了对应的testvar变量,以便对应的任务可以使用这个变量。
执行上例playbook,最终输出的信息为"hello 112233 !"
其实,我们也可以为testvar变量设置默认值,这样即使在调用角色时没有传入任何参数,也有默认的值可以使用,同时也不会在调用时因为没有传入对应变量而报错,所以,我们需要在testredhat目录中创建一个defaults目录,并且创建defaults/main.yml文件,defaults/main.yml文件内容如下:
# cat testrole/defaults/main.yml
testvar: "role"
如你所见,我们在defaults/main.yml文件中定义了testvar变量,默认值为"role"
此刻,我们调用testrole时,即使不传入testvar变量,也可以正常的进行调用了,如果不传入testvar变量,则默认所使用"role"作为变量值。
[root@server4 ~]# vim test.yml
[root@server4 ~]# cat test.yml
- hosts: testB
roles:
- role: testredhat
需要注意的是,在默认情况下,角色中的的变量是全局可访问的。
这样说可能不容易理解,不如来看一个小示例,如下
重新建立一个role并写入以下文件:
[root@server4 ~]# mkdir testlinux
[root@server4 ~]# mkdir testlinux/tasks
[root@server4 ~]# mkdir testlinux/defaults
[root@server4 ~]# vim testlinux/tasks/main.yml
[root@server4 ~]# vim testlinux/defaults/main.yml
[root@server4 ~]# cat testlinux/tasks/main.yml
- debug:
msg: "hello {{ testvar }}"
[root@server4 ~]# cat testlinux/defaults/main.yml
testvar: "linux"
testredhat角色中的文件内容:
[root@server4 ~]# cat testredhat/tasks/main.yml
- debug:
msg: "hello {{ testvar }}!"
[root@server4 ~]# cat testredhat/defaults/main.yml
testvar: 'role'
如上图所示,我定义了两个示例角色,这两个示例角色中都使用了名为testvar的变量,而且在这两个角色中,testvar变量都有各自的默认值,在testredhat角色中,testvar的默认值为"role",在testlinux角色中,testvar的默认值为"linux"。
[
root@server4 ~]# vim test.yml
[root@server4 ~]# cat test.yml
- hosts: testB
roles:
- role: testredhat
vars:
testvar: "666666666"
- role: testlinux
在test.yml文件中,我调用了这两个角色,在调用testredhat角色时,我传入了testvar变量,其值为666666666,但是在调用testlinux角色时,没有传入testvar变量,按照正常的理解,当执行test.yml文件时,testrole应该使用"zsythink"作为testvar变量的值,demorole应该使用默认值"demo"作为testvar变量的值,那么我们来执行一下test.yml,看看结果与我们想象的是否相同,执行结果如下
如你所见,结果与我预想的并不相同,无论是testredhat还是testredhat,都使用了"666666666"作为了testvar的变量值,出现上述状况的原因我们刚才已经提到过,原因是:在默认情况下,角色中的变量是全局可访问的,上例中,当将testvar变量的值设置为"666666666"时,就表示将testredhat和testredhat中的testvar变量的值都设置成了"666666666",所以最终输出信息时,两个角色的testvar变量都使用了相同的值。
如果想要解决上述问题,则可以将变量的访问域变成角色所私有的,如果想要将变量变成角色私有的,则需要设置/etc/ansible/ansible.cfg文件,将129行的private_role_vars的值设置为yes,默认情况下,"private_role_vars = yes"是被注释掉的,将前面的注释符去掉皆可,设置完成后,再次执行上例中的test.yml文件,输出结果如下:
默认情况下,我们无法多次调用同一个角色,也就是说,如下playbook只会调用一次testrole角色:
[root@server4 ~]# vim test.yml
[root@server4 ~]# cat test.yml
- hosts: testB
roles:
- role: testredhat
- role: testredhat
执行上例playbook会发现,testrole的debug模块只输出了一次:
如果想要多次调用同一个角色,有两种方法,如下:
方法一:设置角色的allow_duplicates属性 ,让其支持重复的调用。
方法二:调用角色时,传入的参数值不同。
方法一需要为角色设置allow_duplicates属性,而此属性需要设置在meta/main.yml文件中,所以我们需要在testrole中创建meta/main.yml文件,写入如下内容:
[root@server4 ~]# vim testredhat/meta/main.yml
[root@server4 ~]# cat testredhat/meta/main.yml
allow_duplicates: true
如上例所示,我们将allow_duplicates属性设置为true,表示可以重复调用同一个角色。
属性设置完毕后,执行如下playbook尝试两次调用同一个角色,是完全可以正常执行的。
说完方法一,现在来说说方法二,当调用角色需要传参时,如果参数的值不同,则可以连续调用多次
下例中,两次调用了testrole角色,两次调用都传入了testvar变量,但是testvar变量的值不同。
[root@server4 ~]# vim test.yml
[root@server4 ~]# cat test.yml
- hosts: testB
roles:
- role: testredhat
vars:
testvar: "cl"
- role: testredhat
vars:
testvar: "987654"
使用上例的方法,也可以对同一角色调用多次。
在上述示例中,我们已经学会了如何在defaults/main.yml文件中定义变量的默认值,不过我们还没有使用过vars/main.yml文件,也没有在其中定义过任何变量,之前提到过**,定义在vars/main.yml文件中的变量优先级比较高,难以被覆盖**,那我们就来动手试试,看看定义在这个文件中的变量的优先级到底有多高,为了使效果更加明显,我们在defaults/main.yml文件和vars/main.yml文件中同时定义testvar变量,并为其赋值不同的值,如下:
[root@server4 ~]# cat testredhat/defaults/main.yml
testvar: 'role'
[root@server4 ~]# cat testredhat/vars/main.yml
testvar: "testvar_in_vars_directory"
同时,我们在调用testrole时,仍然传入testvar变量,看看testvar变量到底会使用哪个值作为最终的值,示例如下:
[root@server4 ~]# vim test.yml
[root@server4 ~]# cat test.yml
- hosts: testB
roles:
- role: testredhat
vars:
testvar: "cl"
执行上例playbook,最终结果如下:
从上述信息可以看出,即使在调用角色的时候传入对应的变量,也无法覆盖定义在vars/main.yml文件中的值,那么我们可以利用这个特性,将你想要确保使用的值定义在vars/main.yml中,以便别人在调用角色时,使用的值就是你定义的值,当然,如果你强烈推荐的值别人压根不想使用,也是有办法灵活的进行覆盖的,比如在调用playbook时使用"-e"选项传入参数,示例如下:
[root@server4 ~]# ansible-playbook -e testvar='forceusethis' test.yml
除了使用"-e"传入的变量的优先级,其他变量(包括主机变量)的优先级均低于vars/main.yml中变量的优先级。
假设现在testredhat需要使用一些模板,那么也可以直接将模板文件放到templates目录中。
比如,testredha中需要使用一个名为test.conf.j2的模板文件,那么我们就将test.conf.j2文件放置在testredha/templates/目录中,test.conf.j2文件内容如下
[root@server4 ~]# cat testredhat/templates/test.conf.j2
something in template;
{{ template_var }}
模板文件中使用到了 template_var变量,我们可以为 template_var变量定义一个默认变量
[root@server4 ~]# cat testredhat/defaults/main.yml
testvar: 'role'
template_var: "template"
然后在testredha中,直接使用这个模板文件
[root@server4 ~]# cat testredhat/tasks/main.yml
- debug:
msg: "hello {{ testvar }}!"
- template:
src: test.conf.j2
dest: /opt/test.conf
在sever3生成的文件:
[root@server3 ~]# cat /opt/test.conf
something in template;
template
如上例所示,我们在使用template任务时,src直接指定了对应的模板文件的名称,并没有指定任何路径,这代表角色会默认去templates子目录中查找对应的文件。
如果你想要在角色中使用一些handlers以便进行触发,则可以直接将对应的handler任务写入到handlers/main.yml文件中,示例如下:
[root@server4 ~]# vim testredhat/handlers/main.yml
[root@server4 ~]# cat testredhat/handlers/main.yml
- name: test_handler
debug:
msg: "this is a test handler"
我直接在handlers/main.yml文件中写入了一个名为"test_handler"任务,以便随时进行触发。
为了能够更加简单的触发对应的handler,我直接将tasks/main.yml中的debug任务的状态强行设置为"changed",示例如下:
注:前文已经总结了changed_when的用法,此处不再赘述。
[root@server4 ~]# vim testredhat/tasks/main.yml
- debug:
msg: "hello testredha!"
changed_when: true
notify: test_handler
如上例所示,当需要notify对应handler时,直接写入handler对应的名称即可,角色会自动去handlers/main.yml文件中查找对应的handler。
最后进行测试: