尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【Docker】2.Docker 核心原理与架构

【Docker】2.Docker 核心原理与架构 文章目录1. 什么是虚拟化、容器化1.1 虚拟化1.2 容器化2. 为什么要虚拟化、容器化2.1 极致的资源利用率与响应速度2.2 环境标准化一次构建随处运行2.2.1 为什么这对于现代开发至关重要2.3 资源弹性伸缩2.4 差异化环境提供2.5 沙箱安全3. Docker 核心原理与架构3.1 虚拟机 vs Docker3.1.1 虚拟机架构完整的“重量级”模拟3.1.2 Docker 架构轻量级的“进程隔离”3.2 Namespace 与 Cgroups 原理3.2.1 Namespace为什么 Namespace 如此高效3.2.2 Cgroups 的配额制3.2.3 Namespace 与 Cgroups3.3 从 LXC 到 Docker 的进化史3.3.1 LXC容器技术的“开荒者”3.3.1.1 为什么 Docker 早期要依赖 LXC3.3.1.2 历史的转折从“借用”到“自立门户”3.3.1.3 自立门户libcontainer 的诞生3.3.1.4 现代架构runC 与 OCI 标准3.3.1.5 总结1. 什么是虚拟化、容器化1.1 虚拟化是指通过虚拟化技术将一台计算机虚拟为多台逻辑计算机。在一台计算机上同时运行多个逻辑计算机每个逻辑计算机可运行不同的操作系统并且应用程序都可以在相互独立的空间内运行而互不影响从而显著提高计算机的工作效率。传统的虚拟化技术如VMware,KVM主要作用在硬件层和操作系统层之间。它通过一个名为Hypervisor虚拟机管理器的软件在物理硬件上“伪造”出多套虚拟硬件。1.2 容器化容器化 (Containerization)又称操作系统层虚拟化。它通过“伪造”操作系统的接口将应用程序及其所需的库文件封装在一个独立的单元中。容器之间共享同一个宿主机内核因此极其轻量。容器技术如Docker则更进一步它作用在操作系统层和程序库层之间。容器不再伪造硬件而是利用Linux内核的隔离特性Namespace和Cgroups直接在宿主机内核上划分子空间。三大时代物理机时代一台机器一个系统资源浪费严重扩展困难。虚拟机时代通过Hypervisor实现资源隔离提高了利用率但Guest OS带来了沉重的额外开销。容器时代舍弃了冗余的Guest OS直接共享内核实现了秒级启动和极高的运行效率。2. 为什么要虚拟化、容器化2.1 极致的资源利用率与响应速度在物理机或虚拟机时代由于每个应用都需要绑定一个完整的操作系统资源的开销是非常庞大的。而容器化通过共享内核的方式实现了资源利用的“高密度”。高密度部署容器极其轻量通常只有几十MB这意味着在同样的硬件资源下你可以运行比虚拟机多出数倍的容器实例。秒级启动因为容器不需要像虚拟机那样经历漫长的硬件自检和内核加载过程OS Boot它本质上只是宿主机上的一个进程。启动应用就像打开一个App一样快。虚拟机需要分钟级的耗时容器只需要毫秒级的耗时。2.2 环境标准化一次构建随处运行作为开发者你一定听过或说过“这段代码在我机器上运行得好好的啊”这就是典型的环境不一致问题。由于开发、测试和生产环境的操作系统版本、依赖库甚至配置文件的细微差别往往会导致难以排查的Bug。Docker通过**镜像Image**技术将应用程序及其运行所需的全部依赖包括二进制文件、库、配置文件等打包在一起。这就像是给你的程序发了一个“集装箱”无论这个集装箱被运到哪里内部的物品和环境都是完全一样的。实现效果消除环境差异确保开发环境、测试环境和生产环境的绝对统一。快速分发与回滚如果上线版本有问题只需一行命令拉取旧版镜像即可实现快速回滚。2.2.1 为什么这对于现代开发至关重要在追求“持续交付”的今天软件发布的频率从月级缩短到了天级甚至小时级。只有实现了资源的高效调度和环境的标准化我们才能在复杂的分布式系统中保持敏捷性而不至于陷入维护环境的泥潭。2.3 资源弹性伸缩在互联网业务中流量往往像潮汐一样有高峰和低谷。比如电商在大促期间流量会瞬间暴涨而平时则相对平稳。容器化让这种“动态调整”变得轻而易举。场景模拟双 11 扩容自动扩容当监控到CPU负载过高时系统可以瞬间启动100个新的容器实例来分担压力。快速收缩活动结束后只需几秒钟即可销毁多余容器释放资源以节省成本。2.4 差异化环境提供在同一个开发团队中不同的项目可能依赖完全不同的软件栈。比如项目A需要Ubuntu Python 2而项目B需要CentOS Python 3。在物理机上同时安装这些环境往往会发生冲突。多版本共存容器化允许你在同一台宿主机上通过不同的容器运行完全互斥的环境互不干扰。轻量级隔离无需为每个环境购买昂贵的物理机或配置沉重的虚拟机。2.5 沙箱安全对于不安全或不稳定的软件我们最担心它会“搞坏”整个操作系统。容器提供了一个独立的运行空间沙箱确保风险被锁定在容器内部。危险操作示范如果在容器内执行rm -rf /*格式化命令它只会破坏当前容器的文件系统而不会影响宿主机或其他运行中的容器。注意虽然容器提供了进程级的隔离但由于共享内核其安全性略逊于完全硬件隔离的虚拟机。在处理极高安全需求时需配合安全策略使用。3. Docker 核心原理与架构3.1 虚拟机 vs Docker3.1.1 虚拟机架构完整的“重量级”模拟虚拟机通过Hypervisor虚拟机管理程序在物理硬件上虚拟出一套完整的硬件环境。在每个虚拟机内部都必须安装一个完整的Guest OS客户操作系统。层级结构硬件 ➔ 宿主机OS➔Hypervisor➔Guest OS➔ 库/依赖 ➔ 应用。资源消耗由于每个VM都有自己的内核即使只运行一个简单的Hello World程序也要加载几百MB甚至几个GB的操作系统文件。启动速度启动过程包含硬件自检和完整的OS引导通常需要几分钟。3.1.2 Docker 架构轻量级的“进程隔离”与虚拟机不同Docker容器不携带自己的内核。它直接利用宿主机的内核通过Docker Engine进行调度和隔离。Docker Engine(Docker引擎)它是容器的“管理者”负责在宿主机操作系统之上创建、运行和管理容器。它不像Hypervisor那样去模拟硬件而是利用Linux内核的隔离特性如Namespace和Cgroups来确保容器间的独立性。虚拟机与Docker容器架构层级对比这种架构上的根本差异直接导致了性能上的巨大鸿沟启动速度Docker只是启动一个受限的进程实现“秒级”甚至“毫秒级”启动而VM需要启动整个OS需要“分钟级”。资源利用Docker引擎占用资源极低一台普通服务器能跑成百上千个容器但可能只能跑十几个虚拟机。封装程度VM封装的是整个操作系统而Docker封装的是应用代码及其依赖环境。3.2 Namespace 与 Cgroups 原理如果把Docker比作一座宏伟的大厦那么支撑它的有两大技术支柱Namespace命名空间和Cgroups控制组Namespace命名空间负责隔离。它负责给进程“蒙上眼睛”让进程觉得自己身处一个独立的世界。Cgroups控制组负责限制。它负责给进程“戴上枷锁”规定它最多能用多少CPU和内存。3.2.1 NamespaceNamespace(命名空间)它是Linux内核用来隔离内核资源的方式。通过将进程的相关资源指定在特定的Namespace中可以让进程只能看到属于自己的那一部分资源而完全感知不到其他进程的存在。Namespace就像是为每一个容器搭建了一个“楚门的世界”。在这个“摄影棚”里容器楚门看到的街道、网络、进程列表全是他自己的。即使外面宿主机有成千上万个进程在跑只要Namespace没开放权限容器就永远觉得自己是这片天地的主宰。Linux内核提供了多种不同类型的Namespace它们分别负责不同维度的隔离共同构建了一个完整的容器运行环境PID Namespace让容器拥有独立的进程ID。比如容器里的1号进程在宿主机上其实可能是一个几万号的普通进程。Network (Net) Namespace提供独立的网络栈网卡、IP、端口。这就是为什么多个容器能同时监听80端口而互不冲突的原因。Mount (MNT) Namespace隔离挂载点。容器看到的根目录 / 是属于它自己的看不到宿主机的文件系统。UTS Namespace让容器拥有独立的主机名 (Hostname) 和域名。IPC Namespace隔离进程间通信如消息队列。User Namespace让容器内的root用户不等于宿主机的root用户增强安全性。为什么 Namespace 如此高效这种“视图隔离”不需要像虚拟机那样去伪造一套硬件。它只是在内核调用时通过参数过滤了一下信息。Namespace改变的是进程的视角而不是它运行的实体。这正是Docker能够实现秒级启动的原因——它只是启动了一个被“蒙住眼睛”的普通进程而不是启动一整个操作系统。3.2.2 Cgroups 的配额制Cgroups(Control Groups, 控制组)它是Linux内核提供的一种机制可以根据需求把一系列系统任务及其子任务整合到按资源划分等级的不同组内从而为系统资源管理提供一个统一的框架。简单来说Cgroups的作用就是限制、记录、隔离进程组所使用的物理资源如CPU、内存、磁盘I/O等。以上述楚门的世界举例如果楚门是饕餮转世一个人可以把一个世界的食物都吃掉占用100% CPU或者搬空了公共水库耗尽所有内存即便他看不见别人世界最终也会因为资源枯竭而崩塌。在Linux中Cgroups是通过一系列子系统来实现精细化控制的cpu限制进程可使用的CPU时间片分配。memory限制任务的可用内存上限并自动生成资源占用报告。blkio对块设备如硬盘的输入/输出I/O进行限制。cpuset为任务分配独立的CPU核心在多核系统中非常有用和内存节点。pids限制cgroup中允许创建的进程总数防止“分身术”耗尽系统资源。3.2.3 Namespace 与 Cgroups这对搭档的配合非常默契它们共同完成了“容器”的定义Namespace隔离解决了安全性和冲突问题。它让容器拥有独立的主机名、网络和PID让应用觉得自己在独占系统。Cgroups限制解决了稳定性和公平性问题。它确保没有哪个“霸道”的容器可以抢走宿主机的全部家当。当你执行docker run时Docker引擎实际上是在后台调用内核API先用Namespace创建一个“隔离的房间”再用Cgroups给这个房间挂上“电表和水表”。这种不模拟硬件、只做内核标记的方式正是Docker极致轻量化的核心秘密。3.3 从 LXC 到 Docker 的进化史3.3.1 LXC容器技术的“开荒者”LXC(Linux Containers)它是Linux内核容器功能的一个用户空间接口。简单来说LXC是最早一批真正把Namespace的隔离能力和Cgroups的限制能力封装在一起并提供给用户使用的工具集。在Docker诞生的早期0.9版本之前Docker自身并不直接操作内核而是像一个“翻译官”一样把用户的指令转发给LXC由LXC去调用底层的隔离和限制功能。形象类比手动工具箱 vs 自动化工厂LXC就像是一个“手动工具箱”它里面有各种扳手和螺丝刀命令工具和模板虽然你能用它盖起一座房子容器但你需要手动去拧每一颗螺丝数据迁移和大规模部署非常麻烦。Docker就像是一座“自动化工厂”它早期借用了 LXC 的工具箱但它加上了流水线镜像技术和物流中心Docker Hub。它不再要求你手动盖房子而是让你直接下载“精装房模块”一键落地。3.3.1.1 为什么 Docker 早期要依赖 LXC降低门槛Namespace和Cgroups的内核API非常底层且复杂LXC提供了第一层封装让开发者能通过简单的命令创建容器。验证可行性LXC证明了在Linux上不使用笨重的虚拟机VM仅靠内核特性就能实现高效隔离。Docker的起步Docker最初被定义为“LXC的二次封装发行版”它的核心贡献在于引入了**镜像Image**的概念解决了“环境一致性”这个痛点。3.3.1.2 历史的转折从“借用”到“自立门户”虽然LXC很好用但它毕竟不是Docker亲生的存在跨平台兼容性差、管理不统一等问题。随着Docker的壮大它开始觉得这个“手动工具箱”束缚了自己的手脚。3.3.1.3 自立门户libcontainer 的诞生虽然LXC帮Docker搞定了早期的底层隔离但Docker团队发现过度依赖外部工具包会导致跨平台兼容性不同发行版的LXC版本不一和开发灵活性受限。于是在0.9版本中Docker引入了libcontainer。这是Docker团队用Go语言编写的、直接与Linux内核交互的模块它彻底取代了LXC成为了Docker的默认执行驱动。3.3.1.4 现代架构runC 与 OCI 标准随着容器技术的爆发厂商们意识到如果不统一标准市场会陷入混乱。于是OCI(Open Container Initiative)标准应运而生。Docker顺应潮流将libcontainer进一步重构并捐赠给了社区这就是现在大名鼎鼎的runC。runCcontainerdrunC一个轻量级的工具只负责按照OCI标准去创建和运行容器。它就像是容器的“启动马达”。containerd一个守护进程负责管理容器的生命周期推送/拉取镜像、管理存储和网络等。它调用runC来干活。形象类比从“一体化机床”到“乐高积木”早期的Docker像是一个大型的一体化机床坏了一个零件可能整个都要检修。而现代Docker架构则变成了**“乐高积木”**标准化接口只要符合OCI这个“积木凸点”标准你可以用Docker的积木也可以用其他的积木如Kubernetes的运行时。解耦的意义这种解耦让系统极其稳定。即使Docker守护进程本身崩溃了已经在运行的容器由runC启动也不会立刻死掉极大地提高了生产环境的容错率。3.3.1.5 总结Docker的进化史本质上是一个**“从依赖工具到成为工具再到制定标准”**的过程LXC时代借用别人的手动工具箱0.9之前。libcontainer时代开发自己的专用生产线0.9 - 1.10。OCI / runC时代制定全球统一的工业生产模具现代版本。
返回列表