Skip to content

微服务部署与编排

本篇是 Go 微服务系列的第十篇,也是最后一篇。前面九篇我们把微服务的开发问题讲完了——服务发现、通信、网关、配置、追踪、稳定性、消息队列。但代码写完只是开始,要让微服务真正跑起来、稳定运行、持续演进,部署与编排是关键。本篇将讲解 Docker 多阶段构建、docker-compose 多服务编排、Kubernetes 基础、Go 微服务的 K8s 部署、ConfigMap/Secret、Helm、CI/CD 流水线、蓝绿与金丝雀发布。

一、Docker 化微服务

1. Go 服务的 Docker 优势

Go 编译产物是静态二进制,Docker 化极其友好:

  • 镜像小:可做到 10~20MB。
  • 启动快:毫秒级。
  • 安全面小:无 OS 大量依赖。
  • 跨平台:一份镜像到处运行。

2. 多阶段构建

多阶段构建(Multi-stage build)是 Go Docker 化的标准做法:第一阶段编译,第二阶段只放二进制。

Dockerfile

dockerfile
# === 阶段 1:构建 ===
FROM golang:1.22-alpine AS builder

WORKDIR /app

# 利用缓存:先拷依赖文件
COPY go.mod go.sum ./
RUN go mod download

# 拷源码
COPY . .

# 静态编译(CGO_ENABLED=0),方便用 scratch/alpine
ARG SERVICE=order-service
RUN CGO_ENABLED=0 GOOS=linux go build \
    -ldflags="-s -w -X main.Version=$(git rev-parse --short HEAD 2>/dev/null || echo dev)" \
    -o /out/server \
    ./cmd/${SERVICE}

# === 阶段 2:运行 ===
FROM alpine:3.19

# 时区与证书
RUN apk --no-cache add ca-certificates tzdata && \
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone

WORKDIR /app
COPY --from=builder /out/server /app/server

# 非 root 用户运行
RUN adduser -D -u 10001 appuser
USER appuser

EXPOSE 8080 50051

ENTRYPOINT ["/app/server"]

构建并运行:

bash
docker build -t order-service:v1 --build-arg SERVICE=order-service .
docker run --rm -p 8080:8080 order-service:v1

3. 极致镜像:scratch

对极致镜像大小有要求的场景,可以用 scratch 作为基础镜像:

dockerfile
FROM scratch
COPY --from=builder /out/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/server"]

最终镜像 ≈ 二进制大小(通常 10~15MB)。但 scratch 没有 shell,无法 exec 进容器调试,生产推荐 alpine/distroless。

4. .dockerignore

避免把本地缓存、测试数据打进构建上下文:

.git
.idea
*.md
tests/
docs/
.env
*.log

二、docker-compose 多服务编排

开发环境常需要同时拉起多个服务(API、DB、Redis、MQ)。docker-compose 是最简单的多容器编排工具。

1. 一个完整的微服务 compose 文件

yaml
# docker-compose.yml
version: "3.9"

services:
  # === 基础设施 ===
  consul:
    image: consul:1.15
    ports:
      - "8500:8500"
    command: agent -dev -client=0.0.0.0

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: app
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  kafka:
    image: bitnami/kafka:3.7
    ports:
      - "9092:9092"
    environment:
      KAFKA_CFG_NODE_ID: 1
      KAFKA_CFG_PROCESS_ROLES: controller,broker
      KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
      KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
      KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT

  jaeger:
    image: jaegertracing/all-in-one:1.55
    ports:
      - "16686:16686"
      - "4318:4318"

  # === 业务服务 ===
  user-service:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        SERVICE: user-service
    image: user-service:dev
    environment:
      APP_ENV: dev
      CONSUL_ADDR: consul:8500
      DB_DSN: "root:root@tcp(mysql:3306)/app"
      OTEL_ENDPOINT: "jaeger:4318"
    depends_on:
      - mysql
      - consul
      - jaeger
    ports:
      - "8081:8080"

  order-service:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        SERVICE: order-service
    image: order-service:dev
    environment:
      APP_ENV: dev
      CONSUL_ADDR: consul:8500
      KAFKA_BROKERS: "kafka:9092"
    depends_on:
      - kafka
      - consul
      - user-service
    ports:
      - "8082:8080"

  gateway:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        SERVICE: gateway
    image: gateway:dev
    environment:
      USER_SERVICE_ADDR: "user-service:8080"
      ORDER_SERVICE_ADDR: "order-service:8080"
    depends_on:
      - user-service
      - order-service
    ports:
      - "8080:8080"

volumes:
  mysql-data:

启动:

bash
docker-compose up -d
docker-compose logs -f gateway
docker-compose down

2. 健康检查与启动顺序

depends_on 只控制启动顺序,不等服务就绪。要等就绪需配合 healthcheck:

yaml
mysql:
  image: mysql:8.0
  # ...
  healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
    interval: 5s
    timeout: 3s
    retries: 10

user-service:
  depends_on:
    mysql:
      condition: service_healthy

三、Kubernetes 基础概念

1. K8s 是什么

Kubernetes(K8s)是 Google 开源的容器编排平台,负责容器的调度、扩缩容、自愈、滚动升级。它已成为云原生的事实标准。

2. 核心概念

  • Pod:K8s 最小调度单位,一个或多个紧密耦合的容器共享网络和存储。
  • Deployment:管理无状态 Pod 副本集,负责滚动升级和回滚。
  • Service:稳定的虚拟 IP + DNS,负载均衡到后端 Pod。
  • Ingress:HTTP 层路由,把外部请求路由到 Service。
  • ConfigMap / Secret:配置和敏感数据。
  • StatefulSet:有状态服务(如数据库)。
  • DaemonSet:每个节点跑一个副本(如日志采集)。
  • Job / CronJob:批处理任务。
  • Namespace:逻辑隔离单元。
  • HPA:水平 Pod 自动扩缩容。

3. 控制平面与工作节点

┌──────────────────────────────┐    ┌──────────────────────────────┐
│       Control Plane          │    │         Worker Node           │
│  ┌────────────────────────┐  │    │  ┌────────────────────────┐  │
│  │ API Server             │◄─┼────┼─►│ kubelet                │  │
│  │ Scheduler              │  │    │  │ kube-proxy             │  │
│  │ Controller Manager     │  │    │  │ Container Runtime      │  │
│  │ etcd                   │  │    │  │ Pods...                │  │
│  └────────────────────────┘  │    │  └────────────────────────┘  │
└──────────────────────────────┘    └──────────────────────────────┘

四、Kubernetes 部署 Go 微服务

1. Deployment

yaml
# deploy/user-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
  namespace: production
  labels:
    app: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: user-service
        version: v1
    spec:
      containers:
        - name: user-service
          image: registry.example.com/user-service:v1
          ports:
            - name: http
              containerPort: 8080
            - name: grpc
              containerPort: 50051
          env:
            - name: APP_ENV
              value: "production"
            - name: DB_DSN
              valueFrom:
                secretKeyRef:
                  name: user-service-secret
                  key: db-dsn
            - name: CONSUL_ADDR
              value: "consul:8500"
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
          lifecycle:
            preStop:
              exec:
                command: ["sh", "-c", "sleep 5"]
      terminationGracePeriodSeconds: 30

2. Service

yaml
apiVersion: v1
kind: Service
metadata:
  name: user-service
  namespace: production
spec:
  selector:
    app: user-service
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: grpc
      port: 50051
      targetPort: 50051
  type: ClusterIP

3. Ingress

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  ingressClassName: nginx
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /users/(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: user-service
                port:
                  number: 80
          - path: /orders/(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: order-service
                port:
                  number: 80

4. HPA 自动扩缩容

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: user-service
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: user-service
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

5. 部署命令

bash
kubectl apply -f deploy/user-service.yaml
kubectl get pods -n production -l app=user-service
kubectl describe deployment user-service -n production
kubectl logs -f deployment/user-service -n production
kubectl scale deployment user-service --replicas=5 -n production
kubectl rollout status deployment/user-service -n production
kubectl rollout undo deployment/user-service -n production

五、ConfigMap 和 Secret 管理配置

1. ConfigMap:非敏感配置

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: user-service-config
  namespace: production
data:
  log_level: "info"
  features_new_cache: "true"
  config.yaml: |
    server:
      port: 8080
      read_timeout: 10
    db:
      max_open: 50

引用方式:

yaml
# 环境变量方式
env:
  - name: LOG_LEVEL
    valueFrom:
      configMapKeyRef:
        name: user-service-config
        key: log_level

# 文件挂载方式(适合整份配置文件)
volumes:
  - name: config
    configMap:
      name: user-service-config
      items:
        - key: config.yaml
          path: config.yaml

2. Secret:敏感配置

bash
# 创建
kubectl create secret generic user-service-secret \
  --from-literal=db-dsn='mysql://user:pass@tcp(host)/db' \
  --from-literal=jwt-secret='super-secret'

引用方式与 ConfigMap 类似,但用 secretKeyRef

3. 与配置中心的关系

K8s ConfigMap + Secret 本身就是一个分布式的配置存储。搭配第 6 篇的配置中心:

  • 静态配置(端口、超时):ConfigMap,Pod 启动时读取。
  • 动态配置(功能开关、限流阈值):Consul KV 等配置中心,热更新。

六、Helm Charts 管理

1. Helm 是什么

Helm 是 K8s 的包管理器,类似 apt / yum。核心概念:

  • Chart:一个可部署的 K8s 资源集合(模板 + 配置)。
  • Release:一次 chart 安装的实例。
  • Repository:chart 仓库。

2. 创建 chart

bash
helm create user-service

生成的目录结构:

user-service/
├── Chart.yaml          # chart 元信息
├── values.yaml         # 默认配置
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   └── _helpers.tpl
└── charts/             # 子 chart 依赖

3. 模板化 YAML

templates/deployment.yaml 中的占位符用 Helm 模板语法,渲染时被替换。在代码块内它原样写成:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-user-service
  labels:
    app: {{ .Release.Name }}-user-service
spec:
  replicas: {{ .Values.replicas }}
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-user-service
        version: {{ .Values.image.tag | default "latest" }}
    spec:
      containers:
        - name: user-service
          image: "{{ .Values.image.repo }}:{{ .Values.image.tag }}"
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

提示:在 VitePress 渲染时,正文里出现的模板占位符需要转义。在 \{\{ .Release.Name \}\} 这种写法下,渲染器不会把它当成 Vue 表达式处理。

values.yaml

yaml
replicas: 3
image:
  repo: registry.example.com/user-service
  tag: v1.0.0
resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

4. 多环境 values

user-service/
├── values.yaml          # 通用默认
├── values-dev.yaml      # dev 覆盖
├── values-prod.yaml     # prod 覆盖

部署:

bash
helm install user-service-prod ./user-service \
  -f values.yaml -f values-prod.yaml \
  -n production

helm upgrade user-service-prod ./user-service \
  -f values.yaml -f values-prod.yaml \
  -n production

helm rollback user-service-prod 1 -n production
helm uninstall user-service-prod -n production

七、CI/CD 流水线设计

1. CI/CD 的核心环节

  • CI(持续集成):代码提交 → lint → 测试 → 构建 → 推镜像。
  • CD(持续交付/部署):镜像就绪 → 部署到 dev → 测试 → 部署到 staging → 部署到 prod。

2. GitHub Actions 示例

.github/workflows/release.yml

yaml
name: Release

on:
  push:
    tags:
      - "v*"

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: "1.22"

      - name: Run tests
        run: go test ./... -race -cover

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: registry.example.com/user-service
          tags: |
            type=ref,event=tag
            type=sha,prefix=sha-

      - name: Login to registry
        uses: docker/login-action@v3
        with:
          registry: registry.example.com
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_PASS }}

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          build-args: |
            SERVICE=user-service

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set Kubeconfig
        run: |
          mkdir -p $HOME/.kube
          echo "${{ secrets.KUBECONFIG }}" > $HOME/.kube/config

      - name: Deploy
        run: |
          helm upgrade --install user-service ./charts/user-service \
            -f values.yaml -f values-prod.yaml \
            --set image.tag=${{ github.ref_name }} \
            -n production --create-namespace

3. GitOps 模式

传统 Push 模式:CI 流水线直接操作 K8s。 GitOps Pull 模式:CI 只更新 Git 仓库里的镜像 tag,集群内的工具(ArgoCD / Flux)监听 Git 变更并同步。

GitOps 优势:

  • Git 是唯一事实来源。
  • 操作可审计、可回滚。
  • 集群无需暴露 CI 凭证。

八、蓝绿部署与金丝雀发布

1. 蓝绿部署

准备两套环境(蓝、绿),切换流量:

                  ┌─────────────┐
        ┌────────►│ Blue (v1)   │◄──── 流量
        │         └─────────────┘
Gateway│
        │         ┌─────────────┐
        └─────────│ Green (v2)  │     ← 新版本部署在这里
                  └─────────────┘

切换时把 Service selector 改成 Green,瞬间切换。问题:需要双倍资源。

K8s 实现:两个 Deployment + Service 用 label 切换。

2. 金丝雀发布

逐步把流量切到新版本:

v1 (90%) ◄── Gateway ──► v2 (10%)

观察一段时间无问题,逐步增加 v2 比例,直到 100%。出问题立即回滚到 v1。

K8s 实现方式:

  • 多 Deployment + 权重路由:用 Nginx Ingress 或 Istio 按权重分流。
  • Rolling Update:K8s 原生滚动升级就是简化版金丝雀(按比例逐步替换)。

Istio VirtualService 流量分割

yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: user-service
spec:
  hosts:
    - user-service
  http:
    - route:
        - destination:
            host: user-service
            subset: v1
          weight: 90
        - destination:
            host: user-service
            subset: v2
          weight: 10

3. A/B 测试

按用户特征(如 user_id 哈希、地理位置)分流,对比业务指标。需要在网关层做精细路由。

九、完整部署示例

下面给出一个端到端的部署示例,覆盖本篇要点。

1. 项目目录结构

user-service/
├── cmd/server/main.go
├── internal/...
├── Dockerfile
├── .dockerignore
├── docker-compose.yml
├── charts/
│   └── user-service/
│       ├── Chart.yaml
│       ├── values.yaml
│       ├── values-prod.yaml
│       └── templates/
│           ├── deployment.yaml
│           ├── service.yaml
│           ├── configmap.yaml
│           ├── secret.yaml
│           └── hpa.yaml
└── .github/workflows/release.yml

2. 优雅关闭的 Go 服务

要让 K8s 滚动升级无掉单,服务必须支持优雅关闭:

go
package main

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

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
	})
	mux.HandleFunc("/users/1", func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(2 * time.Second) // 模拟慢请求
		_, _ = w.Write([]byte(`{"id":1,"name":"alice"}`))
	})

	srv := &http.Server{
		Addr:         ":8080",
		Handler:      mux,
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	go func() {
		log.Printf("server on :8080")
		if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatalf("listen: %v", err)
		}
	}()

	quit := make(chan os.Signal, 1)
	// K8s 终止 Pod 时发 SIGTERM
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("shutting down...")

	// 给 K8s preStop + 服务端 Shutdown 留足时间
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Printf("shutdown: %v", err)
	}
	log.Println("server exited")
}

3. K8s 优雅滚动配置

Pod 删除顺序:

  1. K8s 把 Pod 标记为 Terminating,从 Service endpoints 摘除。
  2. 同时执行 preStop hook(这里 sleep 5 秒,等 kube-proxy 同步完)。
  3. 发送 SIGTERM 给容器。
  4. 服务端进入 Shutdown,等待处理中的请求完成。
  5. terminationGracePeriodSeconds(默认 30s)超时后发 SIGKILL。
yaml
spec:
  template:
    spec:
      containers:
        - name: user-service
          # ...
          lifecycle:
            preStop:
              exec:
                command: ["sh", "-c", "sleep 5"]
      terminationGracePeriodSeconds: 30

4. 一键部署到 K8s

bash
# 创建 namespace
kubectl create namespace production

# 部署基础设施(Consul / MySQL / Kafka 可用 chart)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install consul hashicorp/consul -n production -f consul-values.yaml

# 部署业务服务
helm upgrade --install user-service ./charts/user-service \
  -f ./charts/user-service/values.yaml \
  -f ./charts/user-service/values-prod.yaml \
  -n production

# 查看状态
kubectl get pods -n production
kubectl get svc -n production
kubectl get ingress -n production

# 滚动升级
helm upgrade --install user-service ./charts/user-service \
  -f ./charts/user-service/values.yaml \
  -f ./charts/user-service/values-prod.yaml \
  --set image.tag=v2.0.0 \
  -n production

# 回滚
helm rollback user-service 1 -n production

十、生产化清单

把一个 Go 微服务推向生产,需要核对以下清单:

代码层

  • 优雅启停(信号处理、Shutdown)。
  • 健康检查接口(/health 或 readiness/liveness 分离)。
  • 结构化日志 + TraceID 关联。
  • 配置中心 + 环境变量注入。
  • 熔断 / 限流 / 超时三件套。
  • 指标埋点(RED)。
  • 链路追踪(OpenTelemetry)。

镜像层

  • 多阶段构建,镜像最小化。
  • 非 root 用户运行。
  • 静态编译(CGO_ENABLED=0)。
  • 不可变 tag(不要用 latest)。
  • 镜像签名与扫描(Trivy / Cosign)。

编排层

  • 资源 requests / limits。
  • readiness / liveness probe。
  • HPA 自动扩缩容。
  • PDB(PodDisruptionBudget)防误驱逐。
  • ConfigMap / Secret 管理。
  • 反亲和性(podAntiAffinity)分散部署。

发布层

  • 滚动升级 + preStop + gracePeriod。
  • 金丝雀发布(Istio / Nginx Ingress)。
  • 监控告警(Prometheus + Alertmanager)。
  • 日志聚合(Loki / ELK)。
  • 追踪系统(Jaeger / Tempo)。
  • CI/CD 流水线 + GitOps。

十一、小结

本篇我们学习了微服务的部署与编排:

  • Docker 多阶段构建:第一段编译,第二段只放二进制,镜像做到 10~20MB。
  • docker-compose:开发环境一键拉起多个服务,配 healthcheck 控制启动顺序。
  • Kubernetes:编排标准,核心对象 Pod / Deployment / Service / Ingress / ConfigMap / Secret / HPA。
  • ConfigMap / Secret:静态配置和敏感数据,与配置中心互补。
  • Helm:K8s 的包管理,模板化 YAML + 多环境 values。
  • CI/CD:GitHub Actions / GitLab CI 流水线,GitOps 模式(ArgoCD / Flux)是趋势。
  • 发布策略:蓝绿(双环境切换)、金丝雀(按比例灰度)、A/B(按特征分流)。
  • 优雅关闭:preStop + Shutdown + gracePeriod 三件套,避免滚动升级掉单。
  • 生产化清单:代码、镜像、编排、发布四个层面逐项核对。

至此,整个 Go 微服务系列教程就结束了。从架构概览到部署上线,我们覆盖了一个完整的微服务体系:

  1. 微服务架构概览
  2. 服务注册与发现
  3. gRPC 通信基础
  4. RESTful API 与 Gin 集成
  5. API 网关模式
  6. 分布式配置中心
  7. 链路追踪与可观测性
  8. 熔断、降级与限流
  9. 消息队列与异步通信
  10. 微服务部署与编排(本篇)

延伸阅读

希望本系列能帮助你建立起 Go 微服务的完整知识体系。微服务不是终点,云原生技术仍在演进——Service Mesh、Serverless、eBPF、Wasm 等新方向值得关注。学完本系列,你已经具备了上手实战的能力,下一步就是在真实项目中打磨。祝编程愉快!