Docker Compose 到 K8s 的迁移路径:不是一键转换那么简单
一、Kompose 转换完的 YAML 也别直接部署
Docker Compose 和 K8s 之间的概念鸿沟远超表面相似度。Kompose(kompose convert)能生成初版 YAML,但它只是一个语法翻译器——不理解你的服务应该如何编排、网络如何划分、存储如何处理。
直接部署 Kompose 转出来的 YAML 大概率会遇到:
- 所有服务在同一个 namespace 中,网络全通——和 Compose 的行为一致,但失去了 K8s 的网络隔离能力
- 数据卷映射为 emptyDir——Pod 重建后数据丢失
- 环境变量硬编码在 Deployment 中——没有 ConfigMap/Secret 概念
真正的迁移需要重新理解你的服务在 K8s 的语境下的正确形态。
flowchart TD A[Docker Compose 文件] --> B[Kompose 生成初版] B --> C{需要人工调整的维度} C --> D[网络层] C --> E[存储层] C --> F[配置层] C --> G[编排层] D --> D1[划分 namespace] D --> D2[创建 NetworkPolicy] D --> D3[定义 Service + Ingress] E --> E1[volume → PVC] E --> E2[bind mount → hostPath/PV] E --> E3[确定 StorageClass] F --> F1[环境变量 → ConfigMap] F --> F2[密码 → Secret] F --> F3[敏感信息加密] G --> G1[depends_on → initContainers] G --> G2[restart → liveness probe] G --> G3[scale → HPA] D1 & E1 & F1 & G1 --> H[Review + 测试] H --> I[生产部署]二、概念映射对照表
| Docker Compose | Kubernetes | 迁移要点 |
|---|---|---|
services | Deployment/StatefulSet | 有状态服务用 StatefulSet |
networks | NetworkPolicy+Service | 显式定义网络权限 |
volumes | PersistentVolumeClaim | 指定 StorageClass |
depends_on | initContainers | 等待依赖就绪 |
environment | ConfigMap/Secret | 配置与代码分离 |
ports | Service+Ingress | 集群外访问需要 Ingress |
restart: always | livenessProbe | 健康检查替代无限重启 |
scale | replicas/HPA | 手动副本 + 自动伸缩 |
healthcheck | readinessProbe | 就绪探针控制流量接入 |
三、生产级迁移实践
原始 Compose 文件(示例)
version: '3.8' services: api: image: myapp/api:latest ports: - "3000:3000" environment: - DB_HOST=postgres - DB_PASSWORD=secret123 - REDIS_URL=redis://redis:6379 depends_on: - postgres - redis volumes: - ./uploads:/app/uploads restart: always postgres: image: postgres:16 environment: - POSTGRES_PASSWORD=secret123 volumes: - pgdata:/var/lib/postgresql/data redis: image: redis:7-alpine volumes: - redisdata:/data volumes: pgdata: redisdata:迁移后的 K8s 资源(分步展示)
Step 1: 配置外化——ConfigMap + Secret
apiVersion: v1 kind: ConfigMap metadata: name: api-config namespace: production data: DB_HOST: "postgres-service" REDIS_URL: "redis://redis-service:6379" NODE_ENV: "production" --- apiVersion: v1 kind: Secret metadata: name: api-secrets namespace: production type: Opaque data: # 使用 Sealed Secrets 或 External Secrets Operator 管理 DB_PASSWORD: c2VjcmV0MTIz # base64(secret123)——仅为示例,生产不要硬编码Step 2: 存储层——PVC
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: api-uploads-pvc namespace: production spec: accessModes: - ReadWriteMany # 多副本需要 RWX storageClassName: efs-sc # 根据云厂商选择 resources: requests: storage: 10Gi --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: postgres-pvc namespace: production spec: accessModes: - ReadWriteOnce storageClassName: gp3 resources: requests: storage: 50GiStep 3: 服务 + 部署
apiVersion: apps/v1 kind: Deployment metadata: name: api namespace: production labels: app: api spec: replicas: 3 selector: matchLabels: app: api template: metadata: labels: app: api spec: containers: - name: api image: myapp/api:v1.2.3 # 用具体版本而非 latest ports: - containerPort: 3000 name: http envFrom: - configMapRef: name: api-config - secretRef: name: api-secrets volumeMounts: - name: uploads mountPath: /app/uploads resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" # 替代 restart: always——精确的健康检查 livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 10 periodSeconds: 5 volumes: - name: uploads persistentVolumeClaim: claimName: api-uploads-pvc --- apiVersion: v1 kind: Service metadata: name: api-service namespace: production spec: selector: app: api ports: - port: 80 targetPort: 3000 protocol: TCP type: ClusterIP --- # StatefulSet for PostgreSQL apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres namespace: production spec: serviceName: postgres-service replicas: 1 # PostgreSQL 单实例——生产建议用 Operator selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:16 env: - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: api-secrets key: DB_PASSWORD ports: - containerPort: 5432 volumeMounts: - name: pgdata mountPath: /var/lib/postgresql/data resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m" volumeClaimTemplates: - metadata: name: pgdata spec: accessModes: ["ReadWriteOnce"] storageClassName: gp3 resources: requests: storage: 50Gi四、迁移中容易忽略的问题
数据安全性
Compose 中volumes: pgdata在 Docker 管理的卷中。迁移到 K8s 后数据需要迁移。建议:
- 先在 K8s 启动一个新的 PostgreSQL 实例
- 用
pg_dump/pg_restore导数据 - 验证数据完整性后切 DNS
内部 DNS 解析
Compose 用服务名作为 DNS。K8s 用<service-name>.<namespace>.svc.cluster.local。如果应用代码里硬编码了 Compose 的短主机名,需要改为 K8s 的完整域名——或者通过ConfigMap注入正确的连接字符串。
资源限制
Compose 不限制资源。K8s 如果不设 request/limit,Node 可能被 Pod 吃满。迁移后至少要设resources.requests——不设 limit 也可以,但 request 必须设。
五、总结
Compose 到 K8s 的迁移不是格式转换,是思维方式切换。Compose 假设一切运行在单机、全通网络、无状态;K8s 的设计前提是多节点、网络隔离、声明式状态管理。Konvert 只是帮你省掉了手写 YAML 的体力活,但概念重塑——哪些该放进 ConfigMap、哪些需要 NetworkPolicy、哪些必须 StatefulSet——这个判断才是迁移的真正价值。