Docker

技术栈
工具链
容器虚拟化DevOps镜像编排

概览

Docker

Docker 是由 Solomon Hykes 于 2013 年开源的容器化平台,它将应用及其依赖打包成轻量级、可移植的容器。Docker 是容器技术的事实标准。

核心价值:一次构建、到处运行。消除"在我机器上能跑"问题,实现环境一致性、快速部署、资源隔离。

关键特性

  • 镜像分层构建(Layer),高效复用与分发
  • Dockerfile 声明式构建
  • Docker Compose 多容器编排
  • Docker Hub / Registry 镜像仓库
  • 资源限制(CPU、内存、IO)与 Namespace/Cgroups 隔离

安装

Docker 安装与初始化

1. 环境准备

要求 说明
操作系统 Linux(原生)、macOS 12+(Docker Desktop)、Windows 10/11 Pro/Enterprise(WSL2)
内核版本 Linux Kernel 3.10+(推荐 5.x+)
架构 x86_64 / ARM64(Apple Silicon 支持良好)
磁盘空间 至少 10GB 空闲(镜像和容器占用随使用增长)
虚拟化 Windows/macOS 需启用硬件虚拟化(Intel VT-x / AMD-V)

2. 安装命令

Ubuntu / Debian

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

# 使用官方脚本(推荐)
curl -fsSL https://get.docker.com | sudo bash

# 或手动安装
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list >; /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

CentOS / RHEL

sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
curl -fsSL https://get.docker.com | sudo bash
# 或
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

macOS

# 方式一:Homebrew
brew install --cask docker

# 方式二:手动下载
# https://www.docker.com/products/docker-desktop/

Windows

# 推荐:WSL2 + Docker Desktop
# 1. 启用 WSL2
wsl --install

# 2. 下载安装 Docker Desktop
# https://www.docker.com/products/docker-desktop/

# 或使用 winget
winget install Docker.DockerDesktop

验证安装

docker --version
# Docker version 26.0.0, build 2ae903e

docker run hello-world
# Hello from Docker! 说明安装成功

配置(无 root 运行 + 镜像加速)

# 免 sudo 运行
sudo usermod -aG docker $USER
# 重新登录生效

# 镜像加速(中国大陆)
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <;<EOF
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.xuanyuan.me"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

3. 常见安装问题

问题 解决方案
Cannot connect to Docker daemon sudo systemctl start docker 启动守护进程,确认 dockerd 正在运行
permission denied 执行 sudo usermod -aG docker $USER 后重新登录
镜像拉取慢/超时 配置国内镜像加速器(见上),或配置代理 export HTTP_PROXY=...
WSL2 中 Docker 无法启动 wsl --shutdown 后重启 WSL,确保 WSL 版本为 2:wsl --set-version <distro> 2
macOS Docker Desktop 启动卡住 重启 Docker Desktop,或 killall Docker && open /Applications/Docker.app
磁盘空间不足 docker system prune -a 清理未使用的镜像/容器/卷
Docker Compose 命令不存在 Docker 20.10+ 内置 docker compose(注意是空格非连字符),旧版需安装 docker-compose
CentOS 内核版本过低 升级到 CentOS 7+ 或安装 ELRepo 内核:rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org && rpm -Uvh https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm && yum --enablerepo=elrepo-kernel install kernel-lt

示例

目标

演示 Docker 多阶段构建(Multi-stage Build)技术,将一个 Go 应用从源码编译到最终运行镜像,压缩镜像体积从 ~800MB 降至 ~10MB。

完整代码

示例应用(main.go)

package main

import (
    "fmt"
    "net/http"
    "os"
)

func handler(w http.ResponseWriter, r *http.Request) {
    hostname, _ := os.Hostname()
    fmt.Fprintf(w, "Hello from %s (Go %s)\n", hostname, r.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    fmt.Println("Server listening on :8080")
    http.ListenAndServe(":8080", nil)
}

Dockerfile(多阶段构建)

# ============ 阶段1:编译 ============
FROM golang:1.22-alpine AS builder

# 设置国内代理(可选)
ENV GOPROXY=https://goproxy.cn,direct

WORKDIR /src
COPY main.go .

# 静态编译,剥离调试信息,减小体积
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app main.go

# ============ 阶段2:运行 ============
FROM scratch

# 从构建阶段复制编译好的二进制
COPY --from=builder /app /app

# scratch 镜像没有时区文件,需要的话可以复制
# COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

EXPOSE 8080
ENTRYPOINT ["/app"]

对比:单阶段构建(不推荐)

# 不推荐 — 镜像体积 ~800MB
FROM golang:1.22
WORKDIR /src
COPY main.go .
RUN go build -o /app main.go
EXPOSE 8080
ENTRYPOINT ["/app"]

Docker Compose 生产级编排

# docker-compose.yml
version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: my-go-app:latest
    ports:
      - "8080:8080"
    environment:
      - APP_ENV=production
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
      interval: 10s
      timeout: 3s
      retries: 3
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 128M
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

运行步骤

# 1. 构建镜像
docker build -t my-go-app:multi -f Dockerfile .

# 2. 查看镜像大小
docker images | grep my-go-app
# my-go-app   multi    8.5MB    ← 多阶段构建
# my-go-app   single   823MB    ← 单阶段构建

# 3. 运行容器
docker run -d --name go-app -p 8080:8080 my-go-app:multi

# 4. 测试
curl http://localhost:8080/hello
# Hello from abc123def (Go hello)

# 5. 查看层信息
docker history my-go-app:multi

# 6. 使用 dive 分析镜像(需安装)
dive my-go-app:multi

预期输出

镜像大小对比:
  单阶段镜像:823 MB
  多阶段镜像:8.5 MB  ← 减少 ~99%

docker history 输出:
  IMAGE          CREATED         SIZE
  <missing>      1 min ago       8.5MB  ← 最终只有二进制

关键点

  • FROM scratch 是空镜像,不含任何 OS 层,安全性极高
  • CGO_ENABLED=0 强制纯 Go 静态链接,不依赖 libc
  • -ldflags="-s -w" 去除符号表和调试信息
  • COPY --from=builder 跨阶段复制,构建依赖不进入最终镜像
  • 多阶段适合所有编译型语言:Go、Rust、C/C++、Java(jlink)

Dockerfile 与 Docker Compose 实战

目标

编写 Dockerfile 容器化一个 Python Web 应用,并用 Docker Compose 编排多服务。

完整代码

示例应用(app/main.py)

from flask import Flask
import os
import socket

app = Flask(__name__)

@app.route('/')
def hello():
    return f"Hello from {socket.gethostname()} | ENV={os.getenv('ENV', 'dev')}"

@app.route('/health')
def health():
    return {"status": "ok"}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Dockerfile

# ===== 多阶段构建 =====
# 阶段1:构建依赖
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt

# 阶段2:运行镜像
FROM python:3.12-slim
WORKDIR /app

# 创建非 root 用户
RUN useradd -m -s /bin/bash appuser

# 从 builder 复制依赖
COPY --from=builder /root/.local /home/appuser/.local
COPY app/ ./app/

# 切换到非 root 用户
USER appuser

ENV PATH=/home/appuser/.local/bin:$PATH
ENV ENV=production

EXPOSE 5000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:5000/health || exit 1

CMD ["python", "app/main.py"]

docker-compose.yml

version: '3.9'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: flask-app
    ports:
      - "5000:5000"
    environment:
      - ENV=production
      - REDIS_URL=redis://redis:6379
    depends_on:
      redis:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    container_name: redis
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    restart: unless-stopped
    networks:
      - app-network

  nginx:
    image: nginx:alpine
    container_name: nginx-proxy
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  redis-data:

运行步骤

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

# 查看状态
docker compose ps

# 查看日志
docker compose logs -f app

# 测试
curl http://localhost:5000/
curl http://localhost/       # 通过 Nginx 代理

# 停止
docker compose down

预期输出

Hello from <container-id> | ENV=production

教程

前言

在入门篇中,我们学习了 Docker 的核心概念和基本操作。本篇深入两个关键子系统:网络存储。理解它们是跑好生产级容器的前提。


第一章:Docker 网络深入

1.1 五种网络驱动对比

驱动 适用场景 跨主机 DNS 隔离性
bridge 单机容器通信(默认) ✅ 内置 中等
host 高性能、直接使用宿主机网络
overlay 跨主机容器通信(Swarm)
macvlan 容器直接接入物理网络(IP/MAC 独立)
none 完全禁用网络 最高

1.2 自定义 Bridge 网络(生产推荐)

# 创建自定义网络
docker network create \
  --driver bridge \
  --subnet=172.28.0.0/16 \
  --ip-range=172.28.5.0/24 \
  --gateway=172.28.5.1 \
  my-net

# 同一网络的容器互 Ping(自动 DNS 解析)
docker run -d --name app1 --network my-net nginx
docker run -d --name app2 --network my-net alpine ping app1
# PING app1 (172.28.5.2): 56 data bytes ← 自动域名解析!

默认 bridge 网络不支持 DNS 解析,必须用 --link(已废弃)。自定义网络默认启用 DNS。

1.3 容器间通信模式

场景 1:同一自定义网络
  ContainerA ←→ ContainerB  [通过容器名直接通信]

场景 2:不同网络
  ContainerA (net1) ←→ ContainerB (net2)  [不通,需要 docker network connect]

场景 3:容器 ↔ 宿主机
  Container → host.docker.internal (macOS/Windows)
  Container → 172.17.0.1 (Linux 默认网关)

场景 4:容器 ↔ 外部服务
  默认 NAT 出站,无需配置

1.4 端口映射原理

# -p 宿主机端口:容器端口
docker run -p 8080:80 nginx

# 流量路径:
# 外部请求 → 宿主机:8080 → iptables DNAT → 容器IP:80
# 查看 iptables 规则
iptables -t nat -L DOCKER -n

1.5 网络排错工具箱

# 查看网络列表
docker network ls

# 查看网络详情(含连接容器 IP)
docker network inspect my-net

# 进容器排查
docker exec -it app1 sh
# 容器内:
nslookup app2          # 测试 DNS
ping app2              # 测试连通性
ip addr show           # 查看容器网卡
netstat -tlnp          # 查看监听端口

第二章:Docker 存储深入

2.1 三种存储类型对比

类型 数据位置 生命周期 跨容器 性能 适用场景
Volume /var/lib/docker/volumes/ 独立于容器 生产数据持久化
Bind Mount 宿主机任意路径 独立于容器 最好 开发热更新
tmpfs 内存 随容器 极佳 临时缓存/密钥

2.2 Volume 最佳实践

# 创建命名卷
docker volume create --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/exports/data \
  nfs-vol

# 使用卷
docker run -d \
  -v db-data:/var/lib/mysql \
  -v app-logs:/var/log/app \
  --name mysql mysql:8

# 备份卷数据
docker run --rm \
  -v db-data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/db-backup.tar.gz -C /data .

# 恢复卷数据
docker run --rm \
  -v db-data:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/db-backup.tar.gz -C /data

# 卷迁移
# 1. 源主机导出
docker run --rm -v db-data:/data alpine tar czf - -C /data . >; db.tar.gz
# 2. 传输到目标主机
scp db.tar.gz target-host:
# 3. 目标主机导入
docker volume create db-data
docker run --rm -v db-data:/data -v $(pwd):/src alpine sh -c "cd /data && tar xzf /src/db.tar.gz"

2.3 Bind Mount 开发场景

# docker-compose.yml(开发模式)
services:
  web:
    image: node:20-alpine
    working_dir: /app
    command: npm run dev
    volumes:
      - ./src:/app/src        # 代码热更新
      - /app/node_modules     # 匿名卷,不覆盖 node_modules
    ports:
      - "3000:3000"

2.4 tmpfs 安全场景

# 敏感数据不落盘
docker run --rm \
  --tmpfs /run/secrets:rw,noexec,nosuid,size=64m \
  alpine sh -c "echo 'secret-key' > /run/secrets/key && cat /run/secrets/key"

# Docker Compose
services:
  app:
    tmpfs:
      - /tmp:size=128M,noexec
      - /run/secrets:size=1M,ro

第三章:Dockerfile 安全最佳实践

3.1 十条黄金法则

# 1. 使用特定版本,不用 latest
FROM node:20.11.1-alpine3.19

# 2. 非 root 运行
RUN addgroup -g 1000 app &;& adduser -u 1000 -G app -s /bin/sh -D app
USER app

# 3. 固定依赖版本
COPY package-lock.json ./
RUN npm ci --production

# 4. 最小化层数(用 && 串联)
RUN apk add --no-cache curl &;& \
    curl -sSf https://example.com/script.sh | sh && \
    apk del curl

# 5. 使用 .dockerignore
# node_modules/
# .git/
# *.md

# 6. COPY 优于 ADD(ADD 会自动解压 tar)

# 7. HEALTHCHECK
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget -qO- http://localhost:8080/health || exit 1

# 8. 使用多阶段构建减小攻击面(见例程)

# 9. 不写入密钥
# 用 BuildKit secrets:
# RUN --mount=type=secret,id=npmrc npm install

# 10. 扫描漏洞
# docker scan my-image

3.2 镜像安全扫描

# Docker 官方扫描(需要 Docker Hub 登录)
docker scan my-app:latest

# Trivy(开源,推荐)
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image my-app:latest

# Hadolint(Dockerfile 静态分析)
docker run --rm -i hadolint/hadolint <; Dockerfile

第四章:资源限制与监控

4.1 资源限制

# 硬限制(超出 OOM Kill)
docker run --memory=256m --cpus=1.5 nginx

# Docker Compose
services:
  app:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 128M

4.2 实时监控

# 容器资源概况
docker stats

# 单个容器进程
docker top my-container

# 实时日志
docker logs -f --tail 100 my-container

思考题

  1. Docker 的 bridge 网络与 host 网络在性能上有什么差异?什么时候必须用 host?
  2. 如果两个容器不在同一网络,如何让它们通信?有哪些实现方式?
  3. Volume 和 Bind Mount 在 Docker Desktop(macOS)上的性能差异为什么特别大?
  4. npm cinpm install 在 Dockerfile 中应该用哪个?为什么?
  5. 如何安全地在构建阶段使用私有 npm registry 的 token?

下一步

  • 学习 Docker Compose 生产部署模式
  • 学习 Docker Swarm / Kubernetes 编排
  • 学习 BuildKit 高级缓存策略

Docker 入门教程:容器化核心概念与最佳实践

1. Docker 解决了什么?

在传统部署中:

  • 环境不一致:"我这里能跑"
  • 依赖冲突:A 项目需要 Python 3.9,B 项目需要 Python 3.11
  • 部署繁琐:安装依赖 → 配置环境 → 启动
  • 资源浪费:VM 占用大量内存和磁盘

Docker 通过**镜像(Image)将应用和依赖打包,在任何地方以容器(Container)**形式运行。

2. 核心概念

Image vs Container

  • Image:只读模板,类似 ISO 文件
  • Container:Image 的运行实例,可读可写层叠加

Dockerfile 指令速查

FROM        # 基础镜像
RUN         # 构建时执行命令
COPY        # 复制文件(推荐)
ADD         # 复制 + 解压/URL 下载
WORKDIR     # 设置工作目录
ENV         # 环境变量
EXPOSE      # 声明端口(文档用)
CMD         # 容器启动默认命令(可覆盖)
ENTRYPOINT  # 容器入口(不可覆盖,除非 --entrypoint)
VOLUME      # 声明挂载点
USER        # 切换用户

分层构建与缓存

FROM ubuntu:22.04        # Layer 1: 基础层
RUN apt update           # Layer 2: apt 缓存
RUN apt install -y python # Layer 3: Python
COPY app.py .            # Layer 4: 代码(变更频繁)

Docker 会缓存不变的层,只有变更的层需要重建。

3. 最佳实践

  • ✅ 使用多阶段构建减小镜像体积
  • ✅ 以非 root 用户运行
  • .dockerignore 排除 node_modules/.git 等
  • ✅ 固定基础镜像版本标签(不用 latest
  • ✅ 合并 RUN 指令减少层数:RUN apt update && apt install -y pkg1 pkg2 && rm -rf /var/lib/apt/lists/*
  • ❌ 不要在容器内存储数据(用 Volume)
  • ❌ 不要在一个容器运行多个进程(用 Compose)

4. Docker Compose 实战技巧

# 环境变量复用
x-common-env: &;common-env
  ENV: production
  LOG_LEVEL: info

services:
  app:
    environment:
      <;<: *common-env
      PORT: 5000

# 启动顺序控制
depends_on:
  db:
    condition: service_healthy  # 等待健康检查通过

5. 网络模式

模式 说明
bridge 默认,独立网络栈
host 使用宿主机网络(仅 Linux)
none 无网络
overlay 跨主机容器通信(Swarm)

6. 思考题

  1. CMDENTRYPOINT 的区别?什么场景用哪个?
  2. 为什么推荐 Alpine 作为基础镜像?有什么缺点?
  3. Volume 和 Bind Mount 的区别?

参考资料

暂无参考文献