从 Docker Compose 迁到 K8s:踩坑实录

从 Docker Compose 迁到 K8s:踩坑实录

迁移策略:哪些服务先动

迁移不是一次性的,我们采用的策略是无状态服务优先按流量从小到大

第一批先迁 user-serviceproduct-service——无状态,没有持久化需求,出了问题回滚也容易。第二批迁 order-servicepromo-service,有数据库依赖,需要把 PVC 配好。中间件(MySQL、Redis、RabbitMQ)保持 Docker Compose 运行,不在这次迁移范围里。

整个迁移前后花了大概 6 周,期间线上始终保持可用。


坑一:Pod 一直 CrashLoopBackOff

现象

order-service 部署上去,Pod 状态一直是 CrashLoopBackOffkubectl logs 看到的错误是:

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: ...
Caused by: java.net.UnknownHostException: mysql

排查路径

先看 kubectl describe pod order-service-xxx,Events 里只显示容器反复重启,没有额外信息。从报错入手——UnknownHostException: mysql,说明应用找不到名为 mysql 的主机。

根因

在 Docker Compose 里,服务可以通过服务名(mysql)互相访问,因为它们在同一个用户定义网络里。K8s 里访问其他服务需要用 Service 名 + Namespace,或者如果在同一个 Namespace 里,直接用 Service 名也可以,但前提是那个 Service 已经存在。

问题出在两处:一是数据库还跑在 Docker Compose 里(宿主机),K8s 里的 Pod 根本找不到名叫 mysql 的 Service;二是配置文件里的数据库地址还是写的 mysql,没有改成宿主机 IP。

解决方案

数据库迁移前,应用里的 DB 连接地址改为宿主机 IP + 暴露端口。正式迁移数据库后,用 K8s Service(ExternalName 或 ClusterIP)统一管理。


坑二:服务间调用偶发超时

现象

order-serviceinventory-service 时,偶发 Connection timed out,但重试之后就好了。这种偶发问题最难查,日志里大多数时间都是正常的。

排查路径

在 SkyWalking Trace 图里看慢请求,发现超时的请求在 DNS 解析阶段就花了很长时间(超过 500ms),而正常请求 DNS 解析只要 1ms。问题定位在 DNS 上。

kubectl exec 进容器测试:

kubectl exec -it order-service-pod -- nslookup inventory-service
# 发现偶尔解析很慢,甚至超时

根因

K8s 默认的 DNS 配置里,ndots 值是 5。这意味着对于 inventory-service 这样的短域名,Pod 会先依次尝试以下路径:

  1. inventory-service.order-namespace.svc.cluster.local
  2. inventory-service.svc.cluster.local
  3. inventory-service.cluster.local
  4. inventory-service(最终找到)

每次调用都要经过多次 DNS 查询,CoreDNS 在高并发下出现了性能瓶颈,导致偶发超时。

解决方案

两个方向:

  1. 调用方改用 FQDN(全限定域名):inventory-service.default.svc.cluster.local,直接命中,不走多次查询
  2. 在 Pod 的 dnsConfig 里把 ndots 改小:
spec:
dnsConfig:
options:
- name: ndots
value: "2"

我们采用的是方案二,改完之后偶发超时消失了。


坑三:重启后数据丢失

现象

product-service 的 Pod 在某次节点迁移后重启,发现上传的商品图片都不见了。图片存的是本地文件系统(临时方案,还没迁 Minio)。

根因

Pod 里写入容器文件系统的数据,随着 Pod 重启就消失了。这是 K8s 和 Docker 最本质的区别之一:Docker 容器重启数据还在(除非 --rm),K8s 的 Pod 被重新调度到新节点,之前节点上的数据就没了。

更深层的问题:我们有给这个服务配 PVC,但 StorageClassreclaimPolicy 默认是 Delete,Pod 删除后 PV 也跟着回收了。

解决方案

  1. 修改 PVC 对应的 StorageClass,把 reclaimPolicy 改为 Retain,确保 Pod 删除后 PV 数据不被自动清理
  2. 根本解法是把文件存储迁到对象存储(Minio / 云存储),不依赖节点本地磁盘

迁移完成后的真实状态

最终,无状态服务全部迁上了 K8s,有状态中间件(MySQL、Redis)还跑在原来的云主机上,短期内没有计划迁。

稳定之后,整体比 Docker Compose 阶段顺多了——自动扩缩容、滚动发布、故障自愈这些能力确实省了不少运维精力。但也有一些地方还在打补丁:日志采集方案换了两次才稳定下来,配置管理还有几个服务在用环境变量而不是 ConfigMap。

有一个服务最终没有迁:一个用 Python 写的数据处理脚本,迁移改造的成本高于收益,保持 Docker 单机跑了。