跳转至

上线之后 — CI/CD、平台部署、生产运维与伸缩的边界

这一章讲什么: 「部署成功」不是终点,是运维的起点。 流水线替你守质量门(类型检查→测试→构建),平台替你管机器, 但连接池、备份、迁移顺序、安全中间件的次序、账单——这些没人替你操心。 读完你会有一张上线清单,以及一张「什么时候才需要 Kubernetes」的判定卡。

1. CI/CD:依赖链与质量门

GitHub Actions 的结构三层:workflow(什么时候跑)→ job(并行/串行的 任务块)→ step(命令或社区 action)。on: push/pull_request 定义触发器; strategy.matrix 让同一段步骤对 api 和 frontend 各跑一遍(并行两份)1。

四个 job 排成依赖链,这是本章第一个要学的形状1:

  lint-and-typecheck(matrix: api+frontend)
        │ needs
        ▼
  ┌─────────────┬──────────────┐
  test-api        test-frontend
  (带真 Postgres    (npm run build
   容器做服务!)      验证产物构建)
        └──────┬───────┘
               ▼ needs [test-api, test-api]
        build-docker(两个镜像,gha 缓存
                      命中时 5 分钟 → 30 秒)

要点三个。测试 job 用真数据库:workflow 的 services: 段起一个 PostgreSQL 容器,health check 等它真就绪——「mock 库能过的测试, 真查询可能生产才炸」1。缓存:cache: 'npm' 省 60–80% 安装时间; Docker 层走 cache-from: type=gha。分支保护是把 CI 变成门的最后 一颗螺丝:main 设为「status checks 全过才许合并」——从此无论谁, 同样的检查跑在每一个改动上2。

2. 平台:推代码即上线

选型一张表(Railway 最易上手/Render 价格可预测/Fly.io 全球低延迟/ VPS 全自控);三大云(ECS/Cloud Run 们)被刻意排除——学习阶段 别淹在云厂商的控制台里,概念以后平移3。Railway 路径四步: 加一个 PostgreSQL(托管、自动给连接串)→ 从 GitHub 仓库加 API 服务 (检测到 Dockerfile 自动构建)→ 环境变量进平台面板 → 加前端服务 (VITE_API_URL 是构建参数;部署后改它不生效——变量烘焙在 构建产物里,改地址必须重建前端)。最后回头把 API 的 CORS_ORIGIN 改成前端的线上地址4。

冒烟测试四连(上线后一分钟内做完,别等用户替你测):/healthz 200、 /readyz 200、打开前端看控制台、跑一条真实数据流(登录→建一条记录)、 翻平台日志找启动/迁移/CORS 错误4。VPS 路线的现代化简版: Caddy 当反代——自动申请并续期 Let's Encrypt 证书,告别 「nginx + certbot + cron」的三件套时代5。

3. 生产数据库:三条会要你命的坑

只准 prisma migrate deploy——生产禁用 migrate dev、禁用 db push;deploy 只补跑未应用的迁移,不建新的、不删表、不问确认。 迁移失败会被 _prisma_migrations 表标记为 failed,只能人工解决; 所以部署挂了先看日志,别反复重发6。

连接池会先于你的代码倒下。三实例 × 各自的池,很容易顶到 PostgreSQL 默认 100 连接上限;容器里池大小还可能按宿主 CPU 数算, 虚高。连接串加 ?connection_limit=5 显式收;大了上 PgBouncer。 坑中之坑:迁移不要走连接池——advisory lock 不被池支持; 平台给了 pooled/direct 两条连接串时,应用用 pooled、迁移用 direct。 池配错的典型死状:「too many connections」,高负载下发作, 长得就像数据库 itself 挂了7。

备份要试恢复:pg_dump 定时 + 异地存放只是上半场;「从没恢复过 的备份等于没有备份」——定期真恢复一次8。生产 seed 的规矩: 必须存在的数据(初始管理员、默认分类)写进迁移 SQL——版本化、 部署时自动应用;prisma db seed 是开发工具,生产禁用8。

4. 生产安全:次序就是安全

几条铁律:密钥永不为每个环境复用(开发/预发/生产各一套),离职即轮换; 栈跟踪和内部路径永不进 API 响应9。

npm audit 进 CI:学习项目可以 --audit-level=high + continue-on-error: true(不被深层传递依赖的低风险公告卡死部署), 但「没人看的警告等于没有检查」——生产团队要么提到 critical 并硬失败, 要么挂进定人负责的维护流程10。

中间件次序(一个真实的生产 bug 发生器)11:

  app.set('trust proxy', 1)   ← 最前:平台反代在转发真实 IP,
                                不设则限流把全站用户当一个 IP 封
  helmet()                    ← 安全头
  cors({ origin, credentials })  ← 注意尾部斜杠!origin 不带 /,
                                一个字符错配 = 全部跨域静默失败
  healthRoutes                ← 健康检查在限流之前,平台才能自由轮询
  /api/auth 的严限流
  /api 的宽限流               ← 认证端点会被两个限流叠加;
                                想避免就把 auth 路由挂载出宽限流

多实例部署还有一坑:express-rate-limit 默认内存计数,两实例=每人 两个配额;多实例要接 Redis store11。CORS 是部署后最高频的故障: 前端能开、API 静默失败 → 看控制台,逐字符核对 origin11。

5. 伸缩的边界:Compose 之后是什么,什么时候才轮到 K8s

单机 Compose 的天花板:机器一崩全灭;流量翻倍没法拆两台。编排器 解决的问题就是跨机调度。知识地图(都是「知道存在」级)12:

概念 一句话 Compose 对应
Pod 最小部署单元(≈ 被托管的 docker run) 一个 service 容器
Deployment 「N 个副本永远活着」;崩了自动补;发新版滚动替换 services: 本体
Service Pod 来来去去,网络地址不变 ports:
Ingress 一组机器(集群)对外流量的反代 无

Deployment 清单里能看到前面所有伏笔兑现:readinessProbe 打 /readyz(临时库故障 = 摘流量,不必重启)、livenessProbe 打 /healthz(进程卡死 = 重启);resources requests/limits 防一个 疯 Pod 拖垮整节点;密钥走 secretKeyRef(含明文 Secret 的清单 别进 git)12。迁移在 K8s 里是单独的 Job 或流水线一步—— 三个 Pod 同时各跑一遍迁移,就是事故13。

判定卡:Compose 够用就不要 K8s。 它值得的场景只有四种: 按流量自动扩、多区域、微服务间复杂通信、强 IaC 合规要求;否则 PaaS 更简单也常常更便宜。要练手用 minikube/kind/Docker Desktop 的内置集群;真上生产用托管 K8s(GKE/EKS/AKS),控制面别自己养13。

IaC(Terraform/OpenTofu/Pulumi):点按钮建出来的环境没法复现给 staging、没法交给队友、没法灾后重建——把基础设施写成代码, 走 PR 评审。TypeScript 开发者看 Pulumi:基础设施用你已经在用的语言写14。 Aspire 补的是另一层缺口:多服务开发的本地体验统——一个仪表板看 全部服务的日志、健康、端口,不取代 Docker/K8s14。

6. 上线之后:更新策略与账单

更新即推 main:CI 过→平台滚动部署(新版本通过健康检查后切流量, 旧版本确认退场才停)。三种进阶策略存个名字:blue/green(双环境瞬时 切换、秒级回滚、双倍成本)、canary(先放 5% 流量观察)、feature flags(部署与发布解耦:代码上线但不激活)15。

迁移与更新的次序铁律:迁移先跑,代码后发(所以启动脚本先 migrate deploy 再起服务:迁移失败则旧版继续接客)。回滚的真相: 代码能回滚,加过列的表回不去——回滚代码不等于回退 schema。 解法:expand-contract——第一步迁移只加新列(expand),第二步发 用新列的代码,第三步再删旧列(contract);新老代码在过渡期共用 同一 schema,回滚才安全。绝不把破坏性 schema 变更和业务代码变更 塞进同一次部署15。

成本章给了真实数字感的对照(2026 中时点,会过期):轻量 API 空闲时 80–150 MB 内存、半核 vCPU 就够;前端静态托管≈免费;PostgreSQL 才是 重头(256 MB–1 GB 足矣)。月费量级:Railway $10–20、Render ~$14、 Fly.io $2–6 起、Hetzner VPS €4–8(代价是全都自己管)。学习期的省钱 四招:不用就暂停、容器设资源上限、盯用量面板、能用免费层就别付费16。

7. 作者的判断与证据

  • 「真库测试 > mock 测试(在 CI 里)」是明确的质量判断1。
  • 「Compose 够用就不上 K8s」给了四条具体触发条件,不是模糊劝退13。
  • 「警告没人读等于没有检查」「没恢复过的备份不算备份」是运维谚语级 判断,也是全书最该带走的句子108。
  • 「前向迁移优于回滚」+ expand-contract 是行业通行最佳实践,与 Prisma 官方建议一致。

8. 边界与局限

  • Kubernetes 全部停留在概念地图( knowingly——本书定位是 「知道存在、何时用」);
  • 多实例会话一致性、分布式限流/队列只提了 store 选项,没展开;
  • 合规与审计(SOC2、数据驻留)只在托管认证服务处侧面提到;
  • 平台价格、免费层、GitHub Actions 计费都是强时点信息(2026 年中), 引用前必查当下文档。

判断(我们的,不是书里的): 本章最健康的姿态是克制: K8s/IaC/Aspire 三章只给「认知地图 + 触发条件」,没有为凑深度而 塞操作手册——对一个 20 章的教学项目,「知道自己还不知道什么」 比「照抄一份 YAML」有价值得多。「回滚的真相」(代码回得去、 schema 回不去)则是全章含金量最高的一段:它把「部署顺序」从 操作习惯升格为设计约束。 如果错,会错在: 「PaaS 足够」的口径对小团队成立,但 2026 年 出现了「PaaS 定价飙升 + 云原生工具平民化」的反向趋势(如 Coolify/Dokku 这类自托管 PaaS),「何时离开 PaaS」的边界可能 比书中画的更模糊。

9. 可带走的

  1. CI 依赖链:类型检查 →(真库)测试 → Docker 构建;分支保护让 CI 成为门。
  2. 测试 job 用 services: 起真 PostgreSQL——mock 库过的测试可能生产才炸。
  3. 平台部署四步;VITE_API_URL 是构建参数,改了必须重建前端。
  4. 上线冒烟四连:healthz/readyz/控制台/真实数据流。
  5. 生产迁移只准 migrate deploy;失败看日志,别反复重发。
  6. 连接池:实例数×池大小会顶爆默认 100;迁移走 direct 不走 pooled。
  7. 备份要真恢复一次;必须数据进迁移 SQL,生产禁用 db seed。
  8. 中间件次序:trust proxy → helmet → cors → 健康检查 → 认证限流 → API 限流。
  9. 回滚的真相:代码回得去、schema 回不去;破坏性变更与代码变更分两次部署; expand-contract 三步走。
  10. Compose 够用不上 K8s;触发条件是自动扩/多区域/微服务/强合规。

10. 原文地图

主题 原书章 原文位置
workflow/job/step 与 matrix Building our CI pipeline text/141-fm-building-our-ci-pipeline.txt:29(搜「matrix:」)
services 真库测试 Building our CI pipeline text/141-fm-building-our-ci-pipeline.txt:193(搜「real PostgreSQL database」)
分支保护 Branch protection text/142-fm-branch-protection.txt:9(搜「Require status checks」)
平台选型与排除三大云 Choosing a platform text/143-fm-choosing-a-platform.txt:83(搜「deliberately absent」)
Railway 四步与重建前端 Deploying to Railway text/144-fm-deploying-to-railway.txt:49(搜「trigger a rebuild」)
冒烟测试 Deploying to Railway text/144-fm-deploying-to-railway.txt:61(搜「smoke test」)
Caddy 自动证书 Deploying with Docker to a VPS (advanced) text/146-fm-deploying-with-docker-to-a-vps-advanced.txt:19(搜「Caddy」)
生产迁移铁律 Running migrations in production text/147-fm-running-migrations-in-production.txt:7(搜「Never」)
连接池与 PgBouncer Connection pooling text/148-fm-connection-pooling.txt:29(搜「advisory locks」)
备份要试恢复 Backups text/149-fm-backups.txt:17(搜「never restored」)
生产 seed 规矩 Seeding production data text/150-fm-seeding-production-data.txt:23(搜「Never use」)
trust proxy 与限流次序 Rate limiting text/153-fm-rate-limiting.txt:15(搜「trust proxy」) · text/155-fm-putting-security-middleware-together.txt:7(搜「The order in which you register matters.」)
CORS 尾部斜杠 CORS in production text/154-fm-cors-in-production.txt:22(搜「trailing slashes」)
没人读的警告 Dependency security text/156-fm-dependency-security.txt:42(搜「nobody reads」)
K8s 四概念 Kubernetes core concepts text/159-fm-kubernetes-core-concepts.txt:23(搜「smallest deployable unit.」) · readiness/liveness 见 text/160-fm-from-docker-compose-to-kubernetes.txt:29(搜「readinessProbe」)
迁移是 Job From Docker Compose to Kubernetes text/160-fm-from-docker-compose-to-kubernetes.txt:184(搜「separate Job」)
Compose 够用的判定 When to use Kubernetes text/161-fm-when-to-use-kubernetes.txt:5(搜「not yet」)
IaC 三工具 Infrastructure as Code (IaC) text/162-fm-infrastructure-as-code-iac.txt:35(搜「Terraform」)
滚动/蓝绿/金丝雀/feature flags Updating your deployed application text/165-fm-updating-your-deployed-application.txt:23(搜「rolling」) · text/165-fm-updating-your-deployed-application.txt:109(搜「expand-contract」)
回滚的真相 Updating your deployed application text/165-fm-updating-your-deployed-application.txt:97(搜「does not remove the column」)
成本量级 Cost awareness text/166-fm-cost-awareness.txt:15(搜「80-150 MB」)


  1. 出处:「Building our CI pipeline」第 3–51 段(text/141-fm-building-our-ci-pipeline.txt:29,搜「matrix:」;:35,搜「real PostgreSQL database」)。 ↩↩↩↩

  2. 出处:「Branch protection」第 3–15 段(text/142-fm-branch-protection.txt:9,搜「Require status checks」)。 ↩

  3. 出处:「Choosing a platform」第 21–29 段(text/143-fm-choosing-a-platform.txt:83,搜「deliberately absent」)。 ↩

  4. 出处:「Deploying to Railway」第 21–37 段(text/144-fm-deploying-to-railway.txt:49,搜「trigger a rebuild」;:31,搜「smoke test」)。 ↩↩

  5. 出处:「Deploying with Docker to a VPS (advanced)」第 9–25 段(text/146-fm-deploying-with-docker-to-a-vps-advanced.txt:19,搜「Caddy」)。 ↩

  6. 出处:「Running migrations in production」第 3–19 段(text/147-fm-running-migrations-in-production.txt:7,搜「Never」;:17,搜「_prisma_migrations」)。 ↩

  7. 出处:「Connection pooling」第 3–31 段(text/148-fm-connection-pooling.txt:29,搜「advisory locks」;:25,搜「too many connections」)。 ↩

  8. 出处:「Backups」第 5–9 段与「Seeding production data」第 13–15 段(text/149-fm-backups.txt:17,搜「never restored」;text/150-fm-seeding-production-data.txt:23,搜「Never use」)。 ↩↩↩

  9. 出处:「Environment and secrets」第 3–13 段与「Additional security considerations」第 25–31 段(text/151-fm-environment-and-secrets.txt:13,搜「Never commit」;text/157-fm-additional-security-considerations.txt:55,搜「Never expose stack traces」)。 ↩

  10. 出处:「Dependency security」第 3–25 段(text/156-fm-dependency-security.txt:42,搜「nobody reads」)。 ↩↩

  11. 出处:「Rate limiting」第 5–19 段、「Putting security middleware together」第 3–31 段、「CORS in production」第 9–17 段(text/153-fm-rate-limiting.txt:15,搜「trust proxy」;text/155-fm-putting-security-middleware-together.txt:7,搜「The order in which you register matters.」;text/154-fm-cors-in-production.txt:22,搜「trailing slashes」)。 ↩↩↩

  12. 出处:「Kubernetes core concepts」第 3–21 段与「From Docker Compose to Kubernetes」第 25–45 段(text/159-fm-kubernetes-core-concepts.txt:23,搜「smallest deployable unit.」;text/160-fm-from-docker-compose-to-kubernetes.txt:29,搜「readinessProbe」)。 ↩↩

  13. 出处:「From Docker Compose to Kubernetes」第 47–51 段与「When to use Kubernetes」第 3–15 段(text/160-fm-from-docker-compose-to-kubernetes.txt:184,搜「separate Job」;text/161-fm-when-to-use-kubernetes.txt:5,搜「not yet」)。 ↩↩↩

  14. 出处:「Infrastructure as Code (IaC)」第 3–45 段与「A glimpse of Microsoft Aspire」第 3–15 段(text/162-fm-infrastructure-as-code-iac.txt:35,搜「Terraform」;text/163-fm-a-glimpse-of-microsoft-aspire.txt:31,搜「The Aspire dashboard gives you a unified」)。 ↩↩

  15. 出处:「Updating your deployed application」第 3–55 段(text/165-fm-updating-your-deployed-application.txt:23,搜「rolling」;:33,搜「does not remove the column」;:41,搜「expand-contract」)。 ↩↩

  16. 出处:「Cost awareness」第 3–45 段(text/166-fm-cost-awareness.txt:15,搜「80-150 MB」)。 ↩