基于Java Spring Boot的云原生TodoList Demo 项目,验证云原生核心特性

以下是一个基于 Java Spring Boot 的云原生 TodoList Demo 项目,涵盖 容器化、Kubernetes 编排、CI/CD、可观测性、弹性扩缩容 等核心云原生特性,代码简洁且附详细操作指南,适合入门学习。

项目概览

  • 目标:实现一个支持增删改查(CRUD)的 TodoList 后端服务,通过云原生技术栈部署,展示完整的云原生实践流程。
  • 技术栈
    • 后端:Java 17 + Spring Boot 3.2(轻量、模块化)
    • 数据库:PostgreSQL(云原生持久化存储)
    • 容器化:Docker
    • 编排:Kubernetes(K8s)
    • CI/CD:GitHub Actions
    • 可观测性:Prometheus(监控) + Grafana(可视化) + Loki(日志)
    • 配置管理:Spring Cloud Config(可选,或直接使用 K8s ConfigMap)

一、项目结构

复制代码
todolist-cloudnative/
├── src/                    # 核心业务代码
│   ├── main/
│   │   ├── java/com/example/todo/
│   │   │   ├── controller/  # REST 接口
│   │   │   ├── model/       # 数据库实体
│   │   │   ├── repository/  # 数据访问层
│   │   │   ├── service/     # 业务逻辑层
│   │   │   └── TodoApplication.java  # 启动类
│   │   └── resources/
│   │       ├── application.yml  # 主配置
│   │       └── logback-spring.xml  # 日志配置(Loki 集成)
│   └── test/                # 单元测试
├── docker/                  # Docker 相关配置
│   └── Dockerfile           # 容器化构建脚本(多阶段构建)
├── k8s/                     # K8s 部署配置
│   ├── todo-deployment.yaml    # 应用 Deployment
│   ├── todo-service.yaml       # 应用 Service
│   ├── postgres-statefulset.yaml  # PostgreSQL StatefulSet
│   ├── postgres-service.yaml      # PostgreSQL Service
│   ├── configmap.yaml    # 非敏感配置(K8s 注入)
│   └── secret.yaml       # 敏感配置(数据库密码,K8s Secret)
├── prometheus/            # Prometheus 监控配置
│   └── todolist.yml      # 抓取规则
├── .github/               # GitHub Actions 工作流
│   └── workflows/
│       └── deploy.yml    # CI/CD 自动化流程
└── README.md              # 操作指南

二、核心功能实现(Spring Boot 后端)

1. 初始化项目(Spring Initializr)
  • 访问 https://start.spring.io/,选择:
    • Project: Maven
    • Language: Java
    • Spring Boot: 3.2.0
    • Dependencies:
      • Spring Web(REST 接口)
      • Spring Data JPA(数据库操作)
      • PostgreSQL Driver(数据库驱动)
      • Micrometer Tracing(可观测性)
      • Actuator(健康检查、指标暴露)
2. 数据库模型(src/main/java/com/example/todo/model/TodoItem.java
java 复制代码
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "todo_items")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TodoItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String title;
    
    private String description;
    
    @Column(columnDefinition = "BOOLEAN DEFAULT FALSE")
    private boolean completed;
}
3. 数据访问层(src/main/java/com/example/todo/repository/TodoRepository.java
java 复制代码
import com.example.todo.model.TodoItem;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface TodoRepository extends JpaRepository<TodoItem, Long> {
}
4. 业务逻辑层(src/main/java/com/example/todo/service/TodoService.java
java 复制代码
import com.example.todo.model.TodoItem;
import com.example.todo.repository.TodoRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
@RequiredArgsConstructor
public class TodoService {
    private final TodoRepository todoRepository;

    public TodoItem createTodo(TodoItem todoItem) {
        return todoRepository.save(todoItem);
    }

    public List<TodoItem> getAllTodos() {
        return todoRepository.findAll();
    }

    public TodoItem getTodoById(Long id) {
        return todoRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Todo not found with id: " + id));
    }

    public TodoItem updateTodo(Long id, TodoItem updatedTodo) {
        TodoItem existingTodo = getTodoById(id);
        existingTodo.setTitle(updatedTodo.getTitle());
        existingTodo.setDescription(updatedTodo.getDescription());
        existingTodo.setCompleted(updatedTodo.isCompleted());
        return todoRepository.save(existingTodo);
    }

    public void deleteTodo(Long id) {
        todoRepository.deleteById(id);
    }
}
5. REST 接口(src/main/java/com/example/todo/controller/TodoController.java
java 复制代码
import com.example.todo.model.TodoItem;
import com.example.todo.service.TodoService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/todos")
@RequiredArgsConstructor
public class TodoController {
    private final TodoService todoService;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public TodoItem createTodo(@RequestBody TodoItem todoItem) {
        return todoService.createTodo(todoItem);
    }

    @GetMapping
    public List<TodoItem> getAllTodos() {
        return todoService.getAllTodos();
    }

    @GetMapping("/{id}")
    public TodoItem getTodoById(@PathVariable Long id) {
        return todoService.getTodoById(id);
    }

    @PutMapping("/{id}")
    public TodoItem updateTodo(@PathVariable Long id, @RequestBody TodoItem updatedTodo) {
        return todoService.updateTodo(id, updatedTodo);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteTodo(@PathVariable Long id) {
        todoService.deleteTodo(id);
    }
}
6. 配置外部化(src/main/resources/application.yml
yaml 复制代码
spring:
  datasource:
    url: jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
    username: ${POSTGRES_USER}
    password: ${POSTGRES_PASSWORD}
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: update  # 自动创建表(生产环境建议用 flyway)
    show-sql: true

management:
  endpoints:
    web:
      exposure:
        include: health, info, metrics, prometheus  # 暴露监控端点
  endpoint:
    health:
      show-details: always

三、容器化(Docker)

docker/Dockerfile(多阶段构建,减小镜像体积)
dockerfile 复制代码
# 阶段 1:构建 Maven 项目
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
# 缓存依赖(仅当 pom.xml 变化时重新下载)
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests

# 阶段 2:运行 Spring Boot 应用
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 从构建阶段复制 JAR 包
COPY --from=build /app/target/todo-cloudnative-*.jar ./todo.jar
# 暴露 Spring Boot 端口(8080)
EXPOSE 8080
# 启动命令(禁用 Tomcat 优雅关闭,适配 K8s 探针)
CMD ["java", "-jar", "todo.jar", "--server.port=8080"]

四、Kubernetes 编排(云原生核心)

1. 配置管理(k8s/configmap.yamlk8s/secret.yaml

非敏感配置(ConfigMap,注入数据库名、主机等)

yaml 复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: todo-config
data:
  POSTGRES_DB: "tododb"
  POSTGRES_HOST: "postgres-service"  # K8s Service 名称(服务发现)

敏感配置(Secret,注入数据库用户、密码,需 base64 编码)

bash 复制代码
# 生成 base64 编码(实际生产建议用外部密钥管理系统如 HashiCorp Vault)
echo -n "admin" | base64   # 用户名
echo -n "mypassword" | base64  # 密码
echo -n "5432" | base64    # 端口
yaml 复制代码
apiVersion: v1
kind: Secret
metadata:
  name: todo-secret
type: Opaque
data:
  POSTGRES_USER: "YWRtaW4="       # "admin" 的 base64
  POSTGRES_PASSWORD: "bXlwYXNzd29yZA=="  # "mypassword" 的 base64
  POSTGRES_PORT: "NTQzNg=="       # "5432" 的 base64
2. PostgreSQL 部署(k8s/postgres-statefulset.yaml
yaml 复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres-service
  replicas: 1  # 生产环境建议 3 副本 + 主从复制(Patroni)
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15-alpine
        env:
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: todo-secret
              key: POSTGRES_USER
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: todo-secret
              key: POSTGRES_PASSWORD
        - name: POSTGRES_DB
          valueFrom:
            configMapKeyRef:
              name: todo-config
              key: POSTGRES_DB
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: postgres-data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:  # 持久化存储(云原生存储卷,如 AWS EBS、阿里云盘)
  - metadata:
      name: postgres-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
3. Todo 服务部署(k8s/todo-deployment.yaml
yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: todo-deployment
spec:
  replicas: 2  # 初始副本数(弹性扩缩容基础)
  selector:
    matchLabels:
      app: todo
  template:
    metadata:
      labels:
        app: todo
    spec:
      containers:
      - name: todo
        image: your-docker-username/todolist:v1  # 替换为实际镜像地址
        ports:
        - containerPort: 8080
        env:
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: todo-secret
              key: POSTGRES_USER
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: todo-secret
              key: POSTGRES_PASSWORD
        - name: POSTGRES_HOST
          valueFrom:
            configMapKeyRef:
              name: todo-config
              key: POSTGRES_HOST
        - name: POSTGRES_PORT
          valueFrom:
            secretKeyRef:
              name: todo-secret
              key: POSTGRES_PORT
        - name: POSTGRES_DB
          valueFrom:
            configMapKeyRef:
              name: todo-config
              key: POSTGRES_DB
        livenessProbe:  # 存活探针(自动重启异常实例)
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:  # 就绪探针(流量路由前检查)
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:  # 资源限制(K8s 调度依据)
          requests:
            cpu: "100m"
            memory: "256Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
4. 服务暴露(k8s/todo-service.yamlk8s/todo-ingress.yaml

ClusterIP Service(内部访问)

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: todo-service
spec:
  type: ClusterIP
  selector:
    app: todo
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080

Ingress(外部访问,需安装 NGINX Ingress Controller)

yaml 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: todo-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /todos
        pathType: Prefix
        backend:
          service:
            name: todo-service
            port:
              number: 80

五、CI/CD 自动化(GitHub Actions)

.github/workflows/deploy.yml
yaml 复制代码
name: Deploy to Kubernetes

on:
  push:
    branches: [ "main" ]

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4

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

    - name: Build with Maven
      run: mvn clean package -DskipTests

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3

    - name: Login to Docker Hub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}

    - name: Build and push Docker image
      uses: docker/build-push-action@v5
      with:
        context: .
        file: docker/Dockerfile
        push: true
        tags: your-docker-username/todolist:latest,your-docker-username/todolist:${{ github.sha }}

  deploy-to-k8s:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up kubectl
      uses: azure/setup-kubectl@v3
      with:
        version: 'v1.28.0'

    - name: Deploy to Kubernetes cluster
      run: |
        # 替换镜像标签为最新提交 SHA
        sed -i "s|your-docker-username/todolist:v1|your-docker-username/todolist:${{ github.sha }}|g" k8s/todo-deployment.yaml
        # 应用 K8s 配置
        kubectl apply -f k8s/
      env:
        KUBECONFIG: ${{ secrets.KUBECONFIG }}  # 从 GitHub Secrets 读取集群配置

六、可观测性配置

1. Prometheus 监控(prometheus/todolist.yml
yaml 复制代码
scrape_configs:
  - job_name: "todo_service"
    scrape_interval: 15s
    metrics_path: "/actuator/prometheus"
    static_configs:
      - targets: ["todo-service:80"]  # K8s Service 域名(集群内部可解析)
2. 日志集成(Loki)
  • logback-spring.xml 中配置 Logstash 或直接输出 JSON 格式日志:

    xml 复制代码
    <configuration>
      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
      </appender>
      <root level="info">
        <appender-ref ref="CONSOLE"/>
      </root>
    </configuration>
  • 使用 Promtail 收集日志并发送到 Loki(需额外部署 Promtail)。

3. Grafana 仪表盘(示例查询)
  • 请求速率rate(http_server_requests_seconds_count{job="todo_service"}[5m])
  • 平均延迟rate(http_server_requests_seconds_sum{job="todo_service"}[5m]) / rate(http_server_requests_seconds_count{job="todo_service"}[5m])
  • JVM 内存使用jvm_memory_used_bytes{area="heap"}

七、云原生特性验证

1. 容器化
  • 本地构建镜像:docker build -t todolist:v1 -f docker/Dockerfile .
  • 运行测试:docker run -p 8080:8080 todolist:v1,访问 http://localhost:8080/api/todos 验证接口。
2. Kubernetes 部署
  • 本地搭建 K8s 集群(Minikube 或 Kind):minikube start
  • 应用配置:kubectl apply -f k8s/
  • 查看状态:kubectl get pods,svc,ingress
3. 弹性扩缩容
  • 手动扩缩容:kubectl scale deployment/todo-deployment --replicas=3

  • 自动扩缩容(HPA):

    yaml 复制代码
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: todo-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: todo-deployment
      minReplicas: 2
      maxReplicas: 10
      metrics:
      - type: Resource
        resource:
          name: cpu
          target:
            type: Utilization
            averageUtilization: 70  # CPU 使用率超 70% 自动扩容
4. 故障自愈
  • 手动删除 Pod:kubectl delete pod <pod-name>,观察 K8s 自动重建新 Pod(状态变为 Running)。
5. 持续交付
  • 推送代码到 GitHub main 分支,触发 GitHub Actions 自动构建、测试、部署。

八、总结

通过这个 TodoList Demo,你可以完整体验云原生应用的核心流程:

  1. 容器化:用 Docker 封装应用,确保环境一致性。
  2. Kubernetes 编排:通过 Deployment、Service 等资源实现自动化管理。
  3. CI/CD:GitHub Actions 实现代码提交到生产的全自动化。
  4. 可观测性:Prometheus + Grafana 监控性能,Loki 收集日志。
  5. 弹性扩缩容:HPA 根据负载自动调整资源,保障高可用。

后续可扩展方向:

  • 微服务拆分(如用户服务、待办服务),用 Istio 实现服务网格。
  • 引入数据库读写分离(主从复制)。
  • 添加认证鉴权(OAuth2、JWT)。
  • 使用 Helm 打包 K8s 配置,简化多环境部署。
相关推荐
程序员清风4 小时前
快手一面:为什么要求用Static来修饰ThreadLocal变量?
java·后端·面试
逍遥德4 小时前
Java8 Comparator接口 和 List Steam 排序使用案例
java·spring boot·list·排序算法
前行的小黑炭5 小时前
Android :如何快速让布局适配手机和平板?
android·java·kotlin
_BugMan6 小时前
【IDEA】干活?一个IDEA即可,集成开发平台打造攻略
java·ide·intellij-idea
YA3337 小时前
java设计模式二、工厂
java·开发语言·设计模式
学Linux的语莫7 小时前
kubekey离线搭建k8s高版本>23安装,cri-dockerd通信
云原生·容器·kubernetes
金色天际线-7 小时前
Nginx 优化与防盗链配置指南
java·后端·spring
我爱挣钱我也要早睡!8 小时前
Java 复习笔记
java·开发语言·笔记
Sweety丶╮79410 小时前
【Ansible】的介绍
云原生·ansible