云原生TodoList Demo 项目,验证云原生核心特性

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

项目概览

  • 目标:实现一个支持增删改查(CRUD)的 TodoList 后端服务,通过云原生技术栈部署,展示完整的云原生实践流程。
  • 技术栈
    • 后端:Python + FastAPI(轻量高性能框架)
    • 数据库:PostgreSQL(云原生持久化存储)
    • 容器化:Docker
    • 编排:Kubernetes(K8s)
    • CI/CD:GitHub Actions
    • 可观测性:Prometheus(监控) + Grafana(可视化) + Loki(日志)

一、项目结构

复制代码
todolist-cloudnative/
├── app/                  # 核心业务代码
│   ├── main.py           # FastAPI 入口
│   ├── models.py         # SQLAlchemy 模型(数据库表结构)
│   ├── database.py       # 数据库连接工具
│   └── requirements.txt  # Python 依赖
├── 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    # 配置(非敏感)
│   └── secret.yaml       # 敏感配置(数据库密码)
├── prometheus/           # Prometheus 监控配置
│   └── todolist.yml      # 抓取规则
├── .github/              # GitHub Actions 工作流
│   └── workflows/
│       └── deploy.yml    # CI/CD 自动化流程
└── README.md             # 操作指南

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

1. 安装依赖(app/requirements.txt
txt 复制代码
fastapi==0.104.1
uvicorn==0.23.2
sqlalchemy==2.0.23
psycopg2-binary==2.9.7  # PostgreSQL 驱动
python-multipart==0.0.6  # 文件上传(可选)
prometheus-fastapi-instrumentator==0.3.1  # Prometheus 指标暴露
2. 数据库模型(app/models.py
python 复制代码
from sqlalchemy import Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class TodoItem(Base):
    __tablename__ = "todo_items"
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(255), index=True, nullable=False)
    description = Column(String(500))
    completed = Column(Boolean, default=False)
3. 数据库连接(app/database.py
python 复制代码
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os

# 从环境变量获取数据库连接信息(K8s ConfigMap/Secret 注入)
DATABASE_URL = f"postgresql://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@{os.getenv('POSTGRES_HOST')}:{os.getenv('POSTGRES_PORT')}/{os.getenv('POSTGRES_DB')}"

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
4. FastAPI 入口(app/main.py
python 复制代码
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from . import models, schemas, database
from prometheus_fastapi_instrumentator import Instrumentator

# 初始化数据库表(仅首次运行时创建)
models.Base.metadata.create_all(bind=database.engine)

app = FastAPI(title="TodoList 云原生服务")

# 注入数据库会话
def get_db():
    db = database.SessionLocal()
    try:
        yield db
    finally:
        db.close()

# 自动暴露 Prometheus 指标(请求计数、延迟)
Instrumentator().instrument(app).expose(app)

# Todo 业务接口
@app.post("/todos/", response_model=schemas.TodoItem)
def create_todo(todo: schemas.TodoCreate, db: Session = Depends(get_db)):
    db_todo = models.TodoItem(**todo.dict())
    db.add(db_todo)
    db.commit()
    db.refresh(db_todo)
    return db_todo

@app.get("/todos/{todo_id}", response_model=schemas.TodoItem)
def read_todo(todo_id: int, db: Session = Depends(get_db)):
    db_todo = db.query(models.TodoItem).filter(models.TodoItem.id == todo_id).first()
    if not db_todo:
        raise HTTPException(status_code=404, detail="Todo 不存在")
    return db_todo

# 其他接口(更新、删除)类似,此处省略...
5. Pydantic 模型(app/schemas.py
python 复制代码
from pydantic import BaseModel

class TodoCreate(BaseModel):
    title: str
    description: str = None

class TodoItem(TodoCreate):
    id: int
    completed: bool

三、容器化(Docker)

docker/Dockerfile
dockerfile 复制代码
# 使用 Python 官方轻量镜像
FROM python:3.11-slim-bookworm

# 设置工作目录
WORKDIR /app

# 安装系统依赖(PostgreSQL 驱动需要)
RUN apt-get update && apt-get install -y --no-install-recommends gcc python3-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件并安装
COPY app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制项目代码
COPY app/ ./app/

# 暴露 FastAPI 端口(8000)
EXPOSE 8000

# 启动命令(UVicorn 自动重载仅在开发时启用)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

四、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,需手动创建或通过 CI 注入)

bash 复制代码
# 生成 base64 编码的密码(实际生产建议用外部密钥管理系统)
echo -n "mypassword" | base64
yaml 复制代码
apiVersion: v1
kind: Secret
metadata:
  name: todo-secret
type: Opaque
data:
  POSTGRES_USER: "admin"          # base64 编码值
  POSTGRES_PASSWORD: "bXlwYXNzd29yZA=="  # "mypassword" 的 base64
  POSTGRES_PORT: "5432"
2. PostgreSQL 部署(k8s/postgres-statefulset.yaml
yaml 复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres-service
  replicas: 1  # 生产环境建议 3 副本 + 主从复制
  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:  # 持久化存储(云原生存储卷)
  - 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: 8000
        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: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:  # 就绪探针(流量路由前检查)
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 5
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: 8000

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 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
    static_configs:
      - targets: ["todo-service:80"]  # K8s Service 域名(集群内部可解析)
2. Grafana 仪表盘(示例查询)
  • 请求速率rate(http_requests_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])
3. Loki 日志收集
  • 通过 Fluentd 或 Vector 将 Pod 日志(stdout/stderr)转发到 Loki。
  • Grafana 中查询日志:{job="todo_service"} | json

七、云原生特性验证

1. 容器化
  • 本地构建镜像:docker build -t todolist:v1 -f docker/Dockerfile .
  • 运行测试:docker run -p 8000:8000 todolist:v1,访问 http://localhost:8000/docs 验证接口。
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 配置,简化多环境部署。
相关推荐
喂完待续7 小时前
【序列晋升】28 云原生时代的消息驱动架构 Spring Cloud Stream的未来可能性
spring cloud·微服务·云原生·重构·架构·big data·序列晋升
jzzy_hony7 小时前
云原生:微服务与Serverless指南
微服务·云原生·serverless
roman_日积跬步-终至千里7 小时前
【软件架构设计(23)】云计算与云原生技术
云原生·云计算
静若繁花_jingjing9 小时前
云原生部署_k8s入门
云原生·容器·kubernetes
真上帝的左手15 小时前
十一、容器化 vs 虚拟化-Kubernetes(K8s)
云原生·容器·kubernetes
落日漫游15 小时前
K8s ConfigMap配置管理全解析
云原生·容器·kubernetes
我真的是大笨蛋15 小时前
K8S-Pod(下)
java·笔记·云原生·容器·kubernetes
紫金修道17 小时前
k8s的容器操作指令
云原生·容器·kubernetes
喝杯白开水!17 小时前
K8s中的控制器DaemonSet、StatefulSet、Job、CronJob、Server发现、健康检查、存储卷(PV),相关知识总结
云原生·容器·kubernetes