ansible系列(30)--ansible的role详解
1. Ansible Roles
ansilbe roles
是自1.2版本引入的新特性,用于层次性、结构化地组织playbook
。roles
能够根据层次型结构自动装载变量文件、tasks
以及handlers
等。要使用roles
只需要在playbook
中使用include
指令即可。简单来讲,roles
就是通过分别将变量、文件、任务、模板及处理器放置于单独的目录中,并可以便捷地include
它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中。
1.1 roles目录结构
roles
官方目录结构,必须按如下方式定义。在每个目录中必须有 main.yml
文件,这些属于强制要求。
[root@xuzhichao /data/ansible/roles]$tree
.
├── playbooks.yml <==执行剧本
└── roles <==角色必须与执行剧本在同一级目录中
├── project <==项目名称
│ ├── default
│ ├── files
│ ├── handlers
│ ├── meta
│ ├── tasks
│ ├── templates
│ └── vars
└── project1 <==另一个项目
- 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
目录。
1.2 roles编写步骤
1.2.1 编写基本的roles
编写roles
的步骤为:
- 创建
roles
项目目录结构,手动创建或使用ansible-galaxy init test roles
; - 编写
roles
的功能,也就是tasks
; - 最后
playbook
引用roles
编写好的tasks
;
-
创建一个测试的
roles
目录,就叫roles_test1
,并创建下面的子目录,最基本的子目录需要有tasks
目录:[root@xuzhichao ~]# mkdir /data/ansible/roles [root@xuzhichao ~]# cd /data/ansible/roles [root@xuzhichao roles]# mkdir roles_test1 [root@xuzhichao roles]# cd roles_test1 [root@xuzhichao roles_test1]# mkdir tasks
-
然后在
tasks
目录中编写相关的任务,在tasks
目录中必须由一个main.yml
文件,可以把需要编写的任务直接写到main.yml
文件中,也可以写到其他文件中,然后在main.yml
文件中通过include
的方式引用这些文件。在tasks
中的文件不需要写play
部分,只需要写task
部分。示例如下:[root@xuzhichao roles_test1]# cat tasks/main.yml - name: Print Vars debug: msg: "ansible_test_roles"
或者:
[root@xuzhichao roles_test1]# cat tasks/task1.yml - name: Print Vars debug: msg: "ansible_test_roles" [root@xuzhichao roles_test1]# cat tasks/main.yml - include: task1.yml
-
写好
task
后,需要编写playbook
调用roles
,playbook
编写如下:[root@xuzhichao roles]# cat rolestest1.yml - hosts: localhost roles: - role: roles_test1
-
运行
playbook
:[root@xuzhichao roles]# ansible-playbook rolestest1.yml PLAY [localhost] ********************************************************************************************************************************************** TASK [Gathering Facts] **************************************************************************************************************************************** ok: [localhost] TASK [roles_test1 : Print Vars] ******************************************************************************************************************************* ok: [localhost] => { "msg": "ansible_test_roles" } PLAY RECAP **************************************************************************************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
1.2.2 roles的调用
在playbook
调用roles
时,如何找到roles
所在的目录呢?有以下几种方式:
-
playbook
文件会在同级目录中寻找与调用的角色同名的roles
,例如在上节中rolestest1.yml
文件中调用了roles_test1
这个角色,会自动在同级目录中找到roles_test1
这个目录,并执行其中的tasks
。[root@xuzhichao roles]# ll total 4 drwxr-xr-x 3 root root 19 Aug 7 16:57 roles_test1 -rw-r--r-- 1 root root 46 Aug 7 17:14 rolestest1.yml
-
playbook
文件还会寻找同级目录中的roles
目录,执行roles
目录中同名的角色项目,例如:[root@xuzhichao roles]# tree . ├── roles │ └── roles_test1 │ └── tasks │ ├── main.yml │ └── task1.yml └── rolestest1.yml
-
当前系统用户的家目录中的
.ansible/roles
目录,即~/.ansible/roles
目录中。 -
也可以修改
ansible
配置文件中定义的roles
的路径,多个路径之间使用冒号:
隔开:[root@xuzhichao roles]# vim /etc/ansible/ansible.cfg roles_path = /etc/ansible/roles:/data/ansible/roles:/opt
-
在
playbook
文件中直接写roles
的绝对路径也可以调用roles
:[root@xuzhichao roles]# cat rolestest1.yml - hosts: localhost roles: - role: /data/ansible/roles/roles_test1
1.2.3 roles中使用变量
-
修改一下
roles
文件,引入变量:[root@xuzhichao roles]# cat roles_test1/tasks/task1.yml - name: Print Vars debug: msg: "{{ var1 }}"
-
在调用这个角色时,则需要传入对应的变量,否则就会报错,调用上例角色的示例如下:
[root@xuzhichao roles]# cat rolestest1.yml - hosts: localhost roles: - role: roles_test1 vars: var1: string1
执行
palybook
,结果如下:[root@xuzhichao roles]# ansible-playbook rolestest1.yml PLAY [localhost] ********************************************************************************************************************************************** TASK [roles_test1 : Print Vars] ******************************************************************************************************************************* ok: [localhost] => { "msg": "string1" }
-
也可以为变量设置默认值,这样即使在调用角色时没有传入任何参数,也有默认的值可以使用,同时也不会在调用时因为没有传入对应变量而报错,需要在
roles_test1
目录中创建一个defaults
目录,并且创建defaults/main.yml
文件,defaults/main.yml
文件内容如下:[root@xuzhichao roles]# cd roles_test1/ [root@xuzhichao roles_test1]# mkdir defaults [root@xuzhichao roles_test1]# cat defaults/main.yml var1: string2
-
此时运行
palybook
,结果仍然是string1
,说明playbook
文件中定义的变量值比defaults/main.yml
文件定义的变量优先级高:[root@xuzhichao roles]# ansible-playbook rolestest1.yml PLAY [localhost] ********************************************************************************************************************************************** TASK [roles_test1 : Print Vars] ******************************************************************************************************************************* ok: [localhost] => { "msg": "string1" }
-
删除
playbook
文件中定义的变量后,运行playbook
文件,显示的变量就是defaults/main.yml
文件定义的变量:[root@xuzhichao roles]# ansible-playbook rolestest1.yml PLAY [localhost] ********************************************************************************************************************************************** TASK [roles_test1 : Print Vars] ******************************************************************************************************************************* ok: [localhost] => { "msg": "string2" }
-
接下来测试一下在
vars/main.yml
文件中定义变量,定义在vars/main.yml
文件中的变量优先级比较高,难以被覆盖,我们测试一下定义在这个文件中的变量的优先级到底有多高,为了使效果更加明显,我们在defaults/main.yml
文件和vars/main.yml
文件,以及vars_files
中同时定义var1
变量,并为其赋值不同的值,如下:#在vars/main.yml中定义变量: [root@xuzhichao roles_test1]# cat vars/main.yml var1: string3 #在defaults/main.yml中定义变量: [root@xuzhichao roles_test1]# cat defaults/main.yml var1: string2 #在vars_files中定义变量: [root@xuzhichao roles]# cat /data/ansible/roles/vars.yml var1: string1 #定义playbook: [root@xuzhichao roles]# cat rolestest1.yml - hosts: localhost vars_files: ./vars.yml roles: - role: roles_test1 vars: var1: string4
-
运行
playbook
文件,最终显示的变量是vars/main.yml
中定义的变量:[root@xuzhichao roles]# ansible-playbook rolestest1.yml PLAY [localhost] ********************************************************************************************************************************************** TASK [roles_test1 : Print Vars] ******************************************************************************************************************************* ok: [localhost] => { "msg": "string3" }
-
变量优先级总结:前文中测试变量优先级时除了在命令行中定义的变量外,
vars_files
中定义的变量优先级最高,这样我们可以得出结论,变量的优先级最高的仍然是ansible-playbook
命令行定义的变脸,其次是vars/main.yml
的变量,其他的变量的定义方式的优先级不变。 -
注意:在默认情况下,角色中的的变量是全局可访问的。就是说在同一个
playbook
中为一个role
定义了变量,那么这个playbook
中其他的role
也会自动使用这个变量,例如:[root@xuzhichao roles]# cat rolestest1.yml - hosts: localhost roles: - role: roles_test1 vars: var1: string4 - role: roles_test2 [root@xuzhichao roles]# ansible-playbook rolestest1.yml PLAY [localhost] ********************************************************************************************************************************************** TASK [roles_test1 : Print Vars] ******************************************************************************************************************************* ok: [localhost] => { "msg": "string4" } TASK [roles_test2 : Print Vars] ******************************************************************************************************************************* ok: [localhost] => { "msg": "string4" }
如果想要解决上述问题,则可以将变量的访问域变成角色所私有的,如果想要将变量变成角色私有的,则需要设置
/etc/ansible/ansible.cfg
文件,将private_role_vars
的值设置为yes
,默认情况下,”private_role_vars = yes”
是被注释掉的,将前面的注释符去掉皆可,设置完成后,再次执行上例中的rolestest1.yml
文件,输出结果如下:[root@xuzhichao roles]# ansible-playbook rolestest1.yml PLAY [localhost] ********************************************************************************************************************************************** TASK [roles_test1 : Print Vars] ******************************************************************************************************************************* ok: [localhost] => { "msg": "string4" } TASK [roles_test2 : Print Vars] ******************************************************************************************************************************* ok: [localhost] => { "msg": "string2" } [root@xuzhichao roles]# cat roles_test2/defaults/main.yml var1: string2
1.2.4 多次调用同一个role
默认情况下,我们无法多次调用同一个角色,也就是说,如下playbook
只会调用一次role
角色:
# cat test.yml
- hosts: test
roles:
- role: testrole
- role: testrole
执行上例playbook
会发现,testrole
的debug
模块只输出了一次,如果想要多次调用同一个角色,有两种方法,如下:
-
方法一:设置角色的
allow_duplicates
属性 ,让其支持重复的调用。# cat testrole/meta/main.yml allow_duplicates: true
-
方法二:调用角色时,传入的参数值不同。
# cat test.yml - hosts: test70 roles: - role: testrole vars: testvar: "123" - role: testrole vars: testvar: "abc"
1.2.5 roles管理模板文件
前文中提到,roles
中把所有的模板文件放置到templates
目录中,我们来测试一下模板文件的使用。
-
新增
templates
目录,建立模板文件:[root@xuzhichao roles]# mkdir roles_test1/templates [root@xuzhichao roles]# cat roles_test1/templates/temp.j2 something in template; {{ template_var }}
-
tasks/main.yml
文件如下:[root@xuzhichao roles]# cat roles_test1/tasks/main.yml - name: Print Vars debug: msg: "{{ var1 }}" - name: Copy Templates template: src: temp.j2 dest: /tmp/temp
-
变量文件如下:
[root@xuzhichao roles]# cat roles_test1/vars/main.yml var1: string1 template_var: tempvar
-
playbook
文件如下:[root@xuzhichao roles]# cat rolestest1.yml - hosts: localhost roles: - role: roles_test1
-
运行
playbook
文件:[root@xuzhichao roles]# ansible-playbook rolestest1.yml PLAY [localhost] ********************************************************************************************************************************************** TASK [roles_test1 : Print Vars] ******************************************************************************************************************************* ok: [localhost] => { "msg": "string1" } TASK [roles_test1 : Copy Templates] *************************************************************************************************************************** changed: [localhost] PLAY RECAP **************************************************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@xuzhichao roles]# cat /tmp/temp something in template; tempvar
1.2.6 roles管理handlers文件
如果想要在角色中使用一些handlers
以便进行触发,则可以直接将对应的handler
任务写入到handlers/main.yml
文件中,示例如下:
-
建立
handlers
目录,建立main.yml
文件:[root@xuzhichao roles]# mkdir roles_test1/templates [root@xuzhichao roles]# mkdir roles_test1/handlers [root@xuzhichao roles]# cd roles_test1/handlers [root@xuzhichao handlers]# vim main.yml - name: test_handler debug: msg: "this is a test handler"
-
为了能够更加简单的触发对应的
handler
,直接将tasks/main.yml
中的debug
任务的状态强行设置为”changed”
,示例如下:[root@xuzhichao roles]# cat roles_test1/tasks/main.yml - name: Print Vars debug: msg: "{{ var1 }}" changed_when: true notify: test_handler - name: Copy Templates template: src: temp.j2 dest: /tmp/temp [root@xuzhichao roles]# cat rolestest1.yml - hosts: localhost roles: - role: roles_test1
-
运行
playbook
文件,直接触发handlers
:[root@xuzhichao roles]# ansible-playbook rolestest1.yml PLAY [localhost] ********************************************************************************************************************************************** TASK [roles_test1 : Print Vars] ******************************************************************************************************************************* changed: [localhost] => { "msg": "string1" } TASK [roles_test1 : Copy Templates] *************************************************************************************************************************** ok: [localhost] RUNNING HANDLER [roles_test1 : test_handler] ****************************************************************************************************************** ok: [localhost] => { "msg": "this is a test handler" } PLAY RECAP **************************************************************************************************************************************************** localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
1.2.7 roles的依赖关系
roles
允许在使用时自动引入其他 role
, role
依赖关系存储在meta/main.yml
文件中。
例如: 安装 wordpress
项目时:
- 需要先确保
nginx
与php-fpm
的role
都能正常运行; - 然后在
wordpress
的role
中定义,依赖关系; - 依赖的
role
有nginx
以及php-fpm
;
-
在
wordpress
的role
中新建meta
目录,编写main.yml
文件:[root@m01 playbook]# cat /root/roles/wordpress/meta/main.yml --- dependencies: - { role: nginx } - { role: php-fpm }
-
在
wodpress
的playbook
中就不需要写安装nginx
和php-fpm
的role
了,只写一个wordpress
就可以了,安装wordpress
之前会自动先部署了nginx
和php-fpm
。[root@m01 playbook]# cat /root/roles/wordpress,yml --- - hosts: NginxWebs roles: - role: wordpress
1.2.8 roles的相互调用
roles
中的各项目之间的文件是可以跨项目互相调用的。
跨项目调用时需要指明要调用的role
的名称。
例如:同时部署了nginx
和apache
两个项目,两个项目中使用了相同的index.html
主页文件,这个蛀牙文件已经在nginx
项目中定义过了。若此时需要在apache
项目中引用,可以使用如下方式:
[root@localhost /data/roles]$vim apache/tasks/html.yml <==页面配置
- name: Copy Index File
copy:
src:roles/nginx/files/index.html #跨项目调用路径要写roles的名称
dest: /var/www/nginx/html/
1.3 roles部署nginx实例
-
首先创建项目目录
nginx
,并创建files
、handlers
、meta
、tasks
、templates
和vars
等目录;用不到的目录可以创建为空目录,也可以不创建,用到时再创建也可以:[root@xuzhichao roles]# mkdir nginx/{tasks,templates,handlers,files,vars} -pv
-
编写
nginx
的任务功能:[root@xuzhichao nginx]# cat tasks/Install_Nginx.yml - name: Install Nginx Server yum: name: nginx state: present [root@xuzhichao nginx]# cat tasks/Configure_Nginx.yml - name: Configure Nginx Conf Files template: src: "{{ item.src }}" dest: "{{ item.dest }}" owner: "{{ item.user }}" group: "{{ item.group }}" mode: "{{ item.mode }}" loop: - { src: "nginx.conf.j2", dest: "/etc/nginx/nginx.conf", user: "root", group: "root", mode: "0644" } - { src: "ansible.web.conf.j2", dest: "/etc/nginx/conf.d/ansible.web.conf", user: "root", group: "root", mode: "0644" } notify: Restart Nginx Server [root@xuzhichao nginx]# cat tasks/Check_Configure.yml - name: Check Nginx Configure shell: cmd: "/usr/sbin/nginx -t" register: check_nginx changed_when: - check_nginx.stdout.find("ok") - false [root@xuzhichao nginx]# cat tasks/Init_Nginx.yml - name: Create Web Site Directory file: path: "{{ web_site_directory }}" state: directory owner: "{{ nginx_user }}" group: "{{ nginx_group }}" mode: "0755" - name: Init Nginx Server copy: src: "{{ nginxfile }}" dest: "{{ web_site_directory }}" owner: "{{ nginx_user }}" group: "{{ nginx_group }}" mode: "0644" [root@xuzhichao nginx]# cat tasks/Start_Nginx.yml - name: Start Nginx Server systemd: name: nginx state: started enabled: yes [root@xuzhichao nginx]# cat tasks/main.yml - include: Install_Nginx.yml - include: Configure_Nginx.yml - include: Check_Configure.yml - include: Init_Nginx.yml - include: Start_Nginx.yml
-
编写
handlers
文件:[root@xuzhichao nginx]# cat handlers/main.yml - name: Restart Nginx Server service: name: nginx state: restarted
-
编写模板文件:
[root@xuzhichao nginx]# cat templates/nginx.conf.j2 user {{ nginx_user }}; worker_processes {{ ansible_processor_vcpus }}; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; access_log logs/access.log main; log_format access_json '{ "@timestamp": "$time_iso8601", ' '"remote_addr": "X-Forwarded_For", ' '"referer": "$http_referer", ' '"request": "$request", ' '"status": $status, ' '"bytes":$body_bytes_sent, ' '"agent": "$http_user_agent", ' '"x_forwarded": "$http_x_forwarded_for", ' '"upstr_addr": "$upstream_addr",' '"upstr_host": "$upstream_http_host",' '"upstreamtime": "$upstream_response_time" }'; server_tokens off; fastcgi_cache_path /data/nginx/fastcgi_cache levels=1:1:1 keys_zone=fastcgi_cache:250m inactive=10m max_size=1g; sendfile on; keepalive_timeout 65; gzip on; include /etc/nginx/conf.d/*.conf; } [root@xuzhichao nginx]# cat templates/ansible.web.conf.j2 server { listen 80; server_name {{ server_name }}; charset utf-8,gbk; #防盗链 valid_referers none blocked server_names *.b.com b.* ~\.baidu\. ~\.google\.; if ( $invalid_referer ) { return 403; } #浏览器图标 location = /favicon.ico { root {{ web_site_directory }}; } location / { root {{ web_site_directory }}; index index.html index.php; } }
-
编写
nginx
主页文件:[root@xuzhichao nginx]# cat files/index.html <h1>ansible web site</h1>
-
编写变量文件:
[root@xuzhichao nginx]# cat vars/main.yml web_site_directory: /data/nginx/ansible nginxfile: index.html nginx_user: nginx nginx_group: nginx server_name: ansible.web.com
-
编写
playbook
文件:[root@xuzhichao nginx]# cat nginx.yml - hosts: 192.168.20.23 remote_user: root roles: - role: nginx
-
整个
roles
的目录结果如下:[root@xuzhichao nginx]# tree . ├── files │ └── index.html ├── handlers │ └── main.yml ├── nginx.yml ├── tasks │ ├── Check_Configure.yml │ ├── Configure_Nginx.yml │ ├── Init_Nginx.yml │ ├── Install_Nginx.yml │ ├── main.yml │ └── Start_Nginx.yml ├── templates │ ├── ansible.web.conf.j2 │ └── nginx.conf.j2 └── vars └── main.yml 5 directories, 12 files
-
运行
palybook
文件:[root@xuzhichao nginx]# ansible-playbook nginx.yml PLAY [192.168.20.23] ****************************************************************************************************************************************** TASK [nginx : Install Nginx Server] *************************************************************************************************************************** ok: [192.168.20.23] TASK [nginx : Configure Nginx Conf Files] ********************************************************************************************************************* ok: [192.168.20.23] => (item={u'dest': u'/etc/nginx/nginx.conf', u'src': u'nginx.conf.j2', u'group': u'root', u'user': u'root', u'mode': u'0644'}) ok: [192.168.20.23] => (item={u'dest': u'/etc/nginx/conf.d/ansible.web.conf', u'src': u'ansible.web.conf.j2', u'group': u'root', u'user': u'root', u'mode': u'0644'}) TASK [nginx : Check Nginx Configure] ************************************************************************************************************************** ok: [192.168.20.23] TASK [nginx : Create Web Site Directory] ********************************************************************************************************************** ok: [192.168.20.23] TASK [nginx : Init Nginx Server] ****************************************************************************************************************************** changed: [192.168.20.23] TASK [nginx : Start Nginx Server] ***************************************************************************************************************************** changed: [192.168.20.23] PLAY RECAP **************************************************************************************************************************************************** 192.168.20.23 : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
-
查看
nginx
主机的相关信息:[root@nginx03 ~]# cat /data/nginx/ansible/index.html <h1>ansible web site</h1> [root@nginx03 ~]# cat /etc/nginx/conf.d/ansible.web.conf server { listen 80; server_name ansible.web.com; charset utf-8,gbk; #防盗链 valid_referers none blocked server_names *.b.com b.* ~\.baidu\. ~\.google\.; if ( $invalid_referer ) { return 403; } #浏览器图标 location = /favicon.ico { root /data/nginx/ansible; } location / { root /data/nginx/ansible; index index.html index.php; } }