Docker 深度剖析:從 Linux 容器技術到鏡像分層存儲的完整架構解析


執行摘要

Docker 徹底改變了軟體部署方式,實現了「Build Once, Run Anywhere」的願景。本文將深入剖析 Docker 的核心技術:

  • Linux 容器基礎:Namespace 隔離與 Cgroups 資源限制
  • 鏡像存儲:UnionFS 分層文件系統與內容尋址
  • 容器運行時:containerd、runc 與 OCI 規範
  • 網路模型:bridge、host、overlay 網路實現
  • 存儲驅動:overlay2、devicemapper、btrfs

目錄

  1. Docker 架構總覽
  2. Linux Namespace 隔離機制
  3. Cgroups 資源控制
  4. UnionFS 與鏡像分層
  5. 容器運行時
  6. Docker 網路模型
  7. 存儲驅動詳解
  8. 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 存儲驅動對比

驅動文件系統優點缺點
overlay2ext4/xfs穩定、性能好需要 d_type 支持
devicemapper塊設備不依賴文件系統配置複雜、性能差
btrfsbtrfs原生快照需要 btrfs 文件系統
zfszfs高級特性記憶體佔用高

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進程、網路、文件系統隔離
限制CgroupsCPU、記憶體、I/O 限制
存儲UnionFS鏡像分層、Copy-on-Write
網路veth + bridge容器網路連通
安全Seccomp + Capabilities系統調用限制
規範OCI容器標準化

參考資料


本文基於 Docker 24.x 版本分析。