高级5-6 小时·全栈部署

Go 服务 → Docker → K8s 全流程

将 Go 微服务容器化后部署到 K8s,包含多阶段构建、健康检查、HPA 自动扩缩容

goDockerK8sgodockerkubernetesfullstack

实战案例:Go 服务 → Docker → K8s 全流程

场景描述:将一个 Go HTTP 服务通过多阶段构建优化成轻量容器镜像,再用 Kubernetes 进行自动化部署、健康检查与弹性伸缩,实现生产级应用的持续交付。

你将学到

  1. 编写生产级 Go 微服务:包含健康检查端点、优雅关闭和环境变量配置。
  2. 使用 Docker 多阶段构建:创建安全、高效且体积最小化的生产镜像。
  3. 为 Kubernetes 编写完整的部署清单:包括 Deployment、Service 和 ConfigMap。
  4. 配置应用的健康探针:设置 Liveness 和 Readiness 探针保障服务高可用。
  5. 设置水平 Pod 自动伸缩:基于 CPU 使用率实现服务的自动扩缩容。

前置知识

在开始本案例前,你需要掌握:

架构设计

我们将构建一个从代码到运行的全链路流程,整体架构如下:

+----------------+     +-----------------+     +-----------------------------+
|  Developer     |     |  Docker Registry|     |       Kubernetes Cluster    |
|  (本地开发)    |     |   (镜像仓库)    |     |                             |
|                |     |                 |     |  +-----------------------+  |
|  Go Source     |---->|  Build & Push   |---->|  |      Deployment       |  |
|  Code          |     |  Multi-stage    |     |  |  +--Pod--+  +--Pod--+  |  |
|                |     |  Docker Image   |     |  |  |Container| |Container|  |  |
+----------------+     +-----------------+     |  |  +--------+ +--------+  |  |
       |                        |               |  |     HPA (自动伸缩)   |  |
       |                        |               |  +-----------------------+  |
       v                        v               |  +-----------------------+  |
  +-----------+          +-----------+          |  |       Service          |  |
  |  Docker   |          |   Image   |          |  |  (LoadBalancer/ClusterIP)  |
  |  Build    |          |   Pull    |          |  +-----------┬-----------+  |
  | (多阶段)  |          | (到集群)  |          |              |              |
  +-----------+          +-----------+          |              v              |
                                               |  +-----------------------+  |
                                               |  |    External Traffic    |  |
                                               |  +-----------------------+  |
                                               +-----------------------------+

流程简述:开发者编写Go代码并提交。通过Docker多阶段构建生成轻量化的生产镜像,推送到镜像仓库。Kubernetes集群从仓库拉取镜像,根据Deployment、Service和HPA的声明配置,自动创建、管理、暴露和伸缩应用实例。

实现步骤

步骤1:编写生产级 Go 服务

首先,我们创建一个简单的HTTP服务,它具备健康检查、优雅关闭和读取环境变量的能力,这是微服务部署的基础。

1.1 初始化项目

mkdir go-k8s-app && cd go-k8s-app
go mod init github.com/yourusername/go-k8s-app

1.2 创建 main.go

package main

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

func main() {
	// 从环境变量读取配置,模拟从K8s ConfigMap注入
	appName := getEnv("APP_NAME", "go-k8s-app")
	version := getEnv("APP_VERSION", "1.0.0")
	port := getEnv("PORT", "8080")

	log.Printf("Starting %s version %s", appName, version)

	// 设置路由
	mux := http.NewServeMux()

	// 健康检查端点,用于K8s探针
	mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, `{"status": "OK", "time": "%s"}`, time.Now().Format(time.RFC3339))
	})

	// 就绪检查端点,用于K8s就绪探针
	mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
		// 可以在这里检查依赖服务(如数据库)是否就绪
		// 模拟一个简单的就绪逻辑
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "Ready")
	})

	// 主业务逻辑
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Received request from %s", r.RemoteAddr)
		w.Header().Set("Content-Type", "text/plain")
		fmt.Fprintf(w, "Hello from %s!\n", appName)
	})

	// 创建HTTP服务器
	server := &http.Server{
		Addr:         ":" + port,
		Handler:      mux,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 15 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	// 优雅关闭处理
	go func() {
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
		<-sigChan
		log.Println("Shutting down gracefully...")
		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()
		if err := server.Shutdown(ctx); err != nil {
			log.Printf("Server forced to shutdown: %v", err)
		}
	}()

	log.Printf("Server listening on port %s", port)
	if err := server.ListenAndServe(); err != http.ErrServerClosed {
		log.Fatalf("Could not listen on port %s: %v\n", port, err)
	}
	log.Println("Server exited properly")
}

func getEnv(key, fallback string) string {
	if value, ok := os.LookupEnv(key); ok {
		return value
	}
	return fallback
}

步骤2:使用多阶段构建进行 Docker 化

多阶段构建可以显著减小最终镜像的体积,提高安全性,并加快部署速度。

2.1 创建 Dockerfile

# 阶段1:构建阶段
FROM golang:1.21-alpine AS builder

# 设置工作目录
WORKDIR /app

# 复制go.mod和go.sum文件,利用缓存层
COPY go.mod go.sum ./
RUN go mod download

# 复制源代码
COPY . .

# 构建一个静态链接的二进制文件,禁用CGO
# -ldflags参数用于设置版本信息
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /go-k8s-app .

# 阶段2:运行阶段
FROM alpine:latest

# 安装必要的包,ca-certificates用于HTTPS,tzdata用于时区
RUN apk --no-cache add ca-certificates tzdata

# 设置时区
ENV TZ=Asia/Shanghai

# 创建非root用户以增强安全性
RUN adduser -D -u 10001 appuser
USER appuser

# 从构建阶段复制编译好的二进制文件
COPY --from=builder /go-k8s-app /usr/local/bin/

# 声明容器将监听的端口
EXPOSE 8080

# 设置容器启动命令
ENTRYPOINT ["go-k8s-app"]

步骤3:本地构建与测试

在推送到仓库或部署到K8s前,先在本地验证。

3.1 构建 Docker 镜像

docker build -t go-k8s-app:latest .

3.2 运行容器并测试

# 后台运行容器,映射端口并注入环境变量
docker run -d --name go-app -p 8080:8080 -e APP_NAME=my-go-app go-k8s-app:latest

# 测试健康检查
curl http://localhost:8080/healthz
# 预期输出: {"status": "OK", "time": "2023-10-27T10:00:00+08:00"}

# 测试业务接口
curl http://localhost:8080/
# 预期输出: Hello from my-go-app!

# 查看日志
docker logs -f go-app

# 清理本地容器
docker stop go-app && docker rm go-app

步骤4:部署到 Kubernetes

现在我们将应用部署到K8s集群。假设你已有一个可用的集群(如Minikube, Kind或云厂商托管集群)和kubectl已配置好。

4.1 创建 Kubernetes 清单文件 在项目根目录下创建k8s目录用于存放部署文件。

k8s/configmap.yaml - 用于管理环境变量

apiVersion: v1
kind: ConfigMap
metadata:
  name: go-app-config
  labels:
    app: go-app
data:
  APP_NAME: "production-go-app"
  APP_VERSION: "2.0.0"
  # 可以添加更多配置

k8s/deployment.yaml - 应用部署配置

apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-app-deployment
  labels:
    app: go-app
spec:
  replicas: 2  # 初始副本数
  selector:
    matchLabels:
      app: go-app
  template:
    metadata:
      labels:
        app: go-app
    spec:
      containers:
      - name: go-app
        image: go-k8s-app:latest  # 替换为你的镜像仓库地址,如: your-registry/go-k8s-app:v1
        imagePullPolicy: IfNotPresent  # 生产环境建议用 Always
        ports:
        - containerPort: 8080
          name: http
        # 从ConfigMap注入环境变量
        envFrom:
        - configMapRef:
            name: go-app-config
        # 活性探针:如果失败,K8s会重启容器
        livenessProbe:
          httpGet:
            path: /healthz
            port: http
          initialDelaySeconds: 5
          periodSeconds: 10
          failureThreshold: 3
        # 就绪探针:如果失败,会从Service端点中移除Pod
        readinessProbe:
          httpGet:
            path: /readyz
            port: http
          initialDelaySeconds: 3
          periodSeconds: 5
          failureThreshold: 3
        # 资源请求与限制,对于HPA至关重要
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"  # 0.1 核
          limits:
            memory: "128Mi"
            cpu: "200m"  # 0.2 核
        # 启动探针,防止慢启动应用被过早杀死
        startupProbe:
          httpGet:
            path: /healthz
            port: http
          failureThreshold: 30
          periodSeconds: 10

k8s/service.yaml - 服务暴露

apiVersion: v1
kind: Service
metadata:
  name: go-app-service
  labels:
    app: go-app
spec:
  type: ClusterIP  # 内部访问。如果要从外部访问,改为 LoadBalancer 或 NodePort
  ports:
  - port: 80
    targetPort: http  # 引用容器端口名
    protocol: TCP
    name: http
  selector:
    app: go-app  # 匹配带有 app=go-app 标签的 Pod

k8s/hpa.yaml - 水平Pod自动伸缩

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: go-app-hpa
  labels:
    app: go-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: go-app-deployment
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70  # 当平均CPU利用率超过70%时进行扩容
  # 也可以添加内存指标
  # - type: Resource
  #   resource:
  #     name: memory
  #     target:
  #       type: Utilization
  #       averageUtilization: 80

步骤5:部署到集群并验证

5.1 应用配置文件

# 应用所有配置
kubectl apply -f k8s/

# 或者按顺序应用
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/hpa.yaml

5.2 检查部署状态

# 查看Pod状态
kubectl get pods -l app=go-app
# 预期看到两个Running状态的Pod

# 查看部署详情
kubectl describe deployment go-app-deployment

# 查看Service
kubectl get svc go-app-service
# 记下CLUSTER-IP,如 10.96.1.100

# 查看HPA状态
kubectl get hpa go-app-hpa

5.3 测试应用

# 使用kubectl端口转发进行本地测试
kubectl port-forward svc/go-app-service 8080:80 &
# 或者
kubectl port-forward deployment/go-app-deployment 8080:8080 &

# 在另一个终端测试
curl http://localhost:8080/
curl http://localhost:8080/healthz

# 在集群内部测试(需要有另一个Pod)
kubectl run curl-test --image=curlimages/curl -it --rm -- /bin/sh
# 在Pod内执行:
curl http://go-app-service.default.svc.cluster.local

5.4 模拟负载并观察HPA

# 创建一个持续发送请求的Pod
kubectl run load-generator --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://go-app-service; done"

# 在另一个终端监控HPA和Pod
watch kubectl get hpa go-app-pods -l app=go-app

# 等待几分钟后,你应该能看到副本数增加
# 负载停止后,几分钟内副本数会自动缩回

完整项目结构

go-k8s-app/
├── main.go
├── go.mod
├── go.sum
├── Dockerfile
└── k8s/
    ├── configmap.yaml
    ├── deployment.yaml
    ├── service.yaml
    └── hpa.yaml

最佳实践

  1. 多阶段构建:务必使用,可以将最终镜像大小从几百MB减小到几十MB,提升部署速度和安全性。
  2. 区分探针类型Liveness Probe 判断容器是否存活(失败则重启),Readiness Probe 判断容器是否就绪接收流量(失败则从Service端点移除),Startup Probe 适用于启动慢的应用。
  3. 设置资源请求与限制requests 影响Pod调度,limits 防止资源滥用。它们是 HPA 能否正常工作的基础。
  4. 配置镜像拉取策略:开发/测试环境用 IfNotPresent 节省时间,生产环境用 Always 确保总是拉取最新镜像(配合精确的镜像标签,如Git commit SHA)。
  5. 使用非Root用户运行:在 Dockerfile 中创建并切换到 appuser,可以限制容器逃逸后的权限,增强安全性。
  6. 使用声明式配置:将所有K8s配置存入版本控制(如Git),使用 kubectl apply -f 部署,方便审计和回滚。

常见问题

Q1: 为什么使用多阶段构建?直接构建不行吗? A: 直接构建会将Go的编译环境(几百MB的Go工具链)带入最终镜像。多阶段构建只复制编译后的二进制文件到一个极简的运行时镜像(如alpinescratch),极大减小了镜像体积、攻击面和拉取时间。

Q2: Liveness Probe 和 Readiness Probe 有什么区别?我需要同时设置吗? A: Liveness Probe:判断容器是否“活着”。失败则K8s会重启容器。适用于进程卡死、死锁等场景。 Readiness Probe:判断容器是否“准备好”接收流量。失败则K8s会将Pod的IP从Service的Endpoints中移除,停止向其转发流量。适用于应用需要加载大量数据或建立外部连接后才就绪的场景。 通常建议同时设置。Readiness Probe失败不会导致重启,但会影响服务可用性;Liveness Probe失败则直接重启容器,可能引起服务抖动。

Q3: HPA 是如何决定何时扩缩容的? A: HPA控制器周期性地(默认15秒)通过Metrics API获取Pod的资源指标(如CPU、内存利用率)。它会计算所有Pod该指标的平均值,与你设定的目标值(如averageUtilization: 70)进行比较。如果当前值持续超过目标值,则触发扩容;如果持续低于目标值(且大于minReplicas),则触发缩容。扩缩容有冷却时间,防止抖动。

扩展挑战

  1. 添加持久化存储:为应用创建一个PersistentVolumeClaim (PVC),并在Deployment中挂载,用于存储需要持久化的数据。
  2. 配置Pod中断预算:创建一个PodDisruptionBudget (PDB)对象,确保在自动扩缩或节点维护期间,始终有一定数量的Pod处于可用状态。
  3. 实现蓝绿部署:创建两个独立的Deployment(bluegreen),通过修改Service的selector来切换流量,实现零停机部署。
  4. 集成CI/CD流水线:使用GitHub Actions、GitLab CI或Jenkins,在代码推送时自动构建Docker镜像、运行测试、推送到镜像仓库,并更新Kubernetes Deployment。

相关课程

课程相关章节
Go语言实战项目结构与最佳实践
Docker从入门到实践Dockerfile 与镜像构建
Kubernetes核心概念Pod、Deployment 与 Service
Kubernetes核心概念自动扩缩容
Go语言实战错误处理与日志