第2章 容器化基础:Docker安装、Dockerfile编写、镜像构建与优化、Docker Compose

好,咱们进入容器化这一章。说实话,Docker 这东西,现在已经是云原生开发的标配了。你出去面试,要是说自己没用过 Docker,面试官可能都会愣一下。我最早接触 Docker 是在 2016 年,那时候公司还在用虚拟机部署,每次发版都要折腾半天。后来切到 Docker,整个团队的幸福感直线上升。

2.1 Docker 安装与环境配置

安装 Docker 其实不复杂,但不同系统有些小坑。我个人的习惯是,不管用什么系统,都优先用官方推荐的安装方式。

Linux 环境安装

以 Ubuntu 为例,官方推荐用 apt 仓库安装。别去官网下载 .deb 包手动装,那样后续更新很麻烦。

# 卸载旧版本
sudo apt-get remove docker docker-engine docker.io containerd runc

# 设置仓库
sudo apt-get update
sudo apt-get install 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 Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
小提示:安装完成后,记得把当前用户加入 docker 组,不然每次都要 sudo。命令是 sudo usermod -aG docker $USER,然后退出重新登录。

macOS 与 Windows

这两个系统就简单多了。直接下载 Docker Desktop 安装就行。不过我要提醒一句,Docker Desktop 在 macOS 上挺吃资源的,尤其是老款 Intel 芯片的 Mac。我有个同事用 2017 款的 MacBook Pro,开了 Docker 之后风扇就没停过。

安装完跑一下 hello-world 验证:

docker run hello-world

看到 "Hello from Docker!" 就说明搞定了。

2.2 Dockerfile 编写实战

Dockerfile 说白了就是一份构建说明书。你告诉 Docker 怎么一步步把你的应用打包成镜像。我见过很多新手把 Dockerfile 写成了一锅粥,什么都在里面装,最后镜像体积大得吓人。

基础语法

先看一个最简单的例子,一个 Node.js 应用的 Dockerfile:

# 指定基础镜像
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY package*.json ./

# 安装依赖
RUN npm install --production

# 复制源代码
COPY . .

# 暴露端口
EXPOSE 3000

# 启动命令
CMD ["node", "app.js"]

这里有几个关键点:

  • FROM:基础镜像的选择很重要。alpine 版本体积小,但有些包可能不兼容。我一般优先用 slim 版本,平衡体积和兼容性。
  • WORKDIR:设置工作目录,后续的 COPY、RUN、CMD 都会在这个目录下执行。
  • COPY:注意顺序。把不常变动的文件(如 package.json)放在前面,利用 Docker 的缓存机制。源代码放在后面。
  • RUN:每个 RUN 指令都会创建一个新的镜像层。所以尽量合并命令,减少层数。
注意:不要把敏感信息写在 Dockerfile 里!比如数据库密码、API Key 这些。我曾经见过有人直接把 AWS 密钥写死在 Dockerfile 里,然后 push 到了公共仓库...后果可想而知。

多阶段构建

这个技巧我强烈推荐。尤其是编译型语言,比如 Go、Java、C++。多阶段构建可以让你在第一个阶段编译,第二个阶段只保留运行时的产物,体积能小好几倍。

# 第一阶段:编译
FROM golang:1.20 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp .

# 第二阶段:运行
FROM alpine:3.18
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]

你看,最终镜像只有 alpine 加上一个二进制文件,体积从 1GB 降到了 20MB 左右。我在项目中用这个技巧,把 CI/CD 的构建时间从 5 分钟降到了 40 秒。

2.3 镜像构建与优化

镜像构建不只是写个 Dockerfile 然后 docker build 就完事了。优化镜像体积和构建速度,是每个云原生工程师的必修课。

构建命令

# 基本构建
docker build -t myapp:1.0 .

# 指定 Dockerfile 路径
docker build -f ./deploy/Dockerfile -t myapp:1.0 .

# 带构建参数
docker build --build-arg VERSION=1.0 -t myapp:1.0 .

镜像优化技巧

优化项 说明 效果
选择合适的基础镜像 优先用 alpine、slim 版本 体积减少 50%-80%
合并 RUN 指令 用 && 连接多个命令 减少镜像层数
清理缓存 apt clean、npm cache clean 减少无用文件
多阶段构建 分离编译和运行环境 体积减少 90% 以上
利用 .dockerignore 排除不需要的文件 加快构建速度

我的经验:有一次线上服务频繁 OOM,排查了半天发现是镜像里装了个调试工具包,占了几百兆内存。从那以后,我要求团队所有生产镜像必须用多阶段构建,运行时镜像里只保留最小依赖。

.dockerignore 文件

这个文件的作用和 .gitignore 类似。把 node_modules、.git、日志文件这些不需要的东西排除掉,构建速度能快不少。

node_modules
.git
*.log
.env
dist
.cache

2.4 Docker Compose 编排

单机环境下,Docker Compose 是管理多容器应用的神器。你想想看,一个微服务项目可能包含前端、后端、数据库、缓存、消息队列...要是每个都手动 docker run,那不得累死。

docker-compose.yml 示例

version: '3.8'

services:
  web:
    build: ./web
    ports:
      - "3000:3000"
    depends_on:
      - api
    environment:
      - API_URL=http://api:8080

  api:
    build: ./api
    ports:
      - "8080:8080"
    depends_on:
      - db
      - redis
    environment:
      - DB_HOST=db
      - REDIS_HOST=redis

  db:
    image: postgres:15-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:

常用命令

# 启动所有服务
docker compose up -d

# 查看日志
docker compose logs -f

# 重新构建并启动
docker compose up -d --build

# 停止并删除容器
docker compose down

# 查看服务状态
docker compose ps
实用技巧:开发环境可以用 docker compose up 前台运行,方便看日志。生产环境用 docker compose up -d 后台运行。另外,docker compose down -v 会删除数据卷,小心使用!

Compose 文件的最佳实践

  • 环境变量分离:敏感信息用 .env 文件管理,不要硬编码在 yml 里。
  • 健康检查:给关键服务加上 healthcheck,确保依赖服务就绪后再启动。
  • 资源限制:用 deploy.resources 限制 CPU 和内存,防止某个服务把资源吃光。
  • 网络隔离:默认 Compose 会创建一个网络,服务之间用服务名通信。也可以自定义网络,实现更精细的隔离。

嗯,容器化这块内容其实挺多的。但掌握了 Dockerfile 编写、镜像优化和 Compose 编排,你已经能应付大部分日常工作了。下一章我们会聊 Kubernetes,那才是云原生的重头戏。

对了,有个事我得提醒你。Docker 虽然好用,但别什么应用都往容器里塞。有状态的服务(比如数据库)在容器里跑,数据持久化和网络性能都是挑战。我建议数据库这类服务还是用云服务商提供的托管服务,省心很多。