# 云原生与容器化

用生活化的比喻,让你从"手动部署 War 包"到"能用 Kubernetes 管理千台游戏服务器"

前置知识:第11章 游戏服务器架构(微服务、网关、负载均衡)、第13章 游戏数据存储特殊需求(MySQL 主从、Redis 集群)


阅读指南(初学者必看)

为什么你需要学习云原生与容器化?

想象你是一家游戏公司的运维负责人:

  • 新版本上线 → 手动部署 100 台服务器 → 通宵加班 → 还是出错了
  • 活动爆服 → 临时加机器 → 申请机器要 3 天 → 玩家已经流失了
  • 某台机器挂了 → 玩家数据丢失 → 被老板骂

学完本章,你能回答:

  • Docker 和 Kubernetes 到底是什么?有什么关系?
  • 游戏服务器怎么用 K8s 部署?(有状态服务怎么搞?)
  • 怎么做 CI/CD 流水线?(自动构建、测试、部署)
  • 怎么监控上千台服务器?(Prometheus + Grafana)
  • 怎么自动扩缩容?(HPA + VPA)

本文结构

第一部分:Docker 基础(容器化入门) 第二部分:Kubernetes 核心概念(容器编排) 第三部分:游戏服务器 K8s 实践(有状态服务) 第四部分:CI/CD 流水线(自动化部署) 第五部分:监控与告警(可观测性) 第六部分:自动扩缩容(弹性伸缩)


一、Docker 基础

什么是容器?

生活类比:容器就像"标准化集装箱"——不管里面装什么,尺寸一样,码头(服务器)都能处理。

` 传统部署: 开发环境 → 测试环境 → 生产环境 ↓ ↓ ↓ JDK 8u201 JDK 8u181 JDK 8u191 不同版本 → "我本地好好的,怎么线上不行?"

容器部署: 开发环境 → 测试环境 → 生产环境 ↓ ↓ ↓ 同一个 Docker 镜像(包含 JDK + 代码 + 配置) 一次构建,到处运行 `

Dockerfile 示例

`dockerfile

游戏网关服务 Dockerfile

FROM openjdk:17-jdk-slim

安装必要的工具

RUN apt-get update && apt-get install -y curl

设置工作目录

WORKDIR /app

复制 JAR 包

COPY target/game-gateway-1.0.0.jar app.jar

复制配置文件

COPY config/application.yml config/application.yml

暴露端口

EXPOSE 8080

健康检查

HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3
CMD curl -f http://localhost:8080/actuator/health || exit 1

启动命令

ENTRYPOINT ["java", "-jar", "app.jar"] `

`ash

构建镜像

docker build -t game-gateway:1.0.0 .

运行容器

docker run -d -p 8080:8080 --name gateway game-gateway:1.0.0

查看日志

docker logs -f gateway

进入容器

docker exec -it gateway /bin/bash `


二、Kubernetes 核心概念

K8s 架构

` Master 节点(控制面): ├── API Server:所有操作的入口 ├── etcd:分布式配置存储 ├── Scheduler:调度 Pod 到哪个节点 └── Controller Manager:维护集群状态

Worker 节点(数据面): ├── kubelet:管理节点上的 Pod └── kube-proxy:网络代理和负载均衡 `

核心资源

资源 作用 类比
Pod 最小部署单元(一个或多个容器) 一个工位
Deployment 管理 Pod 的副本和更新 部门主管
Service 暴露 Pod 的网络访问 前台电话
ConfigMap 存储配置数据 公告栏
Secret 存储敏感数据(密码、密钥) 保险箱
StatefulSet 管理有状态应用 有序工位
Ingress HTTP 路由 门禁系统

三、游戏服务器 K8s 实践

无状态服务部署(网关、匹配服务)

`yaml

gateway-deployment.yaml

apiVersion: apps/v1 kind: Deployment metadata: name: game-gateway labels: app: game-gateway spec: replicas: 3 selector: matchLabels: app: game-gateway template: metadata: labels: app: game-gateway spec: containers: - name: gateway image: registry.example.com/game-gateway:1.0.0 ports: - containerPort: 8080 env: - name: SPRING_PROFILES_ACTIVE value: "prod" resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m" livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 5


gateway-service.yaml

apiVersion: v1 kind: Service metadata: name: game-gateway-service spec: selector: app: game-gateway ports:

  • port: 80 targetPort: 8080 type: LoadBalancer `

有状态服务部署(房间服、战斗服)

`yaml

room-statefulset.yaml

apiVersion: apps/v1 kind: StatefulSet metadata: name: game-room spec: serviceName: game-room-headless replicas: 5 selector: matchLabels: app: game-room template: metadata: labels: app: game-room spec: containers: - name: room image: registry.example.com/game-room:1.0.0 ports: - containerPort: 9090 - containerPort: 9091 # 内部通信 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: SERVER_ID value: "$(POD_NAME)" volumeMounts: - name: room-data mountPath: /data volumeClaimTemplates:

  • metadata: name: room-data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi

headless service(用于 Pod 间直接通信)

apiVersion: v1 kind: Service metadata: name: game-room-headless spec: selector: app: game-room ports:

  • port: 9090 name: client
  • port: 9091 name: internal clusterIP: None # Headless,返回 Pod IP 列表 `

为什么游戏服务要用 StatefulSet?

` Deployment vs StatefulSet:

Deployment(无状态):

  • Pod 名字随机(game-gateway-5d4f8b7c9-x2abc)
  • 每个 Pod 完全相同
  • 适合:网关、HTTP API、匹配服务

StatefulSet(有状态):

  • Pod 名字有序(game-room-0, game-room-1, ...)
  • 每个 Pod 有独立网络标识和存储
  • 适合:房间服、战斗服、数据库

游戏场景:

  • 房间服需要稳定的网络标识(玩家连接后不能变)
  • 战斗服需要持久化存储(战斗回放、日志)
  • 数据库需要有序启动(主从复制) `

四、CI/CD 流水线

GitLab CI 配置

`yaml

.gitlab-ci.yml

stages:

  • build
  • test
  • package
  • deploy

variables: MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository" DOCKER_REGISTRY: "registry.example.com"

build: stage: build image: maven:3.8-openjdk-17 script: - mvn compile -DskipTests cache: paths: - .m2/repository

test: stage: test image: maven:3.8-openjdk-17 script: - mvn test artifacts: reports: junit: target/surefire-reports/*.xml

package: stage: package image: maven:3.8-openjdk-17 script: - mvn package -DskipTests - docker build -t /game-server: . - docker push /game-server: only: - main

deploy-staging: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/game-gateway gateway=/game-server: -n staging - kubectl rollout status deployment/game-gateway -n staging environment: name: staging only: - main

deploy-production: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/game-gateway gateway=/game-server: -n production - kubectl rollout status deployment/game-gateway -n production environment: name: production when: manual # 需要手动触发 only: - main `

金丝雀发布

`yaml

1. 先部署 10% 流量到新版本

apiVersion: apps/v1 kind: Deployment metadata: name: game-gateway-canary spec: replicas: 1 # 占总量的 10% selector: matchLabels: app: game-gateway version: canary template: metadata: labels: app: game-gateway version: canary spec: containers: - name: gateway image: game-gateway:1.1.0 # 新版本


2. Ingress 流量分割

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: game-gateway-ingress annotations: nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "10" # 10% 流量到新版本 spec: rules:

  • host: api.game.com http: paths:
    • path: / pathType: Prefix backend: service: name: game-gateway-canary port: number: 80

`


五、监控与告警

Prometheus + Grafana 架构

游戏服务器 → Micrometer(埋点)→ Prometheus(采集)→ Grafana(展示) ↓ AlertManager(告警)→ 钉钉/企业微信/邮件

Java 应用埋点

`java @Configuration public class MetricsConfig {

@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
    return registry -> registry.config()
        .commonTags("application", "game-server")
        .commonTags("env", "production");
}

}

@Service public class GameMetrics {

private final Counter loginCounter;
private final Timer battleTimer;
private final Gauge onlineGauge;

public GameMetrics(MeterRegistry registry) {
    this.loginCounter = Counter.builder("game.login.count")
        .description("Total login count")
        .register(registry);
    
    this.battleTimer = Timer.builder("game.battle.duration")
        .description("Battle duration")
        .register(registry);
    
    this.onlineGauge = Gauge.builder("game.online.players")
        .description("Current online players")
        .register(registry, this, GameMetrics::getOnlineCount);
}

public void recordLogin() {
    loginCounter.increment();
}

public void recordBattle(long durationMs) {
    battleTimer.record(durationMs, TimeUnit.MILLISECONDS);
}

private double getOnlineCount() {
    return SessionManager.getOnlineCount();
}

} `

Prometheus 告警规则

`yaml

prometheus-rules.yaml

groups:

  • name: game-server-alerts rules:
    • alert: HighErrorRate expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1 for: 5m labels: severity: critical annotations: summary: "High error rate detected" description: "Error rate is {{ }} for {{ .service }}"

    • alert: HighLatency expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 0.1 for: 5m labels: severity: warning annotations: summary: "High latency detected" description: "P99 latency is {{ }}s for {{ .service }}"

    • alert: PodCrashLooping expr: rate(kube_pod_container_status_restarts_total[15m]) > 0 for: 5m labels: severity: critical annotations: summary: "Pod is crash looping" description: "{{ .pod }} is restarting frequently"

`


六、自动扩缩容

HPA(Horizontal Pod Autoscaler)

`yaml

hpa.yaml

apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: game-gateway-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: game-gateway minReplicas: 3 maxReplicas: 50 metrics:

  • type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70
  • type: Resource resource: name: memory target: type: Utilization averageUtilization: 80
  • type: Pods pods: metric: name: game_online_players target: type: AverageValue averageValue: "1000" # 每 Pod 1000 人在线

behavior: scaleUp: stabilizationWindowSeconds: 60 policies: - type: Percent value: 100 periodSeconds: 60 scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 10 periodSeconds: 60 `

游戏服务器扩缩容策略

` 无状态服务(网关、匹配服务):

  • HPA 自动扩缩容
  • 基于 CPU / 内存 / 在线人数
  • 缩容时优雅下线(等待连接断开)

有状态服务(房间服、战斗服):

  • 手动扩缩容(房间不能迁移)
  • 新建房间服后,匹配系统只分配到新服
  • 旧服自然排空后下线

数据库(MySQL、Redis):

  • 垂直扩容(升级配置)
  • 读写分离(增加从库)
  • 分片扩容(增加分片) `

自问自答

Q1:Docker 和虚拟机有什么区别?

Docker 是进程级隔离,共享宿主机内核,启动快(秒级)、资源占用小。虚拟机是系统级隔离,有独立内核,启动慢(分钟级)、资源占用大。游戏服务器用 Docker 就够了。

Q2:游戏服务器为什么不适合完全自动扩缩容?

有状态服务(房间服、战斗服)玩家连接后不能迁移,只能新建服务承接新房间。无状态服务(网关)可以完全自动扩缩容。

Q3:K8s 的 Service 是怎么做负载均衡的?

kube-proxy 使用 iptables 或 IPVS 将 Service IP 映射到后端 Pod IP。对于 TCP 长连接游戏,需要用 Headless Service + 客户端负载均衡。

Q4:金丝雀发布和蓝绿发布有什么区别?

蓝绿发布:同时部署两套环境,瞬间切换(需要双倍资源)。金丝雀发布:逐步切流量(5% → 25% → 100%),风险小,资源节省。

Q5:怎么保证 K8s 中游戏服的稳定网络标识?

用 StatefulSet + Headless Service。每个 Pod 有稳定的 DNS 名(game-room-0.game-room-headless.default.svc.cluster.local),玩家连接后不会变。


实践任务

  1. 编写 Dockerfile:将游戏网关服务打包成 Docker 镜像
  2. 部署到 K8s:编写 Deployment + Service + ConfigMap
  3. 搭建 CI/CD:GitLab CI 实现自动构建、测试、部署
  4. 接入监控:Micrometer + Prometheus + Grafana,监控在线人数、延迟
  5. 配置告警:CPU > 80%、错误率 > 1%、P99 延迟 > 100ms 时告警
  6. HPA 实验:配置基于 CPU 的自动扩缩容,模拟负载观察扩缩容行为

与其他章节的关联

本节内容 关联章节 关联点
Docker 第11章 游戏服务器架构 项目结构的容器化
K8s StatefulSet 第11章 游戏服务器架构 房间服、战斗服的有状态部署
监控 第14章 游戏实时通信优化 延迟、丢包率监控
数据库部署 第13章 游戏数据存储特殊需求 MySQL、Redis 的 K8s 部署
负载均衡 第08章 RPC 框架与微服务通信 网关层负载均衡

学习路线总结

恭喜!你已经完成了《Java 后端深度进阶》全部 15 章的学习!

知识地图回顾

` 第一阶段:基础内功(1-3章) JVM 深度原理 → Java 并发编程 → NIO 与 Netty

第二阶段:框架原理(4-5章) Spring 核心原理 → SpringBoot 自动配置

第三阶段:数据与通信(6-8章) 数据库深度进阶 → 字节码与动态编程 → RPC 框架

第四阶段:调优与实战(9-10章) JVM 调优实战 → 高并发与分布式一致性

第五阶段:游戏专项(11-14章) 游戏服务器架构 → 协议设计与优化 → 数据存储 → 实时通信优化

第六阶段:工程化(15章) 云原生与容器化 `

下一步建议

  1. 实战项目:参照第11章的完整项目结构,实现一个多人联机小游戏
  2. 源码阅读:Spring、Netty、Redis 的源码深度阅读
  3. 开源贡献:参与游戏服务器框架(如 ioGame、Pomelo)的社区
  4. 面试准备:重点复习 JVM、并发、Netty、数据库、分布式 5 大板块

上一章:14-游戏实时通信优化