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

在这里插入图片描述

项目概览

  • 目标:实现一个支持增删改查(CRUD)的 TodoList 后端服务,通过云原生技术栈部署,展示完整的云原生实践流程。
  • 技术栈
    • 后端:.NET 8 + ASP.NET Core(轻量、高性能、内置依赖注入)
    • 数据库:PostgreSQL(云原生持久化存储)
    • ORM:Entity Framework Core(EF Core,支持 PostgreSQL)
    • 容器化:Docker(多阶段构建减小镜像体积)
    • 编排:Kubernetes(K8s,声明式管理)
    • CI/CD:GitHub Actions(自动化构建、测试、部署)
    • 可观测性:Prometheus(监控指标) + Grafana(可视化) + Loki(日志聚合)
    • 配置管理:K8s ConfigMap/Secret(外部化配置)

在这里插入图片描述

一、项目结构

todolist-cloudnative/
├── src/                    # 核心业务代码
│   ├── TodoApi/            # ASP.NET Core Web API 项目
│   │   ├── Controllers/    # API 控制器
│   │   ├── Models/         # 数据库模型与 DTO
│   │   ├── Services/       # 业务逻辑层
│   │   ├── Data/           # 数据库上下文(EF Core)
│   │   ├── Program.cs      # 应用入口(配置、中间件、指标)
│   │   └── appsettings.json # 本地配置(开发环境)
│   └── TodoApi.Tests/      # 单元测试(xUnit)
├── docker/                  # Docker 配置
│   └── Dockerfile          # 多阶段构建脚本(.NET 8 镜像)
├── 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      # 抓取规则(.NET 指标路径)
├── scripts/               # 辅助脚本(可选)
│   └── init-db.sql        # 初始化数据库表(SQL 脚本)
├── .github/               # GitHub Actions 工作流
│   └── workflows/
│       └── deploy.yml    # CI/CD 自动化流程(构建、部署)
└── todolist.sln           # 解决方案文件

在这里插入图片描述

二、核心功能实现(.NET 8 + ASP.NET Core)

1. 初始化项目(.NET 8 Web API)
dotnet new webapi -n TodoApi
cd TodoApi
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL  # PostgreSQL 驱动
dotnet add package Prometheus-net.AspNetCore  # Prometheus 指标
dotnet add package AutoMapper  # 对象映射(可选)
2. 数据库模型与上下文(src/TodoApi/Data/TodoDbContext.cs

使用 EF Core 定义模型和数据库上下文,支持迁移。

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Data
{
    public class TodoDbContext : DbContext
    {
        public TodoDbContext(DbContextOptions<TodoDbContext> options) : base(options) { }

        public DbSet<TodoItem> TodoItems => Set<TodoItem>();
    }

    public class TodoItem
    {
        public int Id { get; set; }
        public required string Title { get; set; }
        public string? Description { get; set; }
        public bool IsCompleted { get; set; } = false;
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
        public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
    }
}
3. 业务逻辑与控制器(src/TodoApi/Controllers/TodoController.cs

实现 CRUD 接口,集成 Prometheus 指标统计。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using AutoMapper;
using TodoApi.Data;
using TodoApi.Models;

namespace TodoApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TodoController : ControllerBase
    {
        private readonly TodoDbContext _context;
        private readonly IMapper _mapper;
        private static readonly Counter TodoCounter = Metrics.CreateCounter(
            "todo_operations_total", 
            "Total number of Todo operations", 
            "operation");

        public TodoController(TodoDbContext context, IMapper mapper)
        {
            _context = context;
            _mapper = mapper;
        }

        // 创建待办事项
        [HttpPost]
        public async Task<ActionResult<TodoItem>> CreateTodo(TodoItemCreateDto createDto)
        {
            var todo = _mapper.Map<TodoItem>(createDto);
            _context.TodoItems.Add(todo);
            await _context.SaveChangesAsync();
            TodoCounter.WithLabels("create").Inc();  // 统计创建操作
            return CreatedAtAction(nameof(GetTodo), new { id = todo.Id }, todo);
        }

        // 获取所有待办事项
        [HttpGet]
        public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodos()
        {
            TodoCounter.WithLabels("list").Inc();  // 统计列表操作
            return await _context.TodoItems.ToListAsync();
        }

        // 其他接口(GetById、Update、Delete)类似...
    }

    // DTO(数据传输对象)
    public class TodoItemCreateDto
    {
        public required string Title { get; set; }
        public string? Description { get; set; }
    }
}
4. 配置与指标暴露(src/TodoApi/Program.cs

集成配置、中间件、Prometheus 指标端点。

using Microsoft.EntityFrameworkCore;
using AutoMapper;
using TodoApi.Data;
using TodoApi.Profiles;

var builder = WebApplication.CreateBuilder(args);

// 配置数据库(从 K8s Secret/ConfigMap 注入)
builder.Services.AddDbContext<TodoDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));

// 配置 AutoMapper
builder.Services.AddAutoMapper(typeof(TodoProfile));

// 集成 Prometheus 指标
builder.Services.AddMetrics()
    .AddRuntimeInstrumentation()
    .AddAspNetCoreInstrumentation()
    .AddHttpClientInstrumentation();

var app = builder.Build();

// 中间件
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

// 暴露 Prometheus 指标端点
app.MapMetrics();

// 健康检查(K8s 存活/就绪探针)
app.MapHealthChecks("/health", new HealthCheckOptions
{
    ResponseWriter = async (context, report) =>
    {
        var result = new
        {
            status = report.Status.ToString(),
            checks = report.Entries.Select(e => new { e.Name, e.Status, e.Duration })
        };
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsJsonAsync(result);
    }
});

app.Run();
5. 本地配置(src/TodoApi/appsettings.json

开发环境配置(生产环境通过 K8s 注入)。

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Database=tododb;Username=admin;Password=mypassword"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

在这里插入图片描述

三、容器化(Docker)

docker/Dockerfile(多阶段构建,最小化镜像体积)
# 阶段 1:构建 .NET 8 应用
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS builder
WORKDIR /app

# 缓存依赖(仅当 csproj 或 packages.lock.json 变化时重新下载)
COPY TodoApi/TodoApi.csproj TodoApi/
COPY TodoApi/TodoApi.Tests/TodoApi.Tests.csproj TodoApi.Tests/
RUN dotnet restore TodoApi/TodoApi.csproj

# 复制代码并构建
COPY . .
RUN dotnet publish TodoApi/TodoApi.csproj -c Release -o out

# 阶段 2:运行轻量级镜像(仅包含运行时)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app

# 从构建阶段复制发布文件
COPY --from=builder /app/out .

# 暴露端口(.NET API 默认端口 5000/5001,K8s Service 映射到 80)
EXPOSE 80

# 启动命令(使用 Kestrel 服务器)
ENTRYPOINT ["dotnet", "TodoApi.dll"]

在这里插入图片描述

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

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

非敏感配置(ConfigMap,注入数据库连接字符串等)

apiVersion: v1
kind: ConfigMap
metadata:
  name: todo-config
data:
  ConnectionStrings__DefaultConnection: "Host=postgres-service;Database=tododb;Username=admin;Password=mypassword"

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

# 生成 base64 编码(实际生产建议用 HashiCorp Vault 等工具)
echo -n "admin" | base64   # 用户名
echo -n "mypassword" | base64  # 密码
apiVersion: v1
kind: Secret
metadata:
  name: todo-secret
type: Opaque
data:
  ConnectionStrings__DefaultConnection: "Host=postgres-service;Database=tododb;Username=YWRtaW4=;Password=bXlwYXNzd29yZA=="
2. PostgreSQL 部署(k8s/postgres-statefulset.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
          value: "admin"
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: todo-secret
              key: POSTGRES_PASSWORD  # 假设 Secret 中单独存储密码
        - name: POSTGRES_DB
          value: "tododb"
        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
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: 80
        env:
        - name: ConnectionStrings__DefaultConnection
          valueFrom:
            configMapKeyRef:
              name: todo-config
              key: ConnectionStrings__DefaultConnection
        livenessProbe:  # 存活探针(自动重启异常实例)
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:  # 就绪探针(流量路由前检查)
          httpGet:
            path: /health
            port: 80
          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(内部访问)

apiVersion: v1
kind: Service
metadata:
  name: todo-service
spec:
  type: ClusterIP
  selector:
    app: todo
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

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

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
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 .NET 8
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '8.0.x'

    - name: Restore dependencies
      run: dotnet restore TodoApi/TodoApi.csproj

    - name: Build project
      run: dotnet build TodoApi/TodoApi.csproj -c Release --no-restore

    - name: Publish project
      run: dotnet publish TodoApi/TodoApi.csproj -c Release -o ./publish

    - 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
scrape_configs:
  - job_name: "todo_service"
    scrape_interval: 15s
    metrics_path: "/metrics"
    static_configs:
      - targets: ["todo-service:80"]  # K8s Service 域名(集群内部可解析)
2. 日志集成(Loki)
  • appsettings.json 中配置日志输出为 JSON 格式:
    "Logging": {
      "Console": {
        "FormatterName": "json",
        "FormatterOptions": {
          "IncludeScopes": false,
          "TimestampFormat": "o"
        }
      }
    }
    
  • 部署 Promtail 收集日志并发送到 Loki(需额外配置)。
3. Grafana 仪表盘(示例查询)
  • 请求速率rate(todo_operations_total{job="todo_service"}[5m])
  • 平均延迟rate(http_request_duration_seconds_sum{job="todo_service"}[5m]) / rate(http_request_duration_seconds_count{job="todo_service"}[5m])

七、云原生特性验证

1. 容器化
  • 本地构建镜像:docker build -t todolist:v1 -f docker/Dockerfile .
  • 运行测试:docker run -p 80:80 todolist:v1,访问 http://localhost:80/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):
    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 配置,简化多环境部署。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐