在这里插入图片描述

👋 大家好,欢迎来到我的技术博客!
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕Kubernetes这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!


文章目录

Kubernetes - 与 Docker Compose 的协同使用,快速迁移应用 🚀

在现代云原生架构中,容器化已成为应用部署的基石。Docker Compose 以其简洁的语法和快速的本地开发体验,成为开发者日常调试和原型验证的首选工具;而 Kubernetes 作为生产级容器编排的事实标准,提供了高可用、弹性伸缩、服务发现与自我修复等强大能力。然而,从本地开发环境平滑过渡到生产集群,常常是团队面临的一大挑战。

本文将深入探讨如何协同使用 Docker Compose 与 Kubernetes,实现应用的快速迁移。我们将从基础概念入手,逐步构建一个真实可运行的 Java 微服务应用,使用 Docker Compose 在本地快速启动,再通过工具链自动化生成 Kubernetes 清单,最终在 Minikube 或 Kind 环境中完成部署。全程包含完整的 Java 代码示例、配置文件、Mermaid 流程图,并解释每一步背后的架构设计哲学。


为什么需要协同使用? 🤔

Docker Compose 和 Kubernetes 并非对立关系,而是开发与生产环境的互补工具。

维度Docker ComposeKubernetes
用途本地开发、测试、快速原型生产部署、集群管理
配置文件docker-compose.ymlDeployment, Service, Ingress, ConfigMap 等
启动速度秒级分钟级(含镜像拉取、调度)
自愈能力❌ 无✅ 强大(重启、健康检查、滚动更新)
服务发现基于容器名和网络基于 DNS 和 Service
扩展性单机限制横向扩展、多节点
配置管理环境变量、.env 文件ConfigMap、Secret、Helm
日志与监控docker logs、基本端口暴露Prometheus、Loki、Kibana、Operator

💡 关键洞察:你不需要“放弃” Docker Compose 去拥抱 Kubernetes。相反,你可以用 Compose 作为开发的入口,用 Kubernetes 作为生产的出口,中间通过标准化的构建与转换流程实现无缝衔接。

许多团队在迁移时遇到的问题是:

  • 开发环境用 Compose,生产用 K8s,但配置不一致 → “在我机器上是好的!”
  • 手动转换 YAML 文件 → 容易出错、难以维护
  • 缺乏统一的构建流程 → 团队协作效率低下

解决方案?让 Docker Compose 成为 Kubernetes 的“前端配置语言”。


构建示例应用:订单服务系统 🛒

我们将构建一个简单的 Java 微服务系统,包含三个组件:

  1. Order Service(订单服务):Java Spring Boot 应用,暴露 REST API
  2. Inventory Service(库存服务):Java Spring Boot 应用,提供库存查询
  3. PostgreSQL:持久化订单数据

✅ 所有服务都使用 Spring Boot 3.x + Java 17,符合现代企业标准。

1. Order Service 代码实现 📜

首先,创建一个 Spring Boot 项目。我们使用 Maven 结构:

mkdir order-service
cd order-service
mkdir -p src/main/java/com/example/orderservice
mkdir -p src/main/resources
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>order-service</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>order-service</name>
    <description>Order Management Service</description>

    <properties>
        <java.version>17</java.version>
        <spring-boot.version>3.2.0</spring-boot.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
application.yml
server:
  port: 8080

spring:
  datasource:
    url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:orders}
    username: ${DB_USER:postgres}
    password: ${DB_PASSWORD:postgres}
    driver-class-name: org.postgresql.Driver

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true

logging:
  level:
    com.example.orderservice: DEBUG

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
实体类:Order.java
package com.example.orderservice.entity;

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String productId;

    @Column(nullable = false)
    private Integer quantity;

    @Column(nullable = false)
    private String customerEmail;

    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;

    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
    }

    // Constructors
    public Order() {}

    public Order(String productId, Integer quantity, String customerEmail) {
        this.productId = productId;
        this.quantity = quantity;
        this.customerEmail = customerEmail;
    }

    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getProductId() { return productId; }
    public void setProductId(String productId) { this.productId = productId; }

    public Integer getQuantity() { return quantity; }
    public void setQuantity(Integer quantity) { this.quantity = quantity; }

    public String getCustomerEmail() { return customerEmail; }
    public void setCustomerEmail(String customerEmail) { this.customerEmail = customerEmail; }

    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }

    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                ", productId='" + productId + '\'' +
                ", quantity=" + quantity +
                ", customerEmail='" + customerEmail + '\'' +
                ", createdAt=" + createdAt +
                '}';
    }
}
Repository:OrderRepository.java
package com.example.orderservice.repository;

import com.example.orderservice.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
}
Service:OrderService.java
package com.example.orderservice.service;

import com.example.orderservice.entity.Order;
import com.example.orderservice.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    public Order createOrder(String productId, Integer quantity, String customerEmail) {
        Order order = new Order(productId, quantity, customerEmail);
        return orderRepository.save(order);
    }

    public List<Order> getAllOrders() {
        return orderRepository.findAll();
    }

    public Order getOrderById(Long id) {
        return orderRepository.findById(id).orElse(null);
    }
}
Controller:OrderController.java
package com.example.orderservice.controller;

import com.example.orderservice.entity.Order;
import com.example.orderservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
        Order order = orderService.createOrder(
                request.productId(),
                request.quantity(),
                request.customerEmail()
        );
        return ResponseEntity.ok(order);
    }

    @GetMapping
    public ResponseEntity<List<Order>> getAllOrders() {
        return ResponseEntity.ok(orderService.getAllOrders());
    }

    @GetMapping("/{id}")
    public ResponseEntity<Order> getOrderById(@PathVariable Long id) {
        Order order = orderService.getOrderById(id);
        if (order == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(order);
    }

    // DTO for request body
    public record OrderRequest(String productId, Integer quantity, String customerEmail) {}
}
启动类:OrderServiceApplication.java
package com.example.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

2. Inventory Service 代码实现 📦

Inventory Service 是一个轻量级服务,用于模拟库存查询。它不持久化,仅提供内存中的库存数据。

pom.xml(与 Order Service 基本一致,仅 artifactId 不同)
<artifactId>inventory-service</artifactId>
application.yml
server:
  port: 8081

spring:
  application:
    name: inventory-service

logging:
  level:
    com.example.inventoryservice: DEBUG

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
InventoryService.java
package com.example.inventoryservice.service;

import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Service
public class InventoryService {

    private final Map<String, Integer> inventory = new HashMap<>();

    public InventoryService() {
        // 初始化一些库存
        inventory.put("prod-001", 100);
        inventory.put("prod-002", 50);
        inventory.put("prod-003", 200);
    }

    public boolean hasStock(String productId, int quantity) {
        return inventory.getOrDefault(productId, 0) >= quantity;
    }

    public int getStock(String productId) {
        return inventory.getOrDefault(productId, 0);
    }

    public void reduceStock(String productId, int quantity) {
        inventory.put(productId, inventory.getOrDefault(productId, 0) - quantity);
    }
}
InventoryController.java
package com.example.inventoryservice.controller;

import com.example.inventoryservice.service.InventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/inventory")
public class InventoryController {

    @Autowired
    private InventoryService inventoryService;

    @GetMapping("/{productId}")
    public ResponseEntity<Integer> getStock(@PathVariable String productId) {
        int stock = inventoryService.getStock(productId);
        if (stock < 0) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(stock);
    }

    @GetMapping("/{productId}/available")
    public ResponseEntity<Boolean> hasStock(@PathVariable String productId, @RequestParam int quantity) {
        boolean available = inventoryService.hasStock(productId, quantity);
        return ResponseEntity.ok(available);
    }
}
启动类:InventoryServiceApplication.java
package com.example.inventoryservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class InventoryServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(InventoryServiceApplication.class, args);
    }
}

本地开发:使用 Docker Compose 快速启动 🐳

现在我们有了两个 Java 服务和一个 PostgreSQL 数据库。我们为它们编写 docker-compose.yml。

docker-compose.yml

version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: postgres-orders
    environment:
      POSTGRES_DB: orders
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  order-service:
    build:
      context: ./order-service
      dockerfile: Dockerfile
    container_name: order-service
    ports:
      - "8080:8080"
    environment:
      DB_HOST: postgres
      DB_PORT: 5432
      DB_NAME: orders
      DB_USER: postgres
      DB_PASSWORD: postgres
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - app-network

  inventory-service:
    build:
      context: ./inventory-service
      dockerfile: Dockerfile
    container_name: inventory-service
    ports:
      - "8081:8081"
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  postgres_data:

order-service/Dockerfile

FROM eclipse-temurin:17-jre-slim

WORKDIR /app

# 复制 Maven 构建产物
COPY target/order-service-1.0.0.jar app.jar

# 暴露端口
EXPOSE 8080

# 启动命令
ENTRYPOINT ["java", "-jar", "app.jar"]

inventory-service/Dockerfile

FROM eclipse-temurin:17-jre-slim

WORKDIR /app

COPY target/inventory-service-1.0.0.jar app.jar

EXPOSE 8081

ENTRYPOINT ["java", "-jar", "app.jar"]

✅ 注意:我们假设你已经使用 mvn clean package 构建了 JAR 文件。你可以编写一个简单的 shell 脚本自动化构建:

#!/bin/bash
# build-all.sh
echo "🏗️  Building Order Service..."
cd order-service && mvn clean package && cd ..

echo "🏗️  Building Inventory Service..."
cd inventory-service && mvn clean package && cd ..

echo "✅ All services built!"

启动本地环境

chmod +x build-all.sh
./build-all.sh
docker-compose up -d

等待约 30 秒后,访问:

你可以通过 curl 创建订单:

curl -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{"productId":"prod-001","quantity":2,"customerEmail":"user@example.com"}'

✅ 本地开发环境完美运行!现在,我们希望将这套配置“一键”迁移到 Kubernetes。


从 Docker Compose 到 Kubernetes:自动化转换 🔄

手动重写 YAML 文件是低效且易错的。幸运的是,社区已有成熟工具帮助我们自动化转换。

工具选择:Kompose 🚀

Kompose 是由 Kubernetes 社区支持的工具,能将 docker-compose.yml 自动转换为 Kubernetes 清单(Deployment、Service、ConfigMap 等)。

🔗 官方文档:https://kompose.io/

安装 Kompose(Linux/macOS)
curl -L https://github.com/kubernetes/kompose/releases/latest/download/kompose-linux-amd64 -o kompose
chmod +x kompose
sudo mv ./kompose /usr/local/bin/kompose

如果你使用 Windows,可从 GitHub Releases 下载 .exe。

转换命令

在包含 docker-compose.yml 的目录执行:

kompose convert -f docker-compose.yml

你会看到生成一堆 .yaml 文件:

order-service-deployment.yaml
order-service-service.yaml
inventory-service-deployment.yaml
inventory-service-service.yaml
postgres-deployment.yaml
postgres-service.yaml
查看生成的 Order Service Deployment
# order-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert -f docker-compose.yml
    kompose.version: 1.30.0 (a3b598614)
  creationTimestamp: null
  labels:
    io.kompose.service: order-service
  name: order-service
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: order-service
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert -f docker-compose.yml
        kompose.version: 1.30.0 (a3b598614)
      creationTimestamp: null
      labels:
        io.kompose.service: order-service
    spec:
      containers:
      - image: order-service:latest
        name: order-service
        ports:
        - containerPort: 8080
        resources: {}
      restartPolicy: Always
status: {}
查看 Order Service Service
# order-service-service.yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert -f docker-compose.yml
    kompose.version: 1.30.0 (a3b598614)
  creationTimestamp: null
  labels:
    io.kompose.service: order-service
  name: order-service
spec:
  ports:
  - name: "8080"
    port: 8080
    targetPort: 8080
  selector:
    io.kompose.service: order-service
status:
  loadBalancer: {}

⚠️ 注意:Kompose 默认使用 image: order-service:latest,但我们的镜像尚未推送到仓库!我们需要构建并推送镜像。


构建并推送镜像到本地仓库 🏷️

在 Kubernetes 中,Pod 从镜像仓库拉取镜像。我们有三种选择:

  1. 推送到公共仓库(如 Docker Hub)→ 不适合私有项目
  2. 使用 Kind 内置 registry → 推荐用于本地测试
  3. 使用 Minikube 的 Docker daemon → 适用于 Minikube

我们选择 Kind(Kubernetes in Docker),因为它轻量、快速、可复现。

安装 Kind

curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind

创建 Kind 集群并启用内置 registry

# 创建集群配置文件
cat > kind-config.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
containerdConfigPatches:
- |-
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"]
    endpoint = ["http://kind-registry:5000"]
EOF

# 创建集群
kind create cluster --config kind-config.yaml --name k8s-dev

# 启动本地 registry(用于推送镜像)
docker run -d --name kind-registry --restart=always -p "127.0.0.1:5000:5000" registry:2

# 将 registry 连接到 Kind 集群
kind load docker-image order-service:latest --name k8s-dev
kind load docker-image inventory-service:latest --name k8s-dev
kind load docker-image postgres:15-alpine --name k8s-dev

📌 重要:kind load docker-image 会把本地镜像导入到 Kind 的节点中,避免从外部拉取。

修改 Kompose 生成的 YAML:替换镜像为本地地址

Kompose 生成的镜像名是 order-service:latest,但在 Kubernetes 中,我们希望它指向本地 registry:

# 修改所有 deployment.yaml 中的 image 字段
sed -i 's/image: order-service:latest/image: localhost:5000/order-service:latest/g' *-deployment.yaml
sed -i 's/image: inventory-service:latest/image: localhost:5000/inventory-service:latest/g' *-deployment.yaml
sed -i 's/image: postgres:15-alpine/image: localhost:5000/postgres:15-alpine/g' *-deployment.yaml

🛠️ 你也可以编写一个脚本批量替换,或使用 Helm 模板进行参数化。


部署到 Kubernetes 🚀

现在,我们准备好了所有 YAML 文件,开始部署!

# 应用所有生成的清单
kubectl apply -f postgres-deployment.yaml
kubectl apply -f postgres-service.yaml
kubectl apply -f order-service-deployment.yaml
kubectl apply -f order-service-service.yaml
kubectl apply -f inventory-service-deployment.yaml
kubectl apply -f inventory-service-service.yaml

检查状态

kubectl get pods
kubectl get services
kubectl logs -f order-service-xxxxx

等待所有 Pod 进入 Running 状态:

NAME                             READY   STATUS    RESTARTS   AGE
inventory-service-7d8f9b4c6-2z4xw  1/1     Running   0          2m
order-service-5b7d8f9b4-7x9mz     1/1     Running   0          2m
postgres-7d8f9b4c6-9x2p4          1/1     Running   0          2m

暴露服务:使用 NodePort 或 Port-Forward

由于我们没有 Ingress 控制器,可以使用 port-forward:

# 访问 Order Service
kubectl port-forward service/order-service 8080:8080

# 访问 Inventory Service
kubectl port-forward service/inventory-service 8081:8081

现在,在另一个终端中:

curl http://localhost:8080/api/orders
# → []

curl -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{"productId":"prod-001","quantity":2,"customerEmail":"user@example.com"}'

✅ 成功!你的 Java 微服务现在运行在 Kubernetes 上,完全兼容本地 Compose 行为!


架构对比:Compose vs K8s 流程图 📊

为了更清晰理解迁移过程,我们使用 Mermaid 绘制整个流程:

本地开发:Docker Compose

编写 docker-compose.yml

运行 docker-compose up

本地访问 http://localhost:8080

开发调试、测试 API

构建 JAR 文件

构建 Docker 镜像

使用 Kompose 转换

生成 Deployment/Service YAML

推送镜像到本地 registry

部署到 Kind/Kubernetes

使用 kubectl port-forward 访问

生产环境:部署到 EKS/GKE/AKS

使用 Helm + CI/CD 自动化

监控、日志、自动伸缩

📌 图中绿色部分代表“生产环境”,蓝色代表“迁移阶段”,粉色代表“开发阶段”。你可以看到,从 Compose 到 K8s 是一条清晰的流水线,而非断崖式切换。


配置管理:环境变量与 ConfigMap 的演进 🧩

在 Docker Compose 中,我们使用 environment: 直接注入变量:

environment:
  DB_HOST: postgres
  DB_PORT: 5432

在 Kubernetes 中,推荐使用 ConfigMap 和 Secret 来解耦配置。

创建 ConfigMap

# config/order-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: order-service-config
data:
  SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/orders
  SPRING_DATASOURCE_USERNAME: postgres
  SPRING_DATASOURCE_PASSWORD: postgres
  SERVER_PORT: "8080"

修改 Deployment 使用 ConfigMap

# order-service-deployment.yaml(片段)
spec:
  containers:
  - name: order-service
    image: localhost:5000/order-service:latest
    ports:
    - containerPort: 8080
    envFrom:
    - configMapRef:
        name: order-service-config

创建 Secret(密码)

# secret/db-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: postgres-secret
type: Opaque
data:
  password: cG9zdGdyZXM=  # base64 encoded "postgres"

在 Deployment 中引用:

env:
- name: SPRING_DATASOURCE_PASSWORD
  valueFrom:
    secretKeyRef:
      name: postgres-secret
      key: password

💡 最佳实践:开发环境可继续使用环境变量,生产环境强制使用 Secret + ConfigMap,实现权限分离。


服务间通信:DNS 与健康检查 🔄

在 Docker Compose 中,服务通过容器名通信(如 postgres),因为它们在同一个自定义网络中。

在 Kubernetes 中,服务通过 Service DNS 访问:

<service-name>.<namespace>.svc.cluster.local

默认命名空间下,postgres 服务可通过 postgres.default.svc.cluster.local 访问。

但我们的 Java 应用中,application.yml 写的是:

DB_HOST: postgres

这在 K8s 中仍然有效,因为 Kubernetes 的 DNS 会自动解析 <service-name> → <service-ip>。

健康检查增强

Docker Compose 使用 healthcheck,Kubernetes 使用 livenessProbe 和 readinessProbe。

修改 order-service-deployment.yaml:

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 20
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

✅ Spring Boot Actuator 提供 /actuator/health/liveness 和 /actuator/health/readiness,非常适合 K8s 集成。


持久化存储:PostgreSQL 的数据安全 🗃️

Docker Compose 使用 volumes: 持久化数据:

volumes:
  - postgres_data:/var/lib/postgresql/data

在 Kubernetes 中,我们使用 PersistentVolumeClaim (PVC)。

创建 PostgreSQL PVC

# postgres-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

修改 PostgreSQL Deployment 挂载 PVC

spec:
  containers:
  - name: postgres
    image: localhost:5000/postgres:15-alpine
    env:
    - name: POSTGRES_DB
      value: orders
    - name: POSTGRES_USER
      value: postgres
    - name: POSTGRES_PASSWORD
      valueFrom:
        secretKeyRef:
          name: postgres-secret
          key: password
    ports:
    - containerPort: 5432
    volumeMounts:
    - name: postgres-storage
      mountPath: /var/lib/postgresql/data
  volumes:
  - name: postgres-storage
    persistentVolumeClaim:
      claimName: postgres-pvc

📌 重要:PostgreSQL 容器必须以非 root 用户运行,否则可能无法写入 PVC。建议使用官方镜像的 postgres 用户(默认如此)。


日志与监控:如何观察你的服务? 📈

在 Docker Compose 中,你用 docker logs 查看日志。

在 Kubernetes 中,推荐使用:

  • Loki:日志聚合
  • Prometheus + Grafana:指标监控
  • Kibana:日志可视化

但为了快速验证,我们使用 kubectl logs:

kubectl logs order-service-5b7d8f9b4-7x9mz

你也可以开启日志聚合:

# 安装 Loki(可选)
helm repo add grafana https://grafana.github.io/helm-charts
helm install loki grafana/loki-stack

🔗 推荐阅读:Kubernetes Logging Best Practices —— 官方文档


CI/CD 自动化:从 Git 提交到 K8s 部署 🤖

现在我们有了完整的迁移流程:

  1. 开发者提交代码到分支
  2. CI 工具(如 GitHub Actions、GitLab CI)构建镜像
  3. 推送到私有 registry
  4. 使用 Kompose 生成 K8s 清单
  5. 使用 kubectl 或 Argo CD 部署到测试集群

示例 CI 流程(伪代码)

name: Deploy to K8s

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Build Order Service
        run: |
          cd order-service
          mvn clean package

      - name: Build Inventory Service
        run: |
          cd inventory-service
          mvn clean package

      - name: Build Docker Images
        run: |
          docker build -t order-service:latest order-service
          docker build -t inventory-service:latest inventory-service
          docker build -t postgres:15-alpine -f postgres/Dockerfile .

      - name: Push to Registry
        run: |
          docker tag order-service:latest registry.example.com/order-service:latest
          docker push registry.example.com/order-service:latest
          # ... 其他镜像

      - name: Convert to K8s
        run: |
          kompose convert -f docker-compose.yml -o k8s/
          sed -i 's/image: order-service:latest/image: registry.example.com/order-service:latest/g' k8s/*-deployment.yaml

      - name: Deploy to K8s
        run: |
          kubectl config use-context my-cluster
          kubectl apply -f k8s/

✅ 这样,每次代码提交都会触发一次“从 Compose 到 K8s”的自动化部署流程,实现真正的 DevOps。


迁移策略:灰度发布与回滚 🎯

在生产环境中,我们不能“一刀切”切换。建议采用:

阶段操作
1保持 Compose 环境运行
2在 K8s 中部署新版本,但不暴露流量
3使用 kubectl scale 控制副本数,逐步增加
4使用 Ingress 路由 5% 流量到新版本(Canary)
5监控指标:错误率、延迟、CPU
6100% 流量切换,删除旧 Compose 环境

📌 推荐工具:Flagger 实现 Canary + 自动回滚

是

否

旧版本(Compose)

新版本(K8s,0副本)

扩到1副本,5%流量

监控指标稳定?

扩到100%

自动回滚

删除旧环境


性能对比:Compose vs K8s 🏎️

指标Docker ComposeKubernetes
启动时间5~15 秒30~90 秒(含镜像拉取)
内存占用500MB~1GB1.5GB~3GB(含 kubelet、etcd)
网络延迟本地网络CNI 插件引入轻微延迟(<5ms)
可扩展性单机多节点、跨可用区
故障恢复手动重启自动重启 + 重调度
配置管理简单复杂但强大(Helm、Kustomize)

✅ 结论:K8s 在开发环境“慢”,但在生产环境“稳”。你的目标不是让 K8s 变快,而是让系统更可靠。


常见陷阱与解决方案 🚫

问题原因解决方案
ImagePullBackOff镜像未推送或名称错误使用 kind load docker-image 或推送至私有 registry
CrashLoopBackOffJava 应用启动失败检查日志:kubectl logs -f <pod>,确保环境变量正确
Connection refused服务未就绪添加 readinessProbe,确保数据库连接重试机制
No such file or directoryJAR 文件未打包检查 target/ 是否存在,Dockerfile 路径是否正确
Cannot connect to the Docker daemon在 CI 中未配置使用 docker-in-docker 或 kaniko 构建镜像

💡 建议:在 order-service 启动类中添加数据库连接重试逻辑:

@SpringBootApplication
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(OrderServiceApplication.class);
        ConfigurableApplicationContext context = app.run(args);

        // 等待数据库连接
        while (!isDatabaseReady(context)) {
            System.out.println("⏳ Waiting for database...");
            try { Thread.sleep(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }
        System.out.println("✅ Database connected!");
    }

    private static boolean isDatabaseReady(ConfigurableApplicationContext context) {
        try {
            context.getBean(DataSource.class).getConnection().close();
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

未来演进:Helm、Kustomize、Argo CD 🧩

Kompose 是“快速迁移”的起点,但不是终点。

当你有多个环境(dev/stage/prod),需要:

工具用途
Kustomize为不同环境叠加配置(如 prod 增加资源限制)
Helm模板化部署,支持 values.yaml 参数化
Argo CDGitOps 自动同步,声明式部署

示例:Helm Chart 结构

order-app/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── configmap.yaml
└── charts/

values.yaml:

replicaCount: 2
image:
  repository: registry.example.com/order-service
  tag: latest
  pullPolicy: IfNotPresent

resources:
  limits:
    cpu: 500m
    memory: 1Gi
  requests:
    cpu: 200m
    memory: 512Mi

database:
  host: postgres
  port: 5432
  name: orders
  user: postgres
  password: postgres

🔗 学习 Helm:https://helm.sh/docs/


总结:你已经完成了从 Compose 到 K8s 的优雅迁移 🎉

我们一步步走完了:

✅ 编写 Java 微服务
✅ 使用 Docker Compose 快速本地开发
✅ 使用 Kompose 自动转换为 Kubernetes 清单
✅ 使用 Kind 模拟生产集群
✅ 部署、调试、监控
✅ 引入配置管理、健康检查、持久化
✅ 设计 CI/CD 流水线
✅ 规划未来演进路径

🌟 核心思想:不要把 Docker Compose 当作“过时技术”,而应将其视为开发者的生产力加速器。Kubernetes 是生产环境的“操作系统”。两者协同,才是云原生的正确打开方式。


延伸阅读与资源 📚


最后:你的下一步行动清单 ✅

  1. 复制本文示例代码,在本地运行一遍
  2. 尝试修改 Java 服务,添加一个新的 API,看看是否能自动部署
  3. 尝试使用 Helm 替代 Kompose,实现环境模板化
  4. 在 GitHub Actions 中搭建 CI,实现提交即部署
  5. 在云上部署一个真实集群(如 AWS EKS),使用相同流程

🚀 记住:迁移不是终点,是起点。每一次自动化,都是对团队生产力的解放。


附录:完整项目结构

order-migration-demo/
├── order-service/
│   ├── src/
│   │   └── main/java/com/example/orderservice/...
│   ├── pom.xml
│   └── Dockerfile
├── inventory-service/
│   ├── src/
│   │   └── main/java/com/example/inventoryservice/...
│   ├── pom.xml
│   └── Dockerfile
├── docker-compose.yml
├── build-all.sh
├── k8s/ (由 kompose 生成)
│   ├── order-service-deployment.yaml
│   ├── order-service-service.yaml
│   ├── inventory-service-deployment.yaml
│   ├── inventory-service-service.yaml
│   ├── postgres-deployment.yaml
│   ├── postgres-service.yaml
│   └── ...
└── README.md

💬 结语:技术没有“谁取代谁”,只有“谁配合谁”。Docker Compose 与 Kubernetes,就像铅笔与激光切割机——一个让你快速画草图,一个让你精准制造产品。掌握它们的协同,你就能在敏捷与稳定之间,找到完美的平衡点。

🚀 现在,去构建你的下一个云原生应用吧!


🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐