当需要对多个远程节点,做很多操作的时候,如果将所有的内容都书写到一个playbooks中,这就会产生一个很大的文件,而且里面的某些内容也很难复用。此时不得不考虑怎么样分隔及组织相关的文件。
最基本的,可以将任务列表单独分隔到一个小文件里,然后在tasks中包含该文件即可。同样的handlers其实也是一个任务列表(里面指定的任务都需要有一个全局唯一的名称),所以也可以在handlers中包含单独定义好的handlers任务文件。
playbooks也可以包含其他playbooks文件。
playbooks中的文件包含使得你在大多数情况下并不需要考虑具体功能的实现细节,只需要复用某些已有的文件即可,类似于编程中的封装概念一样,当某个功能封装并测试OK以后,在需要的地方调用即可,如,一个人会开车,但他并不需要了解发动机的原理是什么。
Ansible中的“角色”就是利用文件包含这个功能来使得文件组织的更加清晰明了,并具有很高的复用性。
任务文件包含和重用
如果想在多个playbooks之间服用任务列表,那么include将会是个很好的方法。
官方示例的一个很小的任务列表文件:
--- # possibly saved as tasks/foo.yml - name: placeholder foo command: /bin/foo - name: placeholder bar command: /bin/bar
在playbooks中包含该文件:
tasks: - include: tasks/foo.yml
同时也可以向包含文件内传递参数,称为“参数化包含”。
例如,想要部署多个wordpress,可以将部署操作写到一个文件中,保存为wordpress.yml,然后以下边这样包含文件并传递参数:
tasks: - include: wordpress.yml wp_user=timmy - include: wordpress.yml wp_user=alice - include: wordpress.yml wp_user=bob
在wordpress.yml可以{{ wp_user }}的方式使用变量。
还可以传递一个参数列表:
tasks: - { include: wordpress.yml, wp_user: timmy, ssh_keys: [ 'keys/one.txt', 'keys/two.txt' ] }
以上两种传递参数都称为显式传参,在vars中定义的变量特可以在被包含文件中使用。
也可以在handlers中包含文件。如,想要定义一个重启apache的handler,这时候只需要为所有的playbooks定义一个handler即可,保存为handlers.yml:
--- # this might be in a file like handlers/handlers.yml - name: restart apache service: name=httpd state=restarted
在playbooks中包含该文件:
handlers: - include: handlers/handlers.yml
还可以将一个playbooks文件导入到另外一个playbooks中,这样的话,只需要定义一个顶级的且内容为一些较为通用的操作的playbooks文件,然后再文件中包含其他的playbooks即可。
- name: this is a play at the top level of a file hosts: all remote_user: root tasks: - name: say hi tags: foo shell: echo "hi..." - include: load_balancers.yml - include: webservers.yml - include: dbservers.yml
示例:
[root@web1 ansible]# tree /etc/ansible/ /etc/ansible/ ├── handlers │ └── restart.yml ├── hosts ├── main.yml ├── tasks │ └── apache.yml └── templates └── httpd.j2 [root@web1 ansible]# cat tasks/apache.yml --- - name: config httpd.file template: src=templates/httpd.j2 dest=/etc/httpd.conf [root@web1 ansible]# cat handlers/restart.yml --- - name: restart apache service: name=httpd state=restarted [root@web1 ansible]# cat main.yml --- - hosts: webservers remote_user: root vars: http_port: 8085 max_clients: 123 tasks: - include: tasks/apache.yml handlers: - include: handlers/restart.yml [root@web1 ansible]# ansible-playbook /etc/ansible/main.yml PLAY [webservers] ************************************************************* GATHERING FACTS *************************************************************** ok: [192.168.1.65] TASK: [config httpd.file] ***************************************************** changed: [192.168.1.65] PLAY RECAP ******************************************************************** 192.168.1.65 : ok=2 changed=1 unreachable=0 failed=0
注:上边文件虽然定义并包含了重启apache的handler,但并没有任务一个任务来触发该handler。所以并没有执行重启操作,即使配置文件发生了改变。
角色
“文件包含”本身只是很简单且很单纯的包含各个子文件,当文件较多的时候,其组织方式也并是好,且需要人为的来指定包含哪些文件。Ansible中的“角色”会基于已知的文件结构来自动加载特定的变量文件、任务文件一级handlers文件,使用角色来组织内容可以更好的实现共享。
官方的一个使用角色组织内容的文件结构:
site.yml webservers.yml fooservers.yml #playbooks文件,最后ansible执行的就是这些文件 roles/ common/ files/ templates/ #保存操作中所需要的模板文件 tasks/ #保存任务列表文件 handlers/ #保存handler文件 vars/ #保存定义变量的文件 defaults/ #默认变量文件 meta/ #角色的依赖关系 webservers/ files/ templates/ tasks/ handlers/ vars/ defaults/ meta/
在playbooks中这样书写内容,如上边的webservers.yml文件:
--- - hosts: webservers roles: - common - webservers
每个角色都会遵循以下原则:
- 如果`roles/x/tasks/main.yml`存在,里面的任务列表会被添加到`play`中。
- 如果`roles/x/handlers/main.yml`存在,里面的`handlers`会被添加到`play`中。
- 如果`roles/x/vars/main.yml`存在,里面的变量会被添加到`play`中。
- 如果`roles/x/meta/main.yml`存在,里面的角色依赖会被添加到角色列表中。
- 在`roles/x/files`任务所需要被复制的文件,无需绝对路径或者相对路径都可以引用该文件。
- 在`roles/x/files`中的任务脚本都可以直接使用该文件,无需指定绝对路径或者是相对路径。
- 在`roles/x/templates`中的模板,无需指定绝对路径或者相对路径,都可以直接使用文件名引用该文件。
- 需要包含在`roles/x/tasks`中的任务文件时,无需指定绝对路径或者相对路径,可以直接使用文件名包含。
tasks,handlers,vars,meta目录下main.yml中的内容都会被自动调用。
如果某些文件不存在的话,将会自动跳过,所以`vars/`之类的子目录如果用不到的话也可以不创建。
也可以给角色传递参数,在playbooks文件定义:
--- - hosts: webservers roles: - common - { role: foo_app_instance, dir: '/opt/a', port: 5000 } - { role: foo_app_instance, dir: '/opt/b', port: 5001 }
有时候也可以给角色添加执行条件:
--- - hosts: webservers roles: - { role: some_role, when: "ansible_os_family == 'RedHat'" }
当远程节点的操作系统为RedHat的时候才会执行"some_role"中的操作。
如果在某个play中定义了一个tasks任务块,它将会在橘色执行完成后再执行。
定义在角色执行之前或者执行后需要执行的任务:
--- - hosts: webservers pre_tasks: - shell: echo 'hello' roles: - { role: some_role } tasks: - shell: echo 'still busy' post_tasks: - shell: echo 'goodbye'
示例:
[root@web1 ~]# tree /etc/ansible/ /etc/ansible/ ├── hosts ├── roles │ └── webservers │ ├── defaults │ ├── files │ ├── handlers │ │ ├── main.yml │ │ └── restart.yml │ ├── meta │ ├── tasks │ │ ├── apache.yml │ │ └── main.yml │ ├── templates │ │ └── httpd.j2 │ └── vars │ └── main.yml └── webservers.yml [root@web1 ~]# cat /etc/ansible/roles/webservers/tasks/main.yml --- - include: apache.yml [root@web1 ~]# cat /etc/ansible/roles/webservers/tasks/apache.yml --- - name: config httpd.file template: src=httpd.j2 dest=/etc/httpd.conf notify: - restart apache [root@web1 ~]# cat /etc/ansible/roles/webservers/handlers/main.yml --- - include: restart.yml [root@web1 ~]# cat /etc/ansible/roles/webservers/handlers/restart.yml --- - name: restart apache service: name=httpd state=restarted [root@web1 ~]# cat /etc/ansible/roles/webservers/vars/main.yml --- http_port: 8099 max_clients: 321 [root@web1 ansible]# ansible-playbook /etc/ansible/webservers.yml PLAY [webservers] ************************************************************* GATHERING FACTS *************************************************************** ok: [192.168.1.65] TASK: [webservers | config httpd.file] **************************************** changed: [192.168.1.65] NOTIFIED: [webservers | restart apache] *************************************** changed: [192.168.1.65] PLAY RECAP ******************************************************************** 192.168.1.65 : ok=3 changed=2 unreachable=0 failed=0
有些子目录并没有使用到,如meta目录,就可以不用创建。
角色依赖
角色依赖使得当使用某个角色的时候自动将其他角色导入。角色依赖保存在角色目录下的`meta/main.yml`文件中,该文件包含一个角色和参数的列表,这些内容会在某个特定的角色之前插入。即在某个特定的角色执行之前会先执行其依赖。
--- dependencies: - { role: common, some_parameter: 3 } - { role: apache, port: 80 } - { role: postgres, dbname: blarg, other_parameter: 12 }
也可以以绝对路径的方式定义:
--- dependencies: - { role: '/path/to/common/roles/foo', x: 1 }
角色依赖总在包含它们的角色执行之前而执行,而且是递归执行。默认情况下,角色也只能作为一个依赖关系添加一次,如果另一个角色也将它列为一个依赖关系,它将不再运行。但是在`meta/main.yml`设置`allow_duplicates: yes`,可以突破这个限制。如一个称为`car`的角色需要添加一个名为`wheel`的角色作为它的依赖:
--- dependencies: - { role: wheel, n: 1 } - { role: wheel, n: 2 } - { role: wheel, n: 3 } - { role: wheel, n: 4 }
meta/main.yml内容:
--- allow_duplicates: yes dependencies: - { role: tire } - { role: brake }
执行的结果会是类似下边这样:
tire(n=1) brake(n=1) wheel(n=1) tire(n=2) brake(n=2) wheel(n=2) ... car
总结:
角色和文件包含,在平时的运维工作中会经常使用到,而且使得文件组织更加清晰,复用性较高。可以对远程节点主机进行大量且复杂有序的操作。