Docker & Kubernetes 深度解析
一、Docker:容器化基础
核心概念
镜像(Image)= 只读模板,包含代码 + 运行时 + 依赖 + 配置 容器(Container)= 镜像的运行实例,隔离的进程
Docker vs 虚拟机
虚拟机: 硬件 → Hypervisor → [Guest OS → App] [Guest OS → App] 每个 VM 有完整操作系统,几GB,启动分钟级 Docker: 硬件 → Host OS → Docker Engine → [Container][Container] 共享宿主内核,几MB,启动秒级
Dockerfile 关键指令
FROM ubuntu:22.04 # 基础镜像 WORKDIR /app # 工作目录 # 先复制依赖文件,利用层缓存 COPY CMakeLists.txt . RUN apt-get install -y cmake g++ && cmake . && make COPY . . # 再复制源码(源码变动不会让依赖重新构建) EXPOSE 8080 # 声明端口(仅文档用,不自动映射) CMD ["./game_server"] # 容器启动命令
层缓存(Layer Cache) 是 Dockerfile 优化的核心:
- 每条指令产生一个只读层
- 某层变化 → 该层及之后所有层重建
- 所以要把变化频繁的指令放后面(源码 > 依赖 > 基础镜像)
多阶段构建(游戏后端常用)
# 阶段1:编译 FROM gcc:12 AS builder WORKDIR /src COPY . . RUN cmake -DCMAKE_BUILD_TYPE=Release . && make -j$(nproc) # 阶段2:只打包可执行文件,不带编译工具链 FROM ubuntu:22.04 RUN apt-get install -y libstdc++6 # 只装运行时依赖 COPY --from=builder /src/game_server /app/game_server CMD ["/app/game_server"]
编译镜像 2GB → 运行镜像可能只有 100MB,安全 + 省空间。
二、Kubernetes:容器编排
为什么需要 K8s
单机 Docker 够了,但生产环境需要:
- 跨多台机器部署容器
- 某个容器挂了自动重启
- 流量高峰自动扩容
- 滚动发布不中断服务
K8s 解决这些问题。
核心架构
┌─────────────────── Kubernetes Cluster ───────────────────┐ │ │ │ Control Plane(Master节点) │ │ ┌─────────┐ ┌──────────────┐ ┌────────┐ ┌────────┐ │ │ │ API │ │ Scheduler │ │ etcd │ │ Ctrl │ │ │ │ Server │ │(决定放哪里) │ │(状态) │ │ Manager│ │ │ └─────────┘ └──────────────┘ └────────┘ └────────┘ │ │ │ │ Worker Node 1 Worker Node 2 │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ kubelet │ │ kubelet │ │ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ │ │ Pod │ │ │ │ Pod │ │ │ │ │ │ [Container] │ │ │ │ [Container] │ │ │ │ │ └─────────────┘ │ │ └─────────────┘ │ │ │ └──────────────────┘ └──────────────────┘ │ └───────────────────────────────────────────────────────────┘
核心组件职责:
| 组件 | 职责 |
|---|---|
| API Server | 所有操作的入口,kubectl 就是在调它 |
| etcd | 分布式 KV 存储,保存集群所有状态 |
| Scheduler | 决定 Pod 放到哪个 Node 上 |
| Controller Manager | 确保实际状态 = 期望状态(比如 Pod 挂了重建) |
| kubelet | 每个 Node 上的代理,执行 Pod 的增删 |
核心资源对象
Pod — 最小调度单元,一个或多个容器的组合
# 一般不直接创建 Pod,而是通过 Deployment 管理
apiVersion: v1
kind: Pod
spec:
containers:
- name: game-server
image: ea/fc-mobile-server:v1.2.3
ports:
- containerPort: 8080
resources:
requests:
memory: "512Mi"
cpu: "500m" # 0.5 核
limits:
memory: "1Gi"
cpu: "1000m" # 1 核,超过会被限流
Deployment — 管理无状态服务(最常用)
apiVersion: apps/v1
kind: Deployment
metadata:
name: match-service
spec:
replicas: 3 # 期望 3 个副本
selector:
matchLabels:
app: match-service
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # 更新时最多 1 个不可用
maxSurge: 1 # 更新时最多多出 1 个
template:
spec:
containers:
- name: match-service
image: ea/match-service:v2.0.0
StatefulSet — 管理有状态服务(Redis Cluster、数据库)
- 每个 Pod 有固定名称(
redis-0,redis-1) - 有稳定的网络标识和持久化存储
- 游戏中的 Redis、MySQL 主从集群用这个
Service — 服务发现 + 负载均衡
apiVersion: v1
kind: Service
metadata:
name: match-service
spec:
selector:
app: match-service # 转发到带这个 label 的 Pod
ports:
- port: 80
targetPort: 8080
type: ClusterIP # 集群内部访问
# type: LoadBalancer # 暴露到外网,云厂商创建 LB
Ingress — HTTP 路由层(类似 Nginx 反向代理)
# /api/match → match-service
# /api/player → player-service
spec:
rules:
- host: api.fcmobile.ea.com
http:
paths:
- path: /api/match
backend:
service:
name: match-service
port: 80
自动扩缩容(HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: match-service-hpa
spec:
scaleTargetRef:
kind: Deployment
name: match-service
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # CPU 超 70% 触发扩容
游戏场景:赛季开始/节假日活动流量暴涨 → HPA 自动从 3 个 Pod 扩到 30 个,活动结束自动缩回,节省成本。
滚动发布与回滚
# 更新镜像版本 kubectl set image deployment/match-service \ match-service=ea/match-service:v2.1.0 # K8s 自动滚动:先起新版 Pod,健康检查通过后再删旧版 # 零停机,符合 maxUnavailable/maxSurge 约束 # 发现问题,一键回滚 kubectl rollout undo deployment/match-service # 回滚到指定版本 kubectl rollout undo deployment/match-service --to-revision=3
ConfigMap & Secret
# 配置与代码分离,不用重新打镜像 apiVersion: v1 kind: ConfigMap data: MATCH_TIMEOUT: "30" MAX_PLAYERS_PER_ROOM: "100" --- apiVersion: v1 kind: Secret # Base64 编码存储,实际生产用 Vault data: DB_PASSWORD: cGFzc3dvcmQxMjM=
三、游戏后端中的实战模式
FC Mobile 可能的 K8s 架构
外网流量
↓
[Ingress / API Gateway]
↓
[Service Mesh: Istio] ← 服务间通信、熔断、可观测性
↓
┌──────────────────────────────────────────┐
│ Stateless Services(Deployment) │
│ [Match Service] [Player Service] │
│ [LiveOps Service] [Leaderboard Service] │
└──────────────────────────────────────────┘
↓
┌──────────────────────────────────────────┐
│ Stateful Services(StatefulSet) │
│ [Redis Cluster] [MySQL] [Kafka] │
└──────────────────────────────────────────┘
健康检查(必须掌握)
livenessProbe: # 存活探针:失败则重启容器
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
readinessProbe: # 就绪探针:失败则从 Service 摘除(不接流量)
httpGet:
path: /ready
port: 8080
periodSeconds: 3
关键区分:
liveness失败 → 重启 Pod(治疗死锁/OOM)readiness失败 → 从负载均衡摘除,但不重启(服务预热中、依赖未就绪)
四、面试高频问题
Q:Pod 和容器的区别?
Pod 是 K8s 调度的最小单位,一个 Pod 内的容器共享网络命名空间(同一 IP)和存储卷。同一 Pod 内的容器用
localhost通信,适合紧耦合的 sidecar 模式(比如主服务 + 日志收集 agent)。
Q:Deployment vs StatefulSet 怎么选?
无状态服务(API Server、匹配服务)用 Deployment,Pod 可以随意替换。有状态服务(Redis、数据库、需要 sticky session 的游戏房间服务)用 StatefulSet,Pod 有固定身份和持久化存储。
Q:K8s 如何实现零停机发布?
RollingUpdate 策略 + readinessProbe 配合。新 Pod 就绪探针通过后才接流量,旧 Pod 才开始被删除,保证全程有足够的 ready 副本。
Q:镜像很大,CI/CD 慢怎么解决?
多阶段构建减小体积;合理安排 Dockerfile 层顺序利用缓存;用私有镜像仓库(ECR/ACR)部署在同区域;分层推送只上传变化的层。
五、一句话总结
Docker 解决”在我机器上能跑”问题,K8s 解决”在生产环境规模化、可靠运行”问题。游戏后端用 K8s 管理微服务集群,通过 Deployment + HPA 应对流量波动,通过 StatefulSet 管理有状态中间件,通过滚动发布实现不停服更新。
继续吗?可以深入 Service Mesh/Istio、CI/CD Pipeline 设计,或者转向其他 EA 面试准备方向。
