从Docker Compose到Kubernetes:我踩过的5个坑

  • 约1860字
  • 技术
  • 2026年4月24日

3天迁移踩坑,我最后后悔没早点做这件事。

上周把项目的部署方案从Docker Compose切到了Kubernetes。本以为花半天就能搞定,结果整整搞了3天。现在回想起来,有些坑完全可以避免。今天把经验教训分享出来,希望对你有帮助。

坑1:环境变量传递方式完全不同

在Docker Compose里,环境变量直接写在docker-compose.yml里,或者通过.env文件加载,简单直观。

# docker-compose.yml
services:
  api:
    image: myapp/api
    environment:
      - DATABASE_URL=postgres://db:5432/myapp
      - REDIS_URL=redis://cache:6379

到了Kubernetes,你得先创建ConfigMap或者Secret,然后用env或者envFrom注入:

# api-deployment.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
data:
  DATABASE_URL: "postgres://db:5432/myapp"
  REDIS_URL: "redis://cache:6379"
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: api
        envFrom:
        - configMapRef:
            name: myapp-config

当时我没搞懂这区别,第一个Deployment部署上去,Pod一直报" DATABASE_URL undefined"。排查了半天才发现环境变量根本没注入进来。

教训:K8s的配置管理是独立的,需要先创建ConfigMap/Secret,再在Pod里引用。两者不是一套东西。

坑2:健康检查配置差太多

Docker Compose的健康检查很简单:

services:
  api:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

Kubernetes的健康检查分两种: readinessProbe(就绪探针)和 livenessProbe(存活探针)。而且配置字段完全不一样:

containers:
- name: api
  readinessProbe:
    httpGet:
      path: /health
      port: 3000
    initialDelaySeconds: 5
    periodSeconds: 10
  livenessProbe:
    httpGet:
      path: /health
      port: 3000
    initialDelaySeconds: 15
    periodSeconds: 20

我一开始只配了livenessProbe,没配readinessProbe,结果服务刚启动就被流量砸死——Pod还没就绪就开始处理请求了。

教训:readinessProbe控制是否接收流量,livenessProbe控制是否重启。两者用途不同,都很重要。

坑3:网络访问从直接变间接

Docker Compose里,同一个compose文件里的服务可以直接用服务名访问:

# docker-compose.yml
services:
  api:
    depends_on:
      - db
    # 直接用 db 访问数据库服务
    environment:
      - DATABASE_URL=postgres://db:5432/myapp

Kubernetes里,每个Pod有独立的IP,Service才是入口。而且跨namespace的话,还要加上namespace前缀:

# 同namespace
DATABASE_URL: postgres://myapp-db:5432/myapp

# 跨namespace
DATABASE_URL: postgres://myapp-db.default.svc.cluster.local:5432/myapp

当时数据库服务叫db,我直接配postgres://db:5432,结果Pod一直连不上。查了好半天日志才发现K8s里的服务名格式完全不同。

教训:K8s的服务发现用的是DNS,格式是{service-name}.{namespace}.svc.cluster.local。简单场景可以用{service-name}.{namespace},同namespace才能直接用服务名。

坑4:存储卷挂载不是一回事

Docker Compose的卷挂载很简单:

volumes:
  - ./data:/app/data
  - db-data:/var/lib/postgresql/data

Kubernetes要用PersistentVolumeClaim:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: db
        volumeMounts:
        - name: db-data
          mountPath: /var/lib/postgresql/data
      volumes:
      - name: db-data
        persistentVolumeClaim:
          claimName: db-data

临时存储可以用emptyDir,但持久化数据必须用PVC。我一开始不知道有这个区别,数据全存emptyDir里,Pod一重启数据全丢。

教训:K8s的存储模型比Docker Compose复杂很多。开发环境可以用emptyDir,生产环境一定要用PVC。

坑5:服务暴露方式变了

Docker Compose用ports:

services:
  api:
    ports:
      - "3000:3000"

Kubernetes要用Service:

apiVersion: v1
kind: Service
metadata:
  name: myapp-api
spec:
  selector:
    app: myapp-api
  ports:
  - port: 80
    targetPort: 3000
  type: LoadBalancer

而且如果用的是Ingress,还要额外配置Ingress规则。我当时配完Service就以为能访问了,结果外部根本访问不了,最后发现是没配Ingress或者LoadBalancer。

教训:K8s里Pod是调度的最小单位,Service是入口,Ingress是七层路由。三层结构要想清楚。

迁移建议

如果你的项目也要从Docker Compose迁移到Kubernetes,有几点建议:

  1. 先在测试环境跑通:不要直接在生产环境尝试,K8s的学习曲线比较陡,测试环境多踩坑没关系。

  2. 用Helm或Kustomize:手写yaml太痛苦了,Helm有模板,Kustomize可以分环境管理配置。

  3. 逐步迁移:不要一次性把所有服务都搬过去,先迁一个无状态服务试试水。

  4. 做好监控和日志:K8s的调试比Docker Compose复杂很多,没有监控和日志寸步难行。

  5. 准备好回滚方案:迁移出问题能快速回滚到Docker Compose,别把自己逼到绝路。

迁移虽然踩了坑,但K8s的弹性伸缩和自愈能力确实香。忍过这3天,后面部署确实省心很多。值不值?个人角度来说,如果是小项目,Docker Compose够用就不用折腾;如果团队大了要上规模,K8s是必经之路,早踩坑早成长。

相关文章

向 AI 提问有技巧学会这招效率翻倍

为什么同样的 AI 工具,有人用它效率翻倍,有人却觉得鸡肋?关键在于会不会"提问"。本文分享实用提示工程技巧,让 AI 成为你的得力助手。

查看更多

重构还在靠经验?AI分钟级搞定代码优化

面对庞大的遗留代码库,你是否曾对重构望而却步?手动重构耗时长、风险高,一个不留神就可能引入新bug。本文分享如何用AI工具分钟级完成代码重构,包含具体工具、步骤和避坑指南。

查看更多

Git 常见命令总结

Git 是分布式版本控制系统,由 Linux 之父 Linus Torvalds 发起。和 svn 等版本控制的最大区别在于分布式,每个人在本地都有一份完整的代码历史库,在不联网的情况下就可以查所有历史并提交代码。

查看更多