注意:部分文章发布时间较长,可能存在未知因素,购买时建议在本站搜索商家名称,先充分了解商家动态。
交流:唯一投稿邮箱:hostvps@88.com。
在云原生时代,Docker这个词几乎成了容器技术的代名词。但很多人对容器一直有个误解,以为它像虚拟机一样,在硬件之上模拟出一个完整的操作系统。其实完全不是这样,容器的基础不是虚拟化,而是Linux内核提供的namespace和cgroups两大机制。Namespace负责看得见的隔离,让进程认为自己是系统里唯一的住户;Cgroups负责看不见的限制,确保某个容器不能豪夺宿主机全部资源。一虚一实,一软一硬,Docker容器资源隔离就这样建立起来了。
容器隔离的空间维度相对容易理解。Docker启动容器时,内核会为它创建独立的namespace,不同容器拥有各自的PID、网络栈、文件系统挂载点和用户权限视图。在容器里执行ps命令,你只能看到自己的进程;执行ip addr,网卡配置也完全是另外一个世界。这种机制的巧妙之处在于它不创建新的内核,而是复用宿主机内核,仅仅给进程们划出了一道道隐秘的帷帐。宿主机上所有容器共享同一个内核,所以隔离不是绝对的,内核本身属于全部容器共享的公共区域,但这对于大多数应用场景来说已经足够。
时间回到空间隔离之外,更关键的是资源份额的分配。在这个维度上,两个容器可以互相看到对方的文件系统,但一个容器疯跑CPU时却可能把另一个人饿死。这个问题的答案就藏在cgroups里。
Cgroups,全称是control groups,控制组。它是Linux内核用来限制、记录和隔离进程组资源使用的机制。Docker在创建容器时,会在cgroups文件系统下为每个容器建立独立的子目录。往这些目录里写入不同的配置文件,容器就能被精准地限定CPU、内存、磁盘IO和网络带宽。你可以把cgroups理解成一台精密的水流分配器,总进水管是宿主机的物理资源,cgroups负责把水分流到各个容器的小水管中,谁多谁少完全由规则决定。
如果只用一句话概括Docker容器资源隔离的控制方式,那就是限制上限和分配权重。以CPU为例,Docker容器的CPU限制有两种方式:一种是设定固定的CPU配额,容器无论如何也只能使用这么多计算资源;另一种是设定权重,让多个容器按照比例共享CPU资源。在cgroup v1时代,CPU配额的实现依靠cpu.cfs_period_us和cpu.cfs_quota_us两个参数,cfs_period代表调度周期,cfs_quota代表在周期内允许容器使用的CPU时间。如果周期是一百毫秒,配额是五十毫秒,那这个容器最多只能使用一个CPU核心百分之五十的时间。进入cgroup v2时期,接口更简洁,通过cpu.max文件的“quota period”两个参数直接控制,逻辑不变但表达更清晰。
内存限制同样严格。Docker中的-m参数可以设定容器的内存上限,包括物理内存和交换分区。当容器实际使用量接近这个阈值时,内核会启动回收机制,频繁进行内存回收操作。如果仍然无法满足需求,容器内的进程会被OOM Killer杀死。这种机制虽然严厉,但保护了宿主机的稳定性。从实践中来看,内存限制对容器的行为影响往往比CPU限制更直接也更容易触发,因为CPU有波动性,短暂的配额超过可以被调度器平滑消化,而内存的分配一旦超出上限就是生死问题。
有意思的是,真实的Docker生产环境,资源隔离的效果并没有想象中那么完美。很多使用者在部署容器后发现,明明给每个容器都设了合理的内存限制,整体内存占用率却还在持续上升。后来大家逐渐意识到,容器里的进程和应用,并不会感知到外界的资源边界。比如JVM虚拟机,默认启动时往往根据宿主机物理内存大小来确定堆内存分配参数。在裸机上能运行的程序,一旦搬进只有四分之一内存的容器里,JVM仍然按照宿主机内存取百分之二十五作为默认堆,但实际上这个值已经超出了容器的可用资源。虽然现代JVM已经增强了对容器环境的感知能力,但很多运行在Docker容器里的老应用,依然会犯这样的错。
再到CPU方面,多个高负载容器同时运行在同一宿主机上时,调度器虽然努力在不同容器之间切换,但进程在上下文切换上的开销会明显增加。有时候在两核的宿主机上跑两个各占百分之五十CPU的容器,业务响应时间反而比在裸机上直接跑两个进程要慢。原因在于cgroups的CFS调度器并不会感知容器内部多个进程的协作模式,那些频繁互相唤醒的线程组被分散到不同核心上之后,同步开销会抵消掉一部分性能收益。
容器资源隔离的另一个重要挑战是混合部署时的资源竞争。如果宿主机上既有高突发性的Web应用容器,又有持续高CPU占用的离线计算容器,默认的CPU权重策略可能让在线服务产生抖动,进而影响用户体验。为此,云原生领域出现了许多针对容器资源隔离的优化实践,比如设置严格的CPU亲和性、使用更高层次的入口控制器控制CPU弹性、引入动态资源配额调整机制,甚至通过可观测性平台实时监控每个容器的实际资源消耗,及时调整配置。
在Docker的编排体系里,Docker Compose和Docker Swarm都允许在服务级别为容器指定资源限制。当一个宿主机资源不足时,编排器会根据已设定的参数决定哪些任务可以调度以及如何调度。对运维人员来说,理解资源隔离的本质比记住那些参数更重要,因为只有理解了cgroups的原理,才能在排查问题时快速找到方向,而不是盲目地调大或者调小容器规格。
回到文章开头的问题,Docker容器资源隔离的底层密码其实就是Linux内核里那两套朴素又精密的机制。Namespace帮你划清了什么看得见、什么看不见,cgroups帮你划清了什么能用、什么不能用。但密码已经掌握在手中,真正让这个密码发挥价值的,是我们如何理解应用特征、如何配置合理的资源策略,以及如何在动态变化的负载中维持系统的平衡。容器技术快速发展的今天,资源隔离还在向更细粒度、更低开销、更强弹性的方向演进,但它的核心哲学始终没变,所有的隔离都是为了满足应用场景的需求,同时不让任何单一租户在共享基础设施上伤及无辜。
贝壳主机网

