过去几年我在三家云厂商攒了六台 VPS,跑着 DNS、证书签发、文件分享、监控探针之类的十几个服务。 每个服务的部署方式都不一样:有的是 docker run 加一串长到不敢看的参数, 有的是某个凌晨三点写下的 shell 脚本,还有的只记得「好像是在那台机器上跑着的什么东西」。 上个月终于下定决心,把它们全部收编成一份 docker compose 编排。这篇是整理过程的记录。
临时脚本的隐性成本
临时脚本的问题不在于「能用」,而在于它只记录了当时的快照。 半年后你想改一个环境变量,得先考古:这个脚本现在还有效吗?容器是它在管,还是我后来手动改过? 我有一次重启了一台机器,结果三个服务没起来——它们全靠某个早就被清出 crontab 的脚本拉起。 那次事故之后我意识到,「能跑」和「可控」之间隔着一整套声明式配置。
收编的第一步很朴素:给每台机器建一个目录,每个目录里只放两样东西——compose.yaml 和一个 .env。规则只有一条:任何服务的任何状态变化,都必须先改文件,再执行命令。不允许反向操作。
一份典型的 compose 片段
整理后的文件长得很无聊,这正是我想要的效果。以监控探针为例:
services:
probe:
image: probehub/agent:latest
restart: unless-stopped
env_file: .env
networks:
- probe-net
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:9100/health"]
interval: 30s
retries: 3
networks:
probe-net:
name: probe-net几个刻意的选择:restart: unless-stopped 保证机器重启后服务自己回来; 每个服务都有 healthcheck,状态用 docker compose ps 一眼看穿; 所有机密只进 .env,compose.yaml 本身可以直接进 git。 没有任何魔法,这就是重点。
声明式配置的回报
整理完之后,收益比我预期的更实际:
- 迁移变便宜了。上个月我把一个服务从东京挪到法兰克福,全程就是拷目录、
compose up -d、切 DNS,总共四十分钟,大部分在等 TTL。 - 升级有了后悔药。改一行 image tag,
compose up -d,出问题git revert再来一次。以前升级前要焚香祷告的日子结束了。 - 审计变成了 diff。想知道「这台机器现在和三个月前有什么不一样」?
git log会告诉你,而不是让你去翻 shell 历史。
也有没解决的:跨机器的编排还是靠人肉,每台机器的 compose 文件是独立的,没有也不打算上 Swarm 或 K8s—— 六台机器的规模配不上那份复杂度。声明式的边界划在自己能背下来的范围内,刚刚好。
备份:声明式不等于不用备份
需要泼一盆冷水:compose 文件描述的是「怎么跑」,不是「数据在哪」。 我现在的备份策略分两层——配置文件本身就是 git 仓库,每台机器每天 push 到私有远端; 数据卷则按服务分级:数据库类的用应用自带的 dump 工具每晚导出,同步到另一台异机厂商的 VPS 再冷备一份; 纯缓存类的卷干脆不备份,丢了重建。
备份的验收标准我定得很狠:每年挑一个服务做一次真实还原演习,在一台干净机器上从 git 仓库和备份数据把它完整拉起来,全程计时。 今年演习的结论是四十分钟恢复到可用状态——比去年快了一倍,因为这次的 runbook 就是 compose 文件本身。
「无聊」是最高赞美
整理完之后我最强烈的感受是:这个系统变得很无聊。没有哪个服务需要我记住它的脾气, 没有哪台机器是「特殊的」,重启、升级、迁移全是同一条路径。 曾经我以为基础设施的乐趣在于折腾,现在我觉得乐趣恰恰在于不用折腾—— 晚上睡得着,白天不用盯,出问题了五分钟能定位。
如果你的服务器上还躺着几个「当时跑通了就再没敢动」的脚本,找个周末把它们写进 compose 文件吧。 过程毫无惊喜,而毫无惊喜正是这件事的全部意义。