Docker 容器化
用标准集装箱打包你的应用,"一次构建,到处运行"
前置知识:Linux 基本命令 + 第01章 CI/CD 流水线
阅读指南(初学者必看)
为什么你需要学习 Docker?
你一定经历过这些场景:
- "在我电脑上能跑啊!" —— 环境不一致导致的问题
- 新同事入职,配环境配了一天
- 测试环境和生产环境配置不同,出了 Bug 测不出来
Docker 就是解决"环境一致性"问题的。 把应用和依赖一起打包成镜像,不管在哪运行都是同样的环境。
学完本章,你能回答:
- Docker 镜像、容器、仓库分别是什么?
- 怎么写一个生产级别的 Dockerfile(多阶段构建)?
- 怎么用 Docker Compose 一键启动完整的游戏开发环境?
- 镜像怎么优化才能又小又快?
本文结构
第一部分:Docker 核心概念(建立认知)
第二部分:Dockerfile 最佳实践(动手写)
第三部分:Docker Compose 编排(多服务管理)
第四部分:镜像优化与安全
一、Docker 核心概念
生活类比:Docker 就像"集装箱"。以前货物大小不一,装卸麻烦;现在所有货物装进标准集装箱,轮船和卡车只需要处理集装箱,不用管里面装什么。
Docker 三大概念:
镜像(Image)── 只读模板,包含应用和依赖
→ 就像装好货物的集装箱,可以复制
容器(Container)── 镜像的运行实例
→ 就像正在运输的集装箱
仓库(Registry)── 存放镜像的地方
→ 就像集装箱码头
镜像 vs 容器 vs 仓库
| 概念 | 类比 | 说明 |
|---|---|---|
| 镜像(Image) | 集装箱的设计图纸 | 只读,可以创建多个容器 |
| 容器(Container) | 正在运输的集装箱 | 运行中的实例,可启停 |
| 仓库(Registry) | 集装箱码头 | Docker Hub 是最大的公共仓库 |
Docker 常用命令速查
# 镜像操作
docker build -t game-server:v1 . # 构建镜像
docker pull nginx:alpine # 拉取镜像
docker images # 查看本地镜像
docker rmi game-server:v1 # 删除镜像
# 容器操作
docker run -d -p 8080:8080 game-server:v1 # 启动容器
docker ps # 查看运行中的容器
docker logs <container-id> # 查看日志
docker exec -it <container-id> /bin/sh # 进入容器
docker stop <container-id> # 停止容器
docker rm <container-id> # 删除容器
二、Dockerfile 最佳实践
游戏服务器 Dockerfile(多阶段构建)
# 游戏服务器 Dockerfile(多阶段构建)
# 阶段1:构建
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline # 先下载依赖(缓存层)
COPY src ./src
RUN mvn package -DskipTests
# 阶段2:运行(更小的基础镜像)
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# JVM 容器感知(JDK 8u191+ 自动检测容器内存限制)
ENV JAVA_OPTS="-XX:+UseG1GC -XX:MaxGCPauseMillis=100"
COPY --from=builder /app/target/game-server.jar .
EXPOSE 8080 8443
# 使用非 root 用户运行
RUN addgroup -S gameserver && adduser -S gameserver -G gameserver
USER gameserver
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8080/health || exit 1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar game-server.jar"]
游戏客户端 Nginx Dockerfile
# 游戏客户端 Nginx Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build:prod
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
优化前 vs 优化后
# ❌ 优化前(问题:镜像大、层多、每次改代码都重新装依赖)
FROM node:latest
COPY . /app
WORKDIR /app
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]
# ✅ 优化后(多阶段构建 + 缓存层优化)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]
多阶段构建的好处
| 对比项 | 单阶段构建 | 多阶段构建 |
|---|---|---|
| 镜像大小 | ~800MB(含构建工具) | ~200MB(只含运行时) |
| 安全性 | 低(含源码和构建工具) | 高(只有运行必需文件) |
| 构建速度 | 每次全量构建 | 利用缓存层加速 |
| 攻击面 | 大 | 小 |
.dockerignore 文件
node_modules
.git
.env
*.log
.vscode
coverage
dist
三、Docker Compose 编排
# docker-compose.yml - 游戏全栈开发环境
version: '3.8'
services:
# 游戏网关
gateway:
build: ./gateway
ports:
- "8080:8080"
- "8443:8443"
depends_on:
- room-server
- match-server
environment:
- ROOM_SERVER_URL=ws://room-server:9000
- MATCH_SERVER_URL=http://match-server:9001
# 房间服务器
room-server:
build: ./room-server
ports:
- "9000:9000"
depends_on:
- redis
- mysql
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/game
- SPRING_REDIS_HOST=redis
# 匹配服务器
match-server:
build: ./match-server
ports:
- "9001:9001"
depends_on:
- redis
# Redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
command: redis-server --maxmemory 512mb
# MySQL
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: game
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
# Prometheus(监控)
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
# Grafana(看板)
grafana:
image: grafana/grafana
ports:
- "3000:3000"
volumes:
mysql-data:
Docker Compose 常用命令
docker-compose up -d # 启动所有服务(后台)
docker-compose down # 停止并删除所有容器
docker-compose logs -f # 查看所有服务日志
docker-compose ps # 查看服务状态
docker-compose build # 重新构建镜像
docker-compose restart <svc> # 重启单个服务
四、镜像优化与安全
镜像优化原则
- 镜像越小越好:减少攻击面、加快传输、减少启动时间
- 层数越少越好:减少构建时间,但可读性和缓存要平衡
- 不要存敏感信息:密码、密钥用环境变量或 Docker Secrets
- 一个容器只做一件事:网关是网关,数据库是数据库
安全最佳实践
| 措施 | 说明 |
|---|---|
| 非 root 用户 | 容器被攻破也限于容器内部权限 |
| 最小基础镜像 | Alpine 比 Debian 小且攻击面小 |
| 健康检查 | HEALTHCHECK 让 Docker 知道容器是否正常工作 |
| 只读文件系统 | read_only: true 防止运行时文件被篡改 |
| 资源限制 | 限制 CPU 和内存,防止单个容器拖垮宿主机 |
自问自答
Q:Docker 和虚拟机有什么区别? A:虚拟机模拟完整的操作系统(有自己的内核),Docker 容器共享宿主机内核。所以 Docker 启动快(秒级 vs 分钟级)、占用少(MB vs GB)、性能接近原生。
Q:为什么要用多阶段构建? A:构建阶段需要 Maven、JDK 等工具(~600MB),但运行阶段只需要 JRE(~100MB)。多阶段构建把构建产物复制到一个更小的基础镜像中,最终镜像体积大幅减小,也更安全。
Q:Docker Compose 和 Kubernetes 有什么区别?什么时候用哪个? A:Docker Compose 适合单机开发/测试环境,定义和运行多容器应用。Kubernetes 适合生产环境,提供自动扩缩容、自愈、滚动更新等能力。开发用 Compose,生产用 K8s。
Q:游戏服务器的 Dockerfile 为什么要用非 root 用户? A:安全最佳实践。如果容器被攻破,root 用户可以访问宿主机资源。非 root 用户即使被攻破,影响范围也限于容器内部。
Q:Dockerfile 中 RUN npm install 放在 COPY . . 之后有什么问题? A:每次代码改动都会重新安装依赖,构建极慢。解决:先 COPY package.json,再 RUN npm install,最后 COPY 源码。
实践任务
- 任务1:编写游戏服务器的 Dockerfile(多阶段构建),对比单阶段与多阶段的镜像大小差异
- 任务2:用 Docker Compose 搭建完整开发环境(服务器 + Redis + MySQL + 监控)
- 任务3:优化镜像大小(对比 Alpine vs 标准镜像,利用缓存层)
- 任务4:配置 Docker 健康检查,验证容器异常时自动重启
- 任务5:编写 .dockerignore,排除不需要的文件
- 任务6:编写 Docker 部署文档,记录镜像构建、推送、运行的全流程
初学者常见错误
错误1:镜像越来越大
问题: 每次构建都叠加新层,不清理缓存和临时文件。 解决: 使用多阶段构建;同一 RUN 中合并命令并清理。
错误2:容器内应用监听 127.0.0.1
问题: 容器内的 127.0.0.1 只绑定到容器内部,外部访问不到。 解决: 应用监听 0.0.0.0 或 ::。
错误3:没有配置资源限制
问题: 某个容器内存泄漏导致宿主机 OOM,影响所有服务。
解决: 在 docker-compose.yml 中配置 deploy.resources.limits。
与其他章节的关联
| 本章内容 | 关联章节 | 关联点 |
|---|---|---|
| Docker 镜像构建 | 第01章 CI/CD 流水线 | CI/CD 自动构建 Docker 镜像 |
| Docker Compose | 第03章 Kubernetes 编排 | Compose 是单机版,K8s 是集群版 |
| 容器网络 | 第03章 Kubernetes 编排 | K8s 的 Pod 网络基于容器网络 |
| 容器监控 | 第06章 游戏性能监控 | 容器内采集指标暴露给 Prometheus |
上一章:01-CICD流水线 | 下一章:03-Kubernetes编排