化運(yùn)維實(shí)戰(zhàn):從批量SSH到巡檢告警腳本)
講個(gè)在運(yùn)維圈很常見的場景凌晨三點(diǎn)某臺(tái)機(jī)器磁盤告警你第一反應(yīng)不是查監(jiān)控平臺(tái)而是挨個(gè) ssh 上去執(zhí)行df -h。我剛做運(yùn)維那會(huì)兒手底下二十多臺(tái)服務(wù)器每次巡檢、發(fā)版本、看日志全靠人肉登錄一天下來一半時(shí)間都花在重復(fù)敲命令上。后來痛定思痛把所有能固化的操作都改成了 Python 自動(dòng)化運(yùn)維腳本才慢慢把這種“救火式運(yùn)維”變成“躺平式巡檢”。這篇內(nèi)容不整虛的從 Python 環(huán)境怎么配、虛擬環(huán)境怎么建、常用庫怎么選到批量執(zhí)行命令、系統(tǒng)巡檢、定時(shí)任務(wù)全程按實(shí)際運(yùn)維場景寫你拿到就能照著改。純 Shell 也能寫自動(dòng)化但一旦碰到字符串處理、異常捕獲、多線程這種復(fù)雜邏輯Shell 寫起來特別難受Python 的優(yōu)勢就在這里。不管你是剛?cè)胄械男“走€是寫了多年 Shell 的老手只要想把“登錄服務(wù)器敲命令”變成“跑一條腳本出結(jié)果”這篇文章都值得花十分鐘看完。我會(huì)盡量把參數(shù)、坑、避雷方法都交代清楚。1. 環(huán)境準(zhǔn)備Python版本、虛擬環(huán)境與安裝庫的那些坑1.1 版本選擇不是越新越好先聊版本。很多新手上來就裝最新版 Python然后照著老教程寫代碼結(jié)果庫裝不上或者 API 對不上。自動(dòng)化運(yùn)維場景下的 Python我建議直接用 3.10 或 3.8 這種穩(wěn)定到不能再穩(wěn)定的版本而不是追新。原因很簡單運(yùn)維要的是可預(yù)測性生產(chǎn)環(huán)境跑著的腳本不會(huì)因?yàn)槟闶职W升級了一個(gè)大版本就自動(dòng)適配。你本地跑得歡到了服務(wù)器上 Python 3.6 直接報(bào)語法錯(cuò)誤這種事我見過太多次了。還有一條血淚教訓(xùn)系統(tǒng)自帶的 Python 不要?jiǎng)?。尤?CentOS 7 那批機(jī)器yum 依賴系統(tǒng)里的 Python 2.7你要是手賤把它升級或者替換了大概率 yum 直接廢掉到時(shí)候連包都裝不上。Ubuntu 22.04 自帶 Python 3.10裝上python3-venv和python3-pip就能用省事很多。在服務(wù)器上裝新 Python 時(shí)盡量用官方源碼編譯或者系統(tǒng)包管理器裝不要直接去覆蓋/usr/bin/python3。我的習(xí)慣是系統(tǒng)環(huán)境保持出廠狀態(tài)所有項(xiàng)目依賴都隔離走。1.2 venv虛擬環(huán)境運(yùn)維腳本的隔離區(qū)提到隔離就繞不開venv。你可以把虛擬環(huán)境理解成給每個(gè)項(xiàng)目單獨(dú)分一個(gè)小房間不同的項(xiàng)目有不同的依賴版本互相不干擾。運(yùn)維腳本雖然是腳本但依賴收斂同樣重要。你在一臺(tái)機(jī)器上給系統(tǒng) Python 裝了 paramiko下個(gè)月裝另一個(gè)工具不小心把 paramiko 升級了線上腳本可能就崩了。所以我固定操作是為運(yùn)維項(xiàng)目單獨(dú)建一個(gè) venv名字就叫ops裝什么都扔進(jìn)去跑腳本也全都用這個(gè)環(huán)境里的 Python 解釋器。創(chuàng)建方式很簡單python3 -m venv ~/venvs/ops source ~/venvs/ops/bin/activate pip install paramiko psutil建好之后把當(dāng)前環(huán)境里的依賴導(dǎo)出成requirements.txt方便在別的機(jī)器上復(fù)現(xiàn)pip freeze requirements.txt這里有個(gè)細(xì)節(jié)在 crontab 或 systemd timer 里跑腳本時(shí)source activate不一定生效所以別依賴激活狀態(tài)直接寫 venv 里 Python 的絕對路徑比如~/venvs/ops/bin/python。關(guān)于這個(gè)坑后面第 5 章還會(huì)細(xì)說。1.3 pip安裝時(shí)報(bào)錯(cuò)怎么破安裝庫最容易翻車的不是庫本身而是環(huán)境?!癰ash: pip: command not found” 這個(gè)報(bào)錯(cuò)在新裝機(jī)的 Linux 上非常經(jīng)典。解決辦法不是找什么 get-pip.py 腳本而是先看系統(tǒng)包管理器里有沒有對應(yīng)的包Debian 系直接apt install python3-pipRHEL 系用yum install python3-pip。裝完之后我仍然建議只在 venv 里用 pip。另一個(gè)常見問題是權(quán)限。直接用sudo pip install xxx到系統(tǒng)目錄短期看著爽長期就是給自己埋雷。系統(tǒng)包管理器管理的 Python 包和 pip 裝的包經(jīng)常互相沖突升級一個(gè)庫可能帶崩另一個(gè)工具。我現(xiàn)在的原則很明確系統(tǒng) Python 只用于系統(tǒng)工具業(yè)務(wù)腳本一律進(jìn) venv。如果碰到編譯類安裝失敗比如error: command gcc failed with exit status 1通常是缺 Python 頭文件。Debian 系裝python3-devRHEL 系裝python3-devel裝完再重新 pip install 就好。這個(gè)問題在新版 paramiko 或 cryptography 編譯的時(shí)候特別容易觸發(fā)別慌先把頭文件補(bǔ)齊。至于 pip 下載慢那就配國內(nèi)鏡像源執(zhí)行一次就行pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/現(xiàn)在很多云主機(jī)在境內(nèi)不配鏡像的話裝個(gè)大一點(diǎn)的庫能等半天配置之后速度立刻就不一樣了。2. 入門庫速查先搞清楚你要處理什么場景2.1 標(biāo)準(zhǔn)庫已經(jīng)能解決一半問題說到 Python 做自動(dòng)化運(yùn)維很多人第一反應(yīng)是裝這個(gè)庫裝那個(gè)庫但其實(shí)標(biāo)準(zhǔn)庫已經(jīng)能解決一半以上的問題。比如shutil一個(gè)copytree就能把整個(gè)目錄連文件結(jié)構(gòu)復(fù)制過去比 Shell 里各種find、cp拼接要可靠得多。再比如glob掃描一批符合模式的日志文件幾行代碼就能做歷史日志歸檔。這些標(biāo)準(zhǔn)庫不需要額外安裝正因?yàn)槊赓M(fèi)反而容易被忽略。還有一個(gè)被低估的是subprocess。想在 Python 里執(zhí)行外部命令、拿返回結(jié)果標(biāo)準(zhǔn)做法是import subprocess result subprocess.run([df, -h], capture_outputTrue, textTrue) print(result.stdout)注意這里傳入的參數(shù)是一個(gè)列表而不是一整條字符串。用列表傳參可以避免很多 Shell 注入和轉(zhuǎn)義問題。有些教程為了省事直接寫shellTrue這在自動(dòng)化場景里其實(shí)有風(fēng)險(xiǎn)因?yàn)橐坏┟罾镉胁豢尚诺耐獠枯斎肴菀壮鰡栴}。運(yùn)維腳本里如果必須拼接命令我會(huì)先確認(rèn)命令來源絕對可信否則盡量用列表形式。2.2 遠(yuǎn)程操作三件套paramiko、fabric、netmiko遠(yuǎn)程管理是自動(dòng)化運(yùn)維的重頭戲。如果只想寫個(gè)腳本連幾臺(tái)機(jī)器跑點(diǎn)命令paramiko是最直接的。它在底層實(shí)現(xiàn)了 SSH 協(xié)議給你封裝好 SSHClient 和 SFTP 兩個(gè)接口既可以執(zhí)行命令也能傳文件。fabric則在 paramiko 之上又包了一層任務(wù)和上下文管理適合做部署動(dòng)作但坦白講fabric 的 API 變化比較快新老版本用法差異大新手容易對著老教程踩坑。netmiko是面向網(wǎng)絡(luò)設(shè)備的專門處理設(shè)備 CLI 的交互細(xì)節(jié)比如分頁符、命令回顯長度這類問題。你要維護(hù)一堆交換機(jī)路由器用 paramiko 裸連會(huì)很痛苦因?yàn)榫W(wǎng)絡(luò)設(shè)備經(jīng)常有分頁和交互提示符netmiko 把這些細(xì)節(jié)都處理好了。這幾個(gè)庫的定位差異我用一張表格列出來庫定位適用場景上手難度paramikoSSH 協(xié)議庫服務(wù)器批量命令、文件傳輸中fabric基于 paramiko 的任務(wù)封裝部署腳本、遠(yuǎn)程批量執(zhí)行中netmiko網(wǎng)絡(luò)設(shè)備交互庫交換機(jī)路由器配置巡檢、批量下發(fā)低中ansible自動(dòng)化引擎/框架配置管理、多主機(jī)編排偏高Ansible 其實(shí)不算庫它是一個(gè)獨(dú)立的自動(dòng)化框架用 Python 寫的也支持插件擴(kuò)展。如果你的環(huán)境允許免密 SSH寫 playbook 確實(shí)很爽但如果只是臨時(shí)處理幾臺(tái)機(jī)器Python 腳本反而更輕不用引入一套新的編排語法。2.3 系統(tǒng)信息采集不用再讀 /proc寫巡檢腳本時(shí)系統(tǒng)信息采集是最基礎(chǔ)的活兒。傳統(tǒng)做法是去讀/proc/loadavg、/proc/meminfo這些文件或者調(diào)free、df、uptime命令再解析輸出。不是不行就是麻煩而且不同系統(tǒng)輸出格式有差異。我后來的選擇是直接用psutil這個(gè)庫把所有系統(tǒng)指標(biāo)都封裝成了函數(shù)跨平臺(tái)表現(xiàn)一致。import psutil print(psutil.cpu_percent(interval1)) print(psutil.virtual_memory().percent) print(psutil.disk_usage(/).percent)psutil還能拿進(jìn)程列表、網(wǎng)絡(luò)連接數(shù)做告警和排查非常順手。后面第 4 章的健康巡檢腳本我會(huì)基于它寫一個(gè)完整的例子。2.4 通知與調(diào)度的庫怎么選腳本跑完總要讓人知道結(jié)果。標(biāo)準(zhǔn)庫里的logging負(fù)責(zé)把日志寫到文件smtplib負(fù)責(zé)發(fā)郵件。定時(shí)調(diào)用不推薦在 Python 進(jìn)程里自己做除非是開發(fā)環(huán)境臨時(shí)用。生產(chǎn)環(huán)境直接用 crontab 或者 systemd timer 更穩(wěn)妥因?yàn)椴僮飨到y(tǒng)層面的調(diào)度不依賴你的腳本進(jìn)程一直活著。進(jìn)程內(nèi)調(diào)度方案像schedule、apscheduler更適合開發(fā)環(huán)境或 Windows 服務(wù)器上不方便配計(jì)劃任務(wù)的場景放到正式環(huán)境容易遇到進(jìn)程被殺、時(shí)間漂移這些坑。3. 第一個(gè)實(shí)用腳本批量SSH執(zhí)行命令3.1 先拆需求再寫功能很多人一上來就寫代碼結(jié)果寫到一半發(fā)現(xiàn)沒想清楚要干嘛。批量執(zhí)行命令這個(gè)腳本核心需求其實(shí)就三條能連接一批服務(wù)器、能執(zhí)行指定命令、能把結(jié)果收集回來??此坪唵蔚欠诺蕉_(tái)機(jī)器上串行跑太慢并發(fā)太高又容易被服務(wù)器拒絕所以還得考慮并發(fā)度?;谶@些我選了 paramiko 而不是直接用系統(tǒng)ssh命令原因就是 paramiko 的異常處理更可控你能區(qū)分是認(rèn)證失敗、連接超時(shí)還是命令執(zhí)行失敗。3.2 一個(gè)可用的paramiko批量執(zhí)行腳本直接給一個(gè)能跑的版本假設(shè)你已經(jīng)有 SSH 密鑰服務(wù)器列表寫在代碼里。生產(chǎn)環(huán)境建議把列表挪到配置文件后面我會(huì)說怎么改。#!/usr/bin/env python3 import paramiko import logging from concurrent.futures import ThreadPoolExecutor HOSTS [ {host: 192.168.1.10, user: ops, key_filename: /home/user/.ssh/id_rsa}, {host: 192.168.1.11, user: ops, key_filename: /home/user/.ssh/id_rsa}, ] COMMANDS [hostname, uptime, df -h] logging.basicConfig( levellogging.INFO, format%(asctime)s %(levelname)s %(message)s, filename/tmp/batch_run.log ) def run_on_host(item): ssh paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect( hostnameitem[host], usernameitem[user], key_filenameitem[key_filename], timeout10, banner_timeout10, auth_timeout10, ) for cmd in COMMANDS: stdin, stdout, stderr ssh.exec_command(cmd, timeout15) out stdout.read().decode(utf-8, errorsreplace) err stderr.read().decode(utf-8, errorsreplace) print(f$ {cmd}\n{out.strip()}) if err.strip(): logging.warning(f{item[host]} stderr: {err.strip()}) ssh.close() def main(): with ThreadPoolExecutor(max_workers5) as pool: list(pool.map(run_on_host, HOSTS)) if __name__ __main__: main()幾個(gè)關(guān)鍵點(diǎn)說一下。set_missing_host_key_policy(paramiko.AutoAddPolicy())的意思是自動(dòng)接受首次連接的主機(jī)指紋這在批量環(huán)境里省事但在安全要求高的環(huán)境不建議這么干最好是預(yù)先把 known_hosts 配好。banner_timeout和auth_timeout用來防止某臺(tái)機(jī)器 SSH 握手階段卡死不設(shè)置的話腳本可能掛在某一臺(tái)上。3.3 并發(fā)數(shù)不是越大越好ThreadPoolExecutor 很好用把串行改成并發(fā)只需要加幾行。但max_workers別貪多我個(gè)人推薦 5 到 10 就夠了。你要管理的是幾十臺(tái)服務(wù)器5 個(gè)并發(fā)處理起來就是一分鐘以內(nèi)的事如果設(shè)成 50很多開源防火墻或者商用設(shè)備會(huì)直接把你的 IP 拉黑。記住自動(dòng)化是給運(yùn)維減負(fù)不是給安全設(shè)備送素材。3.4 擴(kuò)展文件分發(fā)和收集執(zhí)行命令只是開始很多時(shí)候還要把安裝包分發(fā)到各臺(tái)機(jī)器或者把日志收集回來。這就要用 SFTP。ssh paramiko.SSHClient() # ... connect 同上 ... sftp ssh.open_sftp() sftp.put(/local/app.tar.gz, /tmp/app.tar.gz) sftp.get(/var/log/app.log, ./collect/192.168.1.10_app.log) sftp.close()這里容易踩的坑是遠(yuǎn)程目錄權(quán)限或者目錄不存在。sftp.put不會(huì)自動(dòng)創(chuàng)建遠(yuǎn)程目錄你得先執(zhí)行mkdir -p或者用sftp.mkdir逐級建目錄。收集日志時(shí)更建議每臺(tái)機(jī)器建一個(gè)子目錄避免文件名沖突比如上面例子里的./collect/192.168.1.10_app.log。3.5 別把密碼硬編碼在腳本里很多初學(xué)者喜歡在腳本里寫password123456我強(qiáng)烈不建議。腳本會(huì)放在服務(wù)器上甚至?xí)粋鞯酱a倉庫里密碼一旦泄漏就是安全事故。更好的方案是優(yōu)先用 SSH 密鑰如果一定要用密碼就用getpass提示交互輸入或者從配置中心讀取。至少也要把密碼放在環(huán)境變量里而不是寫死在源碼中。4. 巡檢告警腳本從手動(dòng)登錄到自動(dòng)出報(bào)告4.1 巡檢指標(biāo)先設(shè)計(jì)好代碼才有意義在寫巡檢腳本之前先問自己一個(gè)問題你到底要關(guān)注哪些指標(biāo)我見過不少巡檢腳本輸出一大堆數(shù)字最后沒人看因?yàn)楦緵]有重點(diǎn)。最基礎(chǔ)也最常用的指標(biāo)無非是這幾個(gè)指標(biāo)建議閾值說明CPU 使用率 80% 持續(xù) 5 分鐘長時(shí)間跑高需要關(guān)注系統(tǒng)負(fù)載 CPU 核心數(shù)負(fù)載過高說明隊(duì)列積壓內(nèi)存使用率 85%接近耗盡易觸發(fā) OOM磁盤使用率 90%再往上可能影響寫日志關(guān)鍵進(jìn)程存活進(jìn)程不存在告警服務(wù)掛了要第一時(shí)間知道關(guān)鍵端口監(jiān)聽端口不可達(dá)告警結(jié)合業(yè)務(wù)場景定義別把指標(biāo)定得太死不同業(yè)務(wù)不一樣。內(nèi)存 90% 對緩存類服務(wù)可能很正常磁盤 80% 對備份機(jī)器也可能就該清理了。閾值先按經(jīng)驗(yàn)給一版跑一段時(shí)間再調(diào)。4.2 本機(jī)巡檢腳本實(shí)例下面這個(gè)腳本可以在單臺(tái)服務(wù)器上跑用來采集和判斷指標(biāo)命中閾值就輸出告警。實(shí)際使用時(shí)可以結(jié)合上一節(jié)提到的 paramiko把它推到遠(yuǎn)程機(jī)器上執(zhí)行。#!/usr/bin/env python3 import os import time import psutil import socket HOST socket.gethostname() CPU_THRESHOLD 80 MEM_THRESHOLD 85 DISK_THRESHOLD 90 def check_cpu(): # 取三次采樣平均值避免瞬時(shí)峰值誤報(bào) total 0.0 for _ in range(3): total psutil.cpu_percent(interval1) avg total / 3 if avg CPU_THRESHOLD: print(f[ALERT] {HOST} CPU usage {avg:.1f}% {CPU_THRESHOLD}%) return avg def check_memory(): mem psutil.virtual_memory() if mem.percent MEM_THRESHOLD: print(f[ALERT] {HOST} memory usage {mem.percent:.1f}% {MEM_THRESHOLD}%) return mem.percent def check_disk(): du psutil.disk_usage(/) if du.percent DISK_THRESHOLD: print(f[ALERT] {HOST} disk usage {du.percent:.1f}% {DISK_THRESHOLD}%) return du.percent if __name__ __main__: cpu check_cpu() mem check_memory() disk check_disk() print(f[INFO] {HOST} CPU{cpu:.1f}% MEM{mem:.1f}% DISK{disk:.1f}%)這里有個(gè)細(xì)節(jié)CPU 使用率采樣如果只取一次特別容易誤報(bào)。運(yùn)維機(jī)器經(jīng)常有定時(shí)任務(wù)、日志壓縮這種瞬時(shí)尖峰所以我的做法是連續(xù)采樣三次取平均寧可慢幾秒也別半夜發(fā)騷擾告警。4.3 閾值判斷要連續(xù)采樣別被瞬時(shí)尖峰騙了關(guān)于“持續(xù)多久才算報(bào)警”也可以用一個(gè)更簡單的做法在內(nèi)存里維護(hù)一個(gè)計(jì)數(shù)連續(xù)三分鐘超過閾值才觸發(fā)告警。不要一個(gè)采樣點(diǎn)超標(biāo)就發(fā)郵件不然晚上你會(huì)被短信轟炸。說實(shí)話告警靈敏度寧低勿高等你的監(jiān)控系統(tǒng)每天發(fā)幾十條信息沒人看的時(shí)候真正的故障反而會(huì)被淹沒。4.4 定時(shí)任務(wù)crontab還是systemd timer巡檢腳本寫好了剩下的就是定時(shí)執(zhí)行。我最常用的是 crontab簡單直接。下面是典型的寫法*/5 * * * * /home/ops/venvs/ops/bin/python /home/ops/scripts/health_check.py /var/log/health_check.log 21這三處必須注意第一Python 解釋器寫絕對路徑不要寫python或者python3因?yàn)?cron 環(huán)境里的 PATH 很精簡可能找不到命令第二腳本也寫絕對路徑cron 的工作目錄不固定第三日志要重定向到文件否則腳本的輸出會(huì)被 cron 的郵件系統(tǒng)吞掉找都找不到。systemd timer 更規(guī)范能配置依賴、隨機(jī)啟動(dòng)延遲但需要寫 service 和 timer 兩個(gè)單元文件對小規(guī)模腳本來說稍微重了一點(diǎn)。我一般是腳本少用 crontab腳本多了再上 systemd timer。4.5 用smtplib發(fā)告警郵件告警怎么通知出去最傳統(tǒng)也穩(wěn)定的方式是郵件。用 smtplib 封裝一個(gè)推送函數(shù)import smtplib from email.mime.text import MIMEText def send_alert(subject, content): smtp_server smtp.example.com smtp_port 465 user opsexample.com passwd your-smtp-auth-code msg MIMEText(content, plain, utf-8) msg[Subject] subject msg[From] user msg[To] ops-teamexample.com server smtplib.SMTP_SSL(smtp_server, smtp_port) server.login(user, passwd) server.sendmail(user, [ops-teamexample.com], msg.as_string()) server.quit()注意用戶名和密碼這里用的是 SMTP 授權(quán)碼不是郵箱登錄密碼?,F(xiàn)在很多群機(jī)器人也有 Webhook 接口原理類似curl 一個(gè) URL 就能推送相對郵件來說更實(shí)時(shí)。但團(tuán)隊(duì)內(nèi)部協(xié)作郵件還是最通用的記錄留痕也方便。5. 常見問題與排查實(shí)錄5.1 “python命令不存在”或“pip找不到”這次熱搜里就有很多類似的報(bào)錯(cuò)比如某個(gè)版本的pnpm不能被識(shí)別為 cmdlet 或者命令本質(zhì)都是同一個(gè)原因執(zhí)行文件的路徑不在 PATH 環(huán)境變量里。Python 也一樣Windows 上安裝時(shí)沒勾選“Add Python to PATH”就會(huì)出現(xiàn)python 不是內(nèi)部或外部命令。Linux 上則是沒裝對應(yīng)的包。解決辦法很簡單Windows 重新安裝一次并勾選 PATH或者在系統(tǒng)環(huán)境變量里手動(dòng)加Linux 就是安裝python3-pip。排列組合如果python3存在但python不存在建個(gè)軟鏈即可。如果pip找不到優(yōu)先用python3 -m pip保證用對解釋器。如果多個(gè) Python 版本混在一起pip 裝進(jìn)了另一個(gè)版本用python3 -m pip install就不會(huì)錯(cuò)。5.2 SSH連接失敗的各種原因批量連接最煩的就是某臺(tái)機(jī)器突然連不上。常見報(bào)錯(cuò)和排查思路我整理成了速查表現(xiàn)象常見原因排查 / 解決Authentication failed用戶名密碼錯(cuò)誤或密鑰對不上檢查用戶名確認(rèn)密鑰是否在目標(biāo)機(jī)的 authorized_keys 中Bad permissions密鑰文件權(quán)限過寬chmod 600 ~/.ssh/id_rsaConnection refusedsshd 未啟動(dòng)、端口被防火墻攔檢查 sshd 服務(wù)狀態(tài)確認(rèn)遠(yuǎn)程端口是否通Error reading SSH protocol banner握手超時(shí)或服務(wù)器響應(yīng)慢設(shè)置banner_timeout和auth_timeout別用默認(rèn)值等太久No host key type configured舊設(shè)備算法不兼容檢查是否需要配置disabled_algorithmsConnection refused出現(xiàn)頻率很高我排查的順序是先 ping 看通不通再看端口再確認(rèn) sshd 是不是在監(jiān)聽。如果端口通但連不上大概率是防火墻的配置問題。5.3 exec_command卡住不返回paramiko 執(zhí)行命令時(shí)如果忘記讀stdout和stderr或者命令本身不退出腳本就會(huì)一直卡著。我的經(jīng)驗(yàn)是每條命令都給timeout參數(shù)比如上面的例子寫成exec_command(cmd, timeout15)超時(shí)之后報(bào)錯(cuò)退出。另外如果命令會(huì)產(chǎn)生大量輸出一定要及時(shí)讀取否則 SSH 通道的緩沖區(qū)滿了客戶端會(huì)阻塞住。還有一種情況是執(zhí)行交互式命令比如top這種要持續(xù)輸出的exec_command不適合要用invoke_shell來回寫交互但那個(gè)更復(fù)雜盡量別碰。5.4 中文亂碼與編碼問題腳本輸出中文亂碼十有八九是編碼沒統(tǒng)一。遠(yuǎn)程服務(wù)器默認(rèn)locale可能是POSIX或C輸出的中文會(huì)變成亂碼本地 Windows 控制臺(tái)默認(rèn) GBKPython 默認(rèn) UTF-8直接打印也容易串。我在代碼里讀取輸出時(shí)統(tǒng)一寫成decode(utf-8, errorsreplace)這樣即使個(gè)別字節(jié)不對也不會(huì)整個(gè)崩潰。另一個(gè)辦法是在命令前面加export LANGen_US.UTF-8;讓遠(yuǎn)程命令的輸出盡量用 UTF-8 編碼。5.5 venv在cron里不生效這個(gè)問題坑了無數(shù)人。你在終端里試腳本一切正常但 crontab 跑起來就報(bào)ModuleNotFoundError。原因很簡單cron 的執(zhí)行環(huán)境非常干凈不會(huì)加載你的~/.bashrcvenv 也沒被激活。解決方法是不要靠 activate直接在 crontab 里寫 venv 的 Python 絕對路徑。同時(shí)依賴庫如果有變動(dòng)要在 venv 里更新requirements.txt并且確認(rèn) cron 用的這個(gè)環(huán)境確實(shí)裝有所有依賴。5.6 不要亂動(dòng)系統(tǒng)Python又得重復(fù)一次真的不要在系統(tǒng) Python 里全局 pip install。很多運(yùn)維事故都是“順手”升級了一個(gè)包結(jié)果系統(tǒng)的某個(gè)工具依賴舊版本直接罷工。RHEL 系的 yum 依賴 Python 2.7 是經(jīng)典案例。所以所有業(yè)務(wù)腳本依賴一律進(jìn) venv系統(tǒng) Python 保持安裝時(shí)的狀態(tài)。這是自動(dòng)化運(yùn)維的基本衛(wèi)生習(xí)慣。6. 從腳本到工具幾條實(shí)踐經(jīng)驗(yàn)寫到這里我把這些年做自動(dòng)化運(yùn)維腳本的個(gè)人體會(huì)講一講沒有大道理都是踩坑踩出來的。第一先小范圍驗(yàn)證再全量執(zhí)行。不管是批量執(zhí)行命令還是分發(fā)文件先挑一臺(tái)測試機(jī)跑通確認(rèn)沒有破壞性操作再放到生產(chǎn)批量里。我見過有人寫完腳本直接全量重啟服務(wù)結(jié)果腳本里少判斷了一個(gè)條件整批機(jī)器一起出問題那場面是真的慘。把HOSTS列表先改成一臺(tái)機(jī)器跑通了再放開。第二腳本要冪等能重復(fù)執(zhí)行。冪等這個(gè)詞聽上去高級其實(shí)很簡單同一個(gè)腳本跑一次和跑十次最終狀態(tài)應(yīng)該一致。比如分發(fā)文件之前先比較文件 hash合了就跳過創(chuàng)建用戶之前先判斷用戶是否存在。自動(dòng)化運(yùn)維最怕的就是腳本沒有防御性跑第二次就報(bào)錯(cuò)或者更糟跑第二次把數(shù)據(jù)覆蓋了。第三日志比結(jié)果重要。任何腳本都應(yīng)該有自己的日志至少記錄什么時(shí)間、在哪臺(tái)機(jī)器上、執(zhí)行了什么命令、返回碼是多少。不要只依賴print用logging寫到固定文件方便事后回溯。運(yùn)維行業(yè)的準(zhǔn)則就是“執(zhí)行過的操作要有留痕”否則出了問題你就是那個(gè)背鍋的。第四參數(shù)化配置不要硬編碼。服務(wù)器列表、用戶名、閾值、路徑全部放到配置文件或環(huán)境變量里。不然每次加一臺(tái)機(jī)器都要改代碼改完還得擔(dān)心改錯(cuò)。簡單做法是用argparse支持命令行傳參再復(fù)雜一點(diǎn)就引入 YAML 配置文件。腳本通用性高了后面接手的人會(huì)感謝你。第五別追求一步到位。一個(gè)腳本能解決一個(gè)問題就很好了后續(xù)需求可以慢慢加。比如批量執(zhí)行命令腳本先支持固定命令再支持傳參再支持文件分發(fā)再支持并發(fā)控制。慢慢迭代出來的工具才最貼合實(shí)際場景一上來就想寫個(gè)超級復(fù)雜的平臺(tái)大概率爛尾。自動(dòng)化運(yùn)維腳本的價(jià)值不在于代碼多花哨而在于把重復(fù)操作固化下來把容易出錯(cuò)的人為判斷變成可重復(fù)的邏輯。我現(xiàn)在打開電腦很多操作已經(jīng)不需要再登錄服務(wù)器了前一天晚上把巡檢腳本跑完第二天早上看結(jié)果就行。這種幸福感就是靠這些不起眼的小腳本積累出來的。你也不妨從今天的第一行import paramiko開始。