脚本写完、测试过了,最后一公里是交付:同事的机器上没有你的目录结构,怎么让他一行命令用上你的工具?这篇讲 Python 标准库自带的答案——zipapp,把整个项目打成一个 .pyz 文件,拷贝即用。文末附全系列回顾。
一、交付的三个常见姿势,各有各的尴尬
- 直接发
.py文件:单文件还好,两个以上模块就散了架;依赖装法靠口头传; - 发 git 仓库:让运维同事
git clone再pip install,对非开发岗门槛偏高; - 发 wheel/Docker:专业,但对「一个 200 行的巡检脚本」是杀鸡用牛刀。
zipapp 卡位在中间:一个文件、双击不了但 python xxx.pyz 一行能跑、可以内嵌解释器 shebang、标准库原生支持。特别适合「内部工具分发给装有 Python 的机器」这个运维最常见的场景。
二、zipapp 是什么:能执行的 zip
.pyz 本质是一个标准 zip 包,多两样东西:入口脚本 __main__.py,以及(可选的)解释器 shebang。Python 会把 .pyz 当脚本执行,实际就是执行包里的 __main__.py。
打包只要一行:
import zipapp
zipapp.create_archive("src目录", target="工具名.pyz", interpreter="python", compressed=True)
三、完整演示(demos/build_zipapp_demo.py)
把系列主角 monitor.py 打成 ops_monitor.pyz 并真实运行:
import shutil
import sys
import zipfile
from pathlib import Path
import zipapp
HERE = Path(__file__).parent
SRC = HERE / "opsmonitor_src" # 打包源:一个普通的 Python 包目录
PYZ = HERE / "ops_monitor.pyz" # 产物:单文件可执行归档
def main() -> None:
# 1. 准备包结构:把 monitor.py 复制成 opsmonitor 包
if SRC.exists():
shutil.rmtree(SRC)
(SRC / "opsmonitor").mkdir(parents=True)
(SRC / "opsmonitor" / "__init__.py").write_text("", encoding="utf-8")
shutil.copy(HERE.parent / "monitor.py", SRC / "opsmonitor" / "monitor.py")
# 入口文件:zipapp 运行时会执行它
(SRC / "__main__.py").write_text(
"from opsmonitor.monitor import main\nmain()\n", encoding="utf-8"
)
# 2. 打包:compressed=True 开启 zip 压缩
zipapp.create_archive(SRC, target=PYZ, interpreter=sys.executable, compressed=True)
print(f"打包完成: {PYZ.name}({PYZ.stat().st_size / 1024:.0f} KB)")
# 3. 验证它本质上就是个 zip
with zipfile.ZipFile(PYZ) as z:
names = z.namelist()
print(f"内部结构: {sorted(names)}")
# 4. 真实运行这个 .pyz——它就是我们的监控脚本
import subprocess
r = subprocess.run([sys.executable, str(PYZ)], capture_output=True, text=True, timeout=60)
print("\n== python ops_monitor.pyz 的真实输出 ==")
print(r.stdout.strip())
if r.returncode != 0:
print(r.stderr)
if __name__ == "__main__":
main()
四、真实运行输出
打包完成: ops_monitor.pyz(3 KB)
内部结构: ['__main__.py', 'opsmonitor/', 'opsmonitor/__init__.py', 'opsmonitor/monitor.py']
== python ops_monitor.pyz 的真实输出 ==
== 检查开始 2026-09-28 20:59:58 ==
CPU 使用率: 16.0%
内存使用率: 73.8%(已用 11.3 GB / 共 15.3 GB)
磁盘 C:\ 已用 92.3%
磁盘 D:\ 已用 70.4%
磁盘 E:\ 已用 5.6%
(未配置 MONITOR_WEBHOOK_URL,以上告警只写入了日志)
== 检查结束 ==
完整链路验证成功:monitor.py 被打成 3 KB 的单文件 → 内部就是标准 zip 结构 → python ops_monitor.pyz 直接跑出完整巡检——而且又抓到了 C 盘 92.3% 的真实告警(这台机器的 C 盘确实快满了,系列写到收官它还在坚持报警)。
opsmonitor_src/ 是特意写进演示的中间产物:真实的打包流程就是「先把散装脚本整理成规范的包结构,再归档」——目录即包、__main__.py 即入口,这个结构习惯比工具本身更值钱。
五、依赖怎么办:.pyz 管代码,不管 pip
要诚实地划清边界:zipapp 只打包你的代码,第三方库不进去(进去了也未必能正常 import——有 C 扩展的包尤其如此)。交付规范是两条腿:
- 代码:
ops_monitor.pyz; - 环境:旁边放一个
requirements.txt(psutil>=5.0),目标机器pip install -r requirements.txt一次装齐。
想更进一步,两条路:把纯 Python 依赖一起塞进 .pyz(装到临时目录再打包,纯 Python 库可行);或者直接上 PyInstaller/Nuitka 出免 Python 环境的单机可执行文件——后者超出标准库范畴,但思路同源:交付物越小、环境依赖越显式,工具越活得久。
六、全系列回顾:十篇攒下了一套什么
【基础层】单机先稳住
01 监控告警 psutil 读指标 + logging 轮转 + Webhook,脚本首跑抓到真实磁盘告警
02 日志分析 流式逐行 + 正则 + Counter,GB 级日志稳稳分析;踩过「捕获组是字符串」的坑
03 磁盘治理 du 等价物 + 扫描/删除分离 + dry-run 铁律,垃圾文件一个不误杀
04 进程守护 识别/观测/拉起闭环,诚实讨论了 systemd 才是正解
↓
【进阶层】从一台到一批
05 健康检查 TCP 探活 + 失败分类 + 线程池并发,5 目标 4051ms → 2021ms
06 任务调度 schedule vs APScheduler,max_instances 防重叠,踩过「任务定时自杀」的坑
07 批量远程 paramiko 安全三原则 + 超时纪律 + 并发骨架 + 诚实的适用边界
↓
【工程层】脚本能带得走、靠得住
08 配置渲染 一份模板 + 一份清单 + StrictUndefined,错误提前到渲染期
09 脚本测试 真实 RED→GREEN,monkeypatch 隔离 psutil,测试即发布闸门
10 打包交付 zipapp 单文件分发,代码与环境依赖显式分离
串起来看,这个系列其实只讲了一件事:把「人肉、重复、靠记忆」的运维动作,改造成「自动、可验证、有防线」的工程活。每篇的演示都在真实环境跑过,每篇都至少记录了一个真实踩坑——运维能力的一半,来自知道什么东西会坏。
七、下一步:从工具到体系
十篇之后的三条进阶路:
- 横向规模化:07 篇的 paramiko 到几十台就是极限,下一站是 Ansible(playbook 把 08 篇的模板渲染 + 07 篇的批量执行产品化);
- 纵向平台化:01/05 篇的脚本输出接 Prometheus + Alertmanager,指标存储、告警路由、静默策略全部专业化;
- 纵深工程质量:09 篇的最小测试扩展成 CI 流水线,10 篇的 zipapp 升级为带版本号的发布流程——工具有了生命周期,才算资产。
工具会过时,库会更名,但「先读现状、小步修改、跑校验、如实报告」的做事方式不会。这就是这个系列最想交付的东西。
本篇代码:demos/build_zipapp_demo.py,已在 Python 3.13.15 / Windows 真实运行,输出原样收录。全系列源码结构见总目录篇。