Python技术迷

fabric,一个神奇的 Python 库!

命令能跑,脚本也能执行,但一到批量机器上就开始拧巴:有的超时,有的环境变量不对,有的明明连上了,命令回显还不完整。 这种活儿,纯 subprocess 硬怼,后面基本都会写成一锅粥。 我后来碰到这类发布、拉日志、批量改配置的脏活,第一反应通常不是先堆 shell,而是把 fabric 掏出来。

fabric 这个库,说白了就是把“登录机器执行命令”这件事,收拾得像回事一点。它不是给你造一个复杂运维平台的,反而更适合那种团队里经常会出现的半自动场景:临时发版、批量重启、拉几台机器的错误日志、顺手做个配置校验。小,但很好使。

先看个最常见的场景,批量查看服务状态:

from fabric import Connection

hosts = ["10.10.1.21", "10.10.1.22", "10.10.1.23"]

for host in hosts:
    conn = Connection(
        host=host,
        user="deploy",
        connect_kwargs={"key_filename": "/home/work/.ssh/id_rsa"},
    )
    result = conn.run("systemctl is-active order-service", hide=True, warn=True)
    print(f"{host} -> {result.stdout.strip() or result.stderr.strip()}")

这段代码没什么花活,但比你手工 ssh 一台台敲,已经省事太多。 而且 warn=True 这个地方我一般都会带上。线上机器的状态检查,本来就不能默认“全成功”,不然有一台挂了,脚本直接中断,后面的信息全丢了,排查反而更烦。

再往前走一步,很多人用 Fabric,喜欢把它当成“远程执行器”。这用法不算错,但有点亏。它真正顺手的地方,是把一组重复操作整理成任务。

比如发版时,我经常会把“上传包、解压、切软链、重启、验活”串起来:

from fabric import task
from invoke import UnexpectedExit

APP_DIR = "/data/apps/invoice-service"
PKG = "invoice-service.tar.gz"

@task
defdeploy(c):
try:
        c.put(f"./dist/{PKG}", f"{APP_DIR}/{PKG}")
        c.run(f"cd {APP_DIR} && tar -xzf {PKG}")
        c.run(f"cd {APP_DIR} && ln -sfn current_20260324 current")
        c.run("systemctl restart invoice-service")
        c.run("sleep 2 && curl -fsS http://127.0.0.1:8080/health")
        print(f"{c.host} 发布完成")
except UnexpectedExit as e:
        print(f"{c.host} 发布失败: {e.result.command}")
raise

命令行直接跑:

fab -H 10.10.1.21,10.10.1.22 deploy

这种写法的好处,不是“优雅”,是复盘的时候你知道自己到底干了什么。 尤其是线上出过事之后,你会特别烦那种散落在聊天记录、shell 历史、个人脑子里的发布动作。Fabric 至少能把动作固化下来,谁发版,都是这一套。

再说一个我更常用的场景:拉日志。 很多故障不是服务挂了,是某几台机器行为不一致。你这时候最怕来回 ssh,看完第一台忘了第二台。Fabric 批量抓关键日志,比人肉翻快不少。

from fabric import Connection

deftail_error(host):
    conn = Connection(host=host, user="deploy")
    cmd = "grep 'TimeoutError\\|Connection refused' /data/logs/app.log | tail -n 20"
    r = conn.run(cmd, hide=True, warn=True)
    print(f"\n===== {host} =====")
    print(r.stdout if r.stdout else"没找到关键错误")

for host in ["10.10.1.21", "10.10.1.22"]:
    tail_error(host)

这里面有个很实际的点: Fabric 不是替代日志平台的。别动不动就把它吹成运维神器。日志平台、监控平台、CI/CD 这些东西它都替不了。它更像一把顺手的扳手——平台没覆盖到的缝隙,或者你临时要查几台机器的时候,它特别顶用。

当然,它也不是没坑。

第一个坑是超时。远程命令一旦卡住,比如脚本等输入、服务启动很慢,任务会一直吊着。这个时候别傻等,超时得自己设:

result = conn.run(
"python3 /data/scripts/rebuild_cache.py",
    hide=False,
    warn=True,
    timeout=60,
)

第二个坑是环境。你本地执行没问题,远程一跑报 command not found,十有八九不是命令没装,是非交互式 shell 没带上你的环境变量。这个我第一眼通常先怀疑 .bashrc、.profile,不是先改代码。

第三个坑是并发。机器一多,串行跑会慢得很明显。但我一般不会上来就并发打满,先确认命令是否幂等。尤其是重启、迁移、清缓存这种动作,脚本写快了不值钱,写炸了才值钱。

所以我对 Fabric 的判断一直很直接: 它不神,也不重。 但只要你手上有一批 Linux 机器,平时总要执行那些“半自动、重复、容易忘步骤”的活儿,它就值得装一个。比纯 shell 稍微规整一点,比上完整平台轻很多,中间这块空档,它补得挺舒服。

很多库的问题不是不能用,是太容易被拿去干不该它干的事。Fabric 也是。 拿它写几套稳定的批量任务,挺好。 拿它硬凑成发布平台,后面大概率还是要拆。