接上篇
3.8角色订制:roles
① 简介
- 对于以上所有的方式有个弊端就是无法实现复用假设在同时部署Web、db、ha 时或不同服务器组合不同的应用就需要写多个yml文件。很难实现灵活的调用。 roles 用于层次性、结构化地组织playbook。
- roles 能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单来讲,roles就是通过分别将变量(vars)、文件(file)、任务(tasks)、模块(modules)及处理器(handlers)放置于单独的目录中,并可以便捷地include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中。
② 角色集合:roles/ mysql/ httpd/ nginx/ files/:存储由copy或script等模块调用的文件;
- tasks/:此目录中至少应该有一个名为main.yml的文件,用于定义各task;其它的文件需要由main.yml进行“包含”调用;
- handlers/:此目录中至少应该有一个名为main.yml的文件,用于定义各handler;其它的文件需要由main.yml进行“包含”调用;
- vars/:此目录中至少应该有一个名为main.yml的文件,用于定义各variable;其它的文件需要由main.yml进行“包含”调用;
- templates/:存储由template模块调用的模板文本;
- meta/:此目录中至少应该有一个名为main.yml的文件,定义当前角色的特殊设定及其依赖关系;其它的文件需要由main.yml进行“包含”调用;
- default/:此目录中至少应该有一个名为main.yml的文件,用于设定默认变量;
③ 角色定制实例
1. 在roles目录下生成对应的目录结构
[root@server ansible]# cd roles/
[root@server roles]# ls
[root@server roles]# mkdir -pv ./{nginx,mysql,httpd}/{files,templates,vars,tasks,handlers,meta,default}
#还可以用这条命令创建
[root@ansible roles]# ansible-galaxy init mysql
[root@server roles]# tree
.
├── httpd
│ ├── default
│ ├── files
│ ├── handlers
│ ├── meta
│ ├── tasks
│ ├── templates
│ └── vars
├── mysql
│ ├── default
│ ├── files
│ ├── handlers
│ ├── meta
│ ├── tasks
│ ├── templates
│ └── vars
└── nginx
├── default
├── files
├── handlers
├── meta
├── tasks
├── templates
└── vars
24 directories, 0 files
2. 定义配置文件 我们需要修改的配置文件为/tasks/main.yml,下面,我们就来修改一下:
[root@server roles]# vim nginx/tasks/main.yml
- name: cp rpm for nginx
copy: src=nginx-1.10.2-1.el7.ngx.x86_64.rpm dest=/tmp/nginx-1.10.2-1.el7.ngx.x86_64.rpm
- name: install
yum: name=/tmp/nginx-1.10.2-1.el7.ngx.x86_64.rpm state=latest
- name: conf
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
tags: nginxconf
notify: new conf to reload
- name: start service
service: name=nginx state=started enabled=true
3. 放置我们所需要的文件到指定目录
因为我们定义的角色已经有了新的组成方式,所以我们需要把文件都放到指定的位置,这样,才能让配置文件找到这些并进行加载。 rpm包放在files目录下,模板放在templates目录下:
[root@server nginx]# cp /tmp/nginx-1.10.2-1.el7.ngx.x86_64.rpm ./files/
[root@server nginx]# cp /tmp/nginx.conf.j2 ./templates/
[root@server nginx]# tree
.
├── default
├── files
│ └── nginx-1.10.2-1.el7.ngx.x86_64.rpm
├── handlers
├── meta
├── tasks
│ └── main.yml
├── templates
│ └── nginx.conf.j2
└── vars
7 directories, 3 files
4. 修改变量文件
我们在模板中定义的变量,也要去配置文件中加上:
[root@server nginx]# vim vars/main.yml
nginxprot: 9999
5. 定义handlers文件
我们在配置文件中定义了notify,所以我么也需要定义handlers,我们来修改配置文件:
[root@server nginx]# vim handlers/main.yml
- name: new conf to reload
service: name=nginx state=restarted
6. 定义剧本文件
接下来,我们就来定义剧本文件,由于大部分设置我们都单独配置在了roles里面,所以,接下来剧本就只需要写一点点内容即可:
[root@server ansible]# vim roles/roles.yml
- hosts: web
remote_user: root
roles:
- nginx
完成后的目录结构
# tree ./
./
├── httpd
│ ├── default
│ ├── files
│ ├── handlers
│ ├── meta
│ ├── tasks
│ ├── templates
│ └── vars
├── mysql
│ ├── default
│ ├── files
│ ├── handlers
│ ├── meta
│ ├── tasks
│ ├── templates
│ └── vars
├── nginx
│ ├── default
│ ├── files
│ │ └── nginx-1.10.2-1.el7.ngx.x86_64.rpm
│ ├── handlers
│ │ └── main.yml
│ ├── meta
│ ├── tasks
│ │ └── main.yml
│ ├── templates
│ │ └── nginx.conf.j2
│ └── vars
│ └── main.yml
└── roles.yml
7. 启动服务 剧本定义完成以后,我们就可以来启动服务了:
[root@server ansible]# ansible-playbook roles.yml
PLAY [web] *********************************************************************
TASK [setup] *******************************************************************
ok: [192.168.37.122]
ok: [192.168.37.133]
TASK [nginx : cp] **************************************************************
ok: [192.168.37.122]
ok: [192.168.37.133]
TASK [nginx : install] *********************************************************
changed: [192.168.37.122]
changed: [192.168.37.133]
TASK [nginx : conf] ************************************************************
changed: [192.168.37.122]
changed: [192.168.37.133]
TASK [nginx : start service] ***************************************************
changed: [192.168.37.122]
changed: [192.168.37.133]
RUNNING HANDLER [nginx : new conf to reload] ***********************************
changed: [192.168.37.122]
changed: [192.168.37.133]
PLAY RECAP *********************************************************************
192.168.37.122 : ok=6 changed=4 unreachable=0 failed=0
192.168.37.133 : ok=6 changed=4 unreachable=0 failed=0
启动过后照例查看端口号:
[root@server ansible]# ansible web -m shell -a "ss -ntulp |grep 9999"
192.168.37.122 | SUCCESS | rc=0 >>
tcp LISTEN 0 128 *:9999 *:* users:(("nginx",pid=7831,fd=6),("nginx",pid=7830,fd=6),("nginx",pid=7829,fd=6))
192.168.37.133 | SUCCESS | rc=0 >>
tcp LISTEN 0 128 *:9999 *:* users:(("nginx",pid=9654,fd=6),("nginx",pid=9653,fd=6),("nginx",pid=9652,fd=6))
可以看出我们的剧本已经执行成功。
4.Ansible使用jinja2管理配置文件以及jinja2语法简介
4.1Jinja2介绍
Jinja2是基于python的模板引擎,功能比较类似于PHP的smarty,J2ee的Freemarker和velocity。它能完全支持unicode,并具有集成的沙箱执行环境,应用广泛。jinja2使用BSD授权
Jinja2的语法是由variables(变量)和statement(语句)组成,如下
1、variables:可以输出数据
my_variables
{{ some_dudes_name | capitalize }}
2、statements: 可以用来创建条件和循环等
if语句:
{% if my_conditional %}
...
{% endif %}
for 语句:
{% for item in all_items %}
`item`
……
{% endfor %}
从上面第二个variables的例子中可以看出,jinja2支持使用带过滤器的Unix型管道操作符,有很多的内置过滤器可供使用。我们可以仅仅用一堆简单if和for就可以建立几乎任何的常规配置文件,不过如果你有意更进一步,jinja2 documentation (Jinja — Jinja Documentation (3.2.x))包含了很多有趣的东西可供了解。我们可以看到ansible允许在模板中使用诸如绘制时间此类的一些额外的模板变量
第一个例子:引用变量
# tree
.
├── site.yml
├── templates
│ └── order.j2
└── vars
└── main.yml
2 directories, 3 files
总调度yml文件:
# cat site.yml
---
- hosts: 192.168.19.154
user: root
vars:
- PROJECT: "JAVA"
SWITCH: "ON"
DBPORT: "8080"
tasks:
- name: create{{ PROJECT }}directory
file: path=/data/{{ PROJECT }} state=directory
- name: template transfor java
template: src=order.j2 dest=/data/{{ PROJECT }}/order.conf
注意:这里 - role: template 和 - template 是一样的!
其他yml文件,如下:
# cat templates/order.j2
project: {{ PROJECT }}
switch: {{ SWITCH }}
dbport: {{ DBPORT }}
测试:
# ansible-playbook templates.yml --syntax-check
playbook: templates.yml
执行:
PLAY [192.168.19.154] **********************************************************
TASK [Gathering Facts] *********************************************************
ok: [192.168.19.154]
TASK [createJAVAdirectory] *****************************************************
changed: [192.168.19.154]
TASK [template transfor java] **************************************************
changed: [192.168.19.154]
PLAY RECAP *********************************************************************
192.168.19.154 : ok=3 changed=2 unreachable=0 failed=0
#cat /data/JAVA/order.conf
project: JAVA
switch: ON
dbport: 8080
第二个例子:for 语句
为远程主机生成服务器列表,加入该列表从192.168.13.201 web01.test.com 到192.168.13.211 web11.test.com 结束,如果手动添加就很不科学了,这里需要使用jinja2语法的for循环通过模板批量生成对应的配置文件,如下:
ansible目录结构:
#cd /etc/ansible/roles/test_hosts
.
├── meta
│ └── main.yml
├── tasks
│ ├── file1.yml
│ └── main.yml
├── templates
│ └── test1.j2
└── vars
└── main.yml
各个目录下yml文件内容
# cat tasks/file1.yml
- name: ansible jinja2 template for hosts config
template: src=test1.j2 dest=/etc/httpd/conf/httpd.conf.test
# cat tasks/main.yml
- include: file1.yml
# cat templates/test1.j2
{% for id in range(201,212) %}
192.168.13.{{ id }} web{{ "%02d" |format(id-200) }}.test.com
{% endfor %}
解释:
{{ id }} 提取for循环中对应的变量id值
"%02d" 调用的是python内置的字符串格式化输出(%d格式化整数)因为是01,02这种格式,所以是保留2位,故用02
然后将结果通过管道符 “|” 传递给format 函数做二次处理。
执行结果:
#cat httpd.conf.test
192.168.13.201 web01.test.com
192.168.13.202 web02.test.com
192.168.13.203 web03.test.com
192.168.13.204 web04.test.com
192.168.13.205 web05.test.com
192.168.13.206 web06.test.com
192.168.13.207 web07.test.com
192.168.13.208 web08.test.com
192.168.13.209 web09.test.com
192.168.13.210 web10.test.com
192.168.13.211 web11.test.com
第三个例子:if语句
说明:如果定义端口号,就绑定定义的端口号,如果不定义端口号,就绑定默认端口号
ansible目录结果
#cd /etc/ansible/roles/mysql_cnf
#tree
.
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
│ └── test3.j2
└── vars
主要的yml文件是templates目录下面的test3.j2
# cat templates/test3.j2
{% if PORT %}
bind_address=10.0.90.27:{{ PORT }}
{% else %}
bind_address=10.0.90.27:3306
{% endif %}
playbook主文件
# cat jinj2_test.yml
---
- hosts: 10.0.90.27
user: root
gather_facts: false
vars:
PORT: 3136
tasks:
- name: copy file to client
template: src=/etc/ansible/roles/mysql_cnf/templates/test3.j2 dest=/root/my.cnf
执行:
# ansible-playbook jinj2_test.yml
PLAY [10.0.90.27] **************************************************************
TASK [copy file to client] *****************************************************
changed: [10.0.90.27]
PLAY RECAP *********************************************************************
10.0.90.27 : ok=1 changed=1 unreachable=0 failed=0
查看
# cat my.cnf
bind_address=10.0.90.27:3136
如果将vars变量去掉,执行结果:
# cat jinj2_test.yml
---
- hosts: 10.0.90.27
user: root
gather_facts: false
vars:
PORT: false
tasks:
- name: copy file to client
template: src=/etc/ansible/roles/mysql_cnf/templates/test3.j2 dest=/root/my.cnf
查看:
# cat my.cnf
bind_address=10.0.90.27:3306
3、Jinja default()设定
精通程序编码的朋友皆知,default()默认值的设定有助于程序的健壮性和简洁性。所幸Jinja也支持该功能,上面的例子中生成Mysql配置文件中的端口定义,如果指定则PORT=3136,否则PORT=3306,我们将该案例改造为使用default()试试
编辑/etc/ansible/roles/mysql_cnf/templates/test3.j2内容如下,这种方法更简介。
bind_address=10.0.90.27:{{ PORT | default(3306) }}
4.2ansible使用jinja2生成apache多主机配置
1、创建目录,创建好之后如下:
#cd /etc/ansible/roles/apache_conf
# tree ./
./
├── meta
│ └── main.yml
├── tasks
│ ├── file.yml
│ └── main.yml
├── templates
│ └── apache.config.j2
└── vars
└── main.yml
4 directories, 5 files
2、创建tasks调度文件,如下:
#cat file.yml
- name: ansible jinja2 template for apache config
template: src=apache.config.j2 dest=/etc/httpd/conf/httpd.conf.template
#cat main.yml
- include: file.yml
3、创建apache的jinja2模板文件,如下
#cat apache.config.j2
NameVirtualHost *:80
{% for vhost in apache_vhost %}
<VirtualHost *:80>
ServerName {{ vhost.servername }}
DocumentRoot {{ vhost.documentroot }}
{% if vhost.serveradmin is defined %}
ServerAdmin {{ vhost.serveradmin }}
{% endif %}
<Directory "{{ vhost.documentroot }}">
AllowOverride All
Options -Indexes FollowSymLinks
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
{% endfor %}
4、创建变量,如下:
#cat vars/main.yml
apache_vhost:
- {servername: "apache.test1.com", documentroot: "/data/test1/"}
- {servername: "apache.test2.com", documentroot: "/data/test2/"}
5、创建总调度yml文件,如下:
#cat /etc/ansible/apache_test.yml
---
- hosts: 10.0.90.27
user: root
gather_facts: no
roles:
- { role: apache_conf }
6、执行测试
#ansible-playbook apache_test.yml
PLAY [10.0.90.27] **************************************************************
TASK [apache_conf : include] ***************************************************
included: /etc/ansible/roles/apache_conf/tasks/file.yml for 10.0.90.27
TASK [apache_conf : ansible jinja2 template for apache config] *****************
changed: [10.0.90.27]
PLAY RECAP *********************************************************************
10.0.90.27 : ok=2 changed=1 unreachable=0 failed=0
7、到客户端查看
#cat httpd.conf.template
NameVirtualHost *:80
<VirtualHost *:80>
ServerName apache.test1.com
DocumentRoot /data/test1/
<Directory "/data/test1/">
AllowOverride All
Options -Indexes FollowSymLinks
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
<VirtualHost *:80>
ServerName apache.test2.com
DocumentRoot /data/test2/
<Directory "/data/test2/">
AllowOverride All
Options -Indexes FollowSymLinks
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
4.3ansible使用jiaja2生成nginx一个模板多种不同配置
说明:为2台Nginx Proxy,1台Nginx Web通过一套模板生成对应的配置
1、ansible目录结构:
# cd roles/nginx_conf/
#tree
.
├── files
├── meta
│ └── main.yml
├── tasks
│ ├── file.yml
│ └── main.yml
├── templates
│ └── nginx.conf.j2
└── vars
└── main.yml
2、tasks目录下文件内容:
#cat tasks/file.yml
- name: nginx.j2 template transfer example
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf.template
#cat tasks/main.yml
- include: file.yml
3、nginx模板文件
#cat templates/nginx.conf.j2
{% if nginx_use_proxy %}
{% for proxy in nginx_proxies %}
upstream {{ proxy.name }}
#server 127.0.0.1:{{ proxy.port }};
server {{ ansible_eth0.ipv4.address }}:{{ proxy.port }};
}
{% endfor %}
{% endif%}
server {
listen 80;
servername {{ nginx_server_name }};
access_log off;
error_log /etc/nginx/nginx_error.log;
rewrite ^ https://$server_name$request_uri? permanent;
}
server {
listen 443 ssl;
server_name {{ nginx_server_name }};
ssl_certificate /etc/nginx/ssl/{{ nginx_ssl_cert_name }};
ssl_certificate_key /etc/nginx/ssl/{{ nginx_ssl_cert_key }};
root {{ nginx_web_root }};
index index.html index.html;
{% if nginx_use_auth %}
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/{{ project_name }}.htpasswd;
{% endif %}
{% if nginx_use_proxy %}
{% for proxy in nginx_proxies %}
location {{ proxy.location }} {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header X-Url-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_redirect off;
proxy_pass http://{{ proxy.name }};
break;
}
{% endfor %}
{% endif %}
{% if nginx_server_static %}
location / {
try_files $url $url/ =404;
}
{% endif %}
}
4、ansible变量文件
cat vars/main.yml
nginx_server_name: www.testnginx.com
nginx_web_root: /data/html/
nginx_proxies:
- name: suspicious
location: /
port: 1234
- name: suspicious-api
location: /api
port: 4567
5、ansible主playbook文件
#cat nginx_test.yml
##The first roles
- name: Nginx Proxy Server's Config Dynamic Create
hosts: "10.0.90.25:10.0.90.26"
remote_user: root
vars:
nginx_use_proxy: true
nginx_ssl_cert_name: ifa.crt
nginx_ssl_cert_key: ifa.key
nginx_use_auth: true
project_name: suspicious
nginx_server_static: true
gather_facts: true
roles:
- role: nginx_conf
##The second roles
- name: Nginx WebServer's Config Dynamic Create
hosts: 10.0.90.27
remote_user: root
vars:
nginx_use_proxy: false
nginx_ssl_cert_name: ifa.crt
nginx_ssl_cert_key: ifa.crt
nginx_use_auth: false
project_name: suspicious
nginx_server_static: false
gather_facts: false
roles:
- role: nginx_conf
6、测试并执行:
#ansible-playbook nginx_test.yml --syntax-check
playbook: nginx_test.yml
执行:
# ansible-playbook nginx_test.yml
PLAY [Nginx Proxy Server's Config Dynamic Create] ******************************
TASK [setup] *******************************************************************
ok: [10.0.90.25]
ok: [10.0.90.26]
TASK [nginx_conf : include] ****************************************************
included: /etc/ansible/roles/nginx_conf/tasks/file.yml for 10.0.90.25, 10.0.90.26
TASK [nginx_conf : nginx.j2 template transfer example] *************************
changed: [10.0.90.26]
changed: [10.0.90.25]
PLAY [Nginx WebServer's Config Dynamic Create] *********************************
TASK [nginx_conf : include] ****************************************************
included: /etc/ansible/roles/nginx_conf/tasks/file.yml for 10.0.90.27
TASK [nginx_conf : nginx.j2 template transfer example] *************************
changed: [10.0.90.27]
PLAY RECAP *********************************************************************
10.0.90.25 : ok=3 changed=1 unreachable=0 failed=0
10.0.90.26 : ok=3 changed=1 unreachable=0 failed=0
10.0.90.27 : ok=2 changed=1 unreachable=0 failed=0
7、查看检测执行结果
到Nginx Proxy 服务器查看配置文件
#cat nginx.conf.template
upstream suspicious
#server 127.0.0.1:1234;
server 10.0.90.26:1234;
}
upstream suspicious-api
#server 127.0.0.1:4567;
server 10.0.90.26:4567;
}
server {
listen 80;
servername www.testnginx.com;
access_log off;
error_log /etc/nginx/nginx_error.log;
rewrite ^ https://$server_name$request_uri? permanent;
}
server {
listen 443 ssl;
server_name www.testnginx.com;
ssl_certificate /etc/nginx/ssl/ifa.crt;
ssl_certificate_key /etc/nginx/ssl/ifa.key;
root /data/html/;
index index.html index.html;
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/suspicious.htpasswd;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header X-Url-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_redirect off;
proxy_pass http://suspicious;
break;
}
location /api {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header X-Url-Scheme $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_redirect off;
proxy_pass http://suspicious-api;
break;
}
location / {
try_files $url $url/ =404;
}
}
到Nginx Web 服务器上查看配置文件
#cat nginx.conf.template
server {
listen 80;
servername www.testnginx.com;
access_log off;
error_log /etc/nginx/nginx_error.log;
rewrite ^ https://$server_name$request_uri? permanent;
}
server {
listen 443 ssl;
server_name www.testnginx.com;
ssl_certificate /etc/nginx/ssl/ifa.crt;
ssl_certificate_key /etc/nginx/ssl/ifa.crt;
root /data/html/;
index index.html index.html;
}
到这里,就结束了。用同样的模板通过简单的if和变量设置就可以完成不同类型主机的Nginx conf配置,所以一方面在了解Ansible强大的模板功能的同时,也需要看到模板质量的重要性。