以下是一个基于 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.yaml
和 k8s/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.yaml
和 k8s/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)
- 在接口中添加日志记录(使用
zap
或logrus
等结构化日志库)。 - 部署 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):
yamlapiVersion: 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,你可以完整体验云原生应用的核心流程:
- 容器化:用 Docker 封装应用,确保环境一致性。
- Kubernetes 编排:通过 Deployment、Service 等资源实现自动化管理。
- CI/CD:GitHub Actions 实现代码提交到生产的全自动化。
- 可观测性:Prometheus + Grafana 监控性能,Loki 收集日志。
- 弹性扩缩容:HPA 根据负载自动调整资源,保障高可用。
后续可扩展方向:
- 微服务拆分(如用户服务、待办服务),用 Istio 实现服务网格。
- 引入数据库读写分离(主从复制)。
- 添加认证鉴权(OAuth2、JWT)。
- 使用 Helm 打包 K8s 配置,简化多环境部署。