三天,我把一台“能跑”的服务器变成了“可恢复”的系统
很多个人项目最初都以同一种方式开始:
把程序跑起来,看到页面能打开,接口能返回数据,就觉得部署完成了。
我也经历过这个阶段。但最近三天,我连续处理了配置恢复、模型接入、AI 平台部署、线上故障、安全收口和数据库灾备。做完之后,我对“服务已经上线”有了新的理解:
能运行只是起点;能排障、能重启、能备份、能恢复,才算真正属于自己。
这篇文章记录的不是某条命令,而是我如何用三天时间,把一套零散运行的个人技术设施整理成一个相对完整的系统。
第一天:恢复的不是文件,而是上下文
第一天的工作从一次重装后的恢复开始。
旧备份里有很多内容:助手身份设定、个人资料、长期记忆、历史日记,以及过去形成的执行习惯。最简单的做法是把整个目录覆盖回去,但这也最危险——旧配置可能覆盖当前环境,甚至影响已经运行的博客、数据库、SSH 和系统服务。
因此我没有进行整包还原,而是选择性恢复:
- 恢复身份与长期记忆;
- 保留当前主配置;
- 不覆盖博客和数据库;
- 不触碰 SSH 与系统服务;
- 对不确定的文件先归类,再决定是否启用。
这件事让我意识到,恢复系统和恢复文件不是一回事。
文件恢复关注“东西还在不在”,系统恢复关注“恢复后会不会破坏现在”。真正可靠的恢复必须理解依赖关系、配置边界和当前运行状态。
同一天,我还重新接入了多个模型,处理模型白名单、思考模式和认证失败。一次 HTTP 401 最终并不是什么复杂故障,只是配置中的认证信息没有正确生效。
但它提醒了我:错误提示往往只是表面,排障真正要确认的是完整链路:
客户端选择模型
↓
模型是否进入允许列表
↓
Provider 地址是否正确
↓
认证信息是否实际加载
↓
上游是否接受请求
只修改其中一处,不代表整条链路已经打通。
第二天:把 AI 多智能体平台部署到生产环境
第二天的目标更直接:部署一个完整的 AI 多智能体管理与对话平台。
它不是单页演示,而是由多个组件组成的应用:
- Next.js 和 React 提供前端界面;
- Fastify 提供后端 API;
- Prisma 管理数据模型和迁移;
- PostgreSQL 保存用户、Agent、房间和消息;
- 独立 Worker 消费任务并调用模型;
- PM2 管理 Node.js 进程;
- Nginx 负责 HTTPS 和反向代理。
整体链路大致如下:
浏览器
↓ HTTPS
Nginx
├── Web → Next.js
└── API → Fastify
↓
PostgreSQL
↑
Worker
↓
OpenAI 兼容模型服务
真正费时间的是环境差异
部署过程中,业务代码反而不是最麻烦的部分,真正消耗时间的是开发环境和服务器环境之间的差异。
首先是内存不足。Next.js 在生产构建期间进行类型检查时触发 OOM。为了完成构建,我临时增加交换空间,并将类型检查从构建流程中拆开。
这能解决眼前问题,但也留下了一项技术债:跳过构建期类型检查不等于类型错误不存在,后续仍需要单独执行类型检查。
其次是原生依赖兼容问题。Argon2 的预编译产物需要更新版本的 GLIBC,而服务器系统版本较旧,最终只能在服务器上从源码重新编译。
这两件事说明,所谓“本地能跑”,只证明代码适配了本地环境,并不证明它具备生产可部署性。
生产环境还会追问:
- 内存够不够?
- 原生依赖是否兼容?
- 进程退出后谁负责拉起?
- 密钥存在哪里?
- 数据库迁移是否可重复?
- 服务是否直接暴露公网?
上线不仅是返回 HTTP 200
平台最终完成了数据库迁移,建立了独立数据库与 15 张业务表,并启动 Web、API 和 Worker 三个进程。
同时,我补齐了几项基础安全措施:
- 使用安全 Cookie 管理会话;
- 对接口进行限流;
- 校验同源请求;
- 使用 Argon2 保存密码哈希;
- 使用 AES-256-GCM 加密模型密钥;
- 浏览器不直接接触 Provider 密钥;
- 服务只监听回环地址,由 Nginx 统一对外提供 HTTPS。
最后,DNS、Nginx、应用来源地址和 SSL 证书必须使用同一个域名。一个单复数拼写差异,就足以让登录、同源校验或证书验证出现问题。
系统不会理解“这两个名字看起来差不多”。它只认完全一致的配置。
第三天:从“已经上线”走向“出了事也能回来”
第三天主要在收尾,但这一天反而最接近真正的运维。
1. 修复混合部署下的 API 500
博客页面可以打开,但公网 API 返回 500。
排查后发现,前端仍把 API 请求转发到一个旧的 Docker 服务名。这个名字在原来的容器网络里可以解析,但当前前端已经改由 PM2 运行在主机上,主机环境并不知道那个容器服务名是什么,于是出现 DNS 解析失败。
问题的本质不是 API 挂了,而是部署架构变化后,旧配置没有同步更新:
过去:前端容器 → Docker 服务名 → 后端容器
现在:前端 PM2 → 主机网络 → 后端 PM2
修复方式是让 Nginx 直接把 /api/ 代理到本机后端,同时把源码中的备用地址改为回环地址,避免下次重新构建后故障复发。
这次排障给我的最大提醒是:
README 描述的架构、配置文件假设的架构和服务器实际运行的架构,可能是三件不同的事。
排障时必须以实时进程、监听端口、日志和请求链路为准。
2. 收口公网端口
应用已经由 Nginx 统一代理,前端和后端业务端口便没有必要直接暴露公网。
我做了双层收口:
- 应用进程只监听
127.0.0.1; - 防火墙删除对应业务端口的公网放行。
只改其中一层都不够稳妥。应用监听回环地址可以减少误暴露,防火墙规则则提供第二道边界。
3. 让进程能够在重启后回来
“PM2 显示 online”只能说明此刻在线,不能证明服务器重启后还能恢复。
因此我保存了当前进程清单,启用开机服务,并实际测试进程恢复。验证标准不是配置文件存在,而是重启管理服务后,前端、API 和 Worker 都能重新上线。
4. 给 PostgreSQL 建立备份闭环
以前只有零散备份,这远远不够。
我最终建立了如下机制:
- 每天自动备份全部业务数据库;
- 保存数据库角色和权限;
- 使用 PostgreSQL 自定义格式归档;
- 保存元数据、逐表行数和 SHA-256 校验;
- 备份完成后再原子写入完成标志;
- 自动轮换超过保留期的旧备份;
- 备份文件仅允许管理员读取。
但“备份命令执行成功”仍然不能证明数据能恢复。
于是又增加了每周恢复演练:系统读取最新备份,在临时 PostgreSQL 容器中恢复角色、数据库和数据,再逐表比对行数。演练环境不映射主机端口、不连接生产数据库,结束后自动删除临时容器和网络。
完整流程变成了:
生产数据库
↓ 自动逻辑备份
备份归档
↓ SHA-256 校验
隔离 PostgreSQL
↓ 恢复角色和数据库
逐表行数比对
↓
输出恢复成功标志
直到这一步,我才愿意说数据库“有备份”。
因为备份不是文件,而是一种经过验证的恢复能力。
三天里最重要的几个教训
1. 运行状态比文档更可信
文档可能过期,配置可能残留,只有当前监听端口、进程、日志和真实请求链路能说明系统正在如何工作。
2. 修改之后必须验证结果
“已经改了”不等于“已经生效”。每次修改至少要验证一层最终结果:HTTP 状态码、进程状态、监听地址、配置测试、数据库连接或恢复结果。
3. 安全不是最后加上的开关
限制监听地址、最小化数据库权限、保护环境变量、收口防火墙端口,这些都应该成为部署的一部分,而不是上线后有空再补。
4. 备份成功不等于恢复成功
没有校验、没有演练、与生产机同盘的备份,只能覆盖部分风险。真正的下一步仍然是建立异地加密副本,并从异地副本完成恢复验证。
5. 自动化不能替代判断
这三天里,很多工作可以自动执行,但哪些文件该恢复、哪些配置不能覆盖、什么时候需要暂停变更,仍然依赖判断。
工具负责提高执行效率,人负责决定边界和承担结果。
最后
三天前,我拥有的是一些“正在运行”的程序。
三天后,它们开始具备更完整的形态:有明确架构、有统一入口、有进程托管、有安全边界、有备份,也有经过隔离验证的恢复流程。
它仍然不完美。异地备份、源码版本管理、最小权限和完整类型检查都还需要继续补齐。
但这三天完成了一个重要转变:
我不再只关心服务能不能启动,也开始关心它为什么能启动、出了问题如何定位、服务器重启后能否回来,以及最坏情况下数据能否恢复。
这大概就是从“部署一个项目”走向“维护一个系统”的分界线。
