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

👋 大家好,欢迎来到我的技术博客!
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕Kubernetes这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!
文章目录
- Kubernetes - 与 Docker Compose 的协同使用,快速迁移应用 🚀
- 为什么需要协同使用? 🤔
- 构建示例应用:订单服务系统 🛒
- 本地开发:使用 Docker Compose 快速启动 🐳
- 从 Docker Compose 到 Kubernetes:自动化转换 🔄
- 构建并推送镜像到本地仓库 🏷️
- 部署到 Kubernetes 🚀
- 架构对比:Compose vs K8s 流程图 📊
- 配置管理:环境变量与 ConfigMap 的演进 🧩
- 服务间通信:DNS 与健康检查 🔄
- 持久化存储:PostgreSQL 的数据安全 🗃️
- 日志与监控:如何观察你的服务? 📈
- CI/CD 自动化:从 Git 提交到 K8s 部署 🤖
- 迁移策略:灰度发布与回滚 🎯
- 性能对比:Compose vs K8s 🏎️
- 常见陷阱与解决方案 🚫
- 未来演进:Helm、Kustomize、Argo CD 🧩
- 总结:你已经完成了从 Compose 到 K8s 的优雅迁移 🎉
- 延伸阅读与资源 📚
- 最后:你的下一步行动清单 ✅
- 附录:完整项目结构
Kubernetes - 与 Docker Compose 的协同使用,快速迁移应用 🚀
在现代云原生架构中,容器化已成为应用部署的基石。Docker Compose 以其简洁的语法和快速的本地开发体验,成为开发者日常调试和原型验证的首选工具;而 Kubernetes 作为生产级容器编排的事实标准,提供了高可用、弹性伸缩、服务发现与自我修复等强大能力。然而,从本地开发环境平滑过渡到生产集群,常常是团队面临的一大挑战。
本文将深入探讨如何协同使用 Docker Compose 与 Kubernetes,实现应用的快速迁移。我们将从基础概念入手,逐步构建一个真实可运行的 Java 微服务应用,使用 Docker Compose 在本地快速启动,再通过工具链自动化生成 Kubernetes 清单,最终在 Minikube 或 Kind 环境中完成部署。全程包含完整的 Java 代码示例、配置文件、Mermaid 流程图,并解释每一步背后的架构设计哲学。
为什么需要协同使用? 🤔
Docker Compose 和 Kubernetes 并非对立关系,而是开发与生产环境的互补工具。
| 维度 | Docker Compose | Kubernetes |
|---|---|---|
| 用途 | 本地开发、测试、快速原型 | 生产部署、集群管理 |
| 配置文件 | docker-compose.yml | Deployment, 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 微服务系统,包含三个组件:
- Order Service(订单服务):Java Spring Boot 应用,暴露 REST API
- Inventory Service(库存服务):Java Spring Boot 应用,提供库存查询
- 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 从镜像仓库拉取镜像。我们有三种选择:
- 推送到公共仓库(如 Docker Hub)→ 不适合私有项目
- 使用 Kind 内置 registry → 推荐用于本地测试
- 使用 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 绘制整个流程:
📌 图中绿色部分代表“生产环境”,蓝色代表“迁移阶段”,粉色代表“开发阶段”。你可以看到,从 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 部署 🤖
现在我们有了完整的迁移流程:
- 开发者提交代码到分支
- CI 工具(如 GitHub Actions、GitLab CI)构建镜像
- 推送到私有 registry
- 使用 Kompose 生成 K8s 清单
- 使用
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 |
| 6 | 100% 流量切换,删除旧 Compose 环境 |
📌 推荐工具:Flagger 实现 Canary + 自动回滚
性能对比:Compose vs K8s 🏎️
| 指标 | Docker Compose | Kubernetes |
|---|---|---|
| 启动时间 | 5~15 秒 | 30~90 秒(含镜像拉取) |
| 内存占用 | 500MB~1GB | 1.5GB~3GB(含 kubelet、etcd) |
| 网络延迟 | 本地网络 | CNI 插件引入轻微延迟(<5ms) |
| 可扩展性 | 单机 | 多节点、跨可用区 |
| 故障恢复 | 手动重启 | 自动重启 + 重调度 |
| 配置管理 | 简单 | 复杂但强大(Helm、Kustomize) |
✅ 结论:K8s 在开发环境“慢”,但在生产环境“稳”。你的目标不是让 K8s 变快,而是让系统更可靠。
常见陷阱与解决方案 🚫
| 问题 | 原因 | 解决方案 |
|---|---|---|
ImagePullBackOff | 镜像未推送或名称错误 | 使用 kind load docker-image 或推送至私有 registry |
CrashLoopBackOff | Java 应用启动失败 | 检查日志:kubectl logs -f <pod>,确保环境变量正确 |
Connection refused | 服务未就绪 | 添加 readinessProbe,确保数据库连接重试机制 |
No such file or directory | JAR 文件未打包 | 检查 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 CD | GitOps 自动同步,声明式部署 |
示例: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 是生产环境的“操作系统”。两者协同,才是云原生的正确打开方式。
延伸阅读与资源 📚
- Docker Compose Documentation
- Kompose Official Guide
- Kind: Kubernetes in Docker
- Spring Boot Production Ready
- Kubernetes Best Practices
- Helm Charts Best Practices
最后:你的下一步行动清单 ✅
- 复制本文示例代码,在本地运行一遍
- 尝试修改 Java 服务,添加一个新的 API,看看是否能自动部署
- 尝试使用 Helm 替代 Kompose,实现环境模板化
- 在 GitHub Actions 中搭建 CI,实现提交即部署
- 在云上部署一个真实集群(如 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,就像铅笔与激光切割机——一个让你快速画草图,一个让你精准制造产品。掌握它们的协同,你就能在敏捷与稳定之间,找到完美的平衡点。
🚀 现在,去构建你的下一个云原生应用吧!
🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)