1. 程式人生 > 其它 >04-任務控制

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