背景与问题界定
某 SaaS 创业公司的早期技术栈完全构建在 Docker Compose 之上:一台 32C/128G 的裸机服务器运行着 20 多个容器,通过 depends_on 控制启动顺序,所有数据卷挂载在 NFS 共享存储上,服务发现依赖静态 links 和 network_mode: host。随着业务增长到日处理 100 万请求,这台"胖单体"服务器成了漂移的单点故障——一次 Docker daemon OOM 导致全站宕机 45 分钟。更糟糕的是,由于所有服务共用一个网络命名空间,端口冲突频发,版本升级无法滚动更新,回滚必须在 10 分钟内重装所有容器。Kubernetes 迁移势在必行,但需要一套渐进式、可回滚的迁移方案。
目标拆解与工程约束
- 零停机迁移:迁移过程中不能有超过 30 秒的服务中断窗口,用户请求必须在 Docker Compose 和 Kubernetes 两个"世界"之间平滑切换,支持新旧基础设施并行的灰度过渡期。
- 配置无感知适配:现有的
.env文件、Compose 中的environment变量和volumes绑定挂载必须在 Kubernetes 中以等效语义运行,不能要求开发团队大量修改应用代码来适配新的运行环境。 - 有状态服务平滑迁移:MySQL、Redis 和 RabbitMQ 等有状态服务的数据不能丢失,需要在 Kubernetes 的 StatefulSet 中重建并同步数据,同时保证混合运行期间数据一致性。
- 成本可控性:从单机 Compose 到多节点 K8s 集群,基础设施成本预期会增加,必须通过合理的 namespace 规划、节点池划分和资源 requests/limits 策略控制溢出成本增长在 30% 以内。
方案设计
采用"绞杀者模式"(Strangler Fig Pattern)分阶段迁移。第一阶段是网络和数据平面的打通:在 Compose 服务器和 K8s 集群之间通过 WireGuard 组建扁平化 overlay 网络,利用 CoreDNS 的双向 DNS 解析让双方服务能互相发现。K8s 集群中的 Service 通过 externalIPs 或 metallb 暴露,Compose 服务通过 extra_hosts 指向 K8s Service IP。
第二阶段从无状态服务开始迁移。将 Compose 中的 web 和 api 服务的 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-operator 和 rabbitmq-operator)启动新集群,通过主从复制从 Compose 中的旧服务同步数据,确认一致后切换 DNS 并下线旧服务。
实施路径与关键决策
- 第一步:搭建 3 节点 K8s 集群(K3s 轻量级发行版,适合从 Compose 升级的场景),配置 Flannel 网络和 Rancher Local Path Provisioner 提供本地持久化存储。
- 第二步:运行
kompose convert -f docker-compose.yml -o k8s-manifests/生成初始 Manifest,但废弃所有volume和service部分,人工重建符合 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"。实际上更好的策略是利用 kompose 或 docker 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。