Docker 深度剖析:從 Linux 容器技術到鏡像分層存儲的完整架構解析
執行摘要
Docker 徹底改變了軟體部署方式,實現了「Build Once, Run Anywhere」的願景。本文將深入剖析 Docker 的核心技術:
- Linux 容器基礎:Namespace 隔離與 Cgroups 資源限制
- 鏡像存儲:UnionFS 分層文件系統與內容尋址
- 容器運行時:containerd、runc 與 OCI 規範
- 網路模型:bridge、host、overlay 網路實現
- 存儲驅動:overlay2、devicemapper、btrfs
目錄
- Docker 架構總覽
- Linux Namespace 隔離機制
- Cgroups 資源控制
- UnionFS 與鏡像分層
- 容器運行時
- Docker 網路模型
- 存儲驅動詳解
- Dockerfile 與鏡像構建
1. Docker 架構總覽
1.1 組件架構
flowchart TB
subgraph Client["Docker 客戶端"]
CLI[docker CLI]
API[Docker API]
end
subgraph Daemon["Docker Daemon (dockerd)"]
REST[REST API Server]
ImageMgr[Image Manager]
ContainerMgr[Container Manager]
NetworkMgr[Network Manager]
VolumeMgr[Volume Manager]
end
subgraph Runtime["容器運行時"]
Containerd[containerd]
Shim[containerd-shim]
Runc[runc]
end
subgraph Kernel["Linux 內核"]
NS[Namespaces]
CG[Cgroups]
UFS[UnionFS]
Netfilter[Netfilter/iptables]
end
CLI --> REST
REST --> ImageMgr
REST --> ContainerMgr
REST --> NetworkMgr
REST --> VolumeMgr
ContainerMgr --> Containerd
Containerd --> Shim
Shim --> Runc
Runc --> NS
Runc --> CG
ImageMgr --> UFS
NetworkMgr --> Netfilter
1.2 容器 vs 虛擬機
flowchart TB
subgraph VM["虛擬機架構"]
VMApp1[App 1]
VMApp2[App 2]
VMGuest1[Guest OS 1]
VMGuest2[Guest OS 2]
VMHypervisor[Hypervisor]
VMHost[Host OS]
VMHardware[Hardware]
VMApp1 --> VMGuest1
VMApp2 --> VMGuest2
VMGuest1 --> VMHypervisor
VMGuest2 --> VMHypervisor
VMHypervisor --> VMHost
VMHost --> VMHardware
end
subgraph Container["容器架構"]
CApp1[App 1]
CApp2[App 2]
CRuntime[Container Runtime]
CHost[Host OS]
CHardware[Hardware]
CApp1 --> CRuntime
CApp2 --> CRuntime
CRuntime --> CHost
CHost --> CHardware
end
| 特性 | 容器 | 虛擬機 |
|---|---|---|
| 啟動時間 | 秒級 | 分鐘級 |
| 資源佔用 | MB 級 | GB 級 |
| 隔離級別 | 進程級 | 硬體級 |
| 性能損耗 | ~1-2% | ~15-20% |
| 安全隔離 | 較弱 | 強 |
2. Linux Namespace 隔離機制
2.1 Namespace 類型
flowchart TB
subgraph Namespaces["Linux Namespaces"]
PID[PID Namespace<br/>進程隔離]
NET[Network Namespace<br/>網路隔離]
MNT[Mount Namespace<br/>文件系統隔離]
UTS[UTS Namespace<br/>主機名隔離]
IPC[IPC Namespace<br/>進程間通訊隔離]
USER[User Namespace<br/>用戶隔離]
CGROUP[Cgroup Namespace<br/>Cgroup 視圖隔離]
TIME[Time Namespace<br/>時間隔離 (5.6+)]
end
2.2 Namespace 系統調用
// 創建新 Namespace 的三種方式
// 1. clone() - 創建新進程時指定 Namespace
int clone_flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET |
CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWUSER;
pid_t pid = clone(child_func, stack_top, clone_flags | SIGCHLD, arg);
// 2. unshare() - 當前進程脫離 Namespace
unshare(CLONE_NEWNS | CLONE_NEWPID);
// 3. setns() - 加入現有 Namespace
int fd = open("/proc/PID/ns/net", O_RDONLY);
setns(fd, CLONE_NEWNET);
2.3 PID Namespace 示例
// 容器內進程 PID 從 1 開始
// 實現容器內的 init 進程
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
static int child_fn(void *arg) {
printf("Container PID: %d\n", getpid()); // 輸出 1
printf("Container PPID: %d\n", getppid()); // 輸出 0
// 在容器內執行進程
execv("/bin/sh", (char *[]){"/bin/sh", NULL});
return 0;
}
int main() {
char stack[1024 * 1024];
char *stack_top = stack + sizeof(stack);
// 創建新的 PID Namespace
pid_t pid = clone(child_fn, stack_top,
CLONE_NEWPID | SIGCHLD, NULL);
printf("Host PID of container: %d\n", pid);
waitpid(pid, NULL, 0);
return 0;
}
2.4 Network Namespace 實現
flowchart LR
subgraph Host["Host Network Namespace"]
eth0[eth0<br/>物理網卡]
docker0[docker0<br/>Linux Bridge]
veth1h[veth1-host]
veth2h[veth2-host]
end
subgraph Container1["Container 1 Network NS"]
eth0_1[eth0<br/>容器內]
end
subgraph Container2["Container 2 Network NS"]
eth0_2[eth0<br/>容器內]
end
eth0 --> docker0
docker0 --> veth1h
docker0 --> veth2h
veth1h -.-> |veth pair| eth0_1
veth2h -.-> |veth pair| eth0_2
# 創建 Network Namespace 並配置網路
ip netns add container1
# 創建 veth pair
ip link add veth1-host type veth peer name eth0 netns container1
# 配置 IP
ip netns exec container1 ip addr add 172.17.0.2/16 dev eth0
ip netns exec container1 ip link set eth0 up
# 連接到 bridge
ip link set veth1-host master docker0
ip link set veth1-host up
3. Cgroups 資源控制
3.1 Cgroups 架構
flowchart TB
subgraph CgroupV2["Cgroup v2 (統一層級)"]
Root[/ 根 cgroup]
System[system.slice]
User[user.slice]
Docker[docker]
Root --> System
Root --> User
Root --> Docker
Container1[container1]
Container2[container2]
Docker --> Container1
Docker --> Container2
end
subgraph Controllers["資源控制器"]
CPU[cpu<br/>CPU 配額]
Memory[memory<br/>記憶體限制]
IO[io<br/>磁盤 I/O]
PID[pids<br/>進程數量]
Cpuset[cpuset<br/>CPU 綁定]
end
Container1 --> Controllers
Container2 --> Controllers
3.2 Cgroup 控制器詳解
# Cgroup v2 目錄結構
/sys/fs/cgroup/
├── cgroup.controllers # 可用控制器
├── cgroup.subtree_control # 子樹啟用的控制器
├── docker/
│ ├── container1/
│ │ ├── cgroup.procs # 進程列表
│ │ ├── cpu.max # CPU 配額
│ │ ├── cpu.weight # CPU 權重
│ │ ├── memory.max # 記憶體上限
│ │ ├── memory.current # 當前使用
│ │ ├── io.max # I/O 限制
│ │ └── pids.max # 最大進程數
│ └── container2/
│ └── ...
3.3 CPU 限制實現
// 設置 CPU 配額
// cpu.max: 格式 "$MAX $PERIOD"
// 例如: "100000 100000" 表示 100% CPU (每 100ms 可用 100ms)
// 例如: "50000 100000" 表示 50% CPU (每 100ms 可用 50ms)
// Docker --cpus=0.5 的實現
echo "50000 100000" > /sys/fs/cgroup/docker/container1/cpu.max
// Docker --cpu-shares=512 的實現 (相對權重)
echo "50" > /sys/fs/cgroup/docker/container1/cpu.weight // 默認 100
3.4 Memory 限制實現
// 設置記憶體限制
// memory.max: 最大使用量(超過會 OOM)
// memory.high: 高水位線(超過會被限制)
// memory.low: 低水位線(盡量保證)
// Docker --memory=512m 的實現
echo "536870912" > /sys/fs/cgroup/docker/container1/memory.max
// Docker --memory-swap=1g 的實現
// Swap = memory-swap - memory
echo "1073741824" > /sys/fs/cgroup/docker/container1/memory.swap.max
3.5 OOM Killer 配置
// 當容器記憶體超限時,觸發 OOM Killer
// memory.oom.group: 是否殺死整個 cgroup
// Docker --oom-kill-disable 的實現
// 禁用 OOM Killer(危險操作)
// Docker --oom-score-adj 的實現
// 調整 OOM 優先級 (-1000 到 1000)
echo "500" > /proc/PID/oom_score_adj
4. UnionFS 與鏡像分層
4.1 分層存儲原理
flowchart TB
subgraph Image["Docker 鏡像(只讀)"]
Layer1[Layer 1: Base OS<br/>ubuntu:22.04]
Layer2[Layer 2: apt install python]
Layer3[Layer 3: pip install flask]
Layer4[Layer 4: COPY app.py]
end
subgraph Container["容器(讀寫)"]
RWLayer[Container Layer<br/>讀寫層]
end
Layer1 --> Layer2 --> Layer3 --> Layer4
Layer4 --> RWLayer
subgraph Union["UnionFS 視圖"]
MergedView[統一文件系統視圖<br/>/bin, /lib, /app.py, ...]
end
RWLayer --> MergedView
4.2 OverlayFS 結構
flowchart LR
subgraph OverlayFS["OverlayFS 掛載點"]
Merged[merged/<br/>統一視圖]
end
subgraph Layers["層級"]
Upper[upper/<br/>容器讀寫層]
Work[work/<br/>工作目錄]
Lower1[lower1/<br/>鏡像層 1]
Lower2[lower2/<br/>鏡像層 2]
LowerN[lowerN/<br/>鏡像層 N]
end
Merged --> Upper
Merged --> Lower1
Merged --> Lower2
Merged --> LowerN
Upper --> Work
# OverlayFS 掛載示例
mount -t overlay overlay \
-o lowerdir=/lower1:/lower2:/lower3,\
upperdir=/upper,\
workdir=/work \
/merged
# Docker 實際掛載
# /var/lib/docker/overlay2/<container-id>/merged
4.3 Copy-on-Write 機制
sequenceDiagram
participant App as 應用程式
participant Upper as 讀寫層 (upper)
participant Lower as 只讀層 (lower)
Note over App,Lower: 讀取操作
App->>Upper: 讀取 /etc/config
alt 文件在 upper 層
Upper->>App: 返回 upper 層文件
else 文件不在 upper 層
Upper->>Lower: 向下查找
Lower->>App: 返回 lower 層文件
end
Note over App,Lower: 寫入操作 (Copy-on-Write)
App->>Upper: 修改 /etc/config
alt 文件已在 upper 層
Upper->>Upper: 直接修改
else 文件在 lower 層
Upper->>Lower: 複製文件到 upper
Upper->>Upper: 修改副本
end
Note over App,Lower: 刪除操作 (Whiteout)
App->>Upper: 刪除 /etc/config
Upper->>Upper: 創建 whiteout 文件<br/>.wh.config
Note over Upper: 遮蓋 lower 層文件
4.4 鏡像內容尋址
# 鏡像 ID = SHA256(config JSON)
# 層 ID = SHA256(層內容)
/var/lib/docker/
├── image/overlay2/
│ ├── imagedb/
│ │ └── content/sha256/
│ │ └── abc123... # 鏡像配置 JSON
│ ├── layerdb/
│ │ └── sha256/
│ │ ├── layer1.../
│ │ │ ├── diff # 內容 SHA256
│ │ │ ├── cache-id # 對應 overlay2 目錄
│ │ │ └── parent # 父層 ID
│ │ └── layer2.../
│ └── repositories.json # 鏡像名稱映射
└── overlay2/
├── abc123.../
│ ├── diff/ # 層內容
│ ├── link # 短 ID
│ └── lower # 下層引用
└── l/ # 符號鏈接目錄
5. 容器運行時
5.1 運行時層次
flowchart TB
subgraph HighLevel["高級運行時"]
Dockerd[dockerd]
Containerd[containerd]
CRI[CRI-O]
end
subgraph LowLevel["低級運行時 (OCI)"]
Runc[runc]
Crun[crun]
Kata[Kata Containers]
gVisor[gVisor/runsc]
end
subgraph Kernel["Linux 內核"]
NS[Namespaces]
CG[Cgroups]
Seccomp[Seccomp]
Caps[Capabilities]
end
Dockerd --> Containerd
Containerd --> Runc
CRI --> Runc
Containerd --> Crun
Containerd --> Kata
Containerd --> gVisor
Runc --> NS
Runc --> CG
Runc --> Seccomp
Runc --> Caps
5.2 容器創建流程
sequenceDiagram
participant CLI as docker CLI
participant Dockerd as dockerd
participant Containerd as containerd
participant Shim as containerd-shim
participant Runc as runc
participant Container as 容器進程
CLI->>Dockerd: docker run nginx
Dockerd->>Containerd: 創建容器請求 (gRPC)
Containerd->>Containerd: 準備 rootfs
Containerd->>Containerd: 生成 OCI config.json
Containerd->>Shim: 啟動 shim 進程
activate Shim
Shim->>Runc: runc create
Runc->>Runc: 創建 Namespaces
Runc->>Runc: 設置 Cgroups
Runc->>Runc: 準備 rootfs (pivot_root)
Runc->>Container: 啟動容器進程
activate Container
Runc-->>Shim: 容器已創建
Shim-->>Containerd: 容器已啟動
Containerd-->>Dockerd: 成功
Dockerd-->>CLI: 容器 ID
Note over Shim,Container: Shim 成為容器的父進程<br/>允許 containerd 重啟而不影響容器
5.3 OCI 規範
// config.json - OCI 運行時規範
{
"ociVersion": "1.0.2",
"process": {
"terminal": false,
"user": {"uid": 0, "gid": 0},
"args": ["nginx", "-g", "daemon off;"],
"env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"],
"cwd": "/",
"capabilities": {
"bounding": ["CAP_NET_BIND_SERVICE"],
"effective": ["CAP_NET_BIND_SERVICE"]
},
"rlimits": [
{"type": "RLIMIT_NOFILE", "hard": 1024, "soft": 1024}
]
},
"root": {
"path": "rootfs",
"readonly": false
},
"hostname": "container",
"mounts": [
{"destination": "/proc", "type": "proc", "source": "proc"},
{"destination": "/dev", "type": "tmpfs", "source": "tmpfs"}
],
"linux": {
"namespaces": [
{"type": "pid"},
{"type": "network"},
{"type": "ipc"},
{"type": "uts"},
{"type": "mount"}
],
"resources": {
"memory": {"limit": 536870912},
"cpu": {"quota": 50000, "period": 100000}
},
"seccomp": {
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{"names": ["read", "write", "exit"], "action": "SCMP_ACT_ALLOW"}
]
}
}
}
6. Docker 網路模型
6.1 網路驅動類型
flowchart TB
subgraph Drivers["Docker 網路驅動"]
Bridge[bridge<br/>默認橋接網路]
Host[host<br/>共享主機網路]
None[none<br/>無網路]
Overlay[overlay<br/>跨主機網路]
Macvlan[macvlan<br/>MAC 地址直通]
IPvlan[ipvlan<br/>IP 層直通]
end
6.2 Bridge 網路實現
flowchart TB
subgraph Host["Host"]
subgraph HostNet["Host Network Stack"]
eth0[eth0<br/>192.168.1.100]
iptables[iptables<br/>NAT/MASQUERADE]
end
docker0[docker0 bridge<br/>172.17.0.1/16]
subgraph Container1["Container 1"]
veth1[eth0<br/>172.17.0.2]
end
subgraph Container2["Container 2"]
veth2[eth0<br/>172.17.0.3]
end
end
eth0 --> iptables
iptables --> docker0
docker0 --> veth1
docker0 --> veth2
# iptables NAT 規則(容器訪問外網)
iptables -t nat -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
# 端口映射規則(外部訪問容器)
# docker run -p 8080:80 nginx
iptables -t nat -A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
iptables -A DOCKER -d 172.17.0.2 -p tcp --dport 80 -j ACCEPT
6.3 Overlay 網路(跨主機)
flowchart TB
subgraph Host1["Host 1"]
C1[Container 1<br/>10.0.0.2]
VTEP1[VTEP<br/>VXLAN 封裝]
eth0_1[eth0<br/>192.168.1.10]
end
subgraph Host2["Host 2"]
C2[Container 2<br/>10.0.0.3]
VTEP2[VTEP<br/>VXLAN 封裝]
eth0_2[eth0<br/>192.168.1.20]
end
C1 --> VTEP1
VTEP1 --> eth0_1
eth0_1 --> |VXLAN over UDP:4789| eth0_2
eth0_2 --> VTEP2
VTEP2 --> C2
7. 存儲驅動詳解
7.1 存儲驅動對比
| 驅動 | 文件系統 | 優點 | 缺點 |
|---|---|---|---|
| overlay2 | ext4/xfs | 穩定、性能好 | 需要 d_type 支持 |
| devicemapper | 塊設備 | 不依賴文件系統 | 配置複雜、性能差 |
| btrfs | btrfs | 原生快照 | 需要 btrfs 文件系統 |
| zfs | zfs | 高級特性 | 記憶體佔用高 |
7.2 Overlay2 目錄結構
/var/lib/docker/overlay2/
├── l/ # 短 ID 符號鏈接
│ ├── ABC123 -> ../abc123.../diff
│ └── DEF456 -> ../def456.../diff
├── abc123.../ # 鏡像層
│ ├── diff/ # 層內容
│ │ ├── bin/
│ │ ├── etc/
│ │ └── usr/
│ ├── link # 短 ID
│ ├── lower # 父層引用
│ └── committed # 已提交標記
├── def456.../ # 容器層
│ ├── diff/ # 讀寫層
│ ├── link
│ ├── lower # 只讀層引用
│ ├── merged/ # 聯合掛載點
│ └── work/ # OverlayFS 工作目錄
└── backingFsBlockDev # 塊設備信息
8. Dockerfile 與鏡像構建
8.1 Dockerfile 指令
# 多階段構建示例
# Stage 1: 編譯
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server
# Stage 2: 運行
FROM alpine:3.18
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/server .
EXPOSE 8080
CMD ["./server"]
8.2 構建過程
sequenceDiagram
participant CLI as docker build
participant Daemon as dockerd
participant Builder as BuildKit
participant Registry as Registry
CLI->>Daemon: docker build -t myapp .
Daemon->>Builder: 解析 Dockerfile
loop 每一層
Builder->>Builder: 計算層緩存 key
alt 緩存命中
Builder->>Builder: 使用緩存層
else 緩存未命中
Builder->>Builder: 創建臨時容器
Builder->>Builder: 執行指令
Builder->>Builder: 提交為新層
end
end
Builder->>Builder: 合併元數據
Builder->>Daemon: 鏡像 ID
Daemon->>CLI: 構建完成
opt 推送鏡像
CLI->>Daemon: docker push
Daemon->>Registry: 上傳各層
end
8.3 BuildKit 優化
flowchart LR
subgraph Traditional["傳統構建"]
T1[Layer 1] --> T2[Layer 2] --> T3[Layer 3]
end
subgraph BuildKit["BuildKit 並行構建"]
B1[Layer 1]
B2[Layer 2]
B3[Layer 3]
B1 --> B4[Final]
B2 --> B4
B3 --> B4
end
# BuildKit 緩存掛載
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# BuildKit secret 掛載
RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret
# BuildKit SSH 掛載
RUN --mount=type=ssh \
git clone git@github.com:private/repo.git
總結
Docker 的核心技術棧:
| 層次 | 技術 | 作用 |
|---|---|---|
| 隔離 | Namespace | 進程、網路、文件系統隔離 |
| 限制 | Cgroups | CPU、記憶體、I/O 限制 |
| 存儲 | UnionFS | 鏡像分層、Copy-on-Write |
| 網路 | veth + bridge | 容器網路連通 |
| 安全 | Seccomp + Capabilities | 系統調用限制 |
| 規範 | OCI | 容器標準化 |
參考資料
本文基於 Docker 24.x 版本分析。