04-任务控制

?、 Ansible 任务控制基本介绍

这?主要来介绍PlayBook中的任务控制。

任务控制类似于编程语?中的if ... 、for ... 等逻辑控制语句。

这?我们给出?个实际场景应?案例去说明在PlayBook中,任务控制如何应?。

在下?的PlayBook中,我们创建了 tomcat、www 和 mysql 三个?户。

安装了Nginx 软件包、并同时更新了 Nginx 主配置?件和虚拟主机配置?件,最后让Nginx 服务处于启动状态。

整个PlayBook从语法上没有任何问题,但从逻辑和写法上仍然有?些地?需要我们去注意及优化:

  1. Nginx启动逻辑?缺考虑。若Nginx的配置?件语法错误则会导
    致启动Nginx失败,以?于PlayBook执?失败。
  2. 批量创建?户,通过指令的罗列过于死板。如果再创建若?个
    ?户,将难以收场。
---
- name: task control playbook example
 hosts: webservers
 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 webserver
     yum: name=nginx state=present
   - name: update nginx main config
     copy: src=nginx.conf dest=/etc/nginx/
   - name: add virtualhost config
     copy: src=www.qfedu.com.conf dest=/etc/nginx/conf.d/
   - name: start nginx server
     service: name=nginx state=started
# cat nginx.conf
user www;
worker_processes 2;

error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

events {
 worker_connections 1024;
}

http {
	include /etc/nginx/mime.types;
     default_type application/octet-stream;
 
      log_format main ‘$remote_addr - $remote_user [$time_local] "$request" ‘ ‘$status $body_bytes_sent "$http_referer" ‘ ‘"$http_user_agent" "$http_x_forwarded_for"‘;
     sendfile on;
     tcp_nopush on;
     keepalive_timeout 0;
     gzip on;
     gzip_min_length 1k;
     gzip_buffers 8 64k;
     gzip_http_version 1.0;
     gzip_comp_level 5;
     gzip_types text/plain application/x-javascript text/css application/json application/xml application/x-shockwave-flash application/javascript image/svg+xml image/x-icon;
     gzip_vary on;
     include /etc/nginx/conf.d/*.conf;
}
# cat www.qfedu.com.conf
server {
     listen 80;
     server_name www.qfedu.com;
     
     root /usr/share/nginx/html;
     
         access_log /var/log/nginx/www.qfedu.comaccess_log main;
         error_log /var/log/nginx/www.qfedu.comerror_log;
         
         add_header Access-Control-Allow-Origin *;
         
         location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
             expires 1d;
         }
         
         location ~ .*\.(js|css)?$ {
             expires 1d;
		 }
}

我们下?将以解决?个个问题的形式去优化上例中的PlayBook。

通过问题的解决,来达到我们学习任务控制的?的。

?、条件判断

解决第?个问题

Nginx启动逻辑?缺考虑。 若Nginx的配置?件语法错误则会导致

启动Nginx失败,以?于PlayBook执?失败。

如果我们能够在启动之前去对Nginx的配置?件语法做正确性的校

验,只有当校验通过的时候我们才去启动或者重启Nginx;否则则跳过

启动Nginx的过程。这样就会避免Nginx 配置?件语法问题?导致的?

法启动Nginx的?险。

Nginx 语法校验

- name: check nginx syntax
  shell: /usr/sbin/nginx -t

那如何将Nginx语法检查的TASK同Nginx启动的TASK关联起来呢?

如果我们能够获得语法检查的TASK的结果,根据这个结果去判断“启动NGINX的TASK”是否执?,这将是?个很好的?案。

如何和获取到语法检查TASK的结果呢? 此时就可以使?之前学到的 Ansible中的注册变量。

获取Task任务结果

- name: check nginx syntax
  shell: /usr/sbin/nginx -t
  register: nginxsyntax

此时有可能还有疑问,我获取到任务结果,但是结果??的内容是个什么样?,

我如何根据内容在后续的PlayBook中使?呢?

通过debug模块去确认返回结果的数据结构

- name: print nginx syntax result
  debug: var=nginxsyntax

通过debug 模块,打印出来的返回结果。 当nginxsyntax.rc 为 0 时语法校验正确。

"nginxsyntax": {
     "changed": true,
     "cmd": "/usr/sbin/nginx -t",
     "delta": "0:00:00.012045",
     "end": "2017-08-12 20:19:04.650718",
     "rc": 0,
     "start": "2017-08-12 20:19:04.638673",
     "stderr": "nginx: the configuration file
/etc/nginx/nginx.conf syntax is ok\nnginx:
configuration file /etc/nginx/nginx.conf test is
successful",

"stderr_lines": [
 	"nginx: the configuration file /etc/nginx/nginx.conf syntax is ok",
     "nginx: configuration file /etc/nginx/nginx.conf test is successful"
],
 "stdout": "",
 "stdout_lines": []
 }

通过条件判断(when) 指令去使?语法校验的结果

 - name: check nginx syntax
   shell: /usr/sbin/nginx -t
   register: nginxsyntax
 - name: print nginx syntax
   debug: var=nginxsyntax
 
 - name: start nginx server
   service: name=nginx state=started
   when: nginxsyntax.rc == 0

改进后的PlayBook

---
- name: task control playbook example
  hosts: webservers
  gather_facts: no
  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 webserver
      yum: name=nginx state=present
    - name: update nginx main config
      copy: src=nginx.conf dest=/etc/nginx/
    - name: add virtualhost config
      copy: src=www.qfedu.com.conf dest=/etc/nginx/conf.d/
    - name: check nginx syntax
      shell: /usr/sbin/nginx -t
      register: nginxsyntax
    - name: print nginx syntax
      debug: var=nginxsyntax
    - name: start nginx server
      service: name=nginx state=started
      when: nginxsyntax.rc == 0
...

以上的逻辑,只要语法检查通过都会去执? "start nginx server"这个TASK。

在这个问题的解决?,我们学习了when 条件判断和注册变量的结合使?。

学习了when条件判断中是可以?持复杂逻辑的。?如现在?到的逻辑运算符 and。

另外 when ?持如下运算符:

==
!=
> >=
< <=
is defined
is not defined
true
false
?持逻辑运算符: and or

三、 循环控制

解决第?个问题

批量创建?户,通过指令的罗列过于死板。如果再创建若?个?户,将难以收场。

如果在创建?户时,抛开PlayBook的实现不说, 单纯的使?shell去批量的创建?些?户。通常会怎么写呢?

#! /bin/bash
createuser="tomcat mysql www"
for i in `echo $createuser`
  do
    useradd $i
  done

那么如果PlayBook中也存在这样的循环控制,我们也可以像写shell?样简单的去完成多?户创建?作。

在PlayBook中使?with_items 去实现循环控制,且循环时的中间变量(上?shell循环中的 $i 变量)只能是关键字 item ,?不能随意?定义。

在上?的基础上,改进的PlayBook在这?使?定义了剧本变量 createuser(?个列表) ,

然后通过with_items 循环遍历变量这个变量来达到创建?户的?的。

cat > test.yml <<EOF
---
- name: variable playbook example
  hosts: 127.0.0.1
  gather_facts: no
  vars:
    createuser:
      - tomcat
      - www
      - mysql
  tasks:
    - name: create user
      user: name={{ item }} state=present
      with_items: "{{ createuser }}"
      
    - name: yum nginx webserver
      yum: name=nginx state=present
    #- name: update nginx main config
    #  copy: src=nginx.conf dest=/etc/nginx/
    #- name: add virtualhost config
    #  copy: src=www.qfedu.com.conf dest=/etc/nginx/conf.d/
    - name: check nginx syntax
      shell: /usr/sbin/nginx -t
      register: nginxsyntax
 
    - name: print nginx syntax
      debug: var=nginxsyntax
 
    - name: start nginx server
      service: name=nginx state=started
      when: nginxsyntax.rc == 0
...
EOF
ansible-playbook test.yml

解决了以上问题,整个PlayBook已经有了很?的改进

新版本循环

cat > test.yml <<EOF
---
- name: loop item
  hosts: 127.0.0.1
  gather_facts: no
  vars:
    some_list:
      - "a"
      - "b"
      - "c"
    num_list:
      - 1
      - 2
      - 3
      - 5
  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
...
EOF
#ansible-playbook -i tengxunyun test.yml
#ansible-playbook -i 127.0.0.1 test.yml # 上面已经指定host了 这里就不用指定-i了
ansible-playbook test.yml

考虑这样?个情况:

若更新了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 服务是已经启动状态。

所以,还需要进?步通过判断 Nngix 的 pid ?件存在,才证明 Nginx 服务本身是启动中,启动中才可以 reload Nginx 服务。

判断?个?件是否存在使? stat 模块

- name: check nginx running
  stat: path=/var/run/nginx.pid
  register: nginxrunning

观察结果,会发现 nginxrunning.stat.exists 的值是 true 就表示启动状态,是 false 就是关闭状态。

接下来下来就可以依据这个结果,来决定是否重新加载 Nginx 服务。

改进PlayBook

cat > test.yml <<EOF
---
- name: tags playbook example
  hosts: 127.0.0.1
  gather_facts: no
  vars:
    createuser:
      - tomcat
      - www
      - mysql
  tasks:
    - name: create user
      user: name={{ item }} state=present
      with_items: "{{ createuser }}"
    - name: yum nginx webserver
      yum: name=nginx state=present
    - name: update nginx main config
      copy: src=nginx.conf dest=/etc/nginx/nginx.conf
      tags: updateconfig
    #- name: add virtualhost config
    #  copy: src=www.qfedu.com.conf dest=/etc/nginx/conf.d/
    #  tags: updateconfig
    - name: check nginx syntax
      shell: /usr/sbin/nginx -t
      register: nginxsyntax
      tags: updateconfig
    - name: check nginx running
      stat: path=/var/run/nginx.pid
      register: nginxrunning
      tags: updateconfig
    - name: print nginx syntax
      debug: var=nginxsyntax
    - name: print nginx syntax
      debug: var=nginxrunning
    - name: reload nginx server
      service: name=nginx state=started
      when: nginxsyntax.rc == 0 and nginxrunning.stat.exists == true
      tags: updateconfig
    - name: start nginx server
      service: name=nginx state=started
      when:
        - nginxsyntax.rc == 0
        - nginxrunning.stat.exists == false
      tags: updateconfig
...
EOF
ansible-playbook test.yml -t updateconfig

指定tags 去执?PlayBook执?时?定要指定tags,

这样再执?的过程中只会执?task 任务上打上tag 标记为 updateconfig 的任务

 # ansible-playbook -i hosts site.yml -t updateconfig

四、 Handlers 属性

观察当前的 Playbook,不能发现,当我的配置?件没有发?变化时,每次依然都会去触发TASK "reload nginx server"。

如何能做到只有配置?件发?变化的时候才去触发TASK "reload nginx server",这样的处理才是最完美的实现。此时可以使?handlers 属性。

改进PlayBook

cat > test.yml <<EOF
---
- name: tags playbook example
  hosts: 127.0.0.1
  gather_facts: no
  vars:
    createuser:
      - tomcat
      - www
      - mysql
  tasks:
    - name: create user
      user: name={{ item }} state=present
      with_items: "{{ createuser }}"
    - name: yum nginx webserver
      yum: name=nginx state=present
    - name: update nginx main config
      copy: src=nginx.conf dest=/etc/nginx/nginx.conf
      tags: updateconfig
      notify: reload nginx server
    #- name: add virtualhost config
    #  copy: src=www.qfedu.com.conf dest=/etc/nginx/conf.d/
    #  tags: updateconfig
    #  notify: reload nginx server
    - name: check nginx syntax
      shell: /usr/sbin/nginx -t
      register: nginxsyntax
      tags: updateconfig
    - name: check nginx running
      stat: path=/var/run/nginx.pid
      register: nginxrunning
      tags: updateconfig
    - name: print nginx syntax
      debug: var=nginxsyntax
    - name: print nginx syntax
      debug: var=nginxrunning
    - name: reload nginx server
      service: name=nginx state=started
      when: nginxsyntax.rc == 0 and nginxrunning.stat.exists == true
      tags: updateconfig
    - name: start nginx server
      service: name=nginx state=started
      when:
        - nginxsyntax.rc == 0
        - nginxrunning.stat.exists == false
      tags: updateconfig
      
  handlers:
    - name: reload nginx server
      service: name=nginx state=reloaded
      when:
        - nginxsyntax.rc == 0
        - nginxrunning.stat.exists == true
...
EOF
ansible-playbook test.yml -t updateconfig

在改进的PlayBook中,我们针对?件发布TASK 任务 "update nginx main 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任务

# ansible-playbook -i hosts site.yml -t updateconfig

?为对Nginx 配置?件稍作修改,只要MD5校验值发?变化即可。此时再执?,发现触发了handlers 中的TASK任务

# ansible-playbook -i hosts site.yml -t updateconfig

1

04-任务控制

上一篇:【GameJam】机核网BOOOM游戏创作挑战开发复盘


下一篇:【DEBUG】 Jenkins Pipeline Build 不下载最新的包