背景与问题界定

某 SaaS 创业公司的早期技术栈完全构建在 Docker Compose 之上:一台 32C/128G 的裸机服务器运行着 20 多个容器,通过 depends_on 控制启动顺序,所有数据卷挂载在 NFS 共享存储上,服务发现依赖静态 linksnetwork_mode: host。随着业务增长到日处理 100 万请求,这台"胖单体"服务器成了漂移的单点故障——一次 Docker daemon OOM 导致全站宕机 45 分钟。更糟糕的是,由于所有服务共用一个网络命名空间,端口冲突频发,版本升级无法滚动更新,回滚必须在 10 分钟内重装所有容器。Kubernetes 迁移势在必行,但需要一套渐进式、可回滚的迁移方案。

目标拆解与工程约束

  1. 零停机迁移:迁移过程中不能有超过 30 秒的服务中断窗口,用户请求必须在 Docker Compose 和 Kubernetes 两个"世界"之间平滑切换,支持新旧基础设施并行的灰度过渡期。
  2. 配置无感知适配:现有的 .env 文件、Compose 中的 environment 变量和 volumes 绑定挂载必须在 Kubernetes 中以等效语义运行,不能要求开发团队大量修改应用代码来适配新的运行环境。
  3. 有状态服务平滑迁移:MySQL、Redis 和 RabbitMQ 等有状态服务的数据不能丢失,需要在 Kubernetes 的 StatefulSet 中重建并同步数据,同时保证混合运行期间数据一致性。
  4. 成本可控性:从单机 Compose 到多节点 K8s 集群,基础设施成本预期会增加,必须通过合理的 namespace 规划、节点池划分和资源 requests/limits 策略控制溢出成本增长在 30% 以内。

方案设计

采用"绞杀者模式"(Strangler Fig Pattern)分阶段迁移。第一阶段是网络和数据平面的打通:在 Compose 服务器和 K8s 集群之间通过 WireGuard 组建扁平化 overlay 网络,利用 CoreDNS 的双向 DNS 解析让双方服务能互相发现。K8s 集群中的 Service 通过 externalIPsmetallb 暴露,Compose 服务通过 extra_hosts 指向 K8s Service IP。

第二阶段从无状态服务开始迁移。将 Compose 中的 webapi 服务的 Dockerfile 转化为 K8s Deployment + Service + ConfigMap。利用 kompose convert 自动生成初始 Kubernetes Manifest,但必须人工审核每个字段——Kompose 生成的 volume 策略通常假设 hostPath,需要替换为 PVC 或 emptyDir。配置变量通过 ConfigMap 和 Secret 管理:

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
data:
  DB_HOST: "mysql-service.database.svc.cluster.local"
  REDIS_URL: "redis://redis-service.database.svc.cluster.local:6379"

第三步是 Ingress 统一入口。将原有的 Nginx Compose 容器的反向代理配置提取出来,转化为 K8s Ingress 资源。选择 ingress-nginx 作为 Ingress Controller,将 proxy_pass 指令映射为 Ingress 的 backend,保留 CORS、限流和 TLS termination 配置。

有状态服务的迁移采用备份恢复 + 数据同步的方式:在 K8s 中使用 operator(如 mysql-operatorrabbitmq-operator)启动新集群,通过主从复制从 Compose 中的旧服务同步数据,确认一致后切换 DNS 并下线旧服务。

实施路径与关键决策

  • 第一步:搭建 3 节点 K8s 集群(K3s 轻量级发行版,适合从 Compose 升级的场景),配置 Flannel 网络和 Rancher Local Path Provisioner 提供本地持久化存储。
  • 第二步:运行 kompose convert -f docker-compose.yml -o k8s-manifests/ 生成初始 Manifest,但废弃所有 volumeservice 部分,人工重建符合 K8s 最佳实践的 YAML。
  • 第三步:按"无状态 Web 服务 -> 后台 Worker -> 缓存层 -> 数据库"的顺序逐步迁移,每个服务在一周的灰度观察期后切换流量,保留旧 Compose 服务作为逃生通道。
  • 第四步:使用 ArgoCD 实现 GitOps 部署,将所有 Manifest 纳入 Git 仓库,配置 PreSync hook 进行数据库 schema 迁移检查,PostSync hook 进行 smoke test。

验证指标与可持续迭代

迁移成功的量化标准:服务可用性从 Compose 时代的 99.5% 提升到 99.95% 以上;部署频率从"每周手动操作一次"变为"每天 20+ 次自动化部署";回滚时间从 10 分钟降至 30 秒;故障域从单台服务器分散到多节点,任意单节点故障不影响整体可用性。

工程落地思考

Docker Compose 到 K8s 的迁移最大的误解是"手动重写所有 YAML"。实际上更好的策略是利用 komposedocker compose config 做初始转换,然后把精力花在 K8s 特有的 Concerns 上:健康检查(livenessProbe vs readinessProbe 的区别!)、资源配额(Compose 没有的资源隔离)、Pod 优雅终止(preStop + terminationGracePeriodSeconds)以及 Service 的会话亲和性。特别是 restart: always 在 Compose 中是 Docker daemon 行为,在 K8s 中对应 restartPolicy: Always 但需要 Deployment 的 Controller 来保证——区别要理解透彻。迁移完成后,团队收益最大的是 “自助式部署” 能力,开发可以直接操作 Git 触发部署而不再需要运维手动执行 docker-compose up -d