Ansible Playbook 的任务控制
文章目录
- Ansible Playbook 的任务控制
- 一、Ansible 任务控制基本介绍
- 二、条件判断
- 1.解决第一个问题
- 2.nginx 语法校验
- 三、循环控制
- 四、Tags 属性
- 五、Handlers 属性
一、Ansible 任务控制基本介绍
任务控制类似于编程语言中的 if …、for …等逻辑控制语句。下面让我们通过一个实际应用场景案列去说明在 playbook 中,任务控制如何应用。
在下面的 playbook 中,我们创建了 tomcat 、www 和 mysql 三个用户。安装了 nginx 软件包、并同时更新了 nginx 主配置文件和虚拟主机配置文件,最后让nginx服务处于启动状态。
整个 playbook 从语法上没有任何问题,但从逻辑和写法上仍然有一些地方需要我们去注意和优化:
1、nginx 启动逻辑欠缺考虑,若nginx的配置文件语法错误则会导致启动 nginx 失败,以至于 playbook 执行失败。
2、批量创建用户,通过指令的罗列过于死板。如果在创建若干个用户,将难以收场。
---
- name: task control playbook example
hosts: web_servers
tasks:
- name: create tomcat user
user: name=tomcat state=present
- name: create www user
user: name=www state=present
- name: create mysql user
user: name=mysql state=present
- name: yum nginx web_servers
yum: name=nginx state=present
- name: update nginx main config
copy: src=nginx.conf dest=/etc/nginx
- name: add virtualhost config
copy: src=www.guan.com.cnf dest=/etc/nginx/conf.d/
- name: start nginx server
service: name=nginx state=started
...
二、条件判断
1.解决第一个问题
nginx 启动逻辑欠考虑,若nginx的配置文件语法错误则会导致启动nginx失败,以至于playbook执行失败。
如果我们能够在启动之前去对nginx的配置文件语法做正确性的校验,只有当校验通过的时候我们才去启动或者重启nginx,否则跳过启动 nginx的过程。这样就会避免nginx 配置文件语法问题而导致的无法启动 nginx 的风险。
2.nginx 语法校验
- name: check ngixn syntax
shell: /usr/sbin/nginx -t
那么如何将 nginx 语法检查的 task 与 nginx 启动的 task 关联起来呢?
如果我们能够获得语法检查的 task 的结果,根据这个结果去判断"启动 nginx 的 task 是否执行,这将是一个很好的方案。"
如何和获取到语法检查 task 的结果呢?此时,就可以使用之前学过的 ansible 中的注册变量。
[root@master1 ~]# vim www.guan.com.conf
[root@master1 ~]# cat www.guan.com.conf
server {
listen 80;
server_name www.guan.com.conf;
root /usr/share/nginx/html;
access_log /var/log/nginx/www.guan.com-access_log main;
error_log /var/log/nginx/www.guan.com-error_log;
add_header Access-Control-Allow-Origin *;
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 1d;
}
location ~ .\.(js|css)$? {
expires 1d;
}
}
[root@master1 ~]# cat site.yml
---
- name: task control playbook example
gather_facts : no
hosts: web_servers
tasks:
- name: yum nginx web_servers
yum: name=nginx state=present
- name: update nginx main config
copy: src=nginx.conf dest=/etc/nginx
- name: add virtualhost config
copy: src=www.guan.com.cnf dest=/etc/nginx/conf.d/
- name: check nginx syntax
shell: /usr/sbin/nginx -t
register: nginx_syntax
- name: print nginx syntax
debug: var=nginx_syntax
- name: start nginx server
service: name=nginx state=started
when: nginx_syntax.rc == 0
...
[root@master1 ~]# ansible-playbook -i hosts site.yml
以上的逻辑,只要语法检查通过都会去执行 " start nginx service " 这个 task。在这个问题里,我们学习了 when
条件判断和注册变量的结合使用。在学了 when 条件判断中是可以支持复杂逻辑的,比如现在用到的逻辑运算符 and
。
另外 when 支持如下运算符:
==
!=
> 、>=
< 、<=
is defined
is not defined
true
false
支持逻辑运算符:and or
三、循环控制
-
批量创建用户,通过指令的罗列过于死板,如果在创建若干个用户,将难以收场。
-
如果在创建用户时,抛开 playbook 的实现不说,单纯的使用shell去批量的创建一些用户,该怎么写才方便呢?
#!/bin/bash
create_user="tomcat mysql www"
for i in `create_user`
do
useradd $i
done
那么如果 playbook 中也存在这样的循环控制,我们也可以像写 shell 一样简单的去完成多用户创建工作。
在 playbook 中使用 with_items
去实现循环控制,且循环时的时间变量(上面shell 循环中 $i
变量)只能是 关键字 item
,而不能是随意自定义的。
基于上面的基础,进一步优化了 playbook
在这里使用定义了剧本变量 create_user(一个列表),然后通过 with_items 循环遍历这个变量来达到创建用户的目的。
---
- name: task control playbook example
hosts: web_servers
gather_facts: no
vars:
create_user:
- tomcat
- www
- mysql
tasks:
- name: create user
user: name={{ item }} state=present
with_items: {{ create_user}}
...
执行
[root@master1 ~]# ansible-playbook -i hosts site01.yml
PLAY [task control playbook example] ******************************************************************
TASK [create user] ***********************************************************************************
changed: [192.168.200.183] => (item=tomcat)
changed: [192.168.200.183] => (item=www)
ok: [192.168.200.183] => (item=mysql)
PLAY RECAP ********************************************************************************************
192.168.200.183 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@master1 ~]#
进一步优化后
[root@master1 ~]# vim loop.yml
[root@master1 ~]# ansible-playbook -i hosts loop.yml
PLAY [loop item] **************************************************************************************
TASK [show item] **************************************************************************************
ok: [192.168.200.182] => (item=a) => {
"a": "VARIABLE IS NOT DEFINED!",
"ansible_loop_var": "item",
"item": "a"
}
ok: [192.168.200.182] => (item=b) => {
"ansible_loop_var": "item",
"b": "VARIABLE IS NOT DEFINED!",
"item": "b"
}
ok: [192.168.200.182] => (item=c) => {
"ansible_loop_var": "item",
"c": "VARIABLE IS NOT DEFINED!",
"item": "c"
}
ok: [192.168.200.183] => (item=a) => {
"a": "VARIABLE IS NOT DEFINED!",
"ansible_loop_var": "item",
"item": "a"
}
ok: [192.168.200.183] => (item=b) => {
"ansible_loop_var": "item",
"b": "VARIABLE IS NOT DEFINED!",
"item": "b"
}
ok: [192.168.200.183] => (item=c) => {
"ansible_loop_var": "item",
"c": "VARIABLE IS NOT DEFINED!",
"item": "c"
}
TASK [show item when item > 3] ************************************************************************
skipping: [192.168.200.183] => (item=1)
skipping: [192.168.200.183] => (item=2)
skipping: [192.168.200.182] => (item=1)
skipping: [192.168.200.182] => (item=2)
skipping: [192.168.200.182] => (item=3)
ok: [192.168.200.182] => (item=5) => {
"5": "VARIABLE IS NOT DEFINED!",
"ansible_loop_var": "item",
"item": 5
}
ok: [192.168.200.182] => (item=6) => {
"6": "VARIABLE IS NOT DEFINED!",
"ansible_loop_var": "item",
"item": 6
}
skipping: [192.168.200.183] => (item=3)
ok: [192.168.200.183] => (item=5) => {
"5": "VARIABLE IS NOT DEFINED!",
"ansible_loop_var": "item",
"item": 5
}
ok: [192.168.200.183] => (item=6) => {
"6": "VARIABLE IS NOT DEFINED!",
"ansible_loop_var": "item",
"item": 6
}
PLAY RECAP ********************************************************************************************
192.168.200.182 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.200.183 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[root@master1 ~]# cat loop.yml
- name: loop item
hosts: all
gather_facts: no
vars:
some_list:
- "a"
- "b"
- "c"
num_list:
- 1
- 2
- 3
- 5
- 6
tasks:
- name: show item
debug:
var: "{{ item }}"
loop: "{{ some_list }}"
- name: show item when item > 3
debug:
var: "{{ item }}"
loop: "{{ num_list }}"
when: item > 3
[root@master1 ~]#
考虑到这样一个情况:
若更新了 nginx 的配置文件后,我们需要通过 playbook 将新的配置发布到生产服务器上,然后重新加载我们的nginx服务器,但以现在的playbook来说,每次更改nginx配置文件后虽然可以通过它发布到生产,但整个 playbook 都要执行一次,这样无形中扩大了变更范围和变更风险。
下面的 Tags 属性就可以解决这个问题。
四、Tags 属性
我们可以通过 play中的tags属性,去解决目前 playbook 变更而导致的扩大变更范围和变更风险的问题。
在改进的 playbook 中,针对文件发布 task 任务 “update nginx main config” 和 “add virtualhost config” 新增了属性 tags ,属性值为 updateconfig。另外我们新增了"reload nginx server" task 任务,当配置文件更新后,去reload nginx 服务。那重新加载需要依赖于 nginx 服务是已经启动状态。所以,还需要进一步通过判断 nginx 的pid 文件存在,才证明 nginx 服务本身是启动中,才可以reload nginx 服务。
判断一个文件是否存在使用 start
模块
- name: check nginx running
stat: path=/var/run/nginx.pid
register: nginx_running
观察结果,会发现 nginx_running.stat.exists 的值是true 就表示启动状态,是false就是关闭状态。
接下来就可以根据这个结果,来决定是否重新加载nginx 服务。
优化之后的 playbook
- name: task control playbook example
hosts: web_servers
gather_facts: no
vars:
create_user:
- tomcat
- www
- mysql
tasks:
- name: create user
user: name={{ item }} state=present
with_items: "{{ create_user }}"
- name: yum nginx web_servers
yum: name=nginx state=present
- name: update nginx main config
copy: src=nginx.conf dest=/etc/nginx/
tags: update_config
- name: add virtualhost config
copy: src=www.guan.com.cnf dest=/etc/nginx/conf.d/
tags: update_config
- name: check nginx syntax
shell: /usr/sbin/nginx -t
register: nginx_syntax
tags: update_config
- name: check nginx running
stat: path=/var/run/nginx.pid
register: nginx_running
tags: update_config
- name: print nginx syntax
debug: var=nginx_syntax
- name: print nginx syntax
debug: var=nginx_running
- name: reload nginx server
service: name=nginx state=started
when: nginx_syntax.rc == 0 and nginx_running.stat.exists == true
tags: update_config
- name: start nginx server
service: name=nginx state=started
when:
- nginx_syntax.rc == 0
- nginx_running.stat.exists == false
tags: update_config
指定 tags 去执行 playbook
执行时一定要指定 tags,这样在执行过程中只会执行 task 任务上打上 tag 标记为 update_config 的任务
[root@master1 ~]# ansible-playbook -i hosts site02.yml -t update_config
五、Handlers 属性
观察当前的 playbook ,不难发现,当我们的配置文件没有发生变化是,每次依然都会去触发 task “reload nginx server”,这样的处理才是最完美的实现。此时,可以使用 handlers 属性。
优化后的 playbook
- name: task control playbook example
hosts: web_servers
gather_facts: no
vars:
create_user:
- tomcat
- www
- mysql
tasks:
- name: create user
user: name={{ item }} state=present
with_items: "{{ create_user }}"
- name: yum nginx web_servers
yum: name=nginx state=present
- name: update nginx main config
copy: src=nginx.conf dest=/etc/nginx/
tags: update_config
notify: reload nginx server
- name: add virtualhost config
copy: src=www.guan.com.cnf dest=/etc/nginx/conf.d/
tags: update_config
notify: reload nginx server
- name: check nginx syntax
shell: /usr/sbin/nginx -t
register: nginx_syntax
tags: update_config
- name: check nginx running
stat: path=/var/run/nginx.pid
register: nginx_running
tags: update_config
- name: start nginx server
service: name=nginx state=started
when:
- nginx_syntax.rc == 0
- nginx_running.stat.exists == false
tags: update_config
handlers:
- name: reload nginx server
service: name=nginx state=reload
when:
- nginx_syntax.rc == 0
- nginx_running.stat.exists == true
在改进的 playbook 中,我们针对文件发布task任务 “update config” 和 “add virtualhost config” 增加了新属性 notify,值为"reload nginx server"。
意思是说,针对这两个文件发布的task,设置一个通知机制,当 ansible 认为文件的内容发生了变化(文件MD5发生了变化了),它就会发送一个通知信号,通知 handlers 中的某一个任务。具体发送到 handlers 中的那个任务,由notify的值 “reload nginx server” 决定。通知发出后 handlers 会根据发送的通知,在handlers中相关的任务重寻找名称为 “reload nginx server” 的任务。
当发现存在这样名字的 task,就会执行它。若没有找到,则什么也不做。若我们要实现这样的机制千万要注意notify属性设置的值,一定要确保能和 handlers 中的 task 名称对应上。
执行
首次执行,若配置文件没有发生变化,可以发现根本就没有触发 handlers 中 task 任务
[root@master1 ~]# ansible-playbook -i hosts site02.yml -t update_config
人为对 nginx 配置文件稍作修改,只要 MD5 校验值发生变化即可。此时,在执行发现出发了 handlers 中的 task 任务。