使用subprocess模組非同步併發執行遠端命令

2021-08-28 03:24:38 字數 4684 閱讀 2080

運維自動化平台不可避免地會涉及到遠端命令執行操作,主要分為兩類主要做法:目標機器安裝agent,或者使用ssh。

saltstack是乙個典型的agent模式的遠端控制工具,麻煩的地方是首先要在目標機器上安裝saltstack的agent。

使用ssh的模組居多,fabric和ansible是此類工具中的典型,這類工具的優點是方便,不用在目標機安裝agent。值得一提的是,這兩個工具都是基於paramiko。

使用ssh執行的另一種做法是,直接呼叫本地的ssh來完成。缺點是針對每一台遠端主機都需要開乙個程序,比起在程式內建立連線要耗費更多的資源。優點也很明顯,比如,公司最近在做ssh的改造,在改造的過程中,可能會出現明明ssh命令可以連線,使用第三方模組就是不靈的情況。ssh的命令和第三方模組在配置上畢竟有差異,需要維護兩份配置。

這篇文章就是要使用程序來完成在批量遠端機器上執行某個命令。

subprocess模組是python2中的乙個官方模組,顧名思義,它主要用於操作子程序。

subprocess.popen()用於建立乙個子程序,它的第乙個引數是列表,即建立程序所需要的引數,類似c語言中的argv。

需要注意的一點是,popen是非同步執行的,也就是說建立popen後會立刻返回,子程序繼續執行。關於非同步,還有很多可以聊的地方,後面另開一篇文章寫一下。

既然是非同步的,我們就需要某種機制來跟它進行通訊。popen提供了多個方式來與子程序通訊。

popen.wait()將主程式掛起等待子程序的結束,結束後會返回子程序的返回碼。

popen.poll()可以檢測子程序是否還在執行,並返回子程序的返回碼。

下面我們將用幾個小例子來不斷擴充套件,最終實現乙個可在多台遠端機器並行執行命令的功能。

我們來寫乙個簡單的示例,說明如何使用subprocess模組遠端執行一條命令。

import subprocess

cmd = 'echo hello'

cmd_arg = ['ssh', 'localhost', cmd]

process = subprocess.popen(

cmd_arg, stdout=subprocess.pipe, stderr=subprocess.pipe)

retcode = process.wait() # 0

out = process.stdout.read() # 'hello'

err = process.stderr.read() # ''

我們首先建立了乙個popen物件,然後等待這個子程序的執行結束,獲取返回值和輸出。

這斷**很簡單,注意stdout=subprocess.pipe,我們想捕捉到子程序的輸出,而不是直接列印到標準標出,所以要求popen把輸出列印到管道供我們讀取。

這裡我們利用了popen的非同步特性,來加快多伺服器任務的執行。

import subprocess

cmd = 'sleep 3 && echo hello'

cmd_arg1 = ['ssh', 'localhost', cmd]

cmd_arg2 = ['ssh', '127.0.0.1', cmd]

process1 = subprocess.popen(

cmd_arg1, stdout=subprocess.pipe, stderr=subprocess.pipe)

process2 = subprocess.popen(

cmd_arg2, stdout=subprocess.pipe, stderr=subprocess.pipe)

retcode1 = process1.wait()

retcode2 = process1.wait()

這次,我們連線了兩個機器並執行耗時3s的操作,由於popen是非同步的,這個指令碼的實際執行時間仍然是3s。這個地方的操作是不是與多執行緒的操作有點類似,哈哈。

由於網路環境和機器配置的不同,我們在不同機器的執行時間是有差別的。有時候,我們需要乙個時間資料來了解哪個機器執行耗時比較久,以此做優化。

這時,我們需要popen.poll()來非同步檢測命令是否執行完成,而不是將主程序掛起等待第乙個子程序。如果第乙個子程序執行時間比第二個子程序要長,我們就獲取不到第二個子程序的執行時間。

為了更好的可讀性,這裡我們沒有加入for迴圈之類的結束,放棄了部分邏輯上的靈活性。

import time

import subprocess

cmd = 'sleep 3 && echo hello'

cmd_arg1 = ['ssh', 'localhost', cmd]

cmd_arg2 = ['ssh', '127.0.0.1', cmd]

process1 = subprocess.popen(

cmd_arg1, stdout=subprocess.pipe, stderr=subprocess.pipe)

process2 = subprocess.popen(

cmd_arg2, stdout=subprocess.pipe, stderr=subprocess.pipe)

process1_ok = false

process2_ok = false

start_time = time.time()

while

true:

if process1_ok and process2_ok:

break

ifnot process1_ok:

if process1.poll() is

notnone:

process1_ok = true

print

'process 1 finished, delta time:', time.time() - start_time

ifnot process2_ok:

if process2.poll() is

notnone:

process2_ok = true

print

'process 2 finished, delta time:', time.time() - start_time

time.sleep(1) # 防止空跑導致cpu占用過高

執行輸出為:

process

1 finished, delta time: 4.01682209969

process

2 finished, delta time: 4.01691508293

最後的執行時間多了1s,這是因為我們做了乙個time.sleep(1)操作。由於我們考查的是哪台機器影響了整體的耗時,而且遠端任務執行時間遠不止1s,所以這裡的1s不影響我們的判斷。當然,適當縮小time.sleep()的引數也是可以的。

我們將上面的指令碼封閉一下,形成乙個可以復用的函式。有點長,但是只是在之前基礎上做了一些簡單的操作,並沒有增加什麼高深的東西。

def

run(hostname, command, user=none):

""" 使用ssh執行遠端命令

hostname 字串或陣列,多個hostname使用陣列

"""ifnot isinstance(hostname, list):

hostname = [hostname]

else:

hostname = list(set(hostname))

# 建立執行命令子程序

processes = {}

for h in hostname:

if user is

none:

user_at_hostname = h

else:

user_at_hostname = '@'.format(user, h)

cmd_args = ['ssh', user_at_hostname, command]

processes[h] = subprocess.popen(

cmd_args, stdout=subprocess.pipe, stderr=subprocess.pipe)

# 等待子程序結束,獲取結果

start_time = time.time()

result = {}

while

true:

running_hostnames = set(processes.keys()) - set(result.keys())

if len(running_hostnames) == 0:

break

for h in running_hostnames:

process = processes[h]

retcode = process.poll()

if retcode is

none:

continue

status = status_succ if retcode == 0

else status_fail

out, err = process.stdout.read(), process.stderr.read()

delta_time = time.time() - start_time

result[h] =

time.sleep(1)

r =

return r

struct 模組 subprocess 模組

struct 模組 就這麼用 import struct 首先匯入此模組 res ncjewgfjsdbvdhj 隨意的值 print len res 15 只是為了展示原res的長度res1 struct.pack i len res 打包,固定i模式,len res print len res1...

subprocess模組 re模組

import subprocess 匯入subprocess模組,該模組的作用為可以通過python 向終端 cmd 傳送命令 while true 進行迴圈,可以讓使用者重複的進行輸入 cmd str input 請輸入終端命令 strip 定義變數cmd str obj subprocess.p...

subprocess模組詳解

subprocess是python與系統互動的乙個庫,該模組允許生成新程序,連線到它們的輸入 輸出 錯誤管道,並獲取它們的返回 該模組旨在替換幾個較舊的模組和功能 os.system os.spawn os.popen popen2 commands 相關函式 subprocess.call arg...