Ansible:更快點,執行過程分析、非同步、效率最佳化【轉】

paul_hch發表於2024-04-17

Ansible你快點:Ansible執行過程分析、非同步、效率最佳化

Ansible雖然方便,但有個"為人詬病"的問題:任務執行速度太慢了,在有大量任務、大量迴圈任務時,其速度之慢真的是會讓人等到崩潰的。

Ansible官方給了一些最佳化選項供使用者選擇,還可以去網上尋找最佳化Ansible相關的外掛。但在調優Ansible之前,應當先去理解Ansible的執行流程,如此才能知道為什麼速度慢、要如何調優以及調優後會造成什麼後果。此外,還應學會測量任務的執行速度

此外,本文還會回顧部分Ansible執行策略,更詳細的執行策略說明,可複習第十章的"理解Ansible執行策略"部分。

11.1 測量任務執行速度:profile_tasks外掛

Ansible官方提供了幾個可用於計時的回撥外掛:

  • (1).profile_tasks:該回撥外掛用於計時每個任務的執行時長

  • (2).profile_roles外掛用於計時每個Role的執行時長

  • (3).timer外掛用於計時每個play執行時長

要使用這些外掛,需要在ansible.cfg配置檔案中的callback_whitelist中加入各外掛。如下:

1
2
3
[defaults]
callback_whitelist = profile_tasks
# callback_whitelist = profile_tasks, profile_roles, timer

上面我只開啟了profile_tasks外掛。

這些回撥外掛會將對應的計時資訊輸出,透過觀察這些計時資訊,便可以知道任務執行消耗了多長時間,並多次比對計時資訊,從而可確定哪種方式更高效。

然後執行幾個任務看看輸出結果,如下playbook檔案內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
- name: test for timer
hosts: timer
gather_facts: no
tasks:
- name: only one debug
debug:
var: inventory_hostname

- name: shell
shell:
cp /etc/fstab /tmp/
loop: "{{ range(0, 100)|list }}"

- name: scp
copy:
src: /etc/hosts
dest: /tmp/
loop: "{{ range(0, 100)|list }}"

其中timer主機組有三個節點,所以整個playbook中,每個節點執行201次任務,總共執行603次任務。以下是開啟profile_tasks後在螢幕中輸出的計時資訊:

1
2
3
4
5
6
7
8
$ ansible-playbook -i timer.host timer.yml
...................
......省略輸出......
...................
=========================================
scp ------------------------------------ 57.96s
shell ---------------------------------- 42.78s
only one debug ------------------------- 0.07s

從結果中可看到,3個節點的debug任務總共花費0.07秒,3個節點的shell任務總共300次任務花費42.78秒,3個節點的scp任務總共300次任務花費57.96秒。

11.2 Ansible執行流程分析

ansible命令或ansible-playbook命令加上-vvv選項,會輸出很多除錯資訊,包括建立的連線、傳送的檔案等等。

例如,下面是Ansible 2.9預設配置中執行每單個任務都涉及到的步驟,其中我省略了大量資訊以便各位能夠看懂關鍵步驟。各位可自行加上-vvv去執行一個任務並觀察輸出資訊,同時可與我所做的註釋做比較。

需注意:不同版本的Ansible為每個任務建立的連線數量不同,Ansible 2.9為每個任務建立7次ssh連線。有的資料或書籍中介紹時說只建立二次、三次、四次ssh連線都是有可能的,版本不同確實是有區別的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 1.第一個連線:獲取使用者家目錄,此處為/root
<node1> ESTABLISH SSH CONNECTION FOR USER: None
<node1> SSH: EXEC ssh -vvv ....... '/bin/sh -c '"'"'echo ~ && sleep 0'"'"''
<node1> (0, '/root\n', ......)

# 2.第二個連線:在家目錄下建立臨時目錄,臨時目錄由配置檔案中remote_tmp指令控制
<node1> ESTABLISH SSH CONNECTION FOR USER: None
<node1> SSH: EXEC ssh -vvv ...... '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /root/.ansible/tmp/ansible-tmp-1575542743.85-116022411851390 `" && ...... `" ) && sleep 0'"'"''

# 3.第三個連線:探測目標節點的平臺和python直譯器的版本資訊
<node1> Attempting python interpreter discovery
<node1> ESTABLISH SSH CONNECTION FOR USER: None
<node1> SSH: EXEC ssh -vvv ......

# 4.第四個連線:將要執行的模組相關的程式碼和引數放到本地臨時檔案中,並使用sftp將任務檔案傳輸到被控節點的臨時檔案中
<node1> ESTABLISH SSH CONNECTION FOR USER: None
<node1> SSH: EXEC ssh -vvv ......
Using module file /usr/lib/python2.7/site-packages/ansible/modules/system/ping.py
......
<node1> SSH: EXEC sftp ......
<node1> (0, 'sftp> put /root/.ansible/tmp/ansible-local-78628na2FKL/tmpaE1RbJ /root/.ansible/tmp/ansible-tmp-1575542743.85-116022411851390/AnsiballZ_ping.py\n', ......

# 5.第五個連線:對目標節點上的任務檔案授以執行許可權
<node1> ESTABLISH SSH CONNECTION FOR USER: None
<node1> SSH: EXEC ssh -vvv ...... '/bin/sh -c '"'"'chmod u+x /root/.ansible/tmp/ansible-tmp-1575542743.85-116022411851390/ /root/.ansible/tmp/ansible-tmp-1575542743.85-116022411851390/AnsiballZ_ping.py && sleep 0'"'"''
......

# 6.第六個連線:執行目標節點上的任務
<node1> ESTABLISH SSH CONNECTION FOR USER: None
<node1> SSH: EXEC ssh -vvv ...... '/bin/sh -c '"'"'/usr/bin/python /root/.ansible/tmp/ansible-tmp-1575542743.85-116022411851390/AnsiballZ_ping.py && sleep 0'"'"''
<node1> (0, '\r\n{"invocation": {"module_args": {"data": "pong"}}, "ping": "pong"}\r\n',
......

# 7.第七個連線:刪除目標節點上的臨時目錄
<node1> ESTABLISH SSH CONNECTION FOR USER: None
<node1> SSH: EXEC ssh -vvv ...... '/bin/sh -c '"'"'rm -f -r /root/.ansible/tmp/ansible-tmp-1575542743.85-116022411851390/ > /dev/null 2>&1 && sleep 0'"'"''
......

總結一下Ansible為每單個任務建立7次ssh連線所作的事情:

  • (1).第一個連線:獲取遠端主機時行目標使用者的家目錄,此處為/root

  • (2).第二個連線:在遠端家目錄下建立臨時目錄,臨時目錄可由ansible.cfg中remote_tmp指令控制

  • (3).第三個連線:探測目標節點的平臺和python直譯器的版本資訊

  • (4).第四個連線:將待執行模組的相關程式碼和引數放到本地臨時檔案中,並使用sftp將任務檔案傳輸到被控節點的臨時檔案中

  • (5).第五個連線:對目標節點上的任務檔案授以執行許可權

  • (6).第六個連線:執行目標節點上的任務

  • (7).第七個連線:刪除目標節點上的臨時目錄,並將執行結果返回給Ansible端

從單個任務的執行流程跳出來,更全域性一點,那麼整個執行流程(預設配置下)大致如下(不考慮inventory階段或執行完任務後的回撥階段,只考慮執行的任務流程):

  • (1).進入第一個play,挑選forks=5設定的5個節點

  • (2).每個節點執行第一個任務,每個節點都會建立7次ssh連線

  • (3).每個節點執行第二個任務,每個節點都再次建立7次ssh連線

  • (4).按照相同邏輯執行該play中其它任務...

  • (5).所有節點執行完該play中的所有任務後,進入下一個play

  • (6).按照上面的流程執行完所有play中的所有任務

以上便是整個執行流程,各位大概也看出來了,Ansible在建立ssh連線方面上實在是"不遺餘力",可能是因為Ansible官方團隊太愛ssh了……開玩笑的啦……。

11.3 回顧Ansible的執行策略

使用forks、serial、strategy等指令可以改變Ansible的執行策略。

預設情況下forks=5,這表明在某一時刻最多隻有5個執行任務的工作程序(還有一個主程序),也即最多隻能挑選5個節點同時執行任務。

serail是play級別的指令,用於指定幾個節點作為一批去執行該play,該play執行完後才讓下一批節點執行該play中的任務。如果不指定serial,則預設的行為等價於將所有節點當作一批。

strategy指令用於指定節點執行任務時的策略,其側重點在於節點而在於任務,預設情況下其策略為linear,表示某個節點先執行完一個任務後等待其餘所有節點都執行完該任務,才統一進入下一個任務。另一種策略是free策略,表示某節點執行完一個任務後不等待其它節點,而是毫不停留的繼續執行該play中的剩餘任務,直到該play執行完成,才釋放節點槽位讓其它未執行任務的節點開始執行任務。

前面的文章已經詳細介紹過Ansible的執行策略,所以此處僅作簡單回顧,如有所遺忘,請複習前面的文章。

11.4 加大forks的值

圖片

11.5 修改執行策略

預設情況下Ansible會讓所有節點(或者serial指定的數量)執行完同一個任務後才讓它們進入下一個任務,這體現了各節點的公平性和實時性:每個節點都能儘早執行到任務。這其實和作業系統的程序排程是類似的概念,只不過相對於作業系統的排程系統來說,Ansible的排程策略實在是太簡陋了。

假設forks設定的比較大,可以一次性讓足夠多的節點併發執行任務,那麼同時設定任務的執行策略為strategy=free便能讓這些執行任務的節點徹底放飛自我。只是剩餘的一部分節點可能會比較悲劇,它們處於排程不公平的一方。但是從整體來說,先讓大部分節點快速完成任務是值得的。

但是要注意,有些場景下要小心使用free策略,特別是節點依賴時。比如,某些節點執行服務A,另一些節點執行服務B,而服務B是依賴於服務A的,那麼必須不能讓執行B服務的節點先執行,對於有節點依賴關係的任務,為了健壯性,一般會定義好等待條件,但是出現等待有可能就意味著浪費。

11.6 使Ansible非同步執行任務

預設情況下,Ansible按照同步執行的方式執行每個任務。即對每個任務來說,都需要等待目標節點執行完該任務後回饋給Ansible端的報告,然後Ansible才認為該節點上的該任務已經執行完成,才會考慮下一步驟,比如free策略下該節點繼續執行下一個任務,或者等待其它節點完成該任務,等等。

11.6.1 async和poll指令

Ansible允許在task級別(且只支援task級別)指定該task是否以非同步模式(即放入後臺)執行,即將該非同步任務放入後臺。例如:

1
2
3
4
5
6
7
8
9
10
- name: it is an async task
copy:
src:
dest:
async: 200
poll: 2
- name: a sync task
copy:
src:
dest:

其中async指令表示該任務將以非同步的模式執行。async指令的值200表示,如果該後臺任務200秒還未完成,則認為該任務失敗。poll指令表示該任務丟入後臺後,Ansible每隔多久去檢查一次非同步任務是否已成功、是否報錯等,只有檢查到已完成後才認為該非同步任務執行完成,才會進入下一個任務。

如此看來,似乎這個非同步執行模式並非想象中那樣真正的非同步:將一個任務放入後臺執行,立即進入下一個任務。而且這裡的非同步似乎會減慢任務的執行流程。比如後臺任務在第3秒完成,也必須等到第4秒檢查的時候才認為執行完成。

如果poll指令的值大於0,這確實不是真正的非同步,每個工作程序必須等待放入後臺的任務執行完成才會進入下一個任務,換句話說,儘管使用了async非同步指令,也仍然會阻塞在該非同步任務上。這會減慢任務的執行速度,但此時執行該非同步任務的Ansible工作程序會放棄CPU,使得CPU可以執行其它程序(對於Ansible控制節點來說,這算哪門子優點?)。

但如果poll指令的值為0,將會以真正的非同步模式執行任務,表示Ansible工作程序不檢查後臺任務的執行狀況,而是直接執行下一個任務。

不管poll指令的值是否大於0,只要使用了非同步,那麼強烈建議將forks指令的值設定的足夠大。比如能夠一次性讓所有節點都開始非同步執行某任務,這樣的話,無論poll的值是否大於0,都能提升效率。

此外,也可以在ansible命令中使用-B N選項指定async功能,N為超時時長,-P N選項指定poll功能,N為檢查後臺任務狀況的時間間隔。

例如:

$ ansible inventory_file -B200 -P 0 -m yum -a 'name=dos2unix' -o -f 20

11.6.2 等待非同步任務

無論是程式語言還是如Ansible一般的工具,只要提供非同步執行模式,都必不可少的需要提供一個等待非同步任務執行完成的功能(注:同步執行模式不需要等待,因為同步執行模式本就是從上到下依次執行的)。

例如下面的任務執行流程:

T1(async) --> T2(sync) --> T3(sync) --> T4(wait T1) --> T5

T1是一個非同步任務,放入後臺執行時立即去執行T2和T3,但是T5這個任務比較特殊,它依賴於T1任務的執行成功,於是在T5任務之前插入一個等待T1非同步任務執行完成的等待任務,只要T1沒有完成,就會一直阻塞在T4上,那麼自然不會執行到T5,如果T1完成了,T4便等待完成,於是可以執行T5。

Ansible中想要等待非同步任務需要藉助於async_status模組,該模組接受一個後臺任務的job id作為引數,然後獲取該後臺任務的狀態並返回。

該模組返回的狀態資訊包含以下幾項屬性:

  • (1).ansible_job_id:非同步任務的job id

  • (2).finished:表示所等待的非同步任務是否已執行完成,值為1表示完成,0表示未完成

  • (3).started:表示所等待的非同步任務是否已開始執行,值為1表示已開始,0表示未開始

例如,下面是官方提供的一個典型的非同步等待示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- name: Asynchronous yum task
yum:
name: nginx
state: present
async: 1000
poll: 0
register: yum_sleeper

- name: Wait for asynchronous job to end
async_status:
jid: '{{ yum_sleeper.ansible_job_id }}'
register: job_result
until: job_result.finished
retries: 30

此示例中,非同步任務註冊了一個變數yum_sleeper,該變數中包含一個ansible_job_id的屬性。將該屬性交給async_status模組的jid選項,該模組便可以獲取該非同步任務的狀態,並將狀態註冊到變數job_result中,結合until指令不斷等待job_result.finished事件發生,即表示非同步任務執行完成。

同時等待多個非同步任務也是常見的需求:只有所有想要等待的任務全都完成了才繼續向下執行。Ansible中可以對async_status模組使用loop迴圈來完成該功能。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
---
- name: test for timer
hosts: timer
gather_facts: no
tasks:
- name: async task 1
shell: sleep 5
async: 200
poll: 0
register: async_task1

- name: async task 2
shell: sleep 10
async: 200
poll: 0
register: async_task2

- name: waiting for all async task
async_status:
jid: "{{item}}"
register: job_result
until: job_result.finished
loop:
- "{{ async_task1.ansible_job_id }}"
- "{{ async_task2.ansible_job_id }}"

- name: after waiting
debug:
msg: "after waiting"

11.6.3 何時使用非同步任務

有時候合理應用非同步任務能大幅提升Ansible的執行效率,但也並非所有場景都能夠使用非同步任務。

總結來說,以下一些場景可能使用到Ansible的非同步特性:

圖片

11.7 開啟ssh長連線

Ansible對ssh的依賴性非常強,最佳化ssh連線在一定程度上也是在最佳化Ansible。

其中一項最佳化是開啟ssh的長連線,即長時間保持連線狀態。開啟長連線後,在ssh連線過期前會一直保持ssh連線已建立的狀態,使得下次和目標節點建立ssh連線時將直接使用該連線。相當於對ssh連線進行了快取。

要開啟ssh長連線,要求Ansible端的openssh版本高於或等於5.6。使用ssh -V可以檢視版本號。然後設定ansible使用ssh連線被控端的連線引數,此處修改/etc/ansible/ansible.cfg,在此檔案中啟動下面的連線選項,其中ControlPersist=5d是控制ssh連線會話保持時長為5天。

ssh_args = -C -o ControlMaster=auto -o ControlPersist=5d

除此之外直接設定/etc/ssh/ssh_config(不是sshd_config,因為ssh命令是客戶端命令)中對應的長連線選項也是可以的。

以後只要有了一次ssh連線,就會將連線保留下來,例如:執行一次Ansible的ad-hoc操作,會建立ssh連線。

ansible centos -m ping

檢視netstat,發現ssh程序的會話一直是established狀態(為了排版,我略了前面3個欄位)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ netstat -tnalp

Local Address Foreign Address State PID/Program name
0.0.0.0:22 0.0.0.0:* LISTEN 1143/sshd
127.0.0.1:25 0.0.0.0:* LISTEN 2265/master
192.168.200.26:58474 192.168.200.59:22 ESTABLISHED 31947/ssh: /root/.a
192.168.200.26:22 192.168.200.1:8189 ESTABLISHED 29869/sshd: root@pt
192.168.200.26:37718 192.168.200.64:22 ESTABLISHED 31961/ssh: /root/.a
192.168.200.26:38894 192.168.200.60:22 ESTABLISHED 31952/ssh: /root/.a
192.168.200.26:48659 192.168.200.61:22 ESTABLISHED 31949/ssh: /root/.a
192.168.200.26:33546 192.168.200.65:22 ESTABLISHED 31992/ssh: /root/.a
192.168.200.26:54824 192.168.200.63:22 ESTABLISHED 31958/ssh: /root/.a
:::22 :::* LISTEN 1143/sshd
::1:25 :::* LISTEN 2265/master

同時,會在當前使用者家目錄的.ansible/cp目錄下生成一些socket檔案,每個ssh連線會話一個檔案。

1
2
3
4
5
6
7
8
$ ls -l ~/.ansible/cp/
total 0
srw------- 1 root root 0 Jun 3 18:26 5c4a6dce87
srw------- 1 root root 0 Jun 3 18:26 bca3850113
srw------- 1 root root 0 Jun 3 18:26 c89359d711
srw------- 1 root root 0 Jun 3 18:26 cd829456ec
srw------- 1 root root 0 Jun 3 18:26 edb7051c84
srw------- 1 root root 0 Jun 3 18:26 fe17ac7eed

這些socket檔案的存放路徑由ansible.cfg檔案中的control_path_dir指令決定。

11.7.1 開啟ssh長連線後的注意事項

開啟ssh長連線固然會將連線快取下來以避免頻繁建立ssh連線,但也因此帶來了一個問題:只要目標節點sshd監聽地址和埠未變,那麼只要ssh長連線未過期,客戶端(比如Ansible)總能使用已快取的連線和目標節點通訊。

這是什麼意思呢?比如A節點上的ssh開啟了長連線(注:長連線是ssh的特性,不是Ansible的特性,所以ssh自身也可以設定),當A透過ssh第一次連線到root@B節點後,A會快取到B節點的ssh連線,如果此時B節點目標使用者root修改了密碼,A節點藉助快取下來的ssh長連線仍然能夠連線到root@B節點。

對於Ansible來說,開啟長連線後可能會帶來一些問題,比如快取了某節點的ssh連線後,又修改了Inventory中該節點的ssh連線變數,但這些連線變數在ssh長連線過期之前將不會生效。對於沒有注意到這一現象的人來說,這樣的問題是非常難以排查的,因為很難想到ssh長連線這方面。

11.8 開啟Pipelining

從前面對Ansible執行任務的流程中可以發現,Ansible執行每個任務時都會在本地將模組(通常是Python指令碼程式)和相關引數打包後透過sftp傳送到目標節點上,然後執行目標節點上的臨時指令碼檔案。這些行為還帶來了副作用,比如多建立了幾個ssh連線來建立臨時目錄、刪除目錄等。

Ansible現在也支援使用ssh的pipelining特性(注意,仍然是ssh的特性),當Ansible中開啟了Pipelining後,一個任務的所有動作都在一個ssh會話中完成,也會省去sftp到遠端的過程,它會直接將要執行任務涉及到的指令(比如python語句)透過遠端shell的方式傳送到目標節點的標準輸入(stdin)中,然後在目標節點執行這些程式碼。

如果不理解這個過程,可以理解下面這個更直觀的ssh命令:

1
2
$ echo 'hostname -I' | ssh root@192.168.200.48 'bash'
192.168.200.48

上面的命令中,ssh連線到192.168.200.48,同時ssh命令會讀取標準輸入中的hostname -I並將其寫入到遠端主機上的標準輸入供bash命令讀取,於是bash命令執行讀取到的資料。所以,相當於是在遠端主機上執行了echo "hostname -I" | bash

類似的,當Ansible開啟Pipelining特性後,會將任務相關的指令(通常是Python語句)透過ssh傳送到目標節點的標準輸入中,然後python直譯器程式讀取指令並執行。相當於:

$ echo 'print("hello world")' | ssh root@192.168.200.48 'python'

也相當於在遠端主機上執行了:

$ echo 'print("hello world")' | python

既然任務相關的指令已經傳送到目標的標準輸入,那自然就不需要再透過傳輸檔案的方式將任務傳輸到目標節點再執行,這顯然也減少了一大堆副作用而建立的ssh連線。事實上,當Ansible開啟了Pipelining特性後,提升的效率是巨大的。

Ansible開啟Pipelining的方式是在配置檔案(如ansible.cfg)中設定pipelining=true,預設是false,即預設Pipelining是禁用狀態。

1
2
$ grep '^pipelining' /etc/ansible/ansible.cfg
pipelining = True

11.8.1 開啟Pipelining後的注意事項

但是要注意,如果在Ansible中使用sudo相關行為時,需要在被控節點的/etc/sudoers中禁用"requiretty"。

例如,對於下面的play:

1
2
3
4
5
6
7
8
9
---
- name: test for timer
hosts: timer
gather_facts: no
become: yes
become_user: root
become_method: sudo
tasks:
- shell: sleep 1

不禁用requiretty將報錯:

1
2
Pseudo-terminal will not be allocated because stdin is not
a terminal.\r\nsudo: sorry, you must have a tty to run sudo

可以透過visudo編輯配置檔案,註釋該選項來禁用requiretty。

1
2
$ grep requiretty /etc/sudoers
Defaults requiretty # 註釋此行表示禁用

之所以要設定/etc/sudoers中的requiretty,是因為ssh遠端執行命令時,它的環境是非登入式非互動式shell,預設不會分配tty,沒有tty,ssh的sudo就無法關閉密碼回顯(使用"-tt"選項強制SSH分配tty)。所以出於安全考慮,/etc/sudoers中預設是開啟requiretty的,它要求只有擁有tty的使用者才能使用sudo,也就是說ssh連線過去不允許執行sudo。

但是,修改設定/etc/sudoers的操作是在被控節點上進行的(或者ansible連線過去修改),其實在ansible端也可以解決sudo的問題,只需在ansible的ssh引數上加上"-tt"選項即可(注:經測試,Ansible2.9中使用-tt會阻塞,-t或—ttt不阻塞但仍然失敗,但我肯定,以前的版本是可以這麼做的,所以相關方案仍然留在此處)。

1
2
3
4
5
$ grep 'ssh_args' /etc/ansible/ansible.cfg
ssh_args = -C -o ControlMaster=auto -o ControlPersist=1d -tt

# 或者在命令列中指定
$ ansible-playbook --ssh-extra-args="-tt"" xxxxxx

11.8.2 開啟Pipelining後的執行流程

開啟Pipelining後,再來看下執行單個任務時的執行流程(為排版也為讓各位能一眼看懂,我省略了一些資訊):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
TASK [test task] *******************************************
task path: /root/ansible/timer.yml:6

# 第一個SSH連線,用於探測目標節點上支援的Python版本
<192.168.200.48> Attempting python interpreter discovery
<192.168.200.48> ESTABLISH SSH CONNECTION FOR USER: None
<192.168.200.48> SSH: EXEC ssh -C -o ......
<192.168.200.48> (0, b'
PLATFORM\nLinux\nFOUND\n
/usr/bin/python\n
/usr/bin/python3.6\n
/usr/bin/python2.7\n
/usr/bin/python3\n
/usr/bin/python\n
ENDFOUND\n', b'')

# 第二個SSH連線用於探測目標節點作業系統的資訊
<192.168.200.48> ESTABLISH SSH CONNECTION FOR USER: None
<192.168.200.48> SSH: EXEC ssh -C -o ......
<192.168.200.48> (0, b'{"osrelease_content":
"NAME=\\"CentOS Linux\\"\\n
VERSION=\\"7 (Core)\\"\\n
ID=\\"centos\\"\\n
ID_LIKE=\\"rhel fedora\\"\\n
VERSION_ID=\\"7\\"\\n
......b'')

# 準備執行任務,載入任務使用的模板檔案,且發現開啟了Pipelining
Using module file ......ansible/modules/commands/command.py
Pipelining is enabled.

# 第三個SSH連線用於執行任務
<192.168.200.48> ESTABLISH SSH CONNECTION FOR USER: None
<192.168.200.48> SSH: EXEC ssh -C ......
'/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
<192.168.200.48> (目標節點執行任務返回的結果)

從執行流程上已經看到,開啟Pipelining後,除了建立兩個必要的SSH連線探測Python版本(老版本的Ansible不支援多Python版本自動探測功能)和作業系統資訊外,執行任務相關的SSH連線只有一個。

11.8.3 開啟和不開啟Pipelining的效率比較

下面是開啟和不開啟Pipelining時執行本文開頭的計時任務,3個節點總共603個任務。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 開啟Pipelining之前
$ ansible-playbook -i timer.host timer.yml
...................
......省略輸出......
...................
=========================================
scp ------------------------------------ 57.96s
shell ---------------------------------- 42.78s
only one debug ------------------------- 0.07s

# 開啟Pipelining後
$ ansible-playbook -i timer.host timer.yml
...................
......省略輸出......
...................
=========================================
scp ------------------------------------ 39.99s
shell ---------------------------------- 20.29s
only one debug ------------------------- 0.07s

可見,使用了Pipelining後,總時間幾乎是對半減,效率提升不可謂不高。

11.9 修改facts收集行為

Ansible預設會收集所有節點的所有facts資訊,而收集facts資訊是非常慢的。

如果能夠確保play中使用不到facts中的資訊,則可以gather_facts: no關閉收集功能。

如果只想要facts中的一部分資訊,那麼在收集時可以指定只收集這一部分資訊,其它的不要收集。例如只想收集網路卡相關資訊可以設定gather_subset=!all,!any,network,這樣可以減少收集的資料量,從而提升效率。

最後,還可以將facts快取下來。關於facts快取,在前面"迴歸Ansible並進階"的章節中已經詳細介紹過。所以此處略過。

11.10 Shell層次上的最佳化:將任務分開執行

在前面"利用Role部署LNMP"的章節最後,我提到過這種最佳化手段。這裡再簡單回顧一下。

在LNMP的示例中,分別為nginx和php和MySQL都單獨定義了自己的Role,它們分別在三批節點上執行。為了統籌這些Role,一般會定義一個匯聚了所有的Role的playbook檔案,稱為入口playbook,比如稱為main.yml或site.yml。

但是,把這些Role聚集到單個playbook檔案中後就必然會產生前後順序關係。比如執行nginx Role的時候,PHP Role和MySQL Role對應的節點都在空閒。這是一種很低效的執行方式。

圖片

這樣一來,分別執行這三個Role的三批節點就可以同時開始執行任務了。

而且,如果某個Role依賴於另一個Role,可以協調它們的順序並取消被依賴Role的後臺執行方式。比如php Role依賴於mysql Role時(只是假設),可以將mysql.yml以非後臺的方式放在php Role的前面執行。

1
2
3
$ ansible-playbook nginx.yml >/tmp/nginx.log &
$ ansible-playbook mysql.yml >/tmp/mysql.log # 被依賴,所以不後臺
$ ansible-playbook php.yml >/tmp/php.log

當然,更推薦也更健壯的方式是在php Role中定義等待MySQL Role的任務。

再者,還可以寫一個簡單的Shell指令碼,在Shell指令碼中加入一些判斷邏輯來決定如何執行這些Role,這樣實現的邏輯可能比Ansible本身還要更豐富。

11.11 第三方策略外掛:Mitogen for Ansible

Ansible的執行策略(即由strategy指令指定的值)是以外掛方式提供的。Ansible官方目前提供了四種策略外掛:

  • (1).linear

  • (2).free

  • (3).host-pinned

  • (4).debug

作為使用Ansible的使用者來說,可以去編寫自己的策略外掛或網上尋找相關的策略外掛。有一款備受青睞的策略外掛名為Mitogen for Ansible。

官方介紹和文件:Mitogen for Ansible--https://mitogen.networkgenomics.com/ansible_detailed.html。

使用該外掛後,將盡可能地儘早執行任務,注意是儘早而不是儘快,它所作的工作不可能會影響一個已編碼完成的Ansible模組的執行速度,換句話說,Mitogen提升的是從某個任務開始到該任務對應模組開始執行之間的速度。按照Mitogen官方描述,該外掛可以將Ansible的執行速度提升到1.25倍-7倍。

不管怎麼官方怎麼說,自己測試一下是最好的。

首先下載並解壓:

1
2
3
$ wget 'https://networkgenomics.com/try/mitogen-0.2.9.tar.gz'
$ mkdir -p ~/.ansible/plugins
$ tar xf mitogen-0.2.9.tar.gz -C ~/.ansible/plugins/

然後在ansible.cfg中設定使用該策略外掛,並指定該策略外掛提供的策略。

1
2
3
[defaults]
strategy_plugins = ~/.ansible/plugins/mitogen-0.2.9/ansible_mitogen/plugins/strategy
strategy = mitogen_linear

mitogen外掛提供了三種策略:

1
2
3
4
5
6
$ ls -1 ~/.ansible/plugins/mitogen-0.2.9/ansible_mitogen/plugins/strategy/
__init__.py
mitogen_free.py
mitogen_host_pinned.py
mitogen_linear.py
mitogen.py

其中:

  • (1).mitogen_linear對應於Ansible自身的linear策略

  • (2).mitogen_free對應於Ansible自身的free策略

  • (3).mitogen_host_pinned對應於Ansible自身的host_pinned策略

然後就可以開始計時測試了,仍然是本文開頭的計時任務。目前Ansible已開啟Pipelining特性,觀察使用mitogen_linear的效率和原生linear的效率相比如何:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 開啟Pipelining但未使用mitogen外掛
$ ansible-playbook -i timer.host timer.yml
...................
......省略輸出......
...................
=========================================
scp ------------------------------------ 39.99s
shell ---------------------------------- 20.29s
only one debug ------------------------- 0.07s

# 開啟Pipelining且使用mitogen外掛
$ ansible-playbook -i timer.host timer.yml
...................
......省略輸出......
...................
=========================================
shell -------------------------------- 8.02s
scp ---------------------------------- 3.37s
only one debug ----------------------- 0.33s

從結果可見,效率的提升非常明顯。

使用mitogen時,有些配置可能和Ansible原生衝突,或需要做額外配置。比如:

  • (1).原生Ansible允許使用forks設定最大併發節點數量,但mitogen預設固定最多32個連線,需要修改環境變數MITOGEN_POOL_SIZE的值來設定最大併發量。

  • (2).mitogen的sudo處理行為和Ansible不一樣,所以可能需要單獨在目標節點的sudoer配置中加入對應使用者的配置。比如your_ssh_username = (ALL) NOPASSWD:/usr/bin/python -c*

使用mitogen後,不少Ansible的內部操作會發生變化,mitogen自然會以相同的結果不同的效率來完成目標,所以作為使用者,不用關心這些內部的事。

最後,如果要使用mitogen,建議閱讀一次官方手冊,瞭解使用mitogen時的一些注意事項。

轉自

Ansible專欄文章之十一:更快點,執行過程分析、非同步、效率最佳化 - 駿馬金龍 - 部落格園
https://www.cnblogs.com/f-ck-need-u/p/17718504.html

相關文章