首页 / 资讯中心 / 文章详情

Python自动化运维实战:从批量SSH到巡检告警脚本

Python自动化运维实战:从批量SSH到巡检告警脚本 ★ FEATURED ARTICLE
讲个在运维圈很常见的场景凌晨三点某台机器磁盘告警你第一反应不是查监控平台而是挨个 ssh 上去执行df -h。我刚做运维那会儿手底下二十多台服务器每次巡检、发版本、看日志全靠人肉登录一天下来一半时间都花在重复敲命令上。后来痛定思痛把所有能固化的操作都改成了 Python 自动化运维脚本才慢慢把这种“救火式运维”变成“躺平式巡检”。这篇内容不整虚的从 Python 环境怎么配、虚拟环境怎么建、常用库怎么选到批量执行命令、系统巡检、定时任务全程按实际运维场景写你拿到就能照着改。纯 Shell 也能写自动化但一旦碰到字符串处理、异常捕获、多线程这种复杂逻辑Shell 写起来特别难受Python 的优势就在这里。不管你是刚入行的小白还是写了多年 Shell 的老手只要想把“登录服务器敲命令”变成“跑一条脚本出结果”这篇文章都值得花十分钟看完。我会尽量把参数、坑、避雷方法都交代清楚。1. 环境准备Python版本、虚拟环境与安装库的那些坑1.1 版本选择不是越新越好先聊版本。很多新手上来就装最新版 Python然后照着老教程写代码结果库装不上或者 API 对不上。自动化运维场景下的 Python我建议直接用 3.10 或 3.8 这种稳定到不能再稳定的版本而不是追新。原因很简单运维要的是可预测性生产环境跑着的脚本不会因为你手痒升级了一个大版本就自动适配。你本地跑得欢到了服务器上 Python 3.6 直接报语法错误这种事我见过太多次了。还有一条血泪教训系统自带的 Python 不要动。尤其 CentOS 7 那批机器yum 依赖系统里的 Python 2.7你要是手贱把它升级或者替换了大概率 yum 直接废掉到时候连包都装不上。Ubuntu 22.04 自带 Python 3.10装上python3-venv和python3-pip就能用省事很多。在服务器上装新 Python 时尽量用官方源码编译或者系统包管理器装不要直接去覆盖/usr/bin/python3。我的习惯是系统环境保持出厂状态所有项目依赖都隔离走。1.2 venv虚拟环境运维脚本的隔离区提到隔离就绕不开venv。你可以把虚拟环境理解成给每个项目单独分一个小房间不同的项目有不同的依赖版本互相不干扰。运维脚本虽然是脚本但依赖收敛同样重要。你在一台机器上给系统 Python 装了 paramiko下个月装另一个工具不小心把 paramiko 升级了线上脚本可能就崩了。所以我固定操作是为运维项目单独建一个 venv名字就叫ops装什么都扔进去跑脚本也全都用这个环境里的 Python 解释器。创建方式很简单python3 -m venv ~/venvs/ops source ~/venvs/ops/bin/activate pip install paramiko psutil建好之后把当前环境里的依赖导出成requirements.txt方便在别的机器上复现pip freeze requirements.txt这里有个细节在 crontab 或 systemd timer 里跑脚本时source activate不一定生效所以别依赖激活状态直接写 venv 里 Python 的绝对路径比如~/venvs/ops/bin/python。关于这个坑后面第 5 章还会细说。1.3 pip安装时报错怎么破安装库最容易翻车的不是库本身而是环境。“bash: pip: command not found” 这个报错在新装机的 Linux 上非常经典。解决办法不是找什么 get-pip.py 脚本而是先看系统包管理器里有没有对应的包Debian 系直接apt install python3-pipRHEL 系用yum install python3-pip。装完之后我仍然建议只在 venv 里用 pip。另一个常见问题是权限。直接用sudo pip install xxx到系统目录短期看着爽长期就是给自己埋雷。系统包管理器管理的 Python 包和 pip 装的包经常互相冲突升级一个库可能带崩另一个工具。我现在的原则很明确系统 Python 只用于系统工具业务脚本一律进 venv。如果碰到编译类安装失败比如error: command gcc failed with exit status 1通常是缺 Python 头文件。Debian 系装python3-devRHEL 系装python3-devel装完再重新 pip install 就好。这个问题在新版 paramiko 或 cryptography 编译的时候特别容易触发别慌先把头文件补齐。至于 pip 下载慢那就配国内镜像源执行一次就行pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/现在很多云主机在境内不配镜像的话装个大一点的库能等半天配置之后速度立刻就不一样了。2. 入门库速查先搞清楚你要处理什么场景2.1 标准库已经能解决一半问题说到 Python 做自动化运维很多人第一反应是装这个库装那个库但其实标准库已经能解决一半以上的问题。比如shutil一个copytree就能把整个目录连文件结构复制过去比 Shell 里各种find、cp拼接要可靠得多。再比如glob扫描一批符合模式的日志文件几行代码就能做历史日志归档。这些标准库不需要额外安装正因为免费反而容易被忽略。还有一个被低估的是subprocess。想在 Python 里执行外部命令、拿返回结果标准做法是import subprocess result subprocess.run([df, -h], capture_outputTrue, textTrue) print(result.stdout)注意这里传入的参数是一个列表而不是一整条字符串。用列表传参可以避免很多 Shell 注入和转义问题。有些教程为了省事直接写shellTrue这在自动化场景里其实有风险因为一旦命令里有不可信的外部输入容易出问题。运维脚本里如果必须拼接命令我会先确认命令来源绝对可信否则尽量用列表形式。2.2 远程操作三件套paramiko、fabric、netmiko远程管理是自动化运维的重头戏。如果只想写个脚本连几台机器跑点命令paramiko是最直接的。它在底层实现了 SSH 协议给你封装好 SSHClient 和 SFTP 两个接口既可以执行命令也能传文件。fabric则在 paramiko 之上又包了一层任务和上下文管理适合做部署动作但坦白讲fabric 的 API 变化比较快新老版本用法差异大新手容易对着老教程踩坑。netmiko是面向网络设备的专门处理设备 CLI 的交互细节比如分页符、命令回显长度这类问题。你要维护一堆交换机路由器用 paramiko 裸连会很痛苦因为网络设备经常有分页和交互提示符netmiko 把这些细节都处理好了。这几个库的定位差异我用一张表格列出来库定位适用场景上手难度paramikoSSH 协议库服务器批量命令、文件传输中fabric基于 paramiko 的任务封装部署脚本、远程批量执行中netmiko网络设备交互库交换机路由器配置巡检、批量下发低中ansible自动化引擎/框架配置管理、多主机编排偏高Ansible 其实不算库它是一个独立的自动化框架用 Python 写的也支持插件扩展。如果你的环境允许免密 SSH写 playbook 确实很爽但如果只是临时处理几台机器Python 脚本反而更轻不用引入一套新的编排语法。2.3 系统信息采集不用再读 /proc写巡检脚本时系统信息采集是最基础的活儿。传统做法是去读/proc/loadavg、/proc/meminfo这些文件或者调free、df、uptime命令再解析输出。不是不行就是麻烦而且不同系统输出格式有差异。我后来的选择是直接用psutil这个库把所有系统指标都封装成了函数跨平台表现一致。import psutil print(psutil.cpu_percent(interval1)) print(psutil.virtual_memory().percent) print(psutil.disk_usage(/).percent)psutil还能拿进程列表、网络连接数做告警和排查非常顺手。后面第 4 章的健康巡检脚本我会基于它写一个完整的例子。2.4 通知与调度的库怎么选脚本跑完总要让人知道结果。标准库里的logging负责把日志写到文件smtplib负责发邮件。定时调用不推荐在 Python 进程里自己做除非是开发环境临时用。生产环境直接用 crontab 或者 systemd timer 更稳妥因为操作系统层面的调度不依赖你的脚本进程一直活着。进程内调度方案像schedule、apscheduler更适合开发环境或 Windows 服务器上不方便配计划任务的场景放到正式环境容易遇到进程被杀、时间漂移这些坑。3. 第一个实用脚本批量SSH执行命令3.1 先拆需求再写功能很多人一上来就写代码结果写到一半发现没想清楚要干嘛。批量执行命令这个脚本核心需求其实就三条能连接一批服务器、能执行指定命令、能把结果收集回来。看似简单但是放到二三十台机器上串行跑太慢并发太高又容易被服务器拒绝所以还得考虑并发度。基于这些我选了 paramiko 而不是直接用系统ssh命令原因就是 paramiko 的异常处理更可控你能区分是认证失败、连接超时还是命令执行失败。3.2 一个可用的paramiko批量执行脚本直接给一个能跑的版本假设你已经有 SSH 密钥服务器列表写在代码里。生产环境建议把列表挪到配置文件后面我会说怎么改。#!/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()几个关键点说一下。set_missing_host_key_policy(paramiko.AutoAddPolicy())的意思是自动接受首次连接的主机指纹这在批量环境里省事但在安全要求高的环境不建议这么干最好是预先把 known_hosts 配好。banner_timeout和auth_timeout用来防止某台机器 SSH 握手阶段卡死不设置的话脚本可能挂在某一台上。3.3 并发数不是越大越好ThreadPoolExecutor 很好用把串行改成并发只需要加几行。但max_workers别贪多我个人推荐 5 到 10 就够了。你要管理的是几十台服务器5 个并发处理起来就是一分钟以内的事如果设成 50很多开源防火墙或者商用设备会直接把你的 IP 拉黑。记住自动化是给运维减负不是给安全设备送素材。3.4 扩展文件分发和收集执行命令只是开始很多时候还要把安装包分发到各台机器或者把日志收集回来。这就要用 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()这里容易踩的坑是远程目录权限或者目录不存在。sftp.put不会自动创建远程目录你得先执行mkdir -p或者用sftp.mkdir逐级建目录。收集日志时更建议每台机器建一个子目录避免文件名冲突比如上面例子里的./collect/192.168.1.10_app.log。3.5 别把密码硬编码在脚本里很多初学者喜欢在脚本里写password123456我强烈不建议。脚本会放在服务器上甚至会被传到代码仓库里密码一旦泄漏就是安全事故。更好的方案是优先用 SSH 密钥如果一定要用密码就用getpass提示交互输入或者从配置中心读取。至少也要把密码放在环境变量里而不是写死在源码中。4. 巡检告警脚本从手动登录到自动出报告4.1 巡检指标先设计好代码才有意义在写巡检脚本之前先问自己一个问题你到底要关注哪些指标我见过不少巡检脚本输出一大堆数字最后没人看因为根本没有重点。最基础也最常用的指标无非是这几个指标建议阈值说明CPU 使用率 80% 持续 5 分钟长时间跑高需要关注系统负载 CPU 核心数负载过高说明队列积压内存使用率 85%接近耗尽易触发 OOM磁盘使用率 90%再往上可能影响写日志关键进程存活进程不存在告警服务挂了要第一时间知道关键端口监听端口不可达告警结合业务场景定义别把指标定得太死不同业务不一样。内存 90% 对缓存类服务可能很正常磁盘 80% 对备份机器也可能就该清理了。阈值先按经验给一版跑一段时间再调。4.2 本机巡检脚本实例下面这个脚本可以在单台服务器上跑用来采集和判断指标命中阈值就输出告警。实际使用时可以结合上一节提到的 paramiko把它推到远程机器上执行。#!/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(): # 取三次采样平均值避免瞬时峰值误报 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}%)这里有个细节CPU 使用率采样如果只取一次特别容易误报。运维机器经常有定时任务、日志压缩这种瞬时尖峰所以我的做法是连续采样三次取平均宁可慢几秒也别半夜发骚扰告警。4.3 阈值判断要连续采样别被瞬时尖峰骗了关于“持续多久才算报警”也可以用一个更简单的做法在内存里维护一个计数连续三分钟超过阈值才触发告警。不要一个采样点超标就发邮件不然晚上你会被短信轰炸。说实话告警灵敏度宁低勿高等你的监控系统每天发几十条信息没人看的时候真正的故障反而会被淹没。4.4 定时任务crontab还是systemd timer巡检脚本写好了剩下的就是定时执行。我最常用的是 crontab简单直接。下面是典型的写法*/5 * * * * /home/ops/venvs/ops/bin/python /home/ops/scripts/health_check.py /var/log/health_check.log 21这三处必须注意第一Python 解释器写绝对路径不要写python或者python3因为 cron 环境里的 PATH 很精简可能找不到命令第二脚本也写绝对路径cron 的工作目录不固定第三日志要重定向到文件否则脚本的输出会被 cron 的邮件系统吞掉找都找不到。systemd timer 更规范能配置依赖、随机启动延迟但需要写 service 和 timer 两个单元文件对小规模脚本来说稍微重了一点。我一般是脚本少用 crontab脚本多了再上 systemd timer。4.5 用smtplib发告警邮件告警怎么通知出去最传统也稳定的方式是邮件。用 smtplib 封装一个推送函数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 授权码不是邮箱登录密码。现在很多群机器人也有 Webhook 接口原理类似curl 一个 URL 就能推送相对邮件来说更实时。但团队内部协作邮件还是最通用的记录留痕也方便。5. 常见问题与排查实录5.1 “python命令不存在”或“pip找不到”这次热搜里就有很多类似的报错比如某个版本的pnpm不能被识别为 cmdlet 或者命令本质都是同一个原因执行文件的路径不在 PATH 环境变量里。Python 也一样Windows 上安装时没勾选“Add Python to PATH”就会出现python 不是内部或外部命令。Linux 上则是没装对应的包。解决办法很简单Windows 重新安装一次并勾选 PATH或者在系统环境变量里手动加Linux 就是安装python3-pip。排列组合如果python3存在但python不存在建个软链即可。如果pip找不到优先用python3 -m pip保证用对解释器。如果多个 Python 版本混在一起pip 装进了另一个版本用python3 -m pip install就不会错。5.2 SSH连接失败的各种原因批量连接最烦的就是某台机器突然连不上。常见报错和排查思路我整理成了速查表现象常见原因排查 / 解决Authentication failed用户名密码错误或密钥对不上检查用户名确认密钥是否在目标机的 authorized_keys 中Bad permissions密钥文件权限过宽chmod 600 ~/.ssh/id_rsaConnection refusedsshd 未启动、端口被防火墙拦检查 sshd 服务状态确认远程端口是否通Error reading SSH protocol banner握手超时或服务器响应慢设置banner_timeout和auth_timeout别用默认值等太久No host key type configured旧设备算法不兼容检查是否需要配置disabled_algorithmsConnection refused出现频率很高我排查的顺序是先 ping 看通不通再看端口再确认 sshd 是不是在监听。如果端口通但连不上大概率是防火墙的配置问题。5.3 exec_command卡住不返回paramiko 执行命令时如果忘记读stdout和stderr或者命令本身不退出脚本就会一直卡着。我的经验是每条命令都给timeout参数比如上面的例子写成exec_command(cmd, timeout15)超时之后报错退出。另外如果命令会产生大量输出一定要及时读取否则 SSH 通道的缓冲区满了客户端会阻塞住。还有一种情况是执行交互式命令比如top这种要持续输出的exec_command不适合要用invoke_shell来回写交互但那个更复杂尽量别碰。5.4 中文乱码与编码问题脚本输出中文乱码十有八九是编码没统一。远程服务器默认locale可能是POSIX或C输出的中文会变成乱码本地 Windows 控制台默认 GBKPython 默认 UTF-8直接打印也容易串。我在代码里读取输出时统一写成decode(utf-8, errorsreplace)这样即使个别字节不对也不会整个崩溃。另一个办法是在命令前面加export LANGen_US.UTF-8;让远程命令的输出尽量用 UTF-8 编码。5.5 venv在cron里不生效这个问题坑了无数人。你在终端里试脚本一切正常但 crontab 跑起来就报ModuleNotFoundError。原因很简单cron 的执行环境非常干净不会加载你的~/.bashrcvenv 也没被激活。解决方法是不要靠 activate直接在 crontab 里写 venv 的 Python 绝对路径。同时依赖库如果有变动要在 venv 里更新requirements.txt并且确认 cron 用的这个环境确实装有所有依赖。5.6 不要乱动系统Python又得重复一次真的不要在系统 Python 里全局 pip install。很多运维事故都是“顺手”升级了一个包结果系统的某个工具依赖旧版本直接罢工。RHEL 系的 yum 依赖 Python 2.7 是经典案例。所以所有业务脚本依赖一律进 venv系统 Python 保持安装时的状态。这是自动化运维的基本卫生习惯。6. 从脚本到工具几条实践经验写到这里我把这些年做自动化运维脚本的个人体会讲一讲没有大道理都是踩坑踩出来的。第一先小范围验证再全量执行。不管是批量执行命令还是分发文件先挑一台测试机跑通确认没有破坏性操作再放到生产批量里。我见过有人写完脚本直接全量重启服务结果脚本里少判断了一个条件整批机器一起出问题那场面是真的惨。把HOSTS列表先改成一台机器跑通了再放开。第二脚本要幂等能重复执行。幂等这个词听上去高级其实很简单同一个脚本跑一次和跑十次最终状态应该一致。比如分发文件之前先比较文件 hash合了就跳过创建用户之前先判断用户是否存在。自动化运维最怕的就是脚本没有防御性跑第二次就报错或者更糟跑第二次把数据覆盖了。第三日志比结果重要。任何脚本都应该有自己的日志至少记录什么时间、在哪台机器上、执行了什么命令、返回码是多少。不要只依赖print用logging写到固定文件方便事后回溯。运维行业的准则就是“执行过的操作要有留痕”否则出了问题你就是那个背锅的。第四参数化配置不要硬编码。服务器列表、用户名、阈值、路径全部放到配置文件或环境变量里。不然每次加一台机器都要改代码改完还得担心改错。简单做法是用argparse支持命令行传参再复杂一点就引入 YAML 配置文件。脚本通用性高了后面接手的人会感谢你。第五别追求一步到位。一个脚本能解决一个问题就很好了后续需求可以慢慢加。比如批量执行命令脚本先支持固定命令再支持传参再支持文件分发再支持并发控制。慢慢迭代出来的工具才最贴合实际场景一上来就想写个超级复杂的平台大概率烂尾。自动化运维脚本的价值不在于代码多花哨而在于把重复操作固化下来把容易出错的人为判断变成可重复的逻辑。我现在打开电脑很多操作已经不需要再登录服务器了前一天晚上把巡检脚本跑完第二天早上看结果就行。这种幸福感就是靠这些不起眼的小脚本积累出来的。你也不妨从今天的第一行import paramiko开始。
阅读完成 · 觉得有帮助?
咨询建站