mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
765 字
2 分钟
Docker 容器启动流程:从镜像到运行
2023-03-12

你执行 docker run nginx,一秒后容器就跑起来了。但这一秒里发生了什么?镜像是怎么变成运行中的进程的?为什么容器里的进程看不到宿主机上的其他进程?为什么一个容器只能用 2G 内存,就算宿主机有 64G?本文深入剖析 Docker 容器启动的完整流程。

Docker 架构概览#

flowchart TB subgraph 客户端 A[Docker CLI] end subgraph Docker Host B[Docker Daemon] --> C[Containerd] C --> D[Containerd-shim] D --> E[runc] E --> F[容器进程] G[镜像存储] --> B H[网络] --> B I[卷] --> B end subgraph 内核 J[namespace] K[cgroup] L[UnionFS] end A -->|API| B E --> J E --> K G --> L

一、Docker 组件架构#

1.1 组件职责#

组件职责
docker CLI命令行客户端
dockerdDocker 守护进程,API 入口
containerd容器运行时管理
containerd-shim容器生命周期管理
runc实际创建容器的工具

1.2 调用链路#

sequenceDiagram participant C as docker CLI participant D as dockerd participant T as containerd participant S as shim participant R as runc participant K as Kernel C->>D: docker run nginx D->>D: 解析参数 D->>T: 创建容器请求 T->>T: 准备 bundle T->>S: 启动 shim S->>R: runc create R->>K: 创建 namespace R->>K: 设置 cgroup R->>K: 挂载文件系统 R-->>S: 容器创建完成 S->>R: runc start S-->>T: 容器运行 T-->>D: 返回容器 ID D-->>C: 显示输出

二、镜像加载#

2.1 镜像结构#

Docker 镜像由多个只读层组成:

flowchart TB subgraph 镜像层 A[nginx:latest] B[基础层: Debian] C[中间层: 依赖] D[应用层: nginx] end subgraph 容器层 E[可写层 R/W] end A --> B --> C --> D --> E

2.2 联合文件系统#

# 查看镜像层
docker image inspect nginx
# 输出
"Layers": [
"sha256:abc123...", # 基础层
"sha256:def456...", # 中间层
"sha256:789abc..." # 应用层
]

UnionFS 工作原理

flowchart LR subgraph OverlayFS A[upper<br/>可写层] --> D[合并视图] B[lower1<br/>只读层] --> D C[lower2<br/>只读层] --> D end E[写入文件] --> A D --> F[读取文件]

2.3 镜像拉取流程#

sequenceDiagram participant C as Client participant D as Docker Daemon participant R as Registry C->>D: docker pull nginx D->>R: GET /v2/nginx/manifests/latest R-->>D: 返回 manifest D->>D: 解析 manifest,获取层列表 loop 每一层 D->>R: GET /v2/nginx/blobs/sha256:xxx R-->>D: 返回层数据 D->>D: 解压并存储层 end D-->>C: Pull complete

三、容器创建流程#

3.1 docker run 执行流程#

flowchart TB A["docker run nginx"] --> B[检查本地镜像] B --> C{镜像存在?} C -->|否| D[拉取镜像] C -->|是| E[创建容器配置] D --> E E --> F[生成容器 ID] F --> G[创建文件系统层] G --> H[准备 OCI bundle] H --> I[调用 containerd] I --> J[创建 namespace] J --> K[设置 cgroup] K --> L[挂载文件系统] L --> M[执行入口命令] M --> N[容器运行]

3.2 OCI Bundle#

# OCI bundle 目录结构
nginx-bundle/
config.json # 容器配置
rootfs/ # 根文件系统
bin/
etc/
usr/
...

config.json 示例:

{
"ociVersion": "1.0.2",
"process": {
"terminal": true,
"user": { "uid": 0, "gid": 0 },
"args": ["nginx", "-g", "daemon off;"],
"env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"],
"cwd": "/"
},
"root": { "path": "rootfs", "readonly": false },
"linux": {
"uidMappings": [{ "containerID": 0, "hostID": 1000, "size": 1 }],
"gidMappings": [{ "containerID": 0, "hostID": 1000, "size": 1 }],
"namespaces": [
{ "type": "pid" },
{ "type": "network" },
{ "type": "ipc" },
{ "type": "uts" },
{ "type": "mount" },
{ "type": "user" }
],
"resources": {
"memory": { "limit": 536870912 },
"cpu": { "quota": 50000, "period": 100000 }
}
}
}

四、命名空间隔离#

4.1 Namespace 类型#

类型隔离内容内核版本
PID进程 ID2.6.24
NET网络栈2.6.29
IPCSystem V IPC2.6.19
MNT挂载点2.4.19
UTS主机名、域名2.6.19
USER用户、用户组3.8
CGROUPCgroup 根目录4.6

4.2 PID Namespace#

// 创建 PID namespace
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
int child_func(void *arg) {
printf("Child PID: %d\n", getpid()); // PID = 1
return 0;
}
int main() {
char stack[1024*1024];
pid_t pid = clone(child_func, stack + sizeof(stack),
CLONE_NEWPID | SIGCHLD, NULL);
printf("Parent PID: %d, Child PID: %d\n", getpid(), pid);
wait(NULL);
return 0;
}

PID 映射:

flowchart TB subgraph 主机 PID Namespace A[PID 1000: docker run] B[PID 1234: nginx 进程] end subgraph 容器 PID Namespace C[PID 1: nginx 进程] end B -.->|映射| C

4.3 Network Namespace#

flowchart TB subgraph 主机网络 A[eth0: 192.168.1.100] B[docker0: 172.17.0.1] end subgraph 容器网络 Namespace C[eth0: 172.17.0.2] D[lo: 127.0.0.1] end A --> B B <-->|veth pair| C
# 查看容器网络 namespace
docker inspect nginx | jq '.[0].NetworkSettings.NetworkId'
# 使用 nsenter 进入网络 namespace
docker inspect nginx | jq '.[0].State.Pid'
nsenter -t <PID> -n ip addr

4.4 User Namespace#

用户 ID 映射:

flowchart LR subgraph 容器 A[root UID 0] end subgraph 主机 B[普通用户 UID 100000] end A -.->|映射| B
# 配置用户 namespace
# /etc/subuid 和 /etc/subgid
user:100000:65536

五、Cgroup 资源限制#

5.1 Cgroup 子系统#

子系统功能
cpuCPU 时间分配
cpuacctCPU 使用统计
cpusetCPU 核心绑定
memory内存限制
blkio块设备 IO
devices设备访问控制
freezer暂停/恢复进程

5.2 内存限制#

# 运行容器时限制内存
docker run -d --memory=512m --memory-swap=1g nginx
# 查看容器 cgroup
docker inspect nginx | jq '.[0].HostConfig.CgroupParent'
# 直接查看 cgroup 配置
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.usage_in_bytes

5.3 CPU 限制#

# 限制 CPU 使用
docker run -d --cpus=1.5 --cpu-shares=512 nginx
# 查看配置
cat /sys/fs/cgroup/cpu/docker/<container_id>/cpu.cfs_quota_us
cat /sys/fs/cgroup/cpu/docker/<container_id>/cpu.cfs_period_us

5.4 Cgroup 实现原理#

// 内核 cgroup 结构(简化)
struct cgroup {
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
struct list_head sets;
struct cgroup_root *root;
};
// cgroup 子系统
struct cgroup_subsys {
struct cgroup_subsys_state *(*css_alloc)(struct cgroup *cgrp);
int (*css_online)(struct cgroup_subsys_state *css);
void (*css_offline)(struct cgroup_subsys_state *css);
void (*css_free)(struct cgroup_subsys_state *css);
// ...
};

六、容器文件系统#

6.1 OverlayFS#

Docker 默认使用 OverlayFS 作为存储驱动:

# 查看存储驱动
docker info | grep "Storage Driver"
# 查看 OverlayFS 挂载
mount | grep overlay

OverlayFS 结构:

容器文件系统挂载点:
/var/lib/docker/overlay2/<id>/merged/
组成:
lowerdir (只读层,可以有多个)
/var/lib/docker/overlay2/l1/diff
/var/lib/docker/overlay2/l2/diff
upperdir (可写层)
/var/lib/docker/overlay2/<id>/diff
workdir (工作目录)
/var/lib/docker/overlay2/<id>/work

6.2 写时复制(CoW)#

sequenceDiagram participant C as 容器进程 participant O as OverlayFS participant L as Lower 层 participant U as Upper 层 C->>O: 读取 /etc/nginx/nginx.conf O->>L: 从 lower 层读取 L-->>C: 返回内容 C->>O: 写入 /etc/nginx/nginx.conf O->>O: CoW: 复制到 upper 层 O->>U: 写入 upper 层 U-->>C: 写入成功 C->>O: 再次读取 O->>U: 从 upper 层读取 U-->>C: 返回修改后内容

6.3 存储驱动对比#

存储驱动特点适用场景
overlay2高性能,推荐Linux 4.x+
aufs历史遗留旧系统
devicemapper块设备级企业环境
btrfs原生快照Btrfs 文件系统
zfs高级特性ZFS 文件系统

七、网络配置#

7.1 Docker 网络模式#

模式说明
bridge默认模式,容器连接 docker0 网桥
host共享主机网络栈
none无网络
container共享其他容器网络
overlay跨主机网络

7.2 Bridge 网络流程#

sequenceDiagram participant D as Docker participant N as netlink participant B as docker0 participant V as veth pair participant C as 容器 D->>N: 创建 veth pair N->>V: veth0 (主机端) N->>V: veth1 (容器端) D->>B: 将 veth0 加入 docker0 D->>C: 将 veth1 移入容器网络 namespace D->>C: 配置 IP 地址 D->>N: 设置路由规则 Note over B,C: 网络连通

7.3 网络配置示例#

# 创建自定义网络
docker network create --driver bridge --subnet=172.20.0.0/16 mynet
# 容器连接网络
docker run -d --network=mynet --ip=172.20.0.10 nginx
# 查看网络
docker network inspect mynet

八、容器生命周期#

8.1 状态转换#

stateDiagram-v2 [*] --> Created: docker create Created --> Running: docker start Running --> Paused: docker pause Paused --> Running: docker unpause Running --> Stopped: docker stop Stopped --> Running: docker start Running --> Restarting: docker restart Restarting --> Running Stopped --> [*]: docker rm Created --> [*]: docker rm

8.2 信号处理#

# 发送信号到容器
docker kill --signal=SIGTERM nginx
# 优雅停止(发送 SIGTERM,等待 10 秒)
docker stop nginx
# 强制停止(发送 SIGKILL)
docker kill nginx

Dockerfile 信号处理:

# 使用 exec 形式,让进程成为 PID 1
CMD ["nginx", "-g", "daemon off;"]
# shell 形式会导致信号无法传递
# CMD nginx -g "daemon off;" # 不推荐

8.3 健康检查#

# Dockerfile 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
# 查看健康状态
docker inspect --format='{{.State.Health.Status}}' nginx

九、安全机制#

9.1 能力机制(Capabilities)#

# 查看容器能力
docker inspect nginx | jq '.[0].HostConfig.CapAdd'
docker inspect nginx | jq '.[0].HostConfig.CapDrop'
# 添加/删除能力
docker run --cap-add=NET_ADMIN --cap-drop=CHOWN nginx

常用能力:

能力说明
CAP_NET_ADMIN网络配置
CAP_SYS_ADMIN系统管理
CAP_CHOWN修改文件所有者
CAP_KILL发送信号
CAP_SETUID/GID切换用户/组

9.2 Seccomp#

# 使用自定义 seccomp 配置
docker run --security-opt seccomp=profile.json nginx
# 禁用 seccomp(不推荐)
docker run --security-opt seccomp=unconfined nginx

9.3 AppArmor/SELinux#

# AppArmor 配置
docker run --security-opt apparmor=docker-default nginx
# SELinux 配置
docker run --security-opt label=level:s0:c100,c200 nginx

十、调试与分析#

10.1 查看容器详情#

# 容器详细信息
docker inspect nginx
# 容器进程
docker top nginx
# 容器资源使用
docker stats nginx
# 容器日志
docker logs -f nginx

10.2 进入容器#

# 使用 docker exec
docker exec -it nginx /bin/bash
# 使用 nsenter
PID=$(docker inspect -f '{{.State.Pid}}' nginx)
nsenter -t $PID -m -u -i -n -p /bin/bash

10.3 导出容器信息#

# 导出容器文件系统
docker export nginx > nginx.tar
# 导出镜像
docker save nginx > nginx-image.tar
# 查看镜像层
docker history nginx

关键要点#

  1. 镜像:分层存储、UnionFS(Union File System)、写时复制
  2. 隔离namespace 实现进程、网络、用户等隔离
  3. 限制cgroup(Control Group) 实现 CPU、内存、IO 限制
  4. 文件系统:OverlayFS 联合挂载
  5. 网络:veth pair + 网桥实现容器互联
  6. 安全:Capabilities、Seccomp、AppArmor

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

Docker 容器启动流程:从镜像到运行
https://blog.souloss.com/posts/principles/principles-docker-container-startup/
作者
Souloss
发布于
2023-03-12
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

相关文章 智能推荐
1
容器完整流程:docker run 背后
容器运行时 当你执行 docker run nginx 时,背后发生了什么?本章完整追踪从 Docker CLI 到容器进程启动的每一步——镜像拉取、OCI Bundle 生成、shim 启动、runc 创建、Namespace/Cgroup/OverlayFS 配置、容器进程执行,让你对 docker run 的每一步都了如指掌。
2
容器全景:从 chroot 到 OCI
容器运行时 从 1979 年的 chroot 到 2020 年代的 OCI 标准,容器技术走过了四十年的演进之路。本章建立容器运行时的全景认知——三大内核基石(Namespace/Cgroup/OverlayFS)、OCI 规范体系、Docker/containerd/runc 的架构关系,让你在深入细节前先看到完整的地图。
3
系列导读
容器运行时 本系列从 Linux 内核的 Namespace、Cgroup、OverlayFS 出发,深入 OCI 规范、runc 源码、containerd 架构,再到容器安全、沙箱运行时、网络、存储、镜像构建、Wasm 容器,最后综合实战构建一个迷你容器运行时——从「会用 Docker」到「理解容器运行时的每一行代码」,每章配有可运行的代码示例与架构图,让你从容器用户进阶到容器运行时工程师。
4
Docker 从入门到实践
工具 系统介绍 Docker 容器技术的完整使用指南——从镜像管理、容器操作、数据卷、网络配置到 Dockerfile 最佳实践与 Docker Compose 服务编排,涵盖云原生时代容器化的核心技能。
5
为什么 Docker 使用分层镜像
技术科普 深入解析 Docker 镜像分层设计的原理,理解 UnionFS、OverlayFS 如何实现高效的镜像存储与分发。