3 Docker核心原理解读
本章将对Docker的各个层面进行深度剖析,从Docker背后的内核知识开始,根据Docker架构讲解Docker的各个模块,最终分析与用户息息相关的网络,数据,安全等问题。
3.1 Docker背后的内核知识
Docker容器本质上是宿主机上的进程,Docker通过namespace实现了资源隔离,通过cgroups实现了资源限制,通过写时复制机制实现了高效的文件操作。
3.1.1 namespace 资源隔离
Docker 实现资源隔离的核心依托 Linux namespace 机制,其核心思想是通过系统调用将进程及其资源划分到独立的“命名空间”中,使不同 namespace 下的进程相互感知不到,从而达成容器级别的隔离。
隔离的核心逻辑
容器隔离的实现是分步递进的:
- 从基础的
chroot命令入手,实现文件系统的根目录隔离,限定进程可见的文件范围; - 扩展至网络层面,通过网络隔离让容器拥有独立的 IP、端口、路由,实现网络设备与端口的隔离;
- 强化进程间隔离,通过 PID 隔离让容器内进程拥有独立的进程编号空间,与宿主机进程隔离;
- 完善权限与身份隔离,通过用户组隔离实现用户与用户组的权限独立,避免权限越界。
Linux 内核 6 大核心 namespace
Linux 内核通过 6 种 namespace 系统调用参数,完整支撑容器的 6 大维度隔离,适配 Linux 内核 3.8 及以上版本:
| namespace | 系统调用参数 | 隔离内容 |
|---|---|---|
| UTS | CLONE_NEWUTS | 主机名与域名(网络标识) |
| IPC | CLONE_NEWIPC | 信号量、消息队列、共享内存(进程间通信) |
| PID | CLONE_NEWPID | 进程编号(进程生命周期隔离) |
| Network | CLONE_NEWNET | 网络设备、网络栈、端口(网络环境隔离) |
| Mount | CLONE_NEWNS | 挂载点(文件系统挂载隔离) |
| User | CLONE_NEWUSER | 用户和用户组(权限隔离) |
核心目的与效果
Linux 实现 namespace 的主要目的是实现轻量级虚拟化(容器)服务。同一 namespace 下的进程可相互感知、协同工作,而对外部 namespace 的进程一无所知。这种隔离机制让容器中的进程产生“错觉”,仿佛独立运行在专属的系统环境中,最终实现容器与宿主机、容器与容器之间的独立隔离效果。
3.2 Docker架构概览
Docker使用了传统的client-server架构模式,用户通过Dockers client与Dockers daemon建立通信,并将请求发送给后者

上图可以看出,Docker daemon是Docker架构中主要的用户接口。它提供了API Server用于来接受来自Dockers client的请求,其后根据不同的请求分发给Docker daemon不同模块执行相应的工作。
3.2.1 Docker daemon
Docker daemon是Docker最核心的后台进程,负责响应来自Docker client的请求,然后将这些请求翻译成系统调用完成容器管理操作。该进程会在后台
启动一个API Server,负责接受由Dockers client发送的请求,接收到请求通过Docker daemon分发调度,再由具体的函数来执行。
3.2.2 Dockers client
是一个泛称,用来向Docker daemon发起请求,执行相应的容器管理操作。他既可以是命令行工具dockers,也可以是任何遵循了Dockers API的客户端。目前社区维护着Docker client种类非常丰富,涵盖了包括大多数语言。
3.2.3 镜像管理
Docker通过distribution,registry,layer等模块实现了镜像的管理,我们将这些模块称为镜像管理。这一功能通过graph组件来完成的。
1.distribution负责与Docker registry交互,上传下载镜像以及存储与v2 registry有关的元数据。
2.registry模块负责与Docker registry有关的身份验证、镜像查找、镜像验证以及管理registry mirror等交互操作。
3.image模块负责与镜像元数据有关的存储、查找,镜像层的索引、查找以及镜像tar包有关的导入、导出等操作。
4.reference负责存储本地所有镜像的repository和tag名,并维护与镜像ID之间的映射关系。
5.layer模块负责与镜像层和容器层元数据有关的增删查改,并负责将镜像层的增删查改操作映射到实际存储镜像层文件系统的graphdriver模块。
3.3 client和daemon
本章节主要了解docker命令的两种模式:client模式和daemon模式
3.3.1 client模式
什么是Docker模式?我们知道docker命令对应的源文件是docker/docker.go,使用方式如下:

OPTIONS参数称为flag,任何时候执行执行一个docker命令,Docker都需要解析这些flag,然后按照用户声明的COMMAND指向子命令对应的操作
如果子命令为daemon,Dockers就会创建一个运行在宿主机的daemon进程,即执行daemon模式,其余子命令都会执行client模式。处于client模式下的dockers命令工作流程包含如下几个步骤:
- 解析flag信息
- 创建client实例
- 执行具体命令:从命令映射到对应方法:cli主要通过反射机制,从用户输入的命令得到匹配的执行方法。执行对应方法,发起请求:步骤如下:解析传入的参数,并针对参数进行配置处理,获取与Dockers daemon通信所需要的认证配置信息。获取命令业务类型,给Dockers daemon发送POST、GET等请求。最后就是读取来自Dockers daemon的返回结果。
3.3.2 daemon模式
上一节提到了Dockers运行时如果使用docker daemon子命令就会运行Dockers daemon。本节主要重点讲解Dockers daemon在启动过程中所做的工作。一旦dockers进入了daemon模式,剩下的初始化和启动工作就都由Dockers的dockers/daemon.go来完成。
Docker daemon通过一个server模块接受来自clien的请求,然后根据请求类型,交由具体的方法去执行。因此daemon首先要启动并初始化这个server另一方面Dockers进程需要初始化一个daemon对象来负责处理server接收到的请求。
3.3.3 从client到daemon
前面小节描述了Dockers client和daemon启动并初始化的全过程,接下来的问题就是一个已经在运行的daemon是如何响应并处理来自client的请求呢?
Docker 中已运行的 daemon 响应并处理 client 请求,以 docker run 命令为核心流程展开,整体分为发起请求、创建容器、启动容器与最后一步四个核心阶段,具体逻辑如下:
- 发起请求:
docker run命令开启运行,客户端 Docker 进入 client 模式,完成新 client 初始化后,通过反射机制找到CmdRun方法;CmdRun解析用户参数后,向 daemon 发起POST /containers/create(创建容器)和POST /containers/容器ID/start(启动容器)两个请求,此时 client 端任务结束。daemon 内部维护的 API Server 遵循“约定大于配置”原则响应请求,L6 及之前版本中,daemon 会将容器创建的 Job 交给 Docker Engine 承接,L6.10 版本则按 1.10 版本方式讲解,无需关注版本差异。 - 创建容器:Docker daemon 接收创建请求后,无需真正创建 Linux 容器,仅解析 client 提交的 POST 表单,在 daemon 中新建
container对象;container实体包含Id、Created、Path、Config等核心属性,其中Config定义容器核心配置,daemon属性则让 container 可感知并管理 daemon 进程信息。完成容器信息封装后,daemon 会通过 Response 将结果返回给 client,client 随即发起 start 请求。 - 启动容器:API Server 接收 start 请求后,通知 daemon 执行容器启动操作,对应
daemon/start.go流程;1.7 版本后 Docker 拆分 client 与 daemon 端文件,请求处理逻辑在 daemon 端对应daemon/[请求名].go文件中,可根据关键字定位。此时容器的NetworkSettings、ImageID等参数已在创建阶段赋值,daemon 会在start.go中直接执行container.Start,在宿主机上挂载对应容器;创建容器时无需创建 namespace、配置 cgroup 等 rootfs 相关操作,由containerMonitor(daemon 设为自身 supervisor)负责,containerMonitor.Start实际执行containerMonitor.daemon.Run(container...)。 - 最后一步:daemon 完成容器准备工作后,执行
Run操作,将核心任务交给与容器驱动层交互的execDriver(具体驱动由container决定)完成。execDriver是 daemon 核心组件,封装了对 namespace、cgroup 等 OS 资源的操作,Docker 中execDriver默认实现为native(依赖 libcontainer);daemon 向execDriver提供command(容器配置信息集合)、pipes(标准输入输出重定向)、stdout/stderr回调方法三大参数,等待其返回结果即可完成容器创建。
此外,可通过 docker/container/got 追踪命令运行,L1.0 版本总结了关键追踪路径:docker/daemon/run.go 是命令起始,创建的 clientCli 对应 api/client/cli.go;api/[请求名].go 定义命令触发的 HTTP 请求,api/server/route.go 定义请求对应处理函数,每个请求处理函数对应 daemon/[xxx].go 文件;containerMonitor 是 daemon 负责执行操作的进程,container 对象对应 container/container_unix.go,一般由 execDriver 执行具体操作,最终由底层模块处理。
3.4 libcontainer
说到底,容器是一个与宿主机系统共享内核但是与系统中其他进程资源相互隔离的执行环境,Docker通过对namespaces,cgroups,capabilities以及文件系统的管理和分配来隔离出一个上述环境,这就是Docker容器。
execdriver首先要完成的工作是拿到了Dockers daemon提交的conmmand信息之后生成一份专门的容器配置信息。然后会加载一个预定义的容器配置模板container然后在模板中添加来自command信息。等到上述容器配置模板container中所有项都按照command提供的内容调好之后,一份该容器专属的容器配置就生成好了。注意小写的container其实是一个存储配置信息的对象。Container才是libcontainer里的容器对象。
3.5 Docker镜像管理
可以使用libcontainer快速构建起应用的运行环境,但是当需要进行容器迁移的时或者是对容器的运行环境全盘打包的时候,libcontainer就束手无策了。所以就有了Dockers镜像
3.5.1 什么是Docker镜像
Docker镜像是一个只读的Dockers容器模板,含有启动Dockers容器所需的文件系统结构以及其内容。因此是启动一个Docker容器的基础。Docker镜像是Dockers容器的静态视角,Dockers容器是Dockers镜像的运行状态。
Docker 镜像核心概念笔记
Docker 镜像本质是只读的容器模板,包含启动容器所需的文件系统结构与配置,构成容器的静态运行环境 rootfs;容器则是镜像的运行状态,二者是静态与动态的对应关系。
- rootfs 与文件系统挂载
- rootfs 是容器启动时内部可见的根文件系统,包含类 Unix 系统的典型目录(如
/dev、/bin、/etc等)及运行所需工具与配置。 - Docker 沿用 Linux 内核启动逻辑,将 rootfs 设为只读模式,再通过联合挂载(union mount)在其上层挂载一个读写层;文件修改仅发生在读写层,不会覆写下层只读内容,旧版本文件会被新版本隐藏。
- 镜像核心设计特点
- 分层结构:镜像由多个只读镜像层组成,修改仅作用于最上层读写层,
docker commit时仅保存变更内容,实现跨镜像共享层,大幅节省存储空间。 - 写时复制(Copy-on-Write):多容器共享同一镜像时,无需复制完整镜像文件,仅在文件修改时将变更写入读写层,减少磁盘占用与容器启动时间。
- 内容寻址存储(1.10+ 版本):基于文件内容计算哈希值作为镜像层唯一标识,替代随机 UUID,提升数据完整性校验、安全性与层共享能力,相同内容的层可跨镜像复用。
- 联合挂载:将多个文件系统挂载到同一挂载点,整合为统一视图(如 aufs/overlay 等存储驱动实现);DeviceMapper 等驱动则通过快照技术实现分层效果,无联合挂载概念。
- 容器文件系统组织
运行中的容器文件系统由三部分通过联合挂载整合为一层,对容器进程透明:
- 只读层(read-only layer):镜像本身的分层内容。
- init-layer:存放容器配置文件(如
/etc/hosts、/etc/resolv.conf)。 - 读写层(read-write layer)+ volumes:容器运行时的文件变更与数据卷挂载,实现数据持久化与临时修改。
3.5.2 Docker镜像关键概念
1.registry
当使用dockers run命令启动一个容器时,从哪获取需要的镜像呢?答案是,如果头一次基于某个镜像启动容器,宿主机上并不存在需要的镜像,那么Docker将从registry中下载该镜像并保存到宿主机,否则直接从宿主机镜像完成启动。
registry用以保存Docker镜像,其中包括镜像层次结构和关于镜像的元数据,可以将registry简答地想象成类似于Git仓库之类的实体。
用户可以在自己的数据中心搭建私有的 registry,也可以使用 Docker 官方的公用 registry 服务,即 Docker Hub。它是由 Docker 公司维护的一个公共镜像仓库,供用户下载使用。Docker Hub 中有两种类型的仓库,即用户仓库(user repository)与顶层仓库(top-level repository)。用户仓库由普通的 Docker Hub 用户创建,顶层仓库则由 Docker 公司负责维护,提供官方版本镜像。理论上,顶层仓库中的镜像经过 Docker 公司验证,被认为是架构良好且安全的。
2.repository
repository 即由具有某个功能的 Docker 镜像的所有迭代版本构成的镜像组。由上文可知,registry 由一系列经过命名的 repository 组成,repository 通过命名规范对用户仓库和顶层仓库进行组织。
用户仓库的命名由用户名和 repository 名两部分组成,中间以 “/” 隔开,即 username/repository_name 的形式,repository 名通常表示镜像所具有的功能,如 ansible/ubuntu14.04-ansible;而顶层仓库则只包含 repository 名的部分,如 ubuntu。
读者也许会产生疑问,通常将 ubuntu 视为镜像名称,这里却解释为 repository,那么 repository 和镜像之间是什么关系呢?事实上,repository 是一个镜像集合,其中包含了多个不同版本的镜像,使用标签进行版本区分,如 ubuntu:14.04、ubuntu:12.04 等,它们均属于 ubuntu 这个 repository。
一言以蔽之,registry 是 repository 的集合,repository 是镜像的集合。
3.manifest
manifest主要用于存在于registry中作为Dockers镜像的元数据文件,在push,pull,save,load中作为镜像结构和基础信息的描述文件。在镜像被pull或load到Dockers宿主机时,manifest被转换成本地的镜像配置文件config
3.5.3 Docker镜像构建操作
Docker 镜像的管理主要分为构建与分发两大核心环节:
commit 镜像:基于运行中的容器,将其读写层的变更内容打包为新的只读镜像层,生成一个新镜像。这种方式适合快速保存容器状态,但不利于镜像的复用和版本管理。
build 构建镜像:通过编写 Dockerfile 定义镜像构建的完整流程,执行 docker build 命令自动化完成镜像构建。它是生产环境中推荐的构建方式,具备可重复性、可维护性和可追溯性,能清晰定义镜像的分层结构与依赖关系。
3.5.4 Dockers镜像的分发方法
pull 镜像:从 registry(如 Docker Hub 或私有 registry)拉取指定镜像到本地,若镜像存在多个分层,会自动下载并合并所有依赖层,实现镜像的本地部署。
push 镜像:将本地构建或修改后的镜像上传至 registry,实现镜像的共享与分发,便于团队协作或多环境部署。
docker export 命令导出容器:将运行中的容器文件系统导出为一个 tar 包,可用于容器的备份与迁移,但会丢失镜像的分层信息和元数据。
docker save 命令保存镜像:将一个或多个镜像完整保存为 tar 包,保留镜像的分层结构、标签和元数据,适合镜像的离线备份与迁移,后续可通过 docker load 命令恢复。

浙公网安备 33010602011771号