CS-Notes 中的 Docker 笔记:从环境隔离原理到镜像分层与容器可写层机制
【免费下载链接】CS-Notes:books: 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计项目地址: https://gitcode.com/GitHub_Trending/cs/CS-Notes
本文基于 CS-Notes 仓库「工具」栏目下的 Docker 笔记 展开,系统梳理 Docker 要解决的核心环境配置问题、它与传统虚拟机的本质区别、三大优势及典型使用场景,并深入讲解「镜像 = 类、容器 = 实例」背后的分层文件系统与可写层原理。读完后,你将能够说清楚容器与虚拟机的差异来源、分层镜像为什么利于复用与定制,以及 Docker 在持续集成、弹性云服务和微服务架构中的具体落点。
一、Docker 解决的问题:环境一致性
CS-Notes 在 README 中将 Docker 与 Git、构建工具、正则表达式等并列为「工具」类知识点,其定位是帮助开发者掌握日常工程实践中的基础工具概念。而 Docker 解决的核心问题,笔记原文表述得非常凝练:
由于不同的机器有不同的操作系统,以及不同的库和组件,在将一个应用部署到多台机器上需要进行大量的环境配置操作。
具体来说,所谓「环境」差异通常体现在几个层面:
- 操作系统版本差异:CentOS 7 与 Ubuntu 22.04 上的包管理器、系统库版本各不相同;
- 运行时与依赖版本差异:JDK、Node.js、Python 的解释器版本,以及编译时链接的动态库版本;
- 应用自身组件差异:配置文件路径、环境变量、中间件客户端版本等。
这些差异就是常说的「在我机器上能跑,在服务器上跑不起来」的根源。Docker 的解决思路是把「应用 + 其运行所需的全部组件」打包成一个整体,随应用一起部署。笔记原文强调了一个关键特性:
Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程进行隔离,被隔离的进程独立于宿主操作系统和其它隔离的进程。使用 Docker 可以不修改应用程序代码,不需要开发人员学习特定环境下的技术,就能够将现有的应用程序部署在其它机器上。
从通用实现原理看,这种「对进程进行隔离」主要依赖两类 Linux 内核机制(可结合仓库中 Linux 笔记 对照理解):
- Namespaces(命名空间):实现视图隔离,让容器内的进程拥有独立的进程号、网络、挂载点、主机名等命名空间,从而「看起来」像运行在一台独立机器上;
- Cgroups(控制组):实现资源约束,限制容器内进程可使用的 CPU、内存等资源,防止其影响宿主或其它容器。
正因为隔离发生在「进程」层面而非「硬件」层面,Docker 才能做到不修改应用代码即可完成跨机器部署——这也是它与虚拟机路线最根本的分岔口。
二、与虚拟机的比较:两条不同的虚拟化路线
笔记原文指出:
虚拟机也是一种虚拟化技术,它与 Docker 最大的区别在于它是通过模拟硬件,并在硬件上安装操作系统来实现。
也就是说,虚拟机的隔离边界在硬件层:它先(模拟或直通)出一套 CPU、内存、磁盘等硬件,再在这套硬件之上启动一个完整的客户机操作系统,应用运行在这个客户机 OS 之上。而 Docker 容器的隔离边界在进程层:它直接使用宿主操作系统的内核,容器本质上只是宿主上的一个(组)进程,外加一套受限的资源视图。
围绕这个本质差异,笔记从两个维度做了对比,这里完整继承并整理成表:
启动速度
- 启动虚拟机需要先启动虚拟机的操作系统,再启动应用,这个过程非常慢(通常是分钟级);
- 而启动 Docker相当于启动宿主操作系统上的一个进程,是秒级的操作。
占用资源
- 虚拟机是一个完整的操作系统,需要占用大量的磁盘、内存和 CPU 资源,一台机器只能开启几十个虚拟机;
- Docker 只是一个进程,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个容器。
汇总对比如下(定性结论均出自原文笔记):
| 维度 | 虚拟机 | Docker 容器 |
|---|---|---|
| 虚拟化对象 | 模拟硬件 + 安装完整客户机 OS | 宿主 OS 上的隔离进程 |
| 启动流程 | 启动客户机 OS → 启动应用,慢 | 直接启动宿主上的进程,快 |
| 单机规模 | 受限于完整 OS 开销,约几十台 | 进程级开销,成千上万个 |
| 资源占用 | 磁盘、内存、CPU 开销大 | 只打包应用及组件,开销小 |
需要补充一个适用前提:容器共享宿主内核,因此容器内的操作系统「发行版」特性(如 glibc 版本、系统工具差异)由基础镜像决定,但内核行为(内核版本、内核模块能力)始终取决于宿主机。这与虚拟机中「客户机 OS 与宿主内核解耦」的行为是有区别的,在部署对内核特性敏感的应用(如依赖特定内核模块的场景)时需要留意。
三、Docker 的三大优势
笔记在「启动速度快、占用资源少」之外,进一步总结了三点优势。以下逐条继承原文结论并展开说明。
1. 更容易迁移
原文结论:提供一致性的运行环境。已经打包好的应用可以在不同的机器上进行迁移,而不用担心环境变化导致无法运行。
展开来说,迁移的单位从「应用 + 手工配置的环境」变成了「一个镜像」。无论是从开发机迁移到测试机、测试机迁移到生产集群,还是从一个云平台迁移到另一个平台,只要目标机器具备容器运行时,镜像就能以一致的方式运行。环境差异被封装进了镜像内部,迁移时不再需要逐台机器排查依赖。
2. 更容易维护
原文结论:使用分层技术和镜像,使得应用可以更容易复用重复的部分。复用程度越高,维护工作也越容易。
这一点与第五节的分层结构直接呼应:分层存储让「操作系统基础层」「语言运行时层」「业务代码层」可以各自独立更新。当基础镜像修复了某个安全缺陷时,只需重建基础层,上层的业务层无需重新构建,也无需重新分发整包。重复部分被各镜像共享,维护面自然收窄。
3. 更容易扩展
原文结论:可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像可以非常容易得到我们想要的镜像。
即「站在现成镜像的肩膀上」:以官方或社区维护的语言运行时、数据库、中间件镜像为起点,叠加自己的配置与代码即可派生出新的镜像,而不必从零开始拼装环境。这种扩展性也构成了镜像生态的基础。
四、典型使用场景
笔记列举了三类使用场景,以下完整继承并补充落地方式的说明。
场景一:持续集成
原文表述:持续集成指的是频繁地将代码集成到主干上,这样能够更快地发现错误。Docker 具有轻量级以及隔离性的特点,在将代码集成到一个 Docker 中不会对其它 Docker 产生影响。
落地时,通常把「编译 + 测试」的完整工具链固化进一个 CI 镜像,每次流水线都在全新的容器里执行构建与测试:容器用完即弃,保证每次构建的环境完全一致,不会因开发机上残留的缓存或依赖污染导致「间歇性失败」。轻量级则意味着可以在同一台 CI 机器上并行拉起大量容器,缩短排队时间。
场景二:提供可伸缩的云服务
原文表述:根据应用的负载情况,可以很容易地增加或者减少 Docker。
因为容器启动只需秒级、且单台宿主机可承载大量容器,服务扩缩容就变成了「增删容器实例」这类细粒度操作:负载升高时快速拉起新容器分担流量,负载回落时回收容器释放资源。这与虚拟机路线「扩容 = 开机 + 装系统 + 装环境」的重流程形成鲜明对比,是容器适合做弹性伸缩底座的原因。
场景三:搭建微服务架构
原文表述:Docker 轻量级的特点使得它很适合用于部署、维护、组合微服务。
微服务架构意味着一套系统由数十甚至上百个独立服务组成,每个服务可能使用不同的语言与运行时。如果每个服务都用虚拟机承载,单机规模与运维成本都不可行;而容器化后,每个微服务打包为独立镜像、以独立容器运行,服务间的依赖版本被隔离,单个服务的升级、回滚、替换互不影响,「部署、维护、组合」三件事都变得可操作。CS-Notes 中 分布式、集群、消息队列 等系统设计类笔记中讨论的组件化协作,正可以对应到这些独立部署的服务单元上。
五、镜像与容器:分层文件系统与可写层
这是笔记中信息密度最高的一节,核心论断是:
镜像是一种静态的结构,可以看成面向对象里面的类,而容器是镜像的一个实例。
沿用这个类比:类定义了一份结构蓝图,实例化时才产生具体对象;镜像是「构建期」的产物,是静态的、只读的;容器是「运行期」的产物,是镜像被启动后的一个实例。同一个镜像可以启动出多个容器,各容器的运行状态互不影响,正如同一个类可以创建出多个彼此独立的对象。
只读的分层结构
笔记原文对镜像结构的描述是:
镜像包含着容器运行时所需要的代码以及其它组件,它是一种分层结构,每一层都是只读的(read-only layers)。构建镜像时,会一层一层构建,前一层是后一层的基础。镜像的这种分层存储结构很适合镜像的复用以及定制。
把这句话拆开看,有三个要点:
- 每一层只读:镜像中的任何一层都不能被直接修改,任何「变更」都会体现为新层的叠加;
- 逐层构建、前层是后层基础:典型的构建顺序是「操作系统基础层 → 语言运行层 → 依赖层 → 业务代码层」,后一层依赖前面所有层的叠加结果;
- 分层利于复用与定制:如果两个镜像共享前面的层(例如都基于同一个语言运行时镜像),这些层的内容只需存储一份即可被双方复用;定制时只需在公共层之上追加自己的层,不必复制整个镜像。
可写层:容器 = 镜像 + Writable Layer
笔记原文进一步说明:
构建容器时,通过在镜像的基础上添加一个可写层(writable layer),用来保存着容器运行过程中的修改。
也就是说,容器在镜像的只读层之上再挂一层可写层:运行期间应用对文件系统的任何写操作(新建文件、修改文件、删除文件)都发生在这一层里,不会污染下方的镜像层。这解释了容器行为上的几个常见现象:
- 容器停止后删除,其可写层的修改随之丢弃,镜像本身保持原样——这正是「镜像是静态类、容器是实例」的体现;
- 同一镜像的多个容器各自拥有独立的可写层,运行时互不干扰。
下图展示了镜像层与容器可写层叠加后的文件系统结构(busybox 示例),与上文「只读层 + 可写层」的描述一一对应:
从通用实现原理看,这种「多层只读 + 顶层可写」的结构由联合文件系统(Union Filesystem)支撑:它以堆叠方式把多个目录呈现为一个统一的文件系统视图,写入时采用 copy-up 语义(先从上覆层复制旧文件到可写层再修改)。理解了这一点,就能解释「为什么删除操作也发生在可写层」——删除一个只读层中的文件时,系统实际是在可写层放置了一个白名单标记(whiteout),从统一视图中将其遮蔽。
延伸:镜像如何被一层层构建出来
笔记的参考资料部分列出了多篇关于 Dockerfile 的公开教程,可见「用 Dockerfile 逐层构建镜像」是与本主题紧密相邻的知识点。一个典型的 Dockerfile 与分层结构直接对应——每条指令大致生成一层:
# 基于官方语言运行时镜像(复用社区现成的「前几层」) FROM openjdk:8 # 把应用产物复制到镜像内(新增一层) COPY app.jar /app/app.jar # 声明容器对外暴露的端口(元数据) EXPOSE 8080 # 定义容器启动时执行的命令 CMD ["java", "-jar", "/app/app.jar"]对照第五节的结论可以看出:FROM复用了一个已构建好的基础镜像(前文「更容易扩展」优势的具体体现);后续的每条指令在原有层之上追加新层(「前一层是后一层的基础」);而最终镜像的所有层都是只读的,运行时修改统一落在容器自己的可写层中。
小结
回到 CS-Notes 中这篇 Docker 笔记的主线:Docker 用「进程级隔离」解决了跨机器部署的环境配置问题;它相对虚拟机的优势来自「隔离边界在进程层而非硬件层」这一本质差异,具体表现为秒级启动与单机成千上万的规模;由此衍生出易迁移(环境一致性)、易维护(分层复用)、易扩展(基础镜像生态)三大优势,并落在持续集成、弹性云服务、微服务三类场景中;而其底层抓手就是「镜像 = 只读分层结构、容器 = 镜像 + 可写层的实例」这一套文件系统模型。以上结论均可在 notes/Docker.md 原文中找到出处,文中对 Namespaces/Cgroups 与联合文件系统机制的补充属于通用原理层面的延伸解读,供对照理解。
【免费下载链接】CS-Notes:books: 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计项目地址: https://gitcode.com/GitHub_Trending/cs/CS-Notes
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考