Ansible(三十三)-- ansible roles(角色) 详解

一、角色的定义

角色(roles)是ansible自1.2版本开始引入的新特性,用于层次性,结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单的说,roles就是通过分别将变量、文件、任务、模块及处理器放置于单独的目录中、并可以便捷地include他们的一种机制。角色一般用于基于主机构建服务的场景中、但也可以是用于构建守护进程等场景中。

二、角色示例

为了让你更直观的认识角色的标准结构,我提前创建了一个示例角色,示例角色的目录结构如下:
在testredhat目录下文件的结构:

Ansible(三十三)-- ansible roles(角色) 详解_第1张图片

如你所见,我创建的示例角色的角色名为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,看看这个测试角色能不能正常被调用,执行后结果如下:
Ansible(三十三)-- ansible roles(角色) 详解_第2张图片

如我们所愿,角色被正常调用了,"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 !"
Ansible(三十三)-- ansible roles(角色) 详解_第3张图片
其实,我们也可以为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

Ansible(三十三)-- ansible roles(角色) 详解_第4张图片
需要注意的是,在默认情况下,角色中的的变量是全局可访问的。

这样说可能不容易理解,不如来看一个小示例,如下
重新建立一个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,看看结果与我们想象的是否相同,执行结果如下
Ansible(三十三)-- ansible roles(角色) 详解_第5张图片

如你所见,结果与我预想的并不相同,无论是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文件,输出结果如下:
在这里插入图片描述Ansible(三十三)-- ansible roles(角色) 详解_第6张图片

默认情况下,我们无法多次调用同一个角色,也就是说,如下playbook只会调用一次testrole角色:

[root@server4 ~]# vim test.yml 
[root@server4 ~]# cat test.yml 
- hosts: testB
  roles:
  - role: testredhat
  - role: testredhat

执行上例playbook会发现,testrole的debug模块只输出了一次:

Ansible(三十三)-- ansible roles(角色) 详解_第7张图片
如果想要多次调用同一个角色,有两种方法,如下:

方法一:设置角色的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尝试两次调用同一个角色,是完全可以正常执行的。
Ansible(三十三)-- ansible roles(角色) 详解_第8张图片

说完方法一,现在来说说方法二,当调用角色需要传参时,如果参数的值不同,则可以连续调用多次

下例中,两次调用了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"

Ansible(三十三)-- ansible roles(角色) 详解_第9张图片

使用上例的方法,也可以对同一角色调用多次。

在上述示例中,我们已经学会了如何在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,最终结果如下:
Ansible(三十三)-- ansible roles(角色) 详解_第10张图片

从上述信息可以看出,即使在调用角色的时候传入对应的变量,也无法覆盖定义在vars/main.yml文件中的值,那么我们可以利用这个特性,将你想要确保使用的值定义在vars/main.yml中,以便别人在调用角色时,使用的值就是你定义的值,当然,如果你强烈推荐的值别人压根不想使用,也是有办法灵活的进行覆盖的,比如在调用playbook时使用"-e"选项传入参数,示例如下:

[root@server4 ~]# ansible-playbook -e testvar='forceusethis' test.yml 

Ansible(三十三)-- ansible roles(角色) 详解_第11张图片

除了使用"-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

Ansible(三十三)-- ansible roles(角色) 详解_第12张图片
在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。
最后进行测试:
Ansible(三十三)-- ansible roles(角色) 详解_第13张图片

本博客参考:http://www.zsythink.net/archives/3063

你可能感兴趣的:(Ansible(三十三)-- ansible roles(角色) 详解)