基于go语言的云原生TodoList Demo 项目,验证云原生核心特性

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

项目概览

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

一、项目结构

复制代码
todolist-cloudnative/
├── cmd/                    # 主程序入口
│   └── server/
│       └── main.go         # Gin 服务启动入口
├── internal/               # 内部业务逻辑
│   ├── api/                # HTTP 接口层
│   │   └── v1/
│   │       └── todo.go     # Todo 接口实现
│   ├── model/              # 数据库模型
│   │   └── todo.go         # Todo 结构体与数据库操作
│   └── config/             # 配置加载
│       └── config.go       # 读取环境变量/配置文件
├── pkg/                    # 公共工具库
│   └── prometheus/         # Prometheus 指标注册
│       └── metrics.go      # 自定义监控指标
├── 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      # 抓取规则
├── scripts/               # 辅助脚本(可选)
│   └── init-db.sh         # 初始化数据库表(可选)
├── go.mod                 # Go 模块依赖
├── go.sum                 # 依赖校验
└── README.md              # 操作指南

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

1. 初始化项目(Go Modules)
bash 复制代码
mkdir todolist-cloudnative && cd todolist-cloudnative
go mod init github.com/your-username/todolist-cloudnative
go get -u github.com/gin-gonic/gin  # Web 框架
go get -u github.com/jmoiron/sqlx   # SQL 工具库
go get -u github.com/lib/pq         # PostgreSQL 驱动
go get -u github.com/prometheus/client_golang/prometheus  # 监控指标
2. 配置加载(internal/config/config.go
go 复制代码
package config

import (
	"os"
	"strconv"
)

type Config struct {
	ServerPort     string `env:"SERVER_PORT" default:"8080"`
	PostgresHost   string `env:"POSTGRES_HOST" default:"postgres-service"`
	PostgresPort   int    `env:"POSTGRES_PORT" default:"5432"`
	PostgresDB     string `env:"POSTGRES_DB" default:"tododb"`
	PostgresUser   string `env:"POSTGRES_USER"`
	PostgresPass   string `env:"POSTGRES_PASSWORD"`
}

func Load() (*Config, error) {
	port := os.Getenv("SERVER_PORT")
	if port == "" {
		port = "8080"
	}

	postgresPort, err := strconv.Atoi(os.Getenv("POSTGRES_PORT"))
	if err != nil {
		return nil, err
	}

	return &Config{
		ServerPort:   port,
		PostgresHost: os.Getenv("POSTGRES_HOST"),
		PostgresPort: postgresPort,
		PostgresDB:   os.Getenv("POSTGRES_DB"),
		PostgresUser: os.Getenv("POSTGRES_USER"),
		PostgresPass: os.Getenv("POSTGRES_PASSWORD"),
	}, nil
}
3. 数据库模型(internal/model/todo.go
go 复制代码
package model

import (
	"context"
	"time"

	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"
)

type Todo struct {
	ID        int64     `db:"id" json:"id"`
	Title     string    `db:"title" json:"title"`
	Content   string    `db:"content" json:"content"`
	IsDone    bool      `db:"is_done" json:"is_done"`
	CreatedAt time.Time `db:"created_at" json:"created_at"`
	UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}

type TodoModel struct {
	db *sqlx.DB
}

func NewTodoModel(db *sqlx.DB) *TodoModel {
	return &TodoModel{db: db}
}

func (m *TodoModel) Create(ctx context.Context, todo *Todo) error {
	_, err := m.db.NamedExecContext(ctx,
		`INSERT INTO todos (title, content, is_done, created_at, updated_at)
		VALUES (:title, :content, :is_done, NOW(), NOW())`,
		todo)
	return err
}

// 其他方法(查询、更新、删除)类似...
4. HTTP 接口(internal/api/v1/todo.go
go 复制代码
package v1

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"todolist-cloudnative/internal/config"
	"todolist-cloudnative/internal/model"
)

var (
	todoCounter = promauto.NewCounterVec(prometheus.CounterOpts{
		Name: "todo_operations_total",
		Help: "Total number of Todo operations",
	}, []string{"operation"})
)

type TodoHandler struct {
	cfg    *config.Config
	model  *model.TodoModel
}

func NewTodoHandler(cfg *config.Config, model *model.TodoModel) *TodoHandler {
	return &TodoHandler{cfg: cfg, model: model}
}

// CreateTodo 创建待办事项
func (h *TodoHandler) CreateTodo(c *gin.Context) {
	var req struct {
		Title   string `json:"title" binding:"required"`
		Content string `json:"content"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	todo := &model.Todo{
		Title:   req.Title,
		Content: req.Content,
		IsDone:  false,
	}

	if err := h.model.Create(c, todo); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "创建失败"})
		return
	}

	todoCounter.WithLabelValues("create").Inc()
	c.JSON(http.StatusCreated, todo)
}

// 其他接口(GetTodo、UpdateTodo、DeleteTodo)类似...
5. 服务启动(cmd/server/main.go
go 复制代码
package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"
	"todolist-cloudnative/internal/config"
	"todolist-cloudnative/internal/model"
	"todolist-cloudnative/internal/api/v1"
)

func main() {
	// 加载配置
	cfg, err := config.Load()
	if err != nil {
		log.Fatalf("加载配置失败: %v", err)
	}

	// 连接数据库
	db, err := sqlx.Connect("postgres",
		"host="+cfg.PostgresHost+
			" port="+strconv.Itoa(cfg.PostgresPort)+
			" dbname="+cfg.PostgresDB+
			" user="+cfg.PostgresUser+
			" password="+cfg.PostgresPass+
			" sslmode=disable")
	if err != nil {
		log.Fatalf("数据库连接失败: %v", err)
	}
	defer db.Close()

	// 初始化模型
	todoModel := model.NewTodoModel(db)

	// 初始化 Gin 路由
	router := gin.Default()
	v1Group := router.Group("/api/v1")
	{
		todoHandler := v1.NewTodoHandler(cfg, todoModel)
		v1Group.POST("/todos", todoHandler.CreateTodo)
		// 注册其他接口...
	}

	// 暴露 Prometheus 指标
	router.GET("/metrics", gin.WrapH(promhttp.Handler()))

	// 启动服务
	srv := &http.Server{
		Addr:    ":" + cfg.ServerPort,
		Handler: router,
	}

	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("服务启动失败: %v", err)
		}
	}()

	// 等待终止信号
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	// 优雅关闭
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("服务优雅关闭失败:", err)
	}
	log.Println("服务已退出")
}

三、容器化(Docker)

docker/Dockerfile(多阶段构建,最小化镜像体积)
dockerfile 复制代码
# 阶段 1:构建 Go 二进制文件
FROM golang:1.21-alpine AS builder
WORKDIR /app

# 缓存依赖(仅当 go.mod 或 go.sum 变化时重新下载)
COPY go.mod go.sum ./
RUN go mod download

# 复制代码并构建
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o todo-server ./cmd/server

# 阶段 2:运行轻量级镜像
FROM alpine:3.19
WORKDIR /app

# 安装必要工具(如 CA 证书)
RUN apk add --no-cache ca-certificates

# 从构建阶段复制二进制文件
COPY --from=builder /app/todo-server .

# 暴露端口
EXPOSE 8080

# 启动命令
CMD ["./todo-server"]

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

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

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

yaml 复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: todo-config
data:
  SERVER_PORT: "8080"
  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: SERVER_PORT
          valueFrom:
            configMapKeyRef:
              name: todo-config
              key: SERVER_PORT
        - 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
        livenessProbe:  # 存活探针(自动重启异常实例)
          httpGet:
            path: /metrics  # 或自定义健康检查接口
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:  # 就绪探针(流量路由前检查)
          httpGet:
            path: /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 Go 1.21
      uses: actions/setup-go@v5
      with:
        go-version: '1.21'

    - name: Build with Go
      run: go build -o todo-server ./cmd/server

    - 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: "/metrics"
    static_configs:
      - targets: ["todo-service:80"]  # K8s Service 域名(集群内部可解析)
2. 日志集成(Loki)
  • 在接口中添加日志记录(使用 zaplogrus 等结构化日志库)。
  • 部署 Promtail 收集 Pod 日志并发送到 Loki(需额外配置)。
3. Grafana 仪表盘(示例查询)
  • 请求速率rate(todo_operations_total{operation="create"}[5m])
  • Pod 内存使用container_memory_usage_bytes{container="todo-server"}

七、云原生特性验证

1. 容器化
  • 本地构建镜像:docker build -t todolist:v1 -f docker/Dockerfile .
  • 运行测试:docker run -p 8080:8080 todolist:v1,访问 http://localhost:8080/api/v1/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 配置,简化多环境部署。
相关推荐
The Chosen One9852 小时前
C++ : AVL树-详解
开发语言·c++
PH_modest3 小时前
【Qt跬步积累】—— 初识Qt
开发语言·qt
怀旧,3 小时前
【C++】18. 红⿊树实现
开发语言·c++
xiaopengbc3 小时前
在 Python 中实现观察者模式的具体步骤是什么?
开发语言·python·观察者模式
Python大数据分析@3 小时前
python用selenium怎么规避检测?
开发语言·python·selenium·网络爬虫
ThreeAu.3 小时前
Miniconda3搭建Selenium的python虚拟环境全攻略
开发语言·python·selenium·minicoda·python环境配置
360智汇云4 小时前
k8s交互桥梁:走进Client-Go
golang·kubernetes·交互
zhangfeng11334 小时前
R 语法高亮为什么没有,是需要安装专用的编辑软件,R语言自带的R-gui 功能还是比较简单
开发语言·r语言