Docker 核心技术深度解析
本文深入探讨 Docker 的核心技术,包括 Namespace、Cgroups 和 UnionFS,并涵盖 Dockerfile 最佳实践、网络、存储以及与 Kubernetes 的关系。
1. 引言:容器化与微服务
1.1. 系统架构演进:从单体到微服务
传统分层架构 (Monolithic)
- 结构: 表示层 -> 业务逻辑层 -> 数据访问层 -> 数据库
- 优点: 简单应用易于开发、测试、部署。
- 缺点 (复杂系统): 开发维护困难、部署慢、技术栈单一、扩展性差。
graph LR A[Presentation Layer] --> B(Business Logic Layer) B --> C(Data Access Layer) C --> D{Database}

微服务架构 (Microservices)
- 结构: 将大型应用拆分为小型、独立的服务,通过 API 网关或直接通信。
- 优点: 易于理解和维护、独立部署、技术选型灵活、易于扩展、促进 CI/CD。
- 缺点: 分布式系统复杂性(通信、事务、监控)、运维挑战。
graph LR subgraph Microservices A[Service 1] -->|API| B(API Gateway) C[Service 2] -->|API| B D[Service 3] -->|API| B end B --> E{External Services/UI}

- 微服务改造原则: 按业务能力、领域边界、性能需求等进行拆分。
- 微服务间通讯: 点对点 vs API 网关。
1.2. 为何需要容器化?
微服务架构带来了部署和管理的复杂性。每个服务可能依赖不同的库、环境。容器化技术(如 Docker)提供了一种标准化的方式来打包、分发和运行应用,解决了环境一致性、快速部署和资源隔离等问题,是实现微服务的关键技术之一。
2. Docker 基础
2.1. Docker 是什么?
- Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux 或 Windows 机器上,也可以实现虚拟化。
- 核心概念:
- 镜像 (Image): 一个只读的模板,包含创建 Docker 容器的说明。基于 UnionFS,镜像由多个层 (Layer) 组成。
- 容器 (Container): 镜像的运行实例。容器是可写的,在镜像层之上增加了一个可写层。容器之间、容器与宿主机之间通过 Namespace 进行隔离。
- 仓库 (Repository): 集中存放镜像文件的地方 (如 Docker Hub)。
2.2. Docker vs. 虚拟机

graph TD subgraph "虚拟机 (VM)" App1 --> GuestOS1 App2 --> GuestOS2 GuestOS1 --> Hypervisor GuestOS2 --> Hypervisor Hypervisor --> HostOS HostOS --> Hardware end subgraph "容器 (Container)" AppA --> ContainerEngine[Docker Engine] AppB --> ContainerEngine ContainerEngine --> HostOS_C[Host OS] HostOS_C --> Hardware_C[Hardware] end
特性 | 容器 (Docker) | 虚拟机 (VM) |
---|---|---|
隔离级别 | 进程级 (共享内核) | 操作系统级 (独立内核) |
启动速度 | 秒级 | 分钟级 |
资源占用 | 少 (MB 级镜像, 低内存开销) | 多 (GB 级镜像, 高内存开销) |
性能 | 接近原生 | 有损耗 |
密度 | 高 (单机可运行成百上千个) | 低 (单机运行数十个) |
2.3. 为什么要用 Docker?
- 环境一致性: 打包应用及其所有依赖,消除 “在我机器上可以运行” 的问题。
- 快速交付部署: 加速开发、测试、部署流程 (CI/CD)。
- 资源利用率高: 更轻量,启动更快,系统开销小。
- 弹性伸缩: 快速创建和销毁容器实例。
- 易于迁移: 跨云、跨环境迁移方便。
2.4. 安装 Docker (以 Ubuntu 为例)
1 |
|
2.5. 常用 Docker 命令
- 镜像操作:
docker images
: 列出本地镜像。docker pull <image_name>:<tag>
: 从仓库拉取镜像。docker build -t <repository>/<image_name>:<tag> .
: 根据 Dockerfile 构建镜像。docker rmi <image_id_or_name>
: 删除本地镜像。docker tag <source_image> <target_image>
: 给镜像打标签。docker push <repository>/<image_name>:<tag>
: 推送镜像到仓库。docker save -o <output_file.tar> <image_name>
: 将镜像保存为 tar 文件。docker load -i <input_file.tar>
: 从 tar 文件加载镜像。
- 容器操作:
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
: 创建并启动一个新容器。-d
: 后台运行。-it
: 交互式运行 (通常结合/bin/bash
等)。--name <container_name>
: 指定容器名称。-p <host_port>:<container_port>
: 端口映射。-v <host_path>:<container_path>
: 卷挂载 (数据持久化)。--rm
: 容器退出时自动删除。--network <network_mode>
: 指定网络模式 (bridge, host, none, container:<name|id>, 自定义网络)。--env <key>=<value>
或--env-file <file>
: 设置环境变量。--memory <limit>
: 限制内存。--cpus <limit>
: 限制 CPU 核心数。
docker ps
: 列出正在运行的容器。docker ps -a
: 列出所有容器 (包括已停止的)。docker stop <container_id_or_name>
: 优雅地停止容器 (发送 SIGTERM,超时后 SIGKILL)。docker kill <container_id_or_name>
: 强制停止容器 (发送 SIGKILL)。docker start <container_id_or_name>
: 启动已停止的容器。docker restart <container_id_or_name>
: 重启容器。docker rm <container_id_or_name>
: 删除已停止的容器。docker logs [-f] <container_id_or_name>
: 查看容器日志 (-f
持续跟踪)。docker inspect <container_id_or_name>
: 查看容器/镜像的详细信息 (JSON 格式)。docker exec -it <container_id_or_name> <command>
: 在运行中的容器内执行命令 (常用docker exec -it <id> /bin/bash
)。docker attach <container_id_or_name>
: 连接到正在运行的容器的标准输入、输出和错误流 (不推荐用于执行命令,退出时可能导致容器主进程停止)。docker cp <host_path> <container_id>:<container_path>
: 从主机复制文件到容器。docker cp <container_id>:<container_path> <host_path>
: 从容器复制文件到主机。
3. Docker 核心技术详解
Docker 的实现依赖于 Linux 内核的几项关键技术:Namespace (资源隔离)、Cgroups (资源限制) 和 UnionFS (镜像分层)。
3.1. Namespace (命名空间) - 实现隔离
Namespace 是 Linux 内核提供的用于隔离内核资源的方式。通过将全局系统资源包装在一个抽象层中,使得 Namespace 内的进程看起来拥有它们自己独立的全局资源实例。
原理: 内核通过
struct nsproxy
结构体将不同类型的 Namespace 关联到进程 (struct task_struct
)。主要类型:
- PID (Process ID): 隔离进程 ID。容器内的进程拥有独立的 PID 空间,PID 1 是容器的 init 进程。
- Net (Network): 隔离网络设备、IP 地址、端口、路由表等。每个容器拥有独立的网络栈。
- IPC (InterProcess Communication): 隔离 System V IPC 和 POSIX 消息队列。
- Mnt (Mount): 隔离文件系统挂载点。容器拥有独立的文件系统视图。
- UTS (Unix Timesharing System): 隔离主机名和域名。允许每个容器拥有自己的 hostname。
- User (User ID): 隔离用户和用户组 ID。允许容器内的 root 用户映射到宿主机上的非 root 用户,提高安全性。
- Cgroup: 隔离 Cgroup 根目录。
操作命令:
lsns
: 列出系统中的 Namespace。unshare
: 创建并运行一个带有新 Namespace 的程序。nsenter
: 进入指定的 Namespace 并执行程序。
示例 (进入容器网络 Namespace):
1
2
3
4# 1. 获取容器的 PID
PID=$(docker inspect --format '{{.State.Pid}}' <container_name_or_id>)
# 2. 进入该容器的网络 Namespace 查看 IP 地址
sudo nsenter --target $PID --net ip addr
3.2. Cgroups (控制组) - 实现资源限制
Cgroups (Control Groups) 是 Linux 内核提供的机制,用于限制、核算和隔离一组进程所使用的物理资源 (CPU、内存、磁盘 I/O、网络等)。
核心概念:
- Task: 系统中的一个进程。
- Cgroup: 按某种标准划分的进程组。
- Hierarchy: Cgroup 组成的树状结构,子 Cgroup 继承父 Cgroup 的属性。
- Subsystem/Controller: 具体的资源控制器 (如 cpu, memory, blkio)。一个 Subsystem 只能附加到一个 Hierarchy。
版本:
- v1: 早期版本,每个 Subsystem 需要挂载到不同的 Hierarchy (除了联合挂载),管理较混乱。
- v2: 改进版本,所有 Controller 挂载到统一的 Hierarchy (
/sys/fs/cgroup
),接口更清晰统一。现代 Linux 发行版多使用 v2。
Docker Cgroup Driver:
- Docker 通过 Cgroup Driver 与内核 Cgroups 交互。
cgroupfs
: Docker 直接读写 cgroup 文件系统。简单直接,但如果系统 init system (如 systemd) 也在管理 cgroups,可能导致冲突。systemd
: Docker 通过 systemd 的 API 来管理 cgroups。推荐在 systemd 作为 init system 的系统中使用此驱动,以确保 cgroup 管理的一致性。可以通过docker info | grep -i cgroup
查看和配置。
常用子系统 (Controller):
cpu
:cpu.shares
(v1) /cpu.weight
(v2): 相对权重。CPU 繁忙时,按比例分配 CPU 时间。默认 1024 (v1) 或 100 (v2)。仅在 CPU 竞争时生效。cpu.cfs_period_us
,cpu.cfs_quota_us
(v1) /cpu.max
(v2): 绝对限制。限制在一个周期 (period
, 通常 100ms) 内可使用的 CPU 时间 (quota
)。例如,quota=50000
,period=100000
表示最多使用 50% 的单核 CPU 时间。-1
表示不限制。这是硬限制。cpu.stat
(v1) /cpu.stat
(v2): 统计 CPU 使用时间、节流次数 (nr_throttled
) 和节流时间 (throttled_time
)。
memory
:memory.limit_in_bytes
(v1) /memory.max
(v2): 硬限制。进程使用的内存(包括文件缓存)不能超过此值,否则可能触发 OOM Killer。memory.soft_limit_in_bytes
(v1) /memory.low
(v2): 软限制。系统内存紧张时,优先回收超过软限制的 Cgroup 的内存。memory.usage_in_bytes
(v1) /memory.current
(v2): 当前内存使用量。memory.oom_control
(v1) /memory.oom.group
(v2): 控制 OOM Killer 行为。oom_kill_disable=1
(v1) 或memory.oom.group=1
(v2) 表示当该 Cgroup 发生 OOM 时,杀死 Cgroup 内的进程,而不是禁用 OOM Killer。
blkio
(v1) /io
(v2): 限制块设备的 I/O。pids
: 限制 Cgroup 内的进程数量。cpuset
: 绑定进程到指定的 CPU 核心和内存节点。devices
: 控制对设备文件的访问。
CFS 调度器: Linux 内核默认的 CPU 调度器,通过虚拟运行时间 (vruntime) 保证进程公平地共享 CPU。Cgroups 的
cpu.shares
正是影响 vruntime 的计算,从而影响调度优先级。练习 (限制 CPU):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 1. 创建测试 cgroup (假设使用 cgroup v1)
sudo mkdir /sys/fs/cgroup/cpu/cpudemo
# 2. 启动一个耗费 CPU 的进程 (例如: while true; do :; done &)
# 获取其 PID
PID=<your_busy_process_pid>
# 3. 将进程移入 cgroup
echo $PID | sudo tee /sys/fs/cgroup/cpu/cpudemo/cgroup.procs
# 4. 查看当前 CPU 使用率 (如使用 top)
# 5. 限制 CPU 使用率为 20%
sudo sh -c 'echo 100000 > /sys/fs/cgroup/cpu/cpudemo/cpu.cfs_period_us'
sudo sh -c 'echo 20000 > /sys/fs/cgroup/cpu/cpudemo/cpu.cfs_quota_us'
# 6. 再次查看 CPU 使用率,应被限制在 20% 左右
# 7. 清理
# sudo kill $PID
# sudo rmdir /sys/fs/cgroup/cpu/cpudemo练习 (限制 Memory):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 1. 创建测试 cgroup (假设使用 cgroup v1)
sudo mkdir /sys/fs/cgroup/memory/memorydemo
# 2. 启动一个消耗内存的程序 (例如一个简单的 C 程序不断 malloc)
# 获取其 PID
PID=<your_memory_eater_pid>
# 3. 将进程移入 cgroup
echo $PID | sudo tee /sys/fs/cgroup/memory/memorydemo/cgroup.procs
# 4. 查看内存使用情况 (如使用 top 或 free -m)
# 5. 限制内存为 100MB
echo 104857600 | sudo tee /sys/fs/cgroup/memory/memorydemo/memory.limit_in_bytes
# 6. (可选) 启用 OOM Killer (默认通常是启用的)
# echo 0 | sudo tee /sys/fs/cgroup/memory/memorydemo/memory.oom_control
# 7. 观察进程因超出内存限制而被 OOM Killer 终止
# 8. 清理
# sudo rmdir /sys/fs/cgroup/memory/memorydemo
3.3. Union File System (联合文件系统) - 实现镜像分层
UnionFS 是一种分层、轻量级的文件系统,它允许将多个目录(称为分支或层)的内容叠加在一起,形成一个单一的、一致的文件系统视图。对只读分支的修改会发生在最上层的可写分支中。
- Docker 中的应用: Docker 镜像正是基于 UnionFS 实现的。
- 镜像层 (Image Layers): Dockerfile 中的每条指令(主要是
RUN
,COPY
,ADD
)通常会创建一个新的只读层。这些层堆叠在一起。 - 容器层 (Container Layer): 当基于镜像启动容器时,Docker 会在只读镜像层之上添加一个可写的容器层。
- 镜像层 (Image Layers): Dockerfile 中的每条指令(主要是
- 写时复制 (Copy-on-Write, CoW):
- 当容器需要修改一个存在于下层只读镜像中的文件时,该文件首先会被复制到最上层的可写容器层,然后修改操作在这个副本上进行。原始镜像层的文件保持不变。
- 优点:节省存储空间(多个容器共享只读镜像层),容器启动快(只需创建可写层)。
- 缺点:首次写入时有性能开销,层数过多可能影响性能。
- 存储驱动 (Storage Driver): Docker 使用存储驱动来实现 UnionFS 的功能。不同的驱动有不同的实现方式和性能特点。
overlay2
: 当前推荐的默认驱动。性能好,稳定性高,被 Linux 内核主线支持。使用lowerdir
(只读层) 和upperdir
(可写层) 以及workdir
(内部使用)。aufs
: Docker 最早使用的驱动,稳定但未并入 Linux 主线内核,仅在部分发行版(如早期 Ubuntu)可用。devicemapper
: 基于 LVM 的块级存储驱动。性能较好,但配置复杂,空间管理不如overlay2
灵活。btrfs
,zfs
: 基于相应文件系统的写时复制特性,提供高级功能(如快照),但可能需要特定的文件系统格式化和配置。- 可以通过
docker info | grep -i storage
查看当前使用的驱动。

graph TD subgraph "运行中的容器" WritableLayer["容器可写层 (Container Layer)"] ReadOnlyLayer1["镜像层 N (Image Layer N)"] ReadOnlyLayer2["..."] ReadOnlyLayer3["镜像层 1 (Image Layer 1)"] BaseImage["基础镜像层 (Base Image Layer)"] WritableLayer -- CoW --> ReadOnlyLayer1 ReadOnlyLayer1 --> ReadOnlyLayer2 ReadOnlyLayer2 --> ReadOnlyLayer3 ReadOnlyLayer3 --> BaseImage end style WritableLayer fill:#f9d,stroke:#333,stroke-width:2px
- OverlayFS 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34# 准备目录
mkdir lower upper merged work
# 创建文件
echo "File in Lower" > lower/lower.txt
echo "File in Both (Lower)" > lower/both.txt
echo "File in Upper" > upper/upper.txt
echo "File in Both (Upper)" > upper/both.txt
# 挂载 OverlayFS (需要 root 权限)
sudo mount -t overlay overlay -o lowerdir=`pwd`/lower,upperdir=`pwd`/upper,workdir=`pwd`/work `pwd`/merged
# 查看合并后的视图
ls merged
# lower.txt upper.txt both.txt
# 查看文件内容 (upper 层覆盖 lower 层)
cat merged/both.txt
# File in Both (Upper)
# 在 merged 视图中修改/删除文件 (实际发生在 upper 层)
echo "Modified in merged" > merged/lower.txt
rm merged/upper.txt
# 查看 upper 层的变化
ls upper
# lower.txt both.txt (upper.txt 被标记为删除,通常通过 whiteout 文件实现)
cat upper/lower.txt
# Modified in merged
# 卸载
sudo umount merged
# 清理
rm -rf lower upper merged work
4. Docker 网络
Docker 提供了多种网络模式来连接容器。
4.1. Bridge 模式 (默认)
- 原理: Docker 安装时会创建一个名为
docker0
的虚拟网桥。每当创建一个使用 bridge 模式的容器时,Docker 会:- 创建一对 veth pair (虚拟以太网设备对)。
- 一端连接到
docker0
网桥。 - 另一端放入容器的网络 Namespace,并命名为
eth0
。 - 从
docker0
网桥所在的子网(默认为 172.17.0.0/16)分配一个 IP 地址给容器的eth0
。
- 容器间通信: 同一宿主机上的容器可以通过
docker0
网桥直接通信(使用容器 IP)。 - 访问外部网络:
docker0
网桥通过宿主机的iptables
规则进行 NAT (网络地址转换),使得容器可以访问外部网络。 - 外部访问容器: 需要通过
-p
或-P
参数进行端口映射,Docker 会配置相应的iptables
DNAT 规则将宿主机端口的流量转发到容器端口。

graph LR subgraph Host (e.g., 192.168.1.100) ContainerA[Container A (172.17.0.2)] -- veth_a_peer --> Docker0[docker0 Bridge (172.17.0.1/16)] ContainerB[Container B (172.17.0.3)] -- veth_b_peer --> Docker0 Docker0 -- NAT (iptables) --> HostNIC[Host NIC (eth0)] HostNIC <--> ExternalNetwork{External Network} PortMapping[Port Mapping (e.g., 8080:80 for Container A)] -- iptables DNAT --> ContainerA HostNIC -- PortMapping end style ContainerA fill:#lightblue style ContainerB fill:#lightblue
4.2. Host 模式 (--network=host
)
- 原理: 容器不再拥有独立的网络 Namespace,而是直接共享宿主机的网络栈。
- 优点: 网络性能最高(没有 veth 和网桥的开销),容器可以直接使用宿主机的所有网络接口和端口。
- 缺点: 牺牲了网络隔离性,容器端口可能与宿主机或其他 host 模式容器冲突,安全性较低。
4.3. None 模式 (--network=none
)
- 原理: 容器拥有独立的网络 Namespace,但没有任何网络配置(没有网卡、IP、路由)。只有一个
lo
(loopback) 设备。 - 用途: 用于需要完全自定义网络配置的场景,或者不需要网络的容器。
4.4. Container 模式 (--network=container:<name|id>
)
- 原理: 新创建的容器共享另一个已存在容器的网络 Namespace。它们共享相同的 IP 地址、端口空间和网络接口。
- 用途: 常用于需要紧密协作的容器组,例如一个应用容器和一个监控/代理容器 (Sidecar 模式)。
4.5. Overlay 模式 (多主机网络)
- 原理: 用于连接跨越多个 Docker 主机的容器。它在现有宿主机网络之上创建一个覆盖网络 (Overlay Network),通常使用 VXLAN 等隧道技术将不同主机上容器的网络流量封装起来进行传输。
- 实现: Docker Swarm 模式内置了 overlay 网络驱动。Kubernetes 则使用 CNI (Container Network Interface) 插件 (如 Flannel, Calico, Weave) 来实现跨主机网络,这些插件也常使用 overlay 或 BGP 等技术。
- Libnetwork: Docker 的网络库,提供了可插拔的网络驱动模型,支持 bridge, host, overlay 等。

graph LR subgraph Host 1 ContainerA[Container A (10.0.1.2)] --> VTEP1[VTEP 1] VTEP1 -- VXLAN Tunnel --> Underlay[Underlay Network (e.g., 192.168.1.0/24)] end subgraph Host 2 VTEP2[VTEP 2] --> ContainerB[Container B (10.0.1.3)] Underlay -- VXLAN Tunnel --> VTEP2 end style ContainerA fill:#lightblue style ContainerB fill:#lightblue
4.6. Underlay 模式
- 原理: 不创建覆盖网络,而是直接将容器连接到宿主机所在的物理网络(或底层网络)。容器获得与宿主机同网段的可路由 IP 地址。
- 实现: 通常需要网络管理员的配合,配置物理网络设备或使用特定的网络插件(如 Calico 的 IP-in-IP 或 BGP 模式)。
- 优点: 网络性能好,没有封装开销。
- 缺点: 消耗底层网络 IP 地址,配置相对复杂,依赖底层网络架构。

5. Dockerfile 与镜像构建
Dockerfile 是一个文本文件,包含用于自动化构建 Docker 镜像的指令。
5.1. Dockerfile 常用指令
FROM <image>[:<tag>] [AS <name>]
: 指定基础镜像。必须是第一条非注释指令。AS <name>
用于多阶段构建。LABEL <key>=<value> ...
: 添加元数据到镜像。ENV <key>=<value> ...
: 设置环境变量。这些变量在构建过程中和容器运行时都可用。ARG <name>[=<default_value>]
: 定义构建时参数,只在构建过程中可用。可以通过docker build --build-arg <name>=<value>
传递。RUN <command>
: 在镜像构建过程中执行命令(shell 格式或 exec 格式)。每条RUN
指令会创建一个新的镜像层。COPY [--chown=<user>:<group>] <src>... <dest>
: 从构建上下文复制文件或目录到镜像文件系统。推荐优先使用COPY
。ADD [--chown=<user>:<group>] <src>... <dest>
: 功能类似COPY
,但增加了额外特性:src
可以是 URL。- 如果
src
是本地可识别的压缩包 (tar, gzip, bzip2, xz),会自动解压到dest
。此自动解压行为可能导致不确定性,通常不推荐使用ADD
来处理压缩包,建议使用RUN tar ...
。
WORKDIR /path/to/workdir
: 设置后续RUN
,CMD
,ENTRYPOINT
,COPY
,ADD
指令的工作目录。EXPOSE <port> [<port>/<protocol>...]
: 声明容器运行时监听的端口。这只是元数据,实际端口映射需要在docker run -p
中指定。VOLUME ["/path/to/volume"]
: 创建一个挂载点,用于持久化数据或共享数据。容器运行时会自动创建卷。Dockerfile 中此指令后的修改对该目录无效。USER <user>[:<group>]
: 指定运行后续RUN
,CMD
,ENTRYPOINT
指令时使用的用户名或 UID(以及可选的组名或 GID)。最佳实践:避免使用 root 用户运行容器。ENTRYPOINT ["executable", "param1", "param2"]
(exec 格式, 推荐) 或ENTRYPOINT command param1 param2
(shell 格式): 配置容器启动时运行的命令。docker run
提供的参数会追加到 exec 格式的ENTRYPOINT
之后,或者覆盖 shell 格式的ENTRYPOINT
。CMD ["executable","param1","param2"]
(exec 格式, 推荐),CMD ["param1","param2"]
(作为 ENTRYPOINT 的默认参数), 或CMD command param1 param2
(shell 格式): 提供容器启动的默认命令或参数。- 如果 Dockerfile 同时有
ENTRYPOINT
和CMD
,CMD
的内容会作为ENTRYPOINT
的默认参数。 - 如果
docker run
指定了命令,会覆盖CMD
。 - 如果只有
CMD
,它定义了容器启动时执行的命令。
- 如果 Dockerfile 同时有
ONBUILD <INSTRUCTION>
: 定义当基于此镜像构建新的镜像时,会自动执行的指令。STOPSIGNAL <signal>
: 设置停止容器时发送的系统调用信号 (默认 SIGTERM)。HEALTHCHECK [OPTIONS] CMD <command>
或HEALTHCHECK NONE
: 定义如何检查容器的健康状态。SHELL ["executable", "parameters"]
: 指定RUN
,CMD
,ENTRYPOINT
shell 格式指令使用的默认 shell。
5.2. 构建上下文 (Build Context)
docker build
命令最后指定的路径 (.
表示当前目录) 是构建上下文。- Docker daemon 在构建开始时会将整个构建上下文(除了
.dockerignore
中排除的文件/目录)发送过去。 - 注意: 保持构建上下文尽可能小,只包含构建所需的文件,使用
.dockerignore
排除不必要的文件(如.git
,node_modules
, 临时文件等)。
5.3. 构建缓存 (Build Cache)
- Docker 会缓存镜像层。如果 Dockerfile 的某一行指令及其依赖(如
COPY
的源文件内容)没有改变,Docker 会重用之前构建的缓存层,加快构建速度。 - 一旦某一层缓存失效(指令改变或依赖文件改变),其后的所有层缓存都会失效,需要重新构建。
- 优化策略: 将变化频率低的指令(如安装基础依赖)放在 Dockerfile 前面,变化频率高的指令(如
COPY
应用程序代码)放在后面,以最大化利用缓存。
5.4. 多阶段构建 (Multi-stage Builds)
- 目的: 减小最终镜像体积,只包含运行时必要的依赖,去除构建时工具和中间产物。
- 方法: 在一个 Dockerfile 中使用多个
FROM
指令。每个FROM
开始一个新的构建阶段,可以给阶段命名 (AS <name>
)。使用COPY --from=<stage_name_or_index> <src> <dest>
从之前的阶段复制需要的文件到当前阶段。
1 |
|
5.5. Dockerfile 最佳实践
- 基础镜像:
- 选择官方、经过验证的基础镜像。
- 使用具体标签(如
ubuntu:22.04
),避免使用latest
(可能导致构建不确定性)。 - 优先选择体积小的基础镜像(如
alpine
,distroless
)。Alpine 使用 musl libc,可能与 glibc 应用存在兼容性问题;Distroless 镜像不包含 shell 和包管理器,更安全但调试困难。
- 最小化层数:
- 合并多个
RUN
指令,使用&&
连接命令,并在最后清理缓存 (apt-get clean
,rm -rf /var/lib/apt/lists/*
)。 - 合理规划指令顺序,利用缓存。
- 合并多个
- 指令优化:
- 优先使用
COPY
而不是ADD
。 COPY
/ADD
时,尽量只复制需要的文件,而不是整个目录。- 使用
.dockerignore
排除不需要发送到 daemon 的文件。 - 使用多阶段构建分离构建环境和运行环境。
- 优先使用
- 安全:
- 不要在容器内使用 root 用户运行应用。 使用
USER
指令切换到非 root 用户。需要在RUN
指令中创建用户和组,并调整文件权限。 - 最小化安装包,只安装必要的依赖。
- 定期扫描镜像漏洞 (可以使用 Trivy, Clair 等工具)。
- 不要在容器内使用 root 用户运行应用。 使用
- 可维护性:
LABEL
添加维护者、版本等元数据。ARG
用于传递构建时变量,ENV
用于设置运行时环境变量。- 保持 Dockerfile 简洁、清晰,添加注释。
CMD
与ENTRYPOINT
:- 推荐使用 exec 格式 (
["executable", "param1"]
) 而不是 shell 格式 (command param1
),以正确处理信号。 - 使用
ENTRYPOINT
定义容器主命令,CMD
提供默认参数。
- 推荐使用 exec 格式 (
6. Docker 镜像管理
6.1. 镜像标签与版本管理
- 使用
docker tag <source_image> <repository>/<image_name>:<tag>
为镜像打标签。 - 标签通常用于表示版本号(如
v1.0.0
,2.1-alpine
)。 - 结合 Git tag,可以将代码版本与镜像版本对应起来,实现可追溯的版本管理。
6.2. 镜像仓库 (Registry)
- 公共仓库: Docker Hub 是最常用的公共镜像仓库。
- 私有仓库:
- 可以使用 Docker 官方提供的
registry
镜像快速搭建私有仓库 (docker run -d -p 5000:5000 --restart=always --name registry registry:2
)。 - 企业级私有仓库方案:Harbor, Nexus Repository, JFrog Artifactory 等,提供 UI、权限管理、安全扫描等功能。
- 可以使用 Docker 官方提供的
- 推送与拉取:
docker login <registry_address>
: 登录仓库。docker push <image_name_with_registry_prefix>
: 推送镜像。docker pull <image_name_with_registry_prefix>
: 拉取镜像。
6.3. 镜像安全
- 使用官方或可信的基础镜像。
- 最小化镜像内容。
- 使用非 root 用户运行。
- 定期进行漏洞扫描。
- 考虑使用镜像签名 (如 Docker Content Trust) 验证镜像来源和完整性。
7. Docker 与 Kubernetes
- Docker (或其他符合 OCI 标准的容器运行时,如 containerd, CRI-O) 负责单个容器的生命周期管理(构建、运行、停止)。
- Kubernetes (K8s) 是一个容器编排平台,负责大规模容器集群的自动化部署、扩展、管理和网络。
- 关系: Kubernetes 以 Docker 容器(或 OCI 容器)作为其调度的基本单元(通常封装在 Pod 中)。Kubernetes 利用底层的容器运行时来实际创建和管理容器,并在此之上提供了服务发现、负载均衡、自动伸缩、滚动更新、存储编排等高级功能。
- 虽然 Kubernetes 正在逐步移除对 Docker Engine (dockershim) 的直接依赖,转而使用标准的 CRI (Container Runtime Interface) 与 containerd 或 CRI-O 等运行时交互,但 Docker 构建的 OCI 兼容镜像仍然是 Kubernetes 生态系统中最常用的应用打包格式。
8. 总结
Docker 通过 Namespace、Cgroups 和 UnionFS 等 Linux 内核技术,实现了轻量级的应用隔离和资源限制,彻底改变了软件的开发、分发和部署方式。理解其核心原理、掌握 Dockerfile 最佳实践、熟悉网络和存储配置,对于现代云原生应用的开发和运维至关重要。结合 Kubernetes 等编排工具,Docker 为构建弹性、可扩展、高可用的分布式系统奠定了坚实的基础。