- 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 sayhi="hello world"
- include: webservers.yml
- include: dbservers.yml
any other operations
需要说明的是,在ansible 2.4版本中,添加了includes和imports两种导入的方式,它们对静态和动态导入支持的更细化,而ansible 2.3及以前的include语句已经废弃,但仍可用。相关官方手册地址。
1.4.2 roles
roles意为角色,主要用于封装playbook实现复用性。在ansible中,roles通过文件的组织结构来展现。
对于一个role,它的文件组织结构如下图所示。
首先需要有一个roles目录。同时,在roles目录所在目录中,还要有一个playbook文件,此处为nginx.yml,nginx.yml文件是ansible-playbook需要执行的文件,在此文件中定义了角色,当执行到角色时,将会到roles中对应的角色目录中寻找相关文件。
roles目录中的子目录是即是各个role。例如,此处只有一个名为nginx的role,在role目录中,有几个固定名称的目录(如果没有则忽略)。在这些目录中,还要有一些固定名称的文件,除了固定名称的文件,其他的文件可以随意命名。以下是各个目录的含义:
tasks目录:存放task列表。若role要生效,此目录必须要有一个主task文件main.yml,在main.yml中可以使用include包含同目录(即tasks)中的其他文件。
handlers目录:存放handlers的目录,若要生效,则文件必须名为main.yml文件。
files目录:在task中执行copy或script模块时,如果使用的是相对路径,则会到此目录中寻找对应的文件。
templates目录:在task中执行template模块时,如果使用的是相对路径,则会到此目录中寻找对应的模块文件。
vars目录:定义专属于该role的变量,如果要有var文件,则必须为main.yml文件。
defaults目录:定义角色默认变量,角色默认变量的优先级最低,会被任意其他层次的同名变量覆盖。如果要有var文件,则必须为main.yml文件。
meta目录:用于定义角色依赖,如果要有角色依赖关系,则文件必须为main.yml。
所以,相对完整的role的文件组织结构如下图。
如果是多个role,则在roles同级目录下定义多个入站(作用类似于C语言的main函数)文件(如上面的nginx.yml),并在roles目录下创建对应的role目录即可。
当然,如果不是使用相对路径,那么role的文件结构就无所谓了,但是roles功能开发出来,就是为了解决文件混乱和playbook臃肿问题的。所以如果可以,尽量使用推荐的role文件结构。
另外,如果role中出现的task、var、handler等和单独定义的对象同名冲突了,则优先执行role中的内容。
以下是nginx role的入站文件nginx.yml的内容。
- hosts: centos7
roles:
- nginx
更多更详细的role用法以及组织结构,见下面的示例。
1.5 roles示例:批量自动化安装
下面演示的是使用role批量自动安装nginx和mysql(CentOS 6)或maridb(CentOS 7)的示例。由于被控节点有CentOS 6和CentOS 7两种发行版的操作系统,因此除了要挑选对应的数据库,还要让nginx的配置文件适应各操作系统,因为nginx在这两个版本的系统上配置内容有所不同。在此,nginx、mysql和mariadb是3个role,且让nginx role依赖于mysql或mariadb role。
下面的例子中,有些地方是不太合理或多余的行为,但是作为学习示例,可以很好的理解roles之间的组织结构和相关的操作。
首先是文件的结构。
其中site.yml是入站文件,用于调用nginx、mysql和mariadb这3个role,这个文件中的内容如下。它有3个作用:(1)在调用roles之前,先根据发行版配置好yum源(pre_tasks);(2)调用nginx role,此处没有调用mysql和mariadb这两个role,因为在nginx role的meta/main.yml文件中定义了nginx role依赖于这两个role,所以此处可以不用定义;(3)在执行完nginx role之后,输出一个提示信息(post_tasks)。
cat /yaml/site.yml
- hosts: centos
remote_user: root
pre_tasks:
- name: config the yum repo for centos 7
yum_repository:
name: epel
description: epel
baseurl: http://mirrors.aliyun.com/epel/7/$basearch/
gpgcheck: no
when: ansible_distribution_major_version == "7"
- name: config the yum repo for centos 6
yum_repository:
name: epel
description: epel
baseurl: http://mirrors.aliyun.com/epel/6/$basearch/
gpgcheck: no
when: ansible_distribution_major_version == "6"
roles:
- nginx
post_tasks:
- shell: echo 'deploy nginx/mysql over'
register: ok_var
- debug: msg='{{ ok_var.stdout }}'
以下是nginx role中的各文件内容。其中template复制的源文件都是从centos 6 nginx和centos 7 nginx上提取的,只不过是重新命名了而已。
/yaml/roles/nginx/tasks/main.yml
- name: make sure nginx state is installed
yum: name=nginx state=installed
- name: template nginx.conf
# 基于变量赋值配置文件模板,检查配置文件语法,并在必要的时候触发handler
template: src=nginx{{ ansible_distribution_major_version }}.conf.j2
dest=/etc/nginx/nginx.conf
validate="/usr/sbin/nginx -t -c %s"
notify:
- restart nginx
# 基于jinja2渲染模板文件,且改变时也触发重启操作
- name: copy index.html
template: src=index.html.j2 dest=/usr/share/nginx/html/index.html
notify:
- restart nginx
- name: make sure nginx service is running
service: name=nginx state=started
# 引用变量 nginx_port,在vars/main.yml中定义了
- name: make sure port is open
wait_for: port="{{ nginx_port }}"
/yaml/roles/nginx/handlers/main.yml
- name: restart nginx
service: name=nginx state=restarted
/yaml/roles/nginx/vars/main.yml
nginx_port: 80
# 定义nginx依赖于MySQL或mariadb,具体依赖于哪个,是通过条件进行判断的,centos 6表示依赖于mysql,centos 7表示依赖于mariadb
# 同时传递了两个值给变量hi_var,由于是在依赖的时候传递的,所以这两个变量可直接在依赖的role(mysql role或mariadb role)的playbook中引用
/yaml/roles/nginx/meta/main.yml
dependencies:
- { role: mysql,hi_var: "hello mysql",when: "ansible_distribution_major_version == '6'" }
- { role: mariadb,hi_var: "hello mariadb",when: "ansible_distribution_major_version == '7'" }
上面拷贝了index.html.j2,其内容为:
shell> cat /yaml/roles/nginx/templates/index.html.j2
<h1>hello from {{ ansible_default_ipv4.address }}<h1>
在template执行时,它会使用jinja2引擎对文件中的变量进行替换,使得在拷贝到不同主机时,该index.html的内容是基于远程主机ip的。此处使用的变量是收集到的facts中的变量"ansible_default_ipv4.address"。
以下是mysql role中各文件的内容。注意,MySQL的my.cnf和mariadb的my.cnf默认情况下并不一样(mariadb的my.cnf默认多了一项配置"!includedir /etc/my.cnf.d",MySQL需要取消该项),所以需要分别提供。
/yaml/roles/mysql/tasks/main.yml
- name: make sure mysql is installed
yum: name=mysql-server state=installed
# 特别需要注意下面的初始化命令,由于执行的是shell模块,所以要考虑其幂等性,显然初始化动作是一定要实现幂等性的
- name: do something to initialize mysql
file: path=/mydata/data state=directory owner=mysql group=mysql mode=0755
- shell: /usr/bin/mysql_install_db
- name: copy my.cnf
copy: src=my.cnf dest=/etc/my.cnf
notify:
- restart mysql
- name: make sure mysql is running
service: name=mysqld state=started
- name: make sure mysql port is open
wait_for:
port: "{{ mysql_port }}"
timeout: 10
# 这里输出了nginx/meta/main.yml中传递的变量
- name: echo var passed by nginx
shell: echo "{{ hi_var }}"
register: var_result
- debug: msg="{{ var_result.stdout }}"
/yaml/roles/mysql/handlers/main.yml
- name: restart mysql
service: name=mysqld state=restarted
/yaml/roles/mysql/vars/main.yml
mysql_port: 3306
以下是mariadb role中各文件的内容,和mysql大体上是一致的。注意,MySQL的my.cnf和mariadb的my.cnf默认情况下并不一样,所以需要分别提供。
/yaml/roles/mariadb/tasks/main.yml
- name: make sure mariadb is installed
yum: name=mariadb-server state=installed
- name: do something to initialize mariadb
file: path=/mydata/data state=directory owner=mysql group=mysql mode=0755
- shell: /usr/bin/mysql_install_db
- name: copy my.cnf
copy: src=my.cnf dest=/etc/my.cnf
notify:
- restart mariadb
- name: make sure mariadb is running
service: name=mariadb state=started
- name: make sure mariadb port is open
wait_for:
port: "{{ mariadb_port }}"
timeout: 10
- name: echo var passed by nginx
shell: echo "{{ hi_var }}"
register: var_result
- debug: msg="{{ var_result.stdout }}"
/yaml/roles/mariadb/handlers/main.yml
- name: restart mariadb
service: name=mariadb state=restarted
/yaml/roles/mariadb/vars/main.yml
mariadb_port: 3306
以下是两台机器的测试结果,一台是centos 7(192.168.100.54),一台是centos 6(192.168.100.70)。
PLAY [newhosts] *************************************************************************
TASK [Gathering Facts] ******************************************************************
ok: [192.168.100.70]
ok: [192.168.100.54]
TASK [config the yum repo for centos 7] *************************************************
skipping: [192.168.100.70]
changed: [192.168.100.54]
TASK [config the yum repo for centos 6] *************************************************
skipping: [192.168.100.54]
changed: [192.168.100.70]
TASK [mysql : make sure mysql is installed] *********************************************
skipping: [192.168.100.54]
changed: [192.168.100.70]
TASK [mysql : do something to initialize mysql] *****************************************
skipping: [192.168.100.54]
changed: [192.168.100.70]
TASK [mysql : command] ******************************************************************
skipping: [192.168.100.54]
changed: [192.168.100.70]
TASK [mysql : copy my.cnf] **************************************************************
skipping: [192.168.100.54]
changed: [192.168.100.70]
TASK [mysql : make sure mysql is running] ***********************************************
skipping: [192.168.100.54]
changed: [192.168.100.70]
TASK [mysql : make sure mysql port is open] *********************************************
skipping: [192.168.100.54]
ok: [192.168.100.70]
TASK [mysql : echo var passed by nginx] *************************************************
skipping: [192.168.100.54]
changed: [192.168.100.70]
TASK [mysql : debug] ********************************************************************
skipping: [192.168.100.54]
ok: [192.168.100.70] => {
"changed": false,
"msg": "hello mysql"
TASK [mariadb : make sure mariadb is installed] *****************************************
skipping: [192.168.100.70]
changed: [192.168.100.54]
TASK [mariadb : do something to initialize mariadb] *************************************
skipping: [192.168.100.70]
changed: [192.168.100.54]
TASK [mariadb : command] ****************************************************************
skipping: [192.168.100.70]
changed: [192.168.100.54]
TASK [mariadb : copy my.cnf] ************************************************************
skipping: [192.168.100.70]
changed: [192.168.100.54]
TASK [mariadb : make sure mariadb is running] *******************************************
skipping: [192.168.100.70]
changed: [192.168.100.54]
TASK [mariadb : make sure mariadb port is open] *****************************************
skipping: [192.168.100.70]
ok: [192.168.100.54]
TASK [mariadb : echo var passed by nginx] ***********************************************
skipping: [192.168.100.70]
changed: [192.168.100.54]
TASK [mariadb : debug] ******************************************************************
skipping: [192.168.100.70]
ok: [192.168.100.54] => {
"changed": false,
"msg": "hello mysql"
TASK [nginx : make sure nginx state is installed] ***************************************
changed: [192.168.100.54]
changed: [192.168.100.70]
TASK [nginx : template nginx.conf] ******************************************************
ok: [192.168.100.70]
changed: [192.168.100.54]
TASK [nginx : copy index.html] **********************************************************
changed: [192.168.100.70]
changed: [192.168.100.54]
TASK [nginx : make sure nginx service is running] ***************************************
changed: [192.168.100.54]
changed: [192.168.100.70]
TASK [nginx : make sure port is open] ***************************************************
ok: [192.168.100.70]
ok: [192.168.100.54]
RUNNING HANDLER [mysql : restart mysql] *************************************************
changed: [192.168.100.70]
RUNNING HANDLER [mariadb : restart mariadb] *********************************************
changed: [192.168.100.54]
RUNNING HANDLER [nginx : restart nginx] *************************************************
changed: [192.168.100.54]
TASK [command] **************************************************************************
changed: [192.168.100.54]
changed: [192.168.100.70]
TASK [debug] ****************************************************************************
ok: [192.168.100.54] => {
"changed": false,
"msg": "deploy nginx/mysql over"
ok: [192.168.100.70] => {
"changed": false,
"msg": "deploy nginx/mysql over"
PLAY RECAP ******************************************************************************
192.168.100.54 : ok=19 changed=14 unreachable=0 failed=0
192.168.100.70 : ok=18 changed=12 unreachable=0 failed=0
相信看过上面的roles组织示例,对roles的用法和playbook就有了较深的认识。其实,ansible有一个网站专门存放了一大堆的playbook,算是playbook仓库吧。可以下载下来稍作修改就能使用,即使不使用,借鉴他们的写法也是很值得的。地址:ansible galaxy
另外,根据不同标准组织role可能会让playbook写起来更容易,例如上面的示例中,按照发行版来划分role比上面按照安装软件类型划分可能会更简单些。当然,如果在inventory中就划分好centos 6和centos 7也是可以的。哪种更方便、复用性更好就需要自行考虑了。