# 云原生与容器化
用生活化的比喻,让你从"手动部署 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),玩家连接后不会变。
实践任务
- 编写 Dockerfile:将游戏网关服务打包成 Docker 镜像
- 部署到 K8s:编写 Deployment + Service + ConfigMap
- 搭建 CI/CD:GitLab CI 实现自动构建、测试、部署
- 接入监控:Micrometer + Prometheus + Grafana,监控在线人数、延迟
- 配置告警:CPU > 80%、错误率 > 1%、P99 延迟 > 100ms 时告警
- 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章) 云原生与容器化 `
下一步建议
- 实战项目:参照第11章的完整项目结构,实现一个多人联机小游戏
- 源码阅读:Spring、Netty、Redis 的源码深度阅读
- 开源贡献:参与游戏服务器框架(如 ioGame、Pomelo)的社区
- 面试准备:重点复习 JVM、并发、Netty、数据库、分布式 5 大板块
上一章:14-游戏实时通信优化