Ansible 日常使用技巧 - 運維總結


 

Ansible默認只會創建5個進程並發執行任務,所以一次任務只能同時控制5台機器執行。如果有大量的機器需要控制,例如20台,Ansible執行一個任務時會先在其中5台上執行,執行成功后再執行下一批5台,直到全部機器執行完畢。使用-f選項可以指定進程數,指定的進程數量多一些,不僅會實現全並發,對異步的輪訓poll也會有正面影響。

Ansible默認是同步阻塞模式,它會等待所有的機器都執行完畢才會在前台返回。Ansible可以采取異步執行模式。異步模式下,Ansible會將節點的任務丟在后台,每台被控制的機器都有一個job_id,Ansible會根據這個job_id去輪訓該機器上任務的執行情況,例如某機器上此任務中的某一個階段是否完成,是否進入下一個階段等。即使任務早就結束了,但只有輪訓檢查到任務結束后才認為該job結束。Ansible可以指定任務檢查的時間間隔,默認是10秒。除非指定任務檢查的間隔為0,否則會等待所有任務都完成后,Ansible端才會釋放占用的shell。如果指定時間間隔為0,則Ansible會立即返回(至少得連接上目標主機,任務發布成功之后立即返回),並不會去檢查它的任務進度。

Ansible的同步模式與異步模式
同步模式: 如果節點數太多,ansible無法一次在所有遠程節點上執行任務,那么將先在一部分節點上執行一個任務(每一批節點的數量取決於fork進程數量,默認為5個,可設置),直到這一批所有節點上該任務完全執行完畢才會接入下一個批節點,直到所有節點將該任務都執行完畢,然后重新回到第一批節點開始執行第二個任務。依次類推,直到所有節點執行完所有任務,ansible端才會釋放shell。這是默認同步模式,也就是說在未執行完畢時,ansible是占用當前shell的,任務執行完后,釋放shell了才可以輸入其他命令做其他動作。

異步模式:假如fork控制的並發進程數為5,遠程控制節點為24個,則ansible一開始會將5個節點的任務扔在后台,並每隔一段時間去檢查這些節點的任務完成情況,當某節點完成不會立即返回,而是繼續等待直到5個進程都空閑了,才會將這5個節點的結果返回給ansible端,ansible會繼續將下一批5個節點的任務扔在后台並每隔一段時間進行檢查,依次類推,直到完成所有任務。

在異步模式下,如果設置的檢查時間間隔為0,在將每一批節點的任務丟到后台后都會立即返回ansible,並立即將下一批節點的任務丟到后台,直到所有任務都丟到后台完后,才返回ansible端,ansible才會立即釋放占用的shell。即此時ansible是不會管各個節點任務執行情況的,不管執行成功或失敗。因此在輪訓檢查時間內,ansible仍然正在運行(盡管某批任務已經被放到后台執行了),當前shell進程仍被占用處於睡眠狀態,只有指定的檢查時間間隔為0,才會盡快將所有任務放到后台並釋放shell。

一、Ansible的異步和輪詢  [ async、poll ]
Ansible有時候要執行等待時間很長的操作,這個操作可能要持續很長時間,設置超過ssh的timeout。這種情況下可以選擇在step中指定async和poll來實現異步操作。其中:async表示這個step的最長等待時長, 如果設置為0, 表示一直等待下去直到動作完成poll表示檢查step操作結果的間隔時長

ansible默認的清單文件是/etc/ansible/hosts,也就是ansible和ansible-ploybook執行時默認讀的清單文件。這個可以自行定義。
[root@hostname ~]# cat /etc/ansible/ansible.cfg|grep inventory
#inventory      = /etc/ansible/hosts

[root@hostname ~]# cat /etc/ansible/hosts|tail -2             
[test_server]                   #組名最好不要使用"-",可以使用"_"
172.16.60.241

1)先來看下面初始配置
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  tasks :
    - name : ansible-test
      shell : sleep 10
      #async表示上述shell命令的等待時間,設置為0時會一直等待命令結束
      async : 5
      #poll表示檢查step操作結果的間隔時長,設置為0表示 不用等待結果,繼續做下面的操作,我們可以在下面的step中來驗證這個命令是否成功執行.
      poll : 2

執行下看看是否成功:
[root@hostname ~]# ansible-playbook /etc/ansible/test.yml

PLAY [test_server] *******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]

TASK [ansible-test] ******************************************************************************************************************************
fatal: [172.16.60.241]: FAILED! => {"changed": false, "msg": "async task did not complete within the requested time - 5s"}

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0 

如上,這個step失敗, 因為ansible的任務(就是上面配置中的shell動作)操作時間(10s)超過了最大等待時長(5s)

2)如果將上面的async異步等待時間設置為大於10s,比如12s,則執行就成功了!
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  tasks :
    - name : ansible-test
      shell : sleep 10
      #async表示上述shell命令的等待時間,設置為0時會一直等待命令結束
      async : 12
      #poll表示檢查step操作結果的間隔時長,設置為0表示 不用等待結果,繼續做下面的操作,我們可以在下面的step中來驗證這個命令是否成功執行.
      poll : 2

[root@hostname ~]# ansible-playbook /etc/ansible/test.yml             

PLAY [test_server] *******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]

TASK [ansible-test] ******************************************************************************************************************************
changed: [172.16.60.241]

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

這時候就不怕任務超時了。可以執行一個12s的任務(大於上面shell執行的時間)。另外,如果poll為0,就相當於一個不關心結果的任務。

3)或者將上面的poll數值設置為0,即不用等待ansible任務執行的結果,立即執行下一個step。
即只需要將任務命令推送到ansible客戶機上,不需要等待任務執行完成就立即執行下一個step。

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  tasks :
    - name : ansible-test
      shell : sleep 10
      #async表示上述shell命令的等待時間,設置為0時會一直等待命令結束
      async : 5
      #poll表示檢查step操作結果的間隔時長,設置為0表示 不用等待結果,繼續做下面的操作,我們可以在下面的step中來驗證這個命令是否成功執行.
      poll : 0

[root@hostname ~]# ansible-playbook /etc/ansible/test.yml

PLAY [test_server] *******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]

TASK [ansible-test] ******************************************************************************************************************************
changed: [172.16.60.241]

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

4)如果還想要更方便地看輪詢結果,ansible還提供了這個模塊async_status。
[root@hostname ~]# cat /etc/ansible/test.yml             
- hosts : test_server
  tasks :
    - name : ansible-test
      shell : sleep 3
      async : 8
      poll : 2
      register: kevin_result

    - name: 'check ansible-test task polling results '
      async_status: jid={{ kevin_result.ansible_job_id }}
      register: job_result
      until: job_result.finished
      retries: 10

[root@hostname ~]# ansible-playbook /etc/ansible/test.yml

PLAY [test_server] *******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]

TASK [ansible-test] ******************************************************************************************************************************
changed: [172.16.60.241]

TASK [check ansible-test task polling results] ***************************************************************************************************
changed: [172.16.60.241]

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

第一個job執行異步任務sleep,並且注冊了一個名字叫kevin-result的register變量,用於提供給第二個job作為輪詢對象,並且它自己poll設為2 (即自己輪詢2次)。
register用於在ansible的playbook中task之間的相互傳遞變量,
register 這個功能非常有用。當我們需要判斷對執行了某個操作或者某個命令后,如何做相應的響應處理(執行其他 ansible 語句),則一般會用到register 。
until表示循環。

第二個job使用async_status模塊,進行輪詢並返回輪詢結果。准備檢查10次。

async參數值代表了這個任務執行時間的上限值。即任務執行所用時間如果超出這個時間,則認為任務失敗。此參數若未設置,則為同步執行。
poll參數值:代表了任務異步執行時輪詢的時間間隔。

二、Ansible的並發限制  [ serial、max_fail_percentage ]
當ansible清單文件里設置的組里有很多機器,可以限制一下ansible任務的並發。ansible的並發功能可以在ansible.cfg里修改配置,也可以在playbook中限制服務端的並發數量,這是ansible經常用到的一個關鍵功能。ansible默認情況下只會創建5個進程,所以一次任務只能同時控制5台機器執行。如果有大量的機器需要控制,或者希望減少進程數,那就可以采取異步執行(async),ansible的模塊可以把task放進后台,然后輪詢它(poll)。

使用async和poll這兩個關鍵字便可以並行運行一個任務,即在所有機器上一次性運行。async這個關鍵字會觸發ansible並行運作任務,async的值是ansible等待運行這個任務的最大超時值(如果執行超時任務會強制中斷導致失敗),而poll就是ansible檢查這個任務是否完成的頻率時間。

1) serial參數設置並發數
=====================================================================
一般情況下, ansible會同時在所有服務器上執行用戶定義的操作, 但是用戶可以通過serial參數來定義同時可以在多少太機器上執行操作。

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  serial: 3

  tasks :
    - name: Install telnet
      yum: name=telnet state=installed

即test_server組內的3台機器完全執行完成play后, 其他機器才能開始執行。

接着看下面的配置
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : all
  serial: 7

  tasks :
    - name: Install telnet
      yum: name=telnet state=installed

    - name : Run Serverstart.sh
      command : /bin/bash /opt/scripts/Serverstart.sh
      async : 300
      poll : 10
      register: kevin_result

如上配置,發現當ansible配置控制超過5台機器時,上面ansible中:
a)yum模塊會先在5台機器上跑,完成后再繼續剩余2台的機器;
b)command模塊的任務會一次性在所有機器上都執行了,然后監聽它的回調結果;

這里需要注意下面兩種情況
a)情況一: 設置poll=0
如果上面command模塊是控制機器開啟一個進程放到后台,那就不需要檢查這個任務是否完成了,只需要繼續其他的動作, 
最后再使用wait_for這個模塊去檢查之前的進程是否按預期中開啟了便可。
這時只需要把poll這個值設置為0, 便可以按上面的要求配置ansible不等待job的完成。
b)情況二: 設置async=0
如果有一種需求是有一個task它是需要運行很長的時間,那就需要設置一直等待這個job完成。
這個時候只需要把async的值設成0便可。

簡單總結下,適合使用到ansible的polling特性的場景
- 有一個task需要運行很長的時間,這個task很可能會達到timeout;
- 有一個任務需要在大量的機器上面運行;
- 有一個任務是不需要等待它完成的;

不適合使用polling特性的場景
- task任務是需要運行完后才能繼續另外的任務的;
- task任務能很快的完成;

2) max_fail_percentage:最大失敗百分比
=====================================================================
默認情況下, 只要ansible的group中還有server沒有失敗, ansible就是繼續執行tasks。實際上, 用戶可以通過max_fail_percentage(最大失敗百分比)來限制ansible的並發執行。
只要超過max_fail_percentage的server失敗, ansible就可以中止tasks的執行。serial參數在ansible-1.8以后就開始支持百分比功能了!!

試想一下如果group組里有200台機器,那么如果使用serial來限制並發數量,比如設置serial=10,意思就是一次只執行10台,一直到200台完成。
只要組內還有server沒有失敗, ansible就是繼續執行tasks。這樣就顯得效率很低了,很不方便!這時就可以使用類似控制流的max_fail_percentage功能了!!

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : all
  max_fail_percentage: 30
  serial: 10

  tasks :
    - name: Install telnet
      yum: name=telnet state=installed

    - name : Run Serverstart.sh
      command : /bin/bash /opt/scripts/Serverstart.sh
      async : 300
      poll : 10
      register: kevin_result

如上配置,即10台機器里有30%的機器執行yum模塊的task任務失敗,那么就終止這個10台機器的task任務的執行,接着執行下一組10台機器的task任務,這樣效果就很棒了。

溫馨提示:
實際失敗機器必須大於這個百分比時, tasks任務才會被中止;如果等於這個百分比時,task任務是不會被終止的!

踩坑經驗Ansible並發失敗(fork=100. 但是真正執行playbook時並沒有實現並發)

[root@hostname ~]# cd /usr/lib/python2.7/site-packages/ansible/
[root@hostname ansible]# find . -name ssh.py
./plugins/connection/ssh.py

[root@hostname ansible]# vim plugins/connection/ssh.py
.........
.........
   if C.HOST_KEY_CHECKING and not_in_host_file:
        # lock around the initial SSH connectivity so the user prompt about whether to add
        # the host to known hosts is not intermingled with multiprocess output.
        fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX)
        fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX)

   # create process
  (p, stdin) = self._run(ssh_cmd, in_data)
.........
.........

通過以上文件代碼可以看出:
如果ansible配置"HOST_KEY_CHECKING=True", 並且ansible客戶機信息沒有在ansible服務端的~/.ssh/known_hosts里面, 一個進程就會鎖死~/.ssh/known_hosts文件。
這樣ansible就不能實現並發!

解決方案:
在ansible服務端的/etc/ansible/ansible.cfg文件里配置"host_key_checking = False"    [其實ansible.cfg文件里該項默認配置的就是False]

三、Ansible的任務委托  [ delegate_to、delegate_facts、run_once ]
默認情況下,ansible的所有任務都是在指定的機器上運行的。當在一個獨立的群集環境中配置時,只是想操作其中的某一台主機,或者在特定的主機上運行task任務,此時就需要用到ansible的任務委托功能。使用delegate_to關鍵字可以配置task任務在指定的機器上執行就是說其他的task任務還是在hosts關鍵字配置的機器上運行,到了這個關鍵字所在的任務時,就使用委托的機器運行。

1)委托
=====================================================================
通過"delegate_to", ansible可以把某一個task任務放在委托的機器上執行。即在指定的組內的某一台或多台機器上執行task任務。

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  serial: 10

  tasks :
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: 172.16.60.245

則上面的shell模塊的task任務只會在172.16.60.245這台節點上執行,test_server組內其他的機器不會執行shell任務。

---------------------
如果 "delegate_to: 127.0.0.1" 則可以用local_action來代替。即下面兩個配置效果是一樣的!!
[root@hostname ~]# cat /etc/ansible/test.yml 
- hosts : test_server
  serial: 10

  tasks :
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: 127.0.0.1

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts : test_server
  serial: 10

  tasks :
    - name: test-haha
      local_action: shell echo "test" > /root/test.list

-------------------
如果設置了多個delegate_to,則執行時只會匹配最下面那個。
例如下面配置中,只會執行"delegate_to: 172.16.60.245", 上面那個"delegate_to: 172.16.60.241"就會被忽略了。
[root@hostname ansible]# cat /etc/ansible/test.yml
- hosts : all
  serial: 10

  tasks :
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: 172.16.60.241
      delegate_to: 172.16.60.245

-------------------
delegate_to默認后面只能跟一個主機ip,不能跟多個主機ip。即默認委托到單個主機。
如果有多個ip需要委托,則可以將這些ip重新放一個group,然后delegate_to委托給group組。
delegate_to委托到組的方式:通過items變量方式!!!

[root@hostname ansible]# cat /etc/ansible/hosts |tail -8
[test_server]
172.16.60.241
172.16.60.245
172.16.60.246
127.0.0.1

[kevin_server]
172.16.60.246
127.0.0.1

[root@hostname ansible]# cat /etc/ansible/test.yml 
- hosts: all
  tasks:
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: "{{item}}"
      with_items: "{{groups['kevin_server']}}"

即將shell這個task任務委托給kevin_server組內的機器執行。


2)委托者的facts
=====================================================================
默認情況下, ansible委托任務的facts是inventory_hostname中主機的facts, 而不是被委托機器的facts。

a) delegate_facts
在ansible 2.0 中, 通過設置"delegate_facts: True"可以讓task任務去收集被委托機器的facts。
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: test_server
  tasks:
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: "{{item}}"
      delegate_facts: True
      with_items: "{{groups['kevin_server']}}"

如上配置,表示會收集kevin_server的facts並分配給這些機器, 而不會去收集test_server的facts

b)RUN ONCE
通過設置"run_once: true"來指定該task只能在委托的某一台機器或委托的組內機器上執行一次!!可以和delegate_to 結合使用。
如果沒有delegate_to, 那么這個task默認就會在第一台機器上執行!!!
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: test_server
  tasks:
    - name: test-haha
      shell: echo "test" > /root/test.list
      delegate_to: "{{item}}"
      run_once: true
      delegate_facts: True
      with_items: "{{groups['kevin_server']}}"

四、Ansible的任務暫停  [ local_action、wait_for ]
當Ansible一些任務的運行需要等到一些狀態的恢復,比如某一台主機或者應用剛剛重啟,需要等待其某個端口開啟,這個時候就需要用到Ansible的任務暫停功能。Ansible任務的暫停操作是通過local_action配合wait_for模塊來完成的

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: test_server
  remote_user: root
  gather_facts: no

  tasks:
    - name: kevin_test
      local_action:
        module: wait_for           #模塊名字
        port: 2379
        host: 172.16.60.241
        delay: 10
        timeout: 300
        state: started


使用local_action配合wait_for模塊來完成任務的暫停操作。
上面配置說明kevin_test任務每隔10s檢查指定主機上的2379端口是否開啟,如果操作300s,2379端口任未開啟,將返回失敗信息。

上面host指定了一台機器,如果是需要指定多台機器呢?
可以將執行的多台機器放在一台新group內,然后通過變量去指定group。
[root@hostname ~]# cat /etc/ansible/test.yml             
- hosts: test_server
  remote_user: root
  gather_facts: no

  tasks:
    - name: kevin_test
      local_action:
        module: wait_for
        port: 2379
        host: "{{item}}"
        delay: 10
        timeout: 300
        state: started
      with_items: "{{groups['kevin_server']}}"


如上面配置,每間隔10s檢查指定的kevin_server組內的主機的2379端口是否開啟,如果操作300s,2379端口沒開啟,則返回失敗信息。
注意:上面的"with_items"這一項配置要和"local_action"對齊!!否則會報錯!

五、Ansible如何判斷並中斷執行  [ when、fail ]
在使用ansible-playbook在執行一個腳本時,如何根據腳本返回的內容判斷是否繼續往下執行還是中斷執行?查詢官網可以發現使用register寄存器可以實現記錄腳本輸出,並且使用when+fail模塊來判斷是否往下繼續執行或者中斷

遠端機器172.16.60.242有如下腳本:
[root@242 ~]# cat /mnt/scripts/test.sh 
#!/bin/bash

TEXT=$1

if [ $1 == "kevin" ];then
   echo "Success"
else
   echo "Failed"
fi

[root@242 ~]# /bin/bash /mnt/scripts/test.sh kevin
Success
[root@242 ~]# /bin/bash /mnt/scripts/test.sh kevin234
Failed

現在要求:
a)通過ansible執行172.16.60.242的test.sh腳本,當腳本返回Success時,在172.16.60.242機器上創建一個目錄/opt/kevin。
b)通過ansible執行172.16.60.242的test.sh腳本,當腳本返回Failed時,則中斷執行。


在ansible服務端配置yml文件,相關配置過程如下:
1)如下配置,將command模塊的task任務委托給kevin_server組內的172.16.60.242機器執行。
   先使用了register寄存器,具體寄存了什么內容,可以使用-v參數來查看輸出

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root

  tasks :
    - name: an_bo
      command: /bin/bash /mnt/scripts/test.sh kevin
      delegate_to: 172.16.60.242
      register: result

[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file

PLAY [kevin_server] ******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241]

TASK [an_bo] *************************************************************************************************************************************
changed: [172.16.60.242 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "kevin"], "delta": "0:00:00.004078", 
"end": "2019-10-11 15:35:49.850430", "rc": 0, "start": "2019-10-11 15:35:49.846352", "stderr": "", "stderr_lines": [], "stdout": "Success", 
"stdout_lines": ["Success"]}
changed: [172.16.60.241 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "kevin"], "delta": "0:00:00.004502", 
"end": "2019-10-11 15:35:49.852445", "rc": 0, "start": "2019-10-11 15:35:49.847943", "stderr": "", "stderr_lines": [], "stdout": "Success", 
"stdout_lines": ["Success"]}

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
172.16.60.242              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

可以看出:
register保存的信息就是上面執行結果中"=>"后面的字典信息,信息保存在result變量中。
並且看到"stdout"就是腳本的標准輸出信息,這時可以使用"when"來判斷是否執行或者跳過。

2)使用"when"來判斷是否執行或者跳過。
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root

  tasks :
    - name: an_bo
      command: /bin/bash /mnt/scripts/test.sh kevin
      delegate_to: 172.16.60.242
      register: result
 
    - name: ru_bo
      file: path=/opt/kevin state=directory
      delegate_to: 172.16.60.242
      when: result.stdout == 'Success'

查看執行結果:
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file

PLAY [kevin_server] ******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]
ok: [172.16.60.242]

TASK [an_bo] *************************************************************************************************************************************
changed: [172.16.60.242 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "kevin"], "delta": "0:00:00.002337", "end": "2019-10-11 15:48:20.427582", "rc": 0, "start": "2019-10-11 15:48:20.425245", "stderr": "", "stderr_lines": [], "stdout": "Success", "stdout_lines": ["Success"]}
changed: [172.16.60.241 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "kevin"], "delta": "0:00:00.002579", "end": "2019-10-11 15:48:20.425082", "rc": 0, "start": "2019-10-11 15:48:20.422503", "stderr": "", "stderr_lines": [], "stdout": "Success", "stdout_lines": ["Success"]}

TASK [ru_bo] *************************************************************************************************************************************
changed: [172.16.60.241 -> 172.16.60.242] => {"changed": true, "gid": 0, "group": "root", "mode": "0755", "owner": "root", "path": "/opt/kevin", "size": 6, "state": "directory", "uid": 0}
ok: [172.16.60.242 -> 172.16.60.242] => {"changed": false, "gid": 0, "group": "root", "mode": "0755", "owner": "root", "path": "/opt/kevin", "size": 6, "state": "directory", "uid": 0}

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
172.16.60.242              : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

可以發現,當腳本返回Success時,已經在172.16.60.242機器上創建一個目錄/opt/kevin。

3)現在將腳本輸出內容修改為"Failed" (即執行腳本時,$1為非kevin字符)
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root

  tasks :
    - name: an_bo
      command: /bin/bash /mnt/scripts/test.sh shibo
      delegate_to: 172.16.60.242
      register: result
 
    - name: if stdout 'Failed',Interrupt execution
      delegate_to: 172.16.60.242
      fail: msg="Check Failed"
      when: result.stdout == 'Failed'

    - name: ru_bo
      file: path=/opt/kevin state=directory
      delegate_to: 172.16.60.242
      when: result.stdout == 'Success'

查看執行結果:
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file

PLAY [kevin_server] ******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241]

TASK [an_bo] *************************************************************************************************************************************
changed: [172.16.60.241 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "shibo"], "delta": "0:00:00.002767", "end": "2019-10-11 15:57:56.049142", "rc": 0, "start": "2019-10-11 15:57:56.046375", "stderr": "", "stderr_lines": [], "stdout": "Failed", "stdout_lines": ["Failed"]}
changed: [172.16.60.242 -> 172.16.60.242] => {"changed": true, "cmd": ["/bin/bash", "/mnt/scripts/test.sh", "shibo"], "delta": "0:00:00.002698", "end": "2019-10-11 15:57:56.051455", "rc": 0, "start": "2019-10-11 15:57:56.048757", "stderr": "", "stderr_lines": [], "stdout": "Failed", "stdout_lines": ["Failed"]}

TASK [if stdout 'Failed',Interrupt execution] ****************************************************************************************************
fatal: [172.16.60.241 -> 172.16.60.242]: FAILED! => {"changed": false, "msg": "Check Failed"}
fatal: [172.16.60.242 -> 172.16.60.242]: FAILED! => {"changed": false, "msg": "Check Failed"}

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=2    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
172.16.60.242              : ok=2    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

可以看出:
playbook運行到第二個task時就會報錯並拋出msg!根據第二個task任務,腳本輸出結果為"Failed",直接中斷任務執行。那么第三個task任務就不會被執行了。

注意:
result寄存器中的數據都可以拿來使用,如"rc","stderr"等。
當然也有很多種方法,文中的"Failed"是嚴格匹配,也可以使用模糊查找,如"result.stdout.find('Failed') != -1"也可以達到相同的效果

[root@hostname ~]# cat /etc/ansible/test.yml                
- hosts: kevin_server
  remote_user: root

  tasks :
    - name: an_bo
      command: /bin/bash /mnt/scripts/test.sh shibo
      delegate_to: 172.16.60.242
      register: result
 
    - name: if stdout 'Failed',Interrupt execution
      delegate_to: 172.16.60.242
      fail: msg="Check Failed"
      when: result.stdout.find('Failed') != -1            # 等同於 when: result.stdout == 'Failed'

    - name: ru_bo
      file: path=/opt/kevin state=directory
      delegate_to: 172.16.60.242
      when: result.stdout == 'Success'

六、Ansible之條件判斷  [ when ]
在日常運維工作中,在有的時候ansble-playbook的結果依賴於變量、fact或者是前一個任務的執行結果,從而需要使用到條件語句。使用ansible-playbook時,可能需要對某些條件進行判斷,只有當滿足條件才執行相應的tasks。有下面幾種條件判斷:

1)when條件判斷:只條滿足when的條件時才執行對應的tasks
=====================================================================
需要注意:when關鍵字后面跟着的是python的表達式,在表達式中我們能夠使用任何的變量或者facts。

另外注意:當需要用遠程主機的一些信息時,gather_facts必須要開啟,默認是開啟狀態!!!!!
[root@hostname ~]# cat /etc/ansible/hosts |tail -3
[kevin_server]
172.16.60.241
172.16.60.242

注意:下面debug中msg后面引用的變量都是在setup模塊中查詢出來的(可直接作為變量引用)
[root@hostname ~]# ansible 172.16.60.242 -m setup|grep ansible_fqdn
        "ansible_fqdn": "webserver02",

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root
  gather_facts: True

  tasks:
    - name: Host 172.16.60.242 run this task
      debug: 'msg=" {{ ansible_default_ipv4.address }}"'
      when: ansible_default_ipv4.address == "172.16.60.242"

    - name: memtotal < 500M and processor_cores == 2 run this task
      debug: 'msg="{{ ansible_fqdn }}"'
      when: ansible_memtotal_mb < 500 and ansible_processor_cores == 2

    - name: all host run this task
      shell: hostname
      register: info

    - name: Hostname is webserver01 Machie run this task
      debug: 'msg="{{ ansible_fqdn }}"'
      when: info['stdout'] == "webserver01"

    - name: Hostname is startswith l run this task
      debug: 'msg="{{ ansible_fqdn }}"'
      when: info['stdout'].startswith('l')

查看執行結果:
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file

PLAY [kevin_server] ******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241]

TASK [Host 172.16.60.242 run this task] **********************************************************************************************************
skipping: [172.16.60.241] => {}
ok: [172.16.60.242] => {
    "msg": " 172.16.60.242"
}

TASK [memtotal < 500M and processor_cores == 2 run this task] ************************************************************************************
skipping: [172.16.60.241] => {}
skipping: [172.16.60.242] => {}

TASK [all host run this task] ********************************************************************************************************************
changed: [172.16.60.241] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.003661", "end": "2019-10-11 17:19:29.912525", "rc": 0, 
"start": "2019-10-11 17:19:29.908864", "stderr": "", "stderr_lines": [], "stdout": "webserver01", "stdout_lines": ["webserver01"]}
changed: [172.16.60.242] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.004133", "end": "2019-10-11 17:19:29.922962", "rc": 0, 
"start": "2019-10-11 17:19:29.918829", "stderr": "", "stderr_lines": [], "stdout": "webserver02", "stdout_lines": ["webserver02"]}

TASK [Hostname is webserver01 Machie run this task] **********************************************************************************************
ok: [172.16.60.241] => {
    "msg": "k8s-master01"
}
skipping: [172.16.60.242] => {}

TASK [Hostname is startswith l run this task] ****************************************************************************************************
skipping: [172.16.60.241] => {}
skipping: [172.16.60.242] => {}

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=3    changed=1    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0   
172.16.60.242              : ok=3    changed=1    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0 

2)when條件判斷之引用變量
=====================================================================
when變量引用錯誤提示:[WARNING]: when statements should not include jinja2 templating delimiters such as {{ }} or {% %}.

正確的引用方式:將{{}} or {% %} 改為()

錯誤寫法示例:when: ansible_default_ipv4.address == {{ webserver01 }}
正確寫法示例:when: ansible_default_ipv4.address == (webserver01)

[root@hostname ~]# cat /etc/ansible/test.yml                                              
- hosts: kevin_server
  remote_user: root
  gather_facts: True

  tasks:
    - name: Host 192.168.1.101 run this task
      #debug: 'msg=" {{ ansible_default_ipv4.address }}"'
      shell: hostname
      when: ansible_default_ipv4.address == (webserver02)

查看執行結果:
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml -e "webserver02=172.16.60.242" 
Using /etc/ansible/ansible.cfg as config file

PLAY [kevin_server] ******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]
ok: [172.16.60.242]

TASK [Host 192.168.1.101 run this task] **********************************************************************************************************
skipping: [172.16.60.241] => {"changed": false, "skip_reason": "Conditional result was False"}
changed: [172.16.60.242] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.004349", "end": "2019-10-11 17:23:39.961860", "rc": 0, 
"start": "2019-10-11 17:23:39.957511", "stderr": "", "stderr_lines": [], "stdout": "webserver02", "stdout_lines": ["webserver02"]}

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
172.16.60.242              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

3)changed_when:先執行task,並對task返回的值進行判斷,當滿足changed_when指定的條件時說明是執行成功的
=====================================================================
需要注意:默認情況下執行了命令的主機狀態都為changed,本例對輸出進行判斷,包含是某個指定字符才能為changed; 

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root
  gather_facts: True

  tasks:
    - name: all host run this task
      shell: hostname
      register: info
      changed_when: '"webserver01" in info.stdout'

查看執行結果:
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml 
Using /etc/ansible/ansible.cfg as config file

PLAY [kevin_server] ******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.241]
ok: [172.16.60.242]

TASK [all host run this task] ********************************************************************************************************************
changed: [172.16.60.241] => {"changed": true, "cmd": "hostname", "delta": "0:00:00.004531", "end": "2019-10-11 17:25:15.865591", "rc": 0, 
"start": "2019-10-11 17:25:15.861060", "stderr": "", "stderr_lines": [], "stdout": "webserver01", "stdout_lines": ["webserver01"]}
ok: [172.16.60.242] => {"changed": false, "cmd": "hostname", "delta": "0:00:00.004694", "end": "2019-10-11 17:25:15.872135", "rc": 0, 
"start": "2019-10-11 17:25:15.867441", "stderr": "", "stderr_lines": [], "stdout": "webserver02", "stdout_lines": ["webserver02"]}

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
172.16.60.242              : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

4)failed_when
=====================================================================
failed_when:當執行失敗后,會將信息存在register的stderr中,通過判斷指定的字符是否在stderr中來確定是否真的失敗;

[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root
  gather_facts: True

  tasks:
    - name: this command prints FAILED when it fails
      command: echo "FAILED"
      register: command_result
      failed_when: "'FAILED' in command_result.stdout"

    - name: this is a test
      shell: echo "haha"

[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file

PLAY [kevin_server] ******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241]

TASK [this command prints FAILED when it fails] **************************************************************************************************
fatal: [172.16.60.241]: FAILED! => {"changed": true, "cmd": ["echo", "FAILED"], "delta": "0:00:00.002550", "end": "2019-10-11 19:19:47.918921", "failed_when_result": true, "rc": 0, "start": "2019-10-11 19:19:47.916371", "stderr": "", "stderr_lines": [], "stdout": "FAILED", "stdout_lines": ["FAILED"]}
fatal: [172.16.60.242]: FAILED! => {"changed": true, "cmd": ["echo", "FAILED"], "delta": "0:00:00.002410", "end": "2019-10-11 19:19:47.943843", "failed_when_result": true, "rc": 0, "start": "2019-10-11 19:19:47.941433", "stderr": "", "stderr_lines": [], "stdout": "FAILED", "stdout_lines": ["FAILED"]}

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
172.16.60.242              : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0 

可以看出,第一個task任務的failed_when已經滿足了,所以就此停止playbook的運行了,下面的task任務也不會執行了!

failed_when其實是ansible的一種錯誤處理機制,是由fail模塊使用了when條件語句的組合效果。
所以,上面的配置也可以調整成下面寫法(上面第一個task可以調整為下面第1和第2個task的寫法,是一樣的效果):
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root
  gather_facts: True

  tasks:
    - name: this command prints FAILED when it fails
      command: echo "FAILED"
      register: command_result

    - name: fail the play if the previous command did not succeed
      fail: msg="the command failed"
      when: "'FAILED' in command_result.stdout"

    - name: this is a test
      shell: echo "haha"

[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file

PLAY [kevin_server] ******************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241]

TASK [this command prints FAILED when it fails] **************************************************************************************************
changed: [172.16.60.241] => {"changed": true, "cmd": ["echo", "FAILED"], "delta": "0:00:00.003989", "end": "2019-10-11 19:19:06.741840", "rc": 0, "start": "2019-10-11 19:19:06.737851", "stderr": "", "stderr_lines": [], "stdout": "FAILED", "stdout_lines": ["FAILED"]}
changed: [172.16.60.242] => {"changed": true, "cmd": ["echo", "FAILED"], "delta": "0:00:00.003135", "end": "2019-10-11 19:19:06.744136", "rc": 0, "start": "2019-10-11 19:19:06.741001", "stderr": "", "stderr_lines": [], "stdout": "FAILED", "stdout_lines": ["FAILED"]}

TASK [fail the play if the previous command did not succeed] *************************************************************************************
fatal: [172.16.60.241]: FAILED! => {"changed": false, "msg": "the command failed"}
fatal: [172.16.60.242]: FAILED! => {"changed": false, "msg": "the command failed"}

PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=2    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
172.16.60.242              : ok=2    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0 

這里就可以看出"failed_when"的作用,它的作用就是當failed_when關鍵字對應的條件成立時,failed_when會將對應的任務的執行狀態設置為失敗,以停止playbook的運行!
但是需要注意的時:failed_when雖然會將任務的執行狀態設置為失敗,但是它並不代表任務真的失敗了!就以上面例子來說,上面的command模塊的確時完全正常的執行了,
只不過在執行之后,failed_when對應的條件成立了,failed_when將command模塊的執行狀態設置為失敗而已!所以,failed_when並不會影響command模塊的執行過程,
只會在條件成立時影響command模塊最終的執行狀態,以便於停止playbook的運行。

因此需要注意:
failed_when:關鍵字的作用是在條件成立時,將對應任務的執行狀態設置為失敗!
changed_when:關鍵字的作用是在條件成立時,將對應任務的執行狀態設置為changed!

七、Ansible之性能優化  [ 提升ansible執行效率 ]
最初,ansible的執行效率和saltstack(基於zeromq消息隊列的方式)相比要慢的多的多,特別是被控節點量很大的時候。但是ansible發展到現在,它的效率得到了極大的改善。在被控節點不太多的時候,默認的設置已經夠快。即使被控節點數量巨大的時候,也可以通過一些優化去極大的提高ansible的執行效率。所以在使用 Ansible 的過程中,當管理的服務器數量增加時,不得不面對一個無法避免的問題執行效率慢,這里列出一些解決辦法。

1.  關閉gathering facts功能

如果觀察過ansible-playbook的執行過程,就會發現ansible-playbook的第1個步驟總是執行gather facts,不論你有沒有在playbook設定這個tasks。
如果你不需要獲取被控機器的fact數據的話,就可以關閉獲取fact數據功能。關閉之后,可以加快ansible-playbook的執行效率,尤其是你管理很大量的機器時,這非常明顯。
關閉獲取facts很簡單,只需要在playbook文件中加上"gather_facts: False" 或者 "gather_facts: No"即可(False和No都為小寫也可以)。
     
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root
     
  tasks:
    - name: this is a test
      shell: echo "haha"
     
執行這個paly,會發現第一個執行的是gather facts,因為默認是打開gather facts功能的!!!!
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file
     
PLAY [kevin_server] ******************************************************************************************************************************
     
TASK [Gathering Facts] ***************************************************************************************************************************
ok: [172.16.60.242]
ok: [172.16.60.241]
     
TASK [this is a test] ****************************************************************************************************************************
changed: [172.16.60.241] => {"changed": true, "cmd": "echo \"haha\"", "delta": "0:00:00.002949", "end": "2019-10-11 19:33:54.883702", "rc": 0, "start": "2019-10-11 19:33:54.880753", "stderr": "", "stderr_lines": [], "stdout": "haha", "stdout_lines": ["haha"]}
changed: [172.16.60.242] => {"changed": true, "cmd": "echo \"haha\"", "delta": "0:00:00.003409", "end": "2019-10-11 19:33:54.884398", "rc": 0, "start": "2019-10-11 19:33:54.880989", "stderr": "", "stderr_lines": [], "stdout": "haha", "stdout_lines": ["haha"]}
     
PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
172.16.60.242              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
     
現在關閉gathering facts功能
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root
  gather_facts: False
     
  tasks:
    - name: this is a test
      shell: echo "haha"
     
再執行這個play,就會發現沒有了gathering facts執行過程,整個執行速度也快了!
[root@hostname ~]# ansible-playbook -v /etc/ansible/test.yml
Using /etc/ansible/ansible.cfg as config file
     
PLAY [kevin_server] ******************************************************************************************************************************
     
TASK [this is a test] ****************************************************************************************************************************
changed: [172.16.60.242] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "echo \"haha\"", "delta": "0:00:00.002571", "end": "2019-10-11 19:35:06.821842", "rc": 0, "start": "2019-10-11 19:35:06.819271", "stderr": "", "stderr_lines": [], "stdout": "haha", "stdout_lines": ["haha"]}
changed: [172.16.60.241] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "echo \"haha\"", "delta": "0:00:00.003121", "end": "2019-10-11 19:35:06.842207", "rc": 0, "start": "2019-10-11 19:35:06.839086", "stderr": "", "stderr_lines": [], "stdout": "haha", "stdout_lines": ["haha"]}
     
PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
172.16.60.242              : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

2.  開啟 SSH pipelining

pipeline是openssh的一個特性,ssh pipelining 是一個加速Ansible執行速度的簡單方法。
 
在ansible執行每個任務的整個流程中,有一個過程是將臨時任務文件put到遠程的ansible客戶機上,然后通過ssh連接過去遠程執行這個任務。
如果開啟了pipelining,一個任務的所有動作都在一個ssh會話中完成,也會省去sftp到遠端的過程,它會直接將要執行的任務在ssh會話中進行。
 
ssh pipelining 默認是關閉!!!!之所以默認關閉是為了兼容不同的sudo 配置,主要是 requiretty 選項。如果不使用sudo,建議開啟!!!
打開此選項可以減少ansible執行沒有傳輸時ssh在被控機器上執行任務的連接數。
不過,如果使用sudo,必須關閉requiretty選項。修改/etc/ansible/ansible.cfg 文件可以開啟pipelining
    
[root@hostname ~]# vim /etc/ansible/ansible.cfg
........
pipelining = True
    
這樣開啟了pipelining之后, ansible執行的整個流程就少了一個PUT腳本去遠程服務端的流程,然后就可以批量對機器執行命令試下,可以明顯感受到速度的提升。
 
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
但是要注意的是:
如果在ansible中使用sudo命令的話(ssh user@host sudo cmd),需要在被控節點的/etc/sudoers中禁用"requiretty"!!!!
 
之所以要設置/etc/sudoers中的requiretty,是因為ssh遠程執行命令時,它的環境是非登錄式非交互式shell,默認不會分配tty,沒有tty,ssh的sudo就無法關閉密碼回顯(使用
"-tt"選項強制SSH分配tty)。所以出於安全考慮,/etc/sudoers中默認是開啟requiretty的,它要求只有擁有tty的用戶才能使用sudo,也就是說ssh連接過去不允許執行sudo。
可以通過visudo編輯配置文件,注釋該選項來禁用它。
 
[root@webserver01 ~]# grep requiretty /etc/sudoers  
# Defaults    requiretty

3.  開啟SSH長連接 (ControlPersist特性)

ansible天然支持openssh,默認連接方式下,它對ssh的依賴性非常強。所以優化ssh連接,在一定程度上也在優化ansible。其中一點是開啟ssh的長連接,即長時間保持連接狀態。
  
Ansible模式是使用SSH和遠程主機進行通信, 所以Ansible對SSH的依賴性非常強, 在OpenSSH 5.6版本以后SSH就支持了Multiplexing(多路復用)。
所以如果Ansible中控機的SSH -V版本高於5.6時, 就可以使用ControlPersist來提高ssh連接速度,從而提高ansible執行效率。
    
[root@hostname ansible]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
    
[root@hostname ansible]# ssh -V
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017
    
我們可以直接在ansible.cfg文件中設置SSH長連接, 設置參數如下:
[root@hostname ansible]# vim /etc/ansible/ansible.cfg
..........
ssh_args = -C -o ControlMaster=auto -o ControlPersist=5d
    
注意:
ConrolPersist=5d, 這個參數是設置整個長連接保持時間為5天。
  
開啟此參數的ssh長連接功能后,在會話過期前會一直建立連接,在netstat的結果中會看到ssh連接是一直established狀態,且通過SSH連接過的設備都會在當前用戶家目錄的
".ansible/cp"目錄下生成一個socket文件,每個會話對應生成一個socket文件。也可以通過netstat命令查看, 會發現有一個ESTABLISHED狀態的連接一直與遠程設備進行着TCP連接。
    
[root@hostname ansible]# ps -ef|grep ssh|grep ansible
root      5614     1  0 23:09 ?        00:00:00 ssh: /root/.ansible/cp/7e37065045 [mux]
root      5617     1  0 23:09 ?        00:00:00 ssh: /root/.ansible/cp/e2056334cd [mux]
    
[root@hostname ansible]# netstat -anptu|grep ESTABLISHED|grep ssh|grep /root
tcp        0      0 172.16.60.246:44430     172.16.60.242:22        ESTABLISHED 5617/ssh: /root/.an
tcp        0      0 172.16.60.246:43498     172.16.60.241:22        ESTABLISHED 5614/ssh: /root/.an
    
[root@hostname ansible]# ls /root/.ansible/cp/
7e37065045  e2056334cd
    
需要注意:
ControlPersist 特性需要高版本的SSH才支持,CentOS 6默認是不支持的,如果需要使用,需要自行升級openssh(確保SSH -V版本高於5.6)。
ControlPersist即持久化socket,一次驗證,多次通信。並且只需要修改 ssh 客戶端就行,也就是 Ansible 機器即可。

4.  開啟accelerate模式  [ 注意:這個只針對centos6系統 ] 

Ansible還有一個accelerate模式, 這和前面的Multiplexing有點類似, 因為都依賴Ansible中控機跟遠程機器有一個長連接。
但是accelerate是使用python程序在遠程機器上運行一個守護進程, 然后Ansible會通過這個守護進程監聽的端口進行通信。
開啟accelerate模式很簡單, 只要在playbook中配置accelerate: true即可.
    
但是需要注意的是:
如果開啟accelerate模式, 則需要在Ansible中控機與遠程機器都安裝python-keyczar軟件包。
下面是在ansible.cfg文件中定義一些accelerate參數, 當然也可以在寫playbook的時候再定義
    
第一步:ansible服務端和客戶端都要安裝python-keyczar
[root@hostname ~]# yum install -y python-keyczar
    
第二步:修改ansible服務端的ansible.cfg文件
[root@hostname ~]# vim /etc/ansible/ansible.cfg
..........
[accelerate]
accelerate_port = 5099
accelerate_timeout = 30
accelerate_connect_timeout = 5.0
    
第三步:修改ansible服務端的ansible-playbook的劇本文件,加入 accelerate: true
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root
  gather_facts: False
  accelerate: true
     
  tasks:
    - name: this is a test
      shell: echo "haha"
    
需要注意:
這種優化方式只針對centos6系統來提高連接速度。在centos7下不可用,否則會報錯:"ERROR! 'accelerate' is not a valid attribute for a Play"
如果ansible沒有性能瓶頸的情況下,不推薦使用這種優化措施!

5.  設置facts緩存

如果細心的話, 就會發現執行playbook的時候, 默認第一個task都是GATHERING FACTS, 這個過程就是Ansible在收集每台主機的facts信息。
方便我們在playbook中直接飲用facts里的信息,當然如果你的playbook中不需要facts信息, 可以在playbook中設置"gather_facts: False"來提高playbook效率.
   
但是如果我們既想在每次執行playbook的時候都能收集facts, 又想加速這個收集過程, 那么就需要配置facts緩存了。
目前Ansible支持使用json文件存儲facts信息。
   
第一種緩存方式:使用json文件緩存
[root@hostname ~]# vim /etc/ansible/ansible.cfg
.........
gathering = smart
fact_caching_timeout = 86400
fact_caching = jsonfile
fact_caching_connection = /dev/shm/ansible_fact_cache
   
正常配置palybook,不需要關閉gathering facts功能
[root@hostname ~]# cat /etc/ansible/test.yml
- hosts: kevin_server
  remote_user: root
    
  tasks:
    - name: this is a test
      shell: echo "haha"
   
查看這個playbook過程,用時1.102s(第一次可能稍微慢點,緩存之后,后面執行就很快了)
[root@hostname ~]# time ansible-playbook /etc/ansible/test.yml
   
PLAY [kevin_server] ******************************************************************************************************************************
   
TASK [this is a test] ****************************************************************************************************************************
changed: [172.16.60.241]
changed: [172.16.60.242]
   
PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
172.16.60.242              : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
   
   
real    0m1.102s
user    0m0.879s
sys     0m0.179s
   
如果去掉上面的facts緩存的四行配置,再次執行上面的playbok,發現用時10s左右!!!
   
查看緩存文件:
[root@hostname ~]# ls /dev/shm/ansible_fact_cache/
172.16.60.241  172.16.60.242
   
第二種緩存方式:使用redis存儲facts文件需安裝redis,還需要安裝python庫
[root@hostname ~]# yum install redis
   
[root@hostname ~]# yum -y install epel-release
[root@hostname ~]# yum install python-pip
[root@hostname ~]# pip install redis
   
[root@hostname ~]# vim /etc/ansible/ansible.cfg
........
gathering = smart
facts_caching_timeout = 86400      #設置緩存過期時間86400秒
facts_caching = redis              # 使用redis或者 (或者使用memcached,即"facts_caching = memcached")
fact_caching_connection = 127.0.0.1:6379
#若redis設置了密碼,比如密碼為"admin",則配置修改如下:
# fact_caching_connection = localhost:6379:0:admin
   
啟動redis
[root@hostname ~]# systemctl start redis
[root@hostname ~]# lsof -i:6379      
COMMAND     PID  USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
redis-ser 29218 redis    4u  IPv4 291786209      0t0  TCP localhost:6379 (LISTEN)
   
執行上面的palybook
[root@hostname ~]# time ansible-playbook /etc/ansible/test.yml
   
PLAY [kevin_server] ******************************************************************************************************************************
   
TASK [this is a test] ****************************************************************************************************************************
changed: [172.16.60.241]
changed: [172.16.60.242]
   
PLAY RECAP ***************************************************************************************************************************************
172.16.60.241              : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
172.16.60.242              : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
   
   
real    0m1.132s
user    0m0.909s
sys     0m0.178s
   
需要注意:
在使用redis緩存后,如果出現異常(若未出現,請忽略):TypeError: the JSON object must be str, not 'bytes'。
解決辦法:
[root@hostname ~]# find / -name ansible
[root@hostname ~]# vim /usr/lib/python2.7/site-packages/ansible/plugins/cache/redis.py
..........
self._cache[key] = json.loads(value.decode('utf-8'))       #修改為這個
   
查看redis存儲情況
[root@hostname ~]# redis-cli
127.0.0.1:6379> keys *
1) "ansible_facts172.16.60.242"
2) "ansible_facts172.16.60.241"
3) "ansible_cache_keys"
   
總之:不同網絡環境下的耗時肯定是不同的,但是設置緩存是肯定可以加快 Ansible 運行速度的,特別是 playbook 的運行。

6.  Ansible取消交互

[root@hostname ~]# vim /etc/ansible/ansible.cfg
........
host_key_checking = False          # 打開注釋即可
   
取消ssh的yes和no的交互:
[root@hostname ~]# vim /root/.ssh/config
UserKnownHostsFile /dev/null
ConnectTimeout 15
StrictHostKeyChecking no
   
或者直接ssh時增加一個參數
[root@hostname ~]# ssh -o StrictHostKeyChecking=no -p22 root@172.16.60.247

7.  Ansible的-t選項,提高ansible執行效率

ansible的"-t"或"--tree"選項是將ansible的執行結果按主機名保存在指定目錄下的文件中。
  
有些時候,ansible執行起來的速度會非常慢,這種慢體現在即使執行的是一個立即返回的簡單命令(如ping模塊),也會耗時很久,且不是因為ssh連接慢導致的。
如果使用-t選項,將第一次執行得到的結果按inventory中定義的主機名保存在文件中,下次執行到同一台主機時速度將會變快很多,即使之后不再加上-t選項,
也可以在一定時間內保持迅速執行。即使執行速度正常(如執行一個Ping命令0.7秒左右),使用-t選項也可以在此基礎上變得更快。
  
除了使用-t選項,使用重定向將結果重定向到某個文件中也是一樣的效果。
這也算是一種ansible提速方式,但在centos6上使用低版本ansible時,有時會出現執行很慢的現象,但不是每次都這樣,且centos7執行速度正常
所以這也是一種"bug"式問題,故這種方式沒有通用性。
  
[root@hostname ~]# time ansible kevin_server -m command -a "hostname"
[root@hostname ~]# time ansible kevin_server -m command -a "hostname" -t /tmp/test
  
[root@hostname ~]# ll /tmp/a
total 8
-rw-r--r-- 1 root root 2780 Oct 12 02:03 172.16.60.241
-rw-r--r-- 1 root root 2776 Oct 12 02:03 172.16.60.242
  
上面做了對比,發現使用-t或重定向方式,將ansible的執行結果按主機名保存在指定目錄下的文件中,ansible執行效率會有所提升。

八、Ansible之變量設置
首先Ansible通過facts組件來收集被管理節點信息,facts收集的信息是json格式的,其中任一項都可以當作變量被直接引用,如在ansible-playbook、jinja2模板中引用。

1.  Ansible facts
facts組件是用來收集被管理節點信息的,使用setup模塊可以獲取這些信息。

[root@ss-server ~]# ansible-doc -s setup
- name: Gathers facts about remote hosts
  setup:
  ..................

如下示例,是setup收集客戶機172.16.60.21的信息示例,由於收集的信息項非常多,這里只是截取了部分內容項。

[root@ss-server ~]# ansible 172.16.60.21 -m setup
172.16.60.21 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "172.16.60.21"
        ], 
        "ansible_all_ipv6_addresses": [
            "fe80::20c:29ff:fe03:a452"
        ], 
        "ansible_apparmor": {
            "status": "disabled"
        }, 
        "ansible_architecture": "x86_64", 
        "ansible_bios_date": "07/02/2019", 
        "ansible_bios_version": "6.00", 
        "ansible_cmdline": {
            "BOOT_IMAGE": "/vmkivin-3.10.0-327.el7.x86_64", 
            "LANG": "en_US.UTF-8", 
            "biosdevname": "0", 
            "crashkernel": "auto", 
            "net.ifnames": "0", 
            "quiet": true, 
            "ro": true, 
            "root": "UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8"
        }, 
........................................
        "ansible_default_ipv6": {}, 
        "ansible_devices": {
            "sda": {
                "holders": [], 
                "host": "SCSI storage controller: LSI Logic / Symbios Logic 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI (rev 01)", 
                "model": "VMware Virtual S", 
                "partitions": {
                    "sda1": {
                        "holders": [], 
                        "sectors": "512000", 
                        "sectorsize": 512, 
                        "size": "250.00 MB", 
                        "start": "2048", 
                        "uuid": "367d6a77-033b-4037-bbcb-416705ead095"
                    }, 
                    "sda2": {
                        "holders": [], 
                        "sectors": "37332992", 
                        "sectorsize": 512, 
                        "size": "17.80 GB", 
                        "start": "514048", 
                        "uuid": "b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8"
                    }, 
................................
        "ansible_user_dir": "/root", 
        "ansible_user_gecos": "root", 
        "ansible_user_gid": 0, 
        "ansible_user_id": "root", 
        "ansible_user_shell": "/bin/bash", 
        "ansible_user_uid": 0, 
        "ansible_userspace_architecture": "x86_64", 
        "ansible_userspace_bits": "64", 
        "ansible_virtualization_role": "guest", 
        "ansible_virtualization_type": "VMware", 
        "module_setup": true
    }, 
    "changed": false
}

使用filter可以篩選指定的facts信息,如下示例:

[root@ss-server ~]# ansible 172.16.60.21 -m setup -a "filter=changed"
172.16.60.21 | SUCCESS => {
    "ansible_facts": {}, 
    "changed": false
}

[root@ss-server ~]# ansible localhost -m setup -a "filter=*ipv4"
localhost | SUCCESS => {
    "ansible_facts": {
        "ansible_default_ipv4": {
            "address": "172.16.60.20", 
            "alias": "eth0", 
            "broadcast": "172.16.60.255", 
            "gateway": "172.16.60.2", 
            "interface": "eth0", 
            "macaddress": "00:0c:29:d9:0b:71", 
            "mtu": 1500, 
            "netmask": "255.255.255.0", 
            "network": "172.16.60.0", 
            "type": "ether"
        }
    }, 
    "changed": false
}

總之,facts收集的信息是json格式的,其里面的任一項都可以在playbook、jinja2模板中當作變量被直接引用。

2.  變量引用json數據的方式
在ansible中,任何一個模塊都會返回json格式的數據,即使是錯誤信息都是json格式的。在ansible中,json格式的數據,其中每一項都可以通過變量來引用它。當然,引用的前提是先將其注冊為變量。例如下面的playbook是將shell模塊中echo命令的結果注冊為變量,並使用debug模塊輸出。

---
- hosts: 172.16.60.21
  tasks:
    - shell: echo good work
      register: kevin_bo
    - debug: var=kevin_bo

debug輸出的結果如下:

TASK [debug] *********************************************
ok: [172.16.60.21] => {
    "kevin_bo": {
        "changed": true, 
        "cmd": "echo good work", 
        "delta": "0:00:00.002086", 
        "end": "2019-05-20 11:23:40.484507", 
        "rc": 0, 
        "start": "2019-05-20 11:23:40.482421", 
        "stderr": "", 
        "stderr_lines": [], 
        "stdout": "good work", 
        "stdout_lines": [
            "good work"
        ]
    }
}

可以看出,結果是一段json格式的數據,最頂端的key為kevin_bo,內部是一大段的字典(即使用大括號包圍的),其中的stdout_lines還包含了一個json數組,也就是所謂的yaml列表項(即使用中括號包圍的)。

2.1  引用json字典數據的方式
如果想要輸出json數據的某一字典項,則應該使用"key.dict"或"key['dict']"的方式引用。例如最常見的stdout項"good work"是想要輸出的項,以下兩種方式都能引用該字典變量。

---
- hosts: 172.16.60.21
  tasks:
    - shell: echo good work
      register: kevin_bo
    - debug: var=kevin_bo.stdout
    - debug: var=kevin_bo['stdout']

ansible-playbook的部分輸出結果如下:

TASK [debug] ************************************************
ok: [172.16.60.21] => {
    "kevin_bo.stdout": "good work"
}

TASK [debug] ************************************************
ok: [172.16.60.21] => {
    "kevin_bo['stdout']": "good work"
}

"key.dict"或"key['dict']"的方式都能引用,但在dict字符串本身就包含"."的時候,應該使用中括號的方式引用。例如:

anykey['172.16.60.21']

2.2  引用json數組數據的方式
如果想要輸出json數據中的某一數組項(列表項),則應該使用"key[N]"的方式引用數組中的第N項,其中N是數組的index,從0開始計算。如果不使用index,則輸出的是整個數組列表例如想要輸出上面的stdout_lines中的"good work",它是數組stdout_lines中第一項所以使用stdout_lines[0]來引用,再加上stdout_lines上面的kevin_bo,於是引用方式如下:

---
- hosts: 172.16.60.21
  tasks:
    - shell: echo good work
      register: kevin_bo
    - debug: var=kevin_bo.stdout_lines[0]

由於stdout_lines中僅有一項,所以即使不使用index的方式即kevin_bo.stdout_lines也能得到期望的結果。輸出結果如下:

TASK [debug] *************************************************
ok: [172.16.60.21] => {
    "kevin_bo.stdout_lines[0]": "good work"
}

接着看下面一段json數據:

"ipv6": [
   {
       "address": "fe80::20c:29ff:fe26:1498", 
       "prefix": "64", 
       "scope": "link"
   }
]

其中key=ipv6,其內部有且僅有是一個列表項,但該列表內包含了數個字典項。要引用列表內的字典,例如上面的address項。應該如下引用:

ipv6[0].address

2.3  引用facts數據
如上介紹已經可以了解json數據中的字典和列表項的引用方式,顯然facts中的一大堆數據就能被引用並派上用場了。例如以下是一段facts數據。

[root@ss-server ~]# ansible localhost -m setup -a "filter=*eth*"    
localhost | SUCCESS => {
    "ansible_facts": {
        "ansible_eth0": {
            "active": true, 
            "device": "eth0", 
            "features": {
                "busy_poll": "off [fixed]", 
                "fcoe_mtu": "off [fixed]", 
                "generic_receive_offload": "on", 
                .........................
            }, 
            "ipv4": {
                "address": "172.16.60.18", 
                "broadcast": "172.16.60.255", 
                "netmask": "255.255.255.0", 
                "network": "172.16.60.0"
            }, 
            "macaddress": "00:0c:29:d9:0b:71", 
            "module": "e1000", 
             ............................
        }
    }, 
    "changed": false
}

顯然,facts數據的頂級key為ansible_facts,在引用時應該將其包含在變量表達式中。但自動收集的facts比較特殊,它以ansible_facts作為key,ansible每次收集后會自動將其注冊為變量,所以facts中的數據都可以直接通過變量引用,甚至連頂級key即ansible_facts都要省略。

例如引用上面的ipv4的地址address項,需要寫成:

ansible_eth0.ipv4.address

而不能寫成:

ansible_facts.ansible_eth0.ipv4.address

但其他任意時候,都應該帶上所有的key!

3.  設置本地facts
在ansible收集facts時,還會自動收集/etc/ansible/facts.d/*.fact文件內的數據到facts中,且以ansible_local做為key目前fact支持兩種類型的文件:ini和json。當然,如果fact文件的json或ini格式寫錯了導致無法解析,那么肯定也無法收集。例如,在/etc/ansible/facts.d目錄下存在一個kevin.fact的文件,其內數據如下:

[root@ss-server ~]# cat /etc/ansible/facts.d/kevin.fact 
{
    "member": {
        "wangbo": {
            "address": "anhui",
            "age": "31"
        },
        "liuxiaoru": {
            "address": "hebei",
            "age": "28"
        }
    }
}

ansible收集facts后的本地facts數據如下:

[root@ss-server ~]# ansible localhost -m setup -a "filter=ansible_local"
localhost | SUCCESS => {
    "ansible_facts": {
        "ansible_local": {
            "kevin": {
                "member": {
                    "wangbo": {
                        "age": "31", 
                        "address": "anhui"
                    }, 
                    "liuxiaoru": {
                        "age": "28", 
                        "address": "hebei"
                    }
                }
            }
        }
    }, 
    "changed": false
}

可見,如果想要引用本地文件中的某個key,除了帶上ansible_local外,還必須得帶上fact文件的文件名。例如,引用father的name。

ansible_local.kevin.member.wangbo.address

4.  輸出和引用變量
上面已經展示了一種變量的引用方式:使用debug的var參數。debug的另一個參數msg也能輸出變量,且msg可以輸出自定義信息,而var參數只能輸出變量。另外,msg和var引用參數的方式有所不同。例如:

---
- hosts: 172.16.60.21
  tasks:
    - debug: 'msg="ipv4 address: {{ansible_eth0.ipv4.address}}"'
    - debug: var=ansible_eth0.ipv4.address

msg引用變量需要加上雙大括號包圍,既然加了大括號,為了防止被解析為內聯字典,還得加引號包圍。這里使用了兩段引號,因為其內部還包括了一個": ",加引號可以防止它被解析為"key: "的格式var參數引用變量則直接指定變量名。這就像bash中引用變量的方式是一樣的,有些時候需要加上$,有些時候不能加$。即當引用的是變量的值的時候,msg就需要加雙大括號,就像加$一樣,而當引用的是變量本身的時候,則msg不能加雙大括號。其實雙大括號是jinja2中的分隔符。執行的部分結果如下:

TASK [debug] *****************************************************
ok: [172.16.60.21] => {
    "msg": "ipv4 address: 172.16.60.21"
}

TASK [debug] *****************************************************
ok: [172.16.60.21] => {
    "ansible_eth0.ipv4.address": "172.16.60.21"
}

在Ansible使用中,幾乎所有地方都可以引用變量,例如循環、when語句、信息輸出語句、template文件等等。只不過有些地方不能使用雙大括號,有些地方需要使用。

5.  注冊和定義變量的各種方式
Ansible中定義變量的方式有很多種,大致有下面七種:1) 將模塊的執行結果注冊為變量;2) 直接定義字典類型的變量;3) role中文件內定義變量;4) 命令行傳遞變量;5) 借助with_items迭代將多個task的結果賦值給一個變量;6) inventory中的主機或主機組變量;7) 內置變量。

5.1  register注冊變量
使用register選項,可以將當前task的輸出結果賦值給一個變量。例如,下面的示例中將echo的結果"good work"賦值給kevin_bo變量。注意,模塊的輸出結果是json格式的,所以,引用變量時要指定引用的對象。

---
- hosts: localhost
  tasks: 
    - shell: echo good work
      register: kevin_bo
    - debug: var=kevin_bo.stdout

5.2  set_fact定義變量
set_fact和register的功能很相似,也是將值賦值給變量。它更像shell中變量的賦值方式,可以將某個變量的值賦值給另一個變量,也可以將字符串賦值給變量。例如:

---
- hosts: 172.16.60.21
  tasks:
    - shell: echo good work
      register: kevin_bo
    - set_fact: var1="{{kevin_bo.stdout}}"
    - set_fact: var2="your name is"
    - debug: msg="{{var2}} {{var1}}"

5.3  vars定義變量
可以在play或task層次使用vars定義字典型變量。如果同名,則task層次的變量覆蓋play層次的變量。例如:

---
- hosts: localhost
  vars: 
    var1: anhui
    var2: beijing
  tasks: 
    - debug: msg="{{var1}} {{var2}}"
      vars: 
        var2: shanghai

輸出結果為:

TASK [debug] ********************************************
ok: [localhost] => {
    "msg": "anhui shanghai"
}

5.4  vars_files定義變量
和vars一樣,只不過它是將變量以字典格式定義在獨立的文件中,且vars_files不能定義在task層次,只能定義在play層次。

---
- hosts: localhost
  vars_files: 
    - /tmp/var_file1.yml
    - var_file2.yml
  tasks: 
    - debug: msg="{{var1}} {{var2}}"

上面var_file2.yml使用的是相對路徑,基於playbook所在的路徑。例如該playbook為/tmp/x.yml,則var_file2.yml也應該在/tmp下。當然,完全可以使用絕對路徑。

5.5  roles中的變量
由於role是整合playbook的,它有默認的文件組織結構。其中有一個目錄vars,其內部的main.yml用於定義變量。還有defaults目錄內的main.yml則是定義role默認變量的,默認變量的優先級最低

[root@ss-server ~]# tree /yaml
/yaml
├── roles
│   └── nginx
│       ├── defaults
│           └── main.yml      
│       ├── files
│       ├── handlers
│       ├── meta
│       ├── tasks
│       ├── templates
│       └── vars
│           └── main.yml
└── site.yml

main.yml中變量定義方式也是字典格式,例如:

---
mysql_port: 3306

5.6  命令行傳遞變量
ansible和ansible-playbook命令的"-e"選項都可以傳遞變量,傳遞的方式有兩種:-e key=value 和 -e @var_file。注意:當key=value方式傳遞變量時,如果變量中包含特殊字符,必須防止其被shell解析。例如:

ansible localhost -m shell -a "echo {{kevin_bo}}" -e 'kevin_bo="good work"'
ansible localhost -m shell -a "echo {{kevin_bo}}" -e @/tmp/var_file1.yml

其中/tmp/var_file1.yml中的內容如下:

---
kevin_bo: good work

5.7  借助with_items疊加變量
ansible中可以借助with_items實現列表迭代的功能,作用於變量注冊的行為上,就可以實現將多個結果賦值給同一個變量。例如下面的playbook中,給出了3個item列表,並在shell模塊中通過固定變量"{{item}}"分別迭代,第一次迭代的是anhui,第二次迭代的是beijing,第三次迭代的是shanghai,也就實現了3次循環。最后,將結果注冊為變量kevin_bo。

---
- hosts: localhost
  remote_user: root
  tasks:
    - name: test 
      shell: echo "{{item}}"
      with_items:
        - anhui
        - beijing
        - shanghai
      register: kevin_bo
    - debug: var=kevin_bo.results[0].stdout
    - debug: var=kevin_bo.results[1].stdout
    - debug: var=kevin_bo.results[2].stdout

每次迭代的過程中,調用item的模塊都會將結果保存在一個key為results的數組中。因此,引用迭代后注冊的變量時,需要在變量名中加上results,並指定數組名。例如上面的kevin_bo.results[N].stdout。此外,還可以使用for循環遍歷列表。例如:

- debug: msg="{% for i in kevin_bo.results %} {{i.stdout}} {% endfor %}"

其實,看一下kevin_bo的輸出就很容易理解了。以下是kevin_bo的第一個列表的輸出。

"kevin_bo": {
    "changed": true, 
    "msg": "All items completed", 
    "results": [
        {
            "_ansible_item_result": true, 
            "_ansible_no_log": false, 
            "_ansible_parsed": true, 
            "changed": true, 
            "cmd": "echo \"anhui\"", 
            "delta": "0:00:00.001942", 
            "end": "2019-05-12 04:45:57.032946", 
            "invocation": {
                "module_args": {
                    "_raw_params": "echo \"anhui\"", 
                    "_uses_shell": true, 
                    "chdir": null, 
                    "creates": null, 
                    "executable": null, 
                    "removes": null, 
                    "warn": true
                }
            }, 
            "item": "anhui", 
            "rc": 0, 
            "start": "2019-05-12 04:45:57.031004", 
            "stderr": "", 
            "stderr_lines": [], 
            "stdout": "anhui", 
            "stdout_lines": [
                "anhui"
            ]
        }

5.8  inventory中主機變量和主機組變量
在inventory文件中可以為主機和主機組定義變量,不僅包括內置變量賦值,還包括自定義變量賦值。例如以下inventory文件(/root/ansible/test.yml)。

172.16.60.20 ansible_ssh_port=22 var1=1        #變量優先級別排第二
[webserver]              #變量優先級別排第一
172.16.60.18
172.16.60.19
172.16.60.20 var1=2      
[webserver:vars]         #變量優先級別排第三
var1=2.2
var2=3
[all:vars]               #變量優先級別排第四
var2=4

其中ansible_ssh_port是主機內置變量,為其賦值22,這類變量是內置類變量。此外還在多處為主機172.16.60.20進行了賦值。其中[webserver:vars]和[all:vars]表示為主機組賦值,前者是為webserver這個組賦值,后者是為所有組賦值。以下是執行語句:

[root@ss-server ~]# ansible 172.16.60.20 -i /root/ansible/test.yml -m shell -a 'echo "{{var1}} {{var2}} {{ansible_ssh_port}}"'
172.16.60.20 | SUCCESS | rc=0 >>
2 3 22

從結果可知:主機組里面的主機變量優先級高於無主機組的主機變量設定的主機組變量優先級高於all特殊組。除了在inventory文件中定義主機、主機組變量,還可以將其定義在host_vars和group_vars目錄下的獨立的文件中,但要求這些host_vars或group_vars這兩個目錄和inventory文件或playbook文件在同一個目錄下,且變量的文件以對應的主機名或主機組名命名例如,inventory文件路徑為/etc/ansible/hosts,playbook文件路徑為/root/ansible/play.yml,則主機172.16.60.20和主機組webserver的變量文件路徑可以為以下幾種:

/etc/ansible/host_vars/172.16.60.20
/etc/ansible/group_vars/webserver
/root/ansible/host_vars/172.16.60.20
/root/ansible/group_vars/webserver

如下是一個host_vars目錄下的文件內容:

[root@ss-server ~]# cat /root/ansible/host_vars/172.16.60.20
var1: 1.1
var2: 2.2
var3: 3.3
var4: 4.4

以下為/root/ansible/play.yml的內容

---
- hosts: 172.16.60.20
  tasks:
    - debug: msg='{{var1}} {{var2}} {{var3}} {{var4}}'

執行結果如下:

TASK [debug] **********************************************
ok: [172.16.60.20] => {
    "msg": "1.1 2.2 3.3 4.4"
}

5.9  內置變量
ansible除了inventory中內置的一堆不可被引用的設置類變量,還有幾個全局都可以引用的內置變量,主要有以下幾個:inventory_hostname、inventory_hostname_short、groups、group_names、hostvars、play_hosts、inventory_dir和ansible_version

1)inventory_hostname inventory_hostname_short
分別代表的是inventory中被控節點的主機名主機名的第一部分,如果定義的是主機別名,則變量的值也是別名。例如inventory中webserver主機組定義為如下:

[webserver]
172.16.60.20
web01 ansible_ssh_host=172.16.60.21
www.kevin.com ansible_ssh_host=172.16.60.22

分別輸出它們的inventory_hostname和inventory_hostname_short。

[root@ss-server ~]# ansible webserver -m debug -a 'msg="{{inventory_hostname}} & {{inventory_hostname_short}}"'
172.16.60.20 | SUCCESS => {
    "msg": "172.16.60.20 & 172"
}
web01 | SUCCESS => {
    "msg": "web01 & web01"
}
www.kevin.com | SUCCESS => {
    "msg": "www.kevin.com & www"
}

2)groups group_names
group_names返回的是主機所屬主機組,如果該主機在多個組中,則返回多個組,如果它不在組中,則返回ungrouped這個特殊組。例如,某個inventory文件如下:

172.16.60.20
172.16.60.21
172.16.60.22
172.16.60.23 
[webserver]
172.16.60.20
[dbserver]
172.16.60.21
db01 ansible_ssh_host=172.16.60.22
www.kevin.com ansible_ssh_host=172.16.60.23
[server:children]
webserver
dbserver

其中172.16.60.20定義在webserver和server中,所以返回這兩個組。同理172.16.60.21返回dbserver和server。172.16.60.22和172.16.60.23則返回ungrouped,雖然它們在dbserver中都定義了別名,但至少將172.16.60.22和172.16.60.23作為主機名時,它們是不在任何主機中的。另一方面,db01和www.kevin.com這兩個別名主機都返回dbserver和server兩個組。groups變量則是返回其所在inventory文件中所有組和其內主機名注意:該變量對每個控制節點都返回一次,所以返回的內容可能非常多。例如,上面的inventory中,如果指定被控節點為dbserver,則會重復返回3次(因為有3台被控主機)該inventory文件。其中的第三台主機www.kevin.com的返回結果為:

www.kevin.com | SUCCESS => {
    "msg": {
        "all": [
            "172.16.60.20", 
            "172.16.60.21", 
            "172.16.60.22", 
            "172.16.60.23", 
            "db01", 
            "www.kevin.com"
        ], 
        "server": [
            "172.16.60.20", 
            "172.16.60.21", 
            "db01", 
            "www.kevin.com"
        ], 
        "webserver": [
            "172.16.60.20"
        ], 
        "dbserver": [
            "172.16.60.21", 
            "db01", 
            "www.kevin.com"
        ], 
        "ungrouped": [
            "172.16.60.20", 
            "172.16.60.21", 
            "172.16.60.22", 
            "172.16.60.23"
        ]
    }
}

3)hostvars
該變量用於引用其他主機上收集的facts中的數據,或者引用其他主機的主機變量、主機組變量其key為主機名或主機組名。舉個例子,假如使用ansible部署一台nginx服務器web01,且配置文件內需要指向另一台數據庫服務器web02的ip地址ip2,可以直接在配置文件中指定ip2,但也可以在模板配置文件中直接引用host2收集的facts數據中的ansible_eth0.ipv4.address變量。例如,dbserver主機組中包含了172.16.60.[21:23]共3台主機。playbook內容如下:

---
- hosts: dbserver
  tasks:
    - debug: msg="{{hostvars['172.16.60.21'].ansible_eth0.ipv4.address}}"

執行結果如下:

TASK [debug] *********************************************************
ok: [172.16.60.21] => {
    "msg": "172.16.60.21"
}
ok: [172.16.60.22] => {
    "msg": "172.16.60.21"
}
ok: [172.16.60.23] => {
    "msg": "172.16.60.21"
}

但是請注意:在引用其他主機facts中數據時,要求被引用主機進行了facts收集動作,或者有facts緩存。否則都沒收集,當然無法引用其facts數據。也就是說,當被引用主機沒有facts緩存時,ansible的控制節點中必須同時包含引用主機和被引用主機。除了引用其他主機的facts數據,還可以引用其他主機的主機變量和主機組變量,且不要求被引用主機有facts數據,因為主機變量和主機組變量是在ansible執行任務前加載的。例如,inventory中格式如下:

172.16.60.18
[dbserver]
172.16.60.21 var1=1.1
172.16.60.22
172.16.60.23
[dbserver:vars]
var2=2.2

playbook內容如下:

---
- hosts: 172.16.60.18
  tasks:
    - debug: msg="{{hostvars['172.16.60.21'].var1}} & {{hostvars['172.16.60.23'].var2}}"

執行結果如下:

TASK [debug] ***************************************
ok: [172.16.60.18] => {
    "msg": "1.1 & 2.2"
}

4)play_hosts inventory_dir
play_hosts代表的是當前play所涉及inventory內的所有主機名列表。inventory_dir是所使用inventory所在的目錄。例如,inventory內容為:

172.16.60.18
[webserver]
172.16.60.21
172.16.60.22
[dbserver]
172.16.60.24
172.16.60.25

那么,該inventory內的任意一或多台主機作為ansible或ansible-playbook的被控節點時,都會返回整個inventory內的所有主機名稱。

5)ansible_version
代表的是ansible軟件的版本號。變量返回的內容如下:

{
      "full": "2.3.1.0", 
      "major": 2, 
      "minor": 3, 
      "revision": 1, 
      "string": "2.3.1.0"
    }

#####  ansibl變量的優先級  #####
1.  extra vars變量(在命令行中使用 -e);優先級最高
2.  在inventory中定義的連接變量(比如ansible_ssh_user);優先級第二
3.  大多數的其他變量(命令行轉換,play中的變量,include的變量,role的變量等);優先級第三
4.  在inventory定義的其他變量;優先級第四
5.  有系統發現的facts;優先級第五
6.  "role默認變量",這個是最默認的值,很容易喪失優先權。優先級最小。

##### inventory清單列表里定義變量:單個主機定義的變量優先級高於主機組定義的變量 #####
經過實驗,ansible使用inventory定義變量的優先級順序從高到低為:
1.  host_vars下定義變量
2. inventory中單個主機定義變量
3. group_vars下定義變量
4. inventory中組定義變量

總之,ansible提供的變量定義方式真的是太豐富了,這些變量可以讓ansible使用起來十分靈活,功能強大!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM