1. 容器化基础:Docker核心概念与实战入门

各位同学,咱们开始第一节课。说实话,容器化这个概念在游戏后端圈子里,这几年已经快成标配了。我最早接触Docker是在2016年,那时候团队还在用虚拟机部署,每次扩容都要等十几分钟。后来切到容器,秒级启动的感觉,真香。

这一章,咱们把Docker最核心的三个东西讲透:镜像、容器、仓库。再加上Docker的安装配置和Dockerfile怎么写。别急,一步步来。

1.1 Docker是什么?为什么游戏服务器需要它?

Docker说白了就是一个轻量级的虚拟化方案。它不像虚拟机那样要模拟整个操作系统,而是直接跑在宿主机内核上,共享内核但隔离进程。

你想想看,游戏服务器最头疼的是什么?环境不一致。开发环境跑得好好的,一上测试就崩,到了生产环境直接炸。Docker把应用和它的依赖打包成一个镜像,走到哪跑到哪,环境问题一次性解决。

核心优势:

  • 秒级启动,比虚拟机快一个数量级
  • 资源利用率高,一台物理机可以跑几十个容器
  • 环境一致性,开发、测试、生产完全一样
  • 易于扩展,配合K8s可以实现自动扩缩容

1.2 三大核心概念:镜像、容器、仓库

1.2.1 镜像(Image)

镜像是什么?你可以把它理解成一个只读的模板。它包含了运行应用所需的一切:代码、运行时、系统工具、库文件、环境变量、配置文件。

我习惯把镜像比作一个"光盘"。你刻录好之后,内容就不能改了。每次修改代码,都要重新"刻录"一个新的镜像版本。

镜像采用分层结构,每一层都是只读的。这样做的好处是:多个镜像可以共享底层的基础层,节省磁盘空间。比如你的游戏服务器镜像和Web服务器镜像,底层可能都是同一个Ubuntu基础镜像。

小技巧:查看本地镜像列表用 docker images,查看镜像的构建历史用 docker history 镜像名。我曾经靠这个命令排查过一个镜像体积过大的问题,发现是某层不小心装了一堆调试工具。

1.2.2 容器(Container)

容器是镜像的运行实例。镜像启动后,在最上层加一个可写层,就变成了容器。你可以对容器进行启动、停止、删除等操作。

嗯,这里要注意:容器是有状态的。你在容器里写的数据,容器删除后就没了。所以游戏服务器的日志、数据库数据,一定要挂载到宿主机或者使用数据卷。

我记得有一次线上事故,同事把游戏日志直接写在容器里,结果容器被调度到另一台机器上,日志全丢了。从那以后,我们团队定了个规矩:所有持久化数据必须走挂载。

避坑指南:我曾经见过有人用 docker commit 来保存容器状态,然后当成新镜像用。千万别这么干!这会导致镜像层数爆炸,而且不可复现。正确的做法是用Dockerfile构建镜像。

1.2.3 仓库(Registry)

仓库就是存放镜像的地方。有公共的Docker Hub,也有私有的Harbor、Nexus等。游戏公司一般都会搭建私有仓库,毕竟代码和配置不能随便公开。

我建议团队至少搭建一个私有仓库。原因很简单:

  • 拉取速度快,内网传输比公网快得多
  • 安全性高,镜像不会泄露
  • 可以控制版本,避免意外使用错误版本

镜像的命名规则是:仓库地址/命名空间/镜像名:标签。比如 registry.game.com/game-server/match-service:v1.2.3

1.3 Docker安装与配置

安装Docker其实很简单,但不同操作系统略有差异。我主要讲Linux环境,因为游戏服务器99%跑在Linux上。

1.3.1 在Ubuntu上安装

# 更新包索引
sudo apt-get update

# 安装依赖
sudo apt-get install -y ca-certificates curl gnupg lsb-release

# 添加Docker官方GPG密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 设置稳定版仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装Docker引擎
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

# 启动Docker
sudo systemctl start docker
sudo systemctl enable docker

# 验证安装
sudo docker run hello-world

1.3.2 配置镜像加速

国内拉取Docker Hub镜像经常超时,我建议配置镜像加速器。编辑 /etc/docker/daemon.json

{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2"
}

个人经验:日志配置很重要。默认情况下Docker日志会无限增长,我曾经见过一个容器日志占满磁盘导致服务宕机。所以一定要限制日志大小和文件数量。

1.3.3 非root用户运行Docker

默认情况下Docker需要root权限。为了方便开发,可以把当前用户加入docker组:

sudo usermod -aG docker $USER
newgrp docker

注意:这样做有安全风险,生产环境不建议这么搞。但在开发环境,能省去每次敲sudo的麻烦。

1.4 Dockerfile编写规范

Dockerfile是构建镜像的"配方"。写得好不好,直接影响镜像大小、构建速度和安全性。我见过太多人把Dockerfile写成"屎山",一个镜像几个G,构建一次半小时。

1.4.1 基础结构

一个标准的Dockerfile长这样:

# 指定基础镜像
FROM ubuntu:20.04

# 设置维护者信息
LABEL maintainer="yourname@game.com"

# 设置环境变量
ENV APP_HOME /app
ENV TZ Asia/Shanghai

# 设置工作目录
WORKDIR $APP_HOME

# 安装依赖
RUN apt-get update && apt-get install -y \
    libssl-dev \
    libcurl4-openssl-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制代码
COPY ./bin/game-server .

# 暴露端口
EXPOSE 8080

# 设置启动命令
CMD ["./game-server"]

1.4.2 编写规范与最佳实践

我总结了几条铁律,照着做基本不会踩坑:

规范 说明 为什么
选择合适的基础镜像 优先用alpine或slim版本 基础镜像越小,攻击面越小,构建越快
合并RUN命令 用 && 连接多个命令 减少镜像层数,每层都会增加体积
清理缓存 安装完依赖后删除apt缓存 避免缓存留在镜像中,减小体积
使用.dockerignore 排除不需要的文件 避免把本地调试文件、git目录等打包进镜像
不要用latest标签 指定具体版本号 latest会变化,导致构建结果不可复现

多阶段构建示例:这是我最常用的技巧,特别适合Go、C++等编译型语言。

# 第一阶段:编译
FROM golang:1.20 AS builder
WORKDIR /build
COPY . .
RUN go build -o game-server .

# 第二阶段:运行
FROM alpine:3.18
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /build/game-server /app/
EXPOSE 8080
CMD ["/app/game-server"]

你看,最终镜像只有十几兆,比直接基于golang镜像小了10倍。为什么?因为运行时不需要Go的编译工具链,只需要编译好的二进制文件。

1.4.3 常见错误与避坑

我刚开始写Dockerfile时也犯过不少错,分享几个典型的:

  • 把配置文件写死在镜像里:不同环境(开发、测试、生产)的配置不同,应该通过环境变量或挂载卷注入
  • 用root用户运行容器:安全风险极大,应该创建专用用户
  • 不设置WORKDIR:默认工作目录是根目录,容易造成文件混乱
  • 暴露不必要的端口:只暴露应用需要的端口,减少攻击面

曾经踩过的坑:有一次我把数据库密码直接写在Dockerfile里,然后推到了公共仓库。虽然很快就删了,但镜像已经被拉取了几百次。从那以后,所有敏感信息都用环境变量或者Secret管理。

1.5 本章小结

这一章我们讲了Docker的三大核心概念:镜像(只读模板)、容器(运行实例)、仓库(镜像存储)。然后手把手装了Docker,配置了镜像加速和日志限制。最后讲了Dockerfile的编写规范和最佳实践。

说实话,这些内容看起来简单,但真正用好需要大量实践。我建议你动手写几个Dockerfile,把公司的游戏服务器容器化试试。下一章我们会讲Docker Compose,到时候就能编排多个容器了。

记住一句话:容器化不是目的,稳定高效的交付才是。咱们下节课见。