我们平时用Docker,点几个命令就能启动一个容器,感觉很方便。但很多人其实不太清楚,这个“容器”到底是怎么跑起来的,和虚拟机又有什么本质区别。Docker之所以能这么轻量、启动这么快,完全依赖于Linux内核的底层能力。简单来说,它并不是在物理机上虚拟出一套完整的操作系统,而是直接把你的进程隔离在一个独立的环境里运行。这种隔离听起来简单,背后却涉及了命名空间、控制组、联合文件系统这几个核心模块。搞懂了它们,才算真正理解Docker。

Docker为什么不用虚拟机也能隔离
很多人第一次接触Docker时都会好奇,为什么它不需要像虚拟机那样装一个完整的操作系统,却能保证进程互不干扰。关键在于Linux内核提供的命名空间机制。命名空间可以把系统的全局资源,比如进程树、网络接口、文件系统挂载点、用户ID等,分别包装成独立的“小世界”。每一个Docker容器启动时docker 原理,系统会为它创建一套全新的命名空间,让容器里的进程以为自己是整个系统的唯一进程,根本看不到宿主机的其他进程。这种隔离是内核层面直接支持的,不需要额外的虚拟化层,所以几乎没有性能损耗。而虚拟机则需要在宿主机上运行一个Hypervisor,再在之上启动完整的Guest OS,每一层都在消耗CPU和内存资源,启动一个虚拟机常常需要几十秒甚至几分钟,Docker容器却可以秒级启动。正是因为这种“进程级”的隔离方式,Docker才能在轻量化的同时做到安全可靠。

容器怎么控制资源不让一个应用吃光所有内存
隔离了环境还不够,如果一个容器里的程序出现了内存泄漏,或者恶意占用CPU,怎么保证它不影响其他容器?这里就要说到控制组了。控制组在Linux里早就存在,它允许管理员对一组进程可以使用的CPU、内存、磁盘IO、网络带宽等资源设置上限。Docker在创建容器的时候,会为这个容器创建一个单独的控制组docker 原理,并写入你指定的资源限制参数。比如你运行容器时加上--memory=512m,控制组就会保证这个容器里的所有进程加起来最多只能使用512MB内存,超出就会被内核杀掉或者触发OOM。同样中国linux操作系统,CPU份额也可以通过--cpus参数来控制。这样一来,即使某个应用出现了异常,它也只能在自己被分配的资源范围内折腾,不会把整个服务器拖垮。生产环境中多租户部署或者混合部署不同业务时,这个能力尤其重要,它可以避免一个“吵闹邻居”影响其他服务的稳定性。

镜像为什么能分层存储还能共享
我们每次用docker pull拉取镜像时,看到的是一层层下载,这些层就是Docker镜像的核心结构。Docker的镜像层是基于联合文件系统来实现的,常见的实现有OverlayFS和AUFS。联合文件系统的特点是,可以把多个只读层叠加在一起,对外呈现成一个完整的文件系统。当你启动一个容器时,Docker会在这些只读层的最上面加一个可写层linux获取当前时间,所有对文件的修改都写在这一层里,下面的只读层不会被改动。这意味着两个不同的容器如果使用同一个基础镜像,它们可以共享下面的所有只读层,只有各自的可写层是独立的。这样做有两个明显的好处:一是节省磁盘空间,同一个镜像拉一遍,所有容器都能复用;二是启动速度极快,因为不需要复制整个文件系统,只需要挂载已有的层。而且当你构建新的镜像时,只需要增加新的层,不需要从头打包,构建效率高得多。

网络是怎么打通让容器之间互相通信
容器启动后,默认会被分配一个独立的网络命名空间,有自己的回环接口和IP地址。Docker会创建一个虚拟网桥,通常是docker0,所有容器默认都连接到这个网桥上。每个容器里会有一个虚拟网卡对,一端连着容器内部,另一端连着网桥,就像物理机上的一根网线。这样一来,容器之间就可以通过IP地址互相访问,就像它们连接在同一个交换机上一样。如果你想让容器暴露给宿主机外部访问,只需要在运行容器时指定端口映射,比如-p 8080:80,Docker就会在宿主机上的iptables里添加一条NAT规则,把宿主机8080端口的流量转发到容器内的80端口。对于更复杂的网络场景,比如跨主机的容器通信,Docker还支持Overlay网络,利用VXLAN等技术把不同宿主机上的容器纳入同一个虚拟二层网络,这在Kubernetes集群中非常常见。理解了这些网络原理之后,排查容器无法访问或者端口冲突的问题就变得有迹可循了。

很多人觉得Docker只是工具层面的事,会用命令就够了,其实深入了解它的原理之后,你会发现自己能更从容地处理实际运维中的各种异常。容器卡住了你知道去看控制组的资源限制是否被触发;镜像构建变慢你能想到是层数太多或基础镜像选择不当;网络不通你懂得去检查网桥和iptables规则。Docker并不是黑科技,它只是把Linux内核已有的能力用更友好的方式组合了起来。把命名空间、控制组、联合文件系统、网络这几个核心概念理清楚,你就能从“会用”变成“懂用”,这对排查问题、优化性能和设计架构都很有帮助。
