ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux虚拟化与容器化落地:KVM、namespace与cgroup实战

Linux虚拟化与容器化落地:KVM、namespace与cgroup实战

一台物理机扛着十来个服务,CPU 常年趴在三成以下,内存却天天在 90% 边缘晃悠,磁盘 IO 一有备份任务就全线卡顿——这种场景我见过太多次。后来大部分团队的选择都是同一套组合拳:把重活拆进 Linux 虚拟化里跑,把轻活塞进容器化里跑。虚拟机给的是完整的系统边界,容器给的是秒级的交付速度,两者不是替代关系,而是分工关系。这篇东西只聊这两条线在 Linux 上到底怎么落地:底层靠什么机制隔离、参数怎么算、命令怎么写、跑起来之后哪些坑一定会踩。不管你是刚接手一台要改造成宿主机的运维,还是写业务代码想搞清楚自己那点资源为啥老被抢的开发,下面的内容都能直接照着抄。我会把 KVM、QEMU、libvirt 这一套虚拟化链路的职责拆开讲,也会把 namespace、cgroup、联合文件系统这层容器地基挖到底,最后落到能跑的实验环境和一份排查速查表上。

1. 先把边界划清楚:虚拟化与容器化各自解决的问题

1.1 从一台被浪费的物理机说起

早年间一个服务上线,标准动作就是申请一台物理机,装系统、配环境、把进程拉起来。问题在于,绝大多数业务对硬件的胃口远没有想象中大:一个内部管理系统吃掉 8 核 32G,实际负载连 10% 都用不到,剩下 90% 就那么闲着。为了把这些碎片榨出来,最直接的想法是"一台上跑多个系统",于是就有了虚拟化。虚拟机管理器把物理 CPU、内存、磁盘、网卡切成若干份,每份包装成一台看起来完全独立的机器,里面装自己的内核、自己的驱动、自己的用户态。对这个系统来说,它根本不知道自己在共享硬件。

这条路解决了资源利用率,但也带来了新麻烦:每台虚拟机都要跑一个完整内核,启动慢,占用固定,镜像动辄几个 G。一个只跑单个二进制的小服务,为了它开一台虚拟机,性价比很低。容器化的思路就换了方向:不虚拟硬件,直接复用宿主内核,只把进程看到的"世界"隔离开——它看到的文件系统、进程表、网络栈、主机名都是独立的一套,但底下跑的还是同一个内核。所以容器启动是毫秒级,镜像可以做到几十兆。理解这条分界线特别重要:虚拟化隔离的是硬件,容器隔离的是视图。

1.2 硬隔离与软隔离:两种思路的本质差异

虚拟机里的进程访问内存,要经过一层由硬件辅助的地址翻译,物理页框的分配权完全在虚拟机管理器手里;容器里的进程访问内存,走的是宿主内核正常的页表,只是被 cgroup 记了一笔账,超了就杀。这是两者安全边界的根本区别,也决定了它们能用在什么场景。虚拟机里跑一个恶意程序,它想突破出去得先攻破虚拟化层,难度很高;容器里跑一个以 root 身份启动的进程,如果没做用户命名空间映射,它和宿主上的 root 之间只隔着几层配置。

我用一个生活化的比喻:虚拟机像是一栋楼里隔出来的独立公寓,有自己独立的门户、水电表、承重墙;容器像是同一间大开间里用隔断分出来的工位,桌子椅子各归各,但空调、电路、消防系统是共用的。工位区省钱、灵活、搬起来快,但一旦有人点了火,整层都跑不掉。所以真正生产环境里的常见形态是混合:底层虚拟机负责硬隔离和运维边界,上层容器负责应用交付,容器跑在虚拟机里,而不是直接跑在裸机上。

1.3 选型判断:什么场景该选哪条路

判断维度倾向虚拟机倾向容器
需要跑不同内核版本或非 Linux 系统是否
单实例启动时间要求秒到分钟级可接受毫秒到秒级
多租户且互不信任是需额外加固
应用打包与交付频率较低很高
资源碎片利用率中等高
需要完整系统级调试能力是受限

我自己的判断顺序是:先看有没有内核态需求(自定义内核模块、特定内核参数、非 Linux 系统),有就必须虚拟机;再看交付节奏,如果一天要发好几次版本,而每次发版都在配环境上耗时间,那就该容器化;最后看信任边界,凡是多租户、对外暴露、跑不可信代码的场景,宁可多花点资源上虚拟机。剩下那些内部服务、无状态、可以随时重建的,容器是最省心的选择。

2. 虚拟化这条线:从硬件指令到一块能用的虚拟磁盘

2.1 全虚拟化与半虚拟化的取舍

早期没有硬件辅助时,虚拟机里的特权指令没法直接执行,得靠二进制翻译动态改写成安全调用,性能损耗相当可观。后来 CPU 厂商在硬件层面加了支持,把处理器分成宿主模式和客户模式,客户机执行特权指令时会触发陷入,由虚拟机管理器接管处理,这就是常说的硬件辅助虚拟化。你在/proc/cpuinfo里看到的vmx(对应一类 CPU)或svm(对应另一类 CPU)就是这项能力的标志位。

半虚拟化走的是另一条路:不假装自己是真硬件,而是让客户机知道自己被虚拟了,主动配合。典型做法是客户机装上专门的驱动,把磁盘和网络的 IO 请求通过共享内存加通知机制直接递给宿主,绕开昂贵的设备模拟。现在这套思路基本都以 virtio 的形式存在:virtio-blk 负责磁盘,virtio-net 负责网卡。所以实践中我的默认配置就是硬件辅助虚拟化打底、virtio 驱动加速 IO,这是性能与兼容性最平衡的组合,只有在装一些老系统、缺 virtio 驱动时,才回退到模拟 IDE 或 e1000 网卡。

2.2 KVM、QEMU、libvirt 三者到底谁管什么

刚接触这块最容易把三个东西搞混。KVM 是内核里的一个模块,加载后让内核具备创建虚拟处理器、管理客户机内存的能力,它只管 CPU 和内存这两块最核心的资源;QEMU 是一个用户态程序,负责模拟磁盘控制器、网卡、串口、显卡这些设备,并负责整个虚拟机的生命周期,它调用 KVM 的接口来跑客户机代码;libvirt 则是一层管理层,提供统一的接口和守护进程,把配置落到 XML 文件里,屏蔽掉不同虚拟化后端和不同版本的差异。

分工用一句话概括:KVM 管算力和内存,QEMU 管设备和组装,libvirt 管配置和生命周期。日常运维基本只跟 libvirt 打交道,用virsh敲命令,用 XML 描述虚拟机;真正排查深层次问题时才会下潜到 QEMU 的日志和进程参数。这个分层的好处很明显,你的虚拟机定义是一份可版本管理的 XML,换机器、改参数、做快照都不需要重新记一套命令。

注意:QEMU 以什么身份跑很关键。默认走非特权用户加若干辅助组的方式,比直接以 root 运行安全得多,但如果你要用桥接网络或直通设备,就可能需要调整权限组而不是直接把整个进程提权。

2.3 CPU 与内存参数怎么算

CPU 分配的核心概念是 vCPU 数量与超分比。假设宿主是 32 个逻辑核,你要跑 8 台虚拟机,每台给 4 个 vCPU,加起来正好 32,超分比 1:1,这是最保守也最稳的做法。如果业务是典型的 Web 服务,大部分时间在等 IO,把超分比放到 1:3 甚至 1:4 也常见,代价是高峰期客户机里的%st(steal time)会升高,表现出来就是"莫名卡顿"。

判断超分是否过头的直接办法,是进到客户机里看top的首行或vmstat 1的st列。我的经验阈值是:st稳定在 3% 以内无所谓,5% 到 10% 需要观察业务 RT,长期超过 15% 就得收敛超分比或者迁走负载。另外,同一台宿主机上尽量别把 CPU 密集型的虚拟机堆在一起,业务错峰比调参数更有效。

内存这边,超分比一般控制在 1.2 到 1.5 倍,前提是开启内存回收机制。常用手段有三个:内存气球,让客户机在内存宽裕时把页还给宿主;页共享合并,把多台虚拟机里内容相同的页(比如相同的内核镜像)合并成一份;交换。前两个默认行为就够用,第三个要谨慎,宿主机一旦开始大量换页,所有虚拟机的延迟会一起飙升。计算示例如下:宿主 128G,预留 8G 给系统,可用 120G,按 1.25 倍超分,可以分配出去的客户机内存总量约 150G,也就是能开 9 台 16G 的虚拟机。这个数字不是让你去顶格用,而是给你一个规划上限。

2.4 存储格式与网络模式的选型

磁盘格式上,raw就是一块裸的稀疏文件,少一层映射,读写路径最短,性能最好,但不支持快照,扩容也麻烦;qcow2支持写时复制、快照、压缩、后端文件链,功能齐全,代价是多一层地址转换和元数据开销。我的做法是:数据库这类对 IO 敏感、不需要频繁快照的,用raw配预分配;普通业务机用qcow2,创建时把preallocation设成metadata,让元数据提前落盘,避免写入时反复分配造成抖动。

网络模式三种最常见:默认的 NAT 网络,宿主做转发,客户机可以出外网但外部访问不进来,适合做实验;桥接网络,客户机直接拿到与宿主同网段的地址,对外就像一台独立机器,生产环境多用这种;还有直通类方案,把物理网卡或虚拟功能直接分配给客户机,性能最好但配置复杂、迁移受限。选哪种先问一句:这台机器需不需要被别人直接访问?需要就桥接,不需要就 NAT,别一上来就折腾直通。

3. 容器化这条线:命名空间与 cgroup 才是真正的地基

3.1 六种命名空间划出的隔离边界

容器的"独立感"全部来自命名空间。挂载命名空间让进程看到自己的文件系统树,别人的挂载点它看不见;进程命名空间让它以为自己是 1 号进程,看不到宿主上其它进程;网络命名空间给它独立的网卡、路由表、端口空间;IPC 命名空间隔开消息队列和共享内存;UTS 命名空间让它有自己的主机名;用户命名空间做 UID 映射,可以把容器里的 root 映射成宿主上的普通用户,这是提升安全性的关键一招。

除了这几个,新版本内核还多了 cgroup 命名空间(让容器内看到自己的资源视图)和时间命名空间。我在实际排障时最常用的是网络和挂载这两个:想知道容器里的端口到底映射到哪,就进它的网络命名空间看;想知道某个文件是从镜像层来的还是运行时挂进去的,就进挂载命名空间看挂载表。这些能力都由内核提供,跟具体用哪个运行时无关,抓住这一点,任何容器工具在你眼里都是"一层壳"。

3.2 cgroup 如何把资源限额真正落实

命名空间负责"看不见",cgroup 负责"用不多"。它把进程分组,然后按组统计和限制资源。现在主流已经切到 v2 版本,各项限制集中在一个统一层级下,路径一般是/sys/fs/cgroup/。常用的几个控制文件值得记住:cpu.max用"配额 周期"的形式限制 CPU 时间,例如50000 100000表示每 100ms 周期内最多用 50ms,等价于半个核;cpu.weight是相对权重,只在争抢时起作用;memory.max是硬上限,超了触发回收,回收不动就杀进程;memory.high是软上限,超了先限速;pids.max限制进程与线程总数,防 fork 炸弹;io.max按设备限制带宽和 IOPS。

这里有个特别容易忽略的点:cpu.max限制的是时间片总量,一个多线程程序照样能瞬间打满所有核再被节流,表现出来的现象是"CPU 使用率被压在 50%,但响应时间抖得厉害"。解决办法不是简单调大配额,而是先确认业务是不是真的需要那么多并行度,很多时候把线程池调小比加配额更有效。

3.3 联合文件系统与镜像分层

镜像之所以能小、能快、能复用,靠的是分层。每一层是一个只读的文件系统快照,多个层叠起来形成最终视图,最上面再加一层可写的薄层给容器运行时用。读文件时自顶向下找,找到就停;写文件时先复制到可写层再改,这叫写时复制。所以同一份基础层被一百个容器共享,磁盘上只存一份。

这套机制有两类实际影响,必须提前知道。第一,容器删了,可写层的数据就没了,所有需要持久化的东西必须显式挂出去。第二,大量写小文件会触发频繁的复制上抬,性能比直接写裸盘差不少,所以数据库这类写密集负载我从不放在容器的可写层里跑,要么挂卷,要么干脆用虚拟机。构建镜像时也要顺着这个逻辑来:变化频率低的放前面,变化频率高的放后面,让缓存尽可能命中,构建时间能从几分钟压到十几秒。

3.4 手动创建一个最小容器看清全貌

光看概念容易飘,不如自己捏一个。下面这段操作不需要任何容器运行时,只用内核接口就能让一个进程"活在自己的世界里":

# 准备一个最小根文件系统目录 mkdir -p /tmp/miniroot/{bin,proc,sys,dev} cp /bin/busybox /tmp/miniroot/bin/ 2>/dev/null || cp /bin/sh /tmp/miniroot/bin/ # 用 unshare 创建新的命名空间,再切根 unshare --mount --uts --ipc --pid --net --fork \ chroot /tmp/miniroot /bin/sh -c 'hostname mini; mount -t proc proc /proc; ps aux'

执行完你会看到主机名变了,进程表里只有自己。原理就是先把几类命名空间新建一份,再用chroot换掉根目录,最后挂上/proc。真实运行时要做得更细致:用pivot_root替代chroot避免逃逸,用 cgroup 把资源限制挂上,用 capabilities 砍掉不需要的特权,用 seccomp 收紧系统调用。但骨架就是这几步,理解了它,再看各种运行时的高阶参数就不会发懵。

4. 实操落地:搭一套能跑起来的实验环境

4.1 宿主机的前置检查与准备

第一步永远是确认硬件能力有没有打开。有些机器在固件里默认关闭了虚拟化支持,装完系统才发现起不来,白折腾半天。

# 1. 确认 CPU 支持硬件虚拟化 grep -Eoc '(vmx|svm)' /proc/cpuinfo # 结果大于 0 才可用 # 2. 确认内核模块加载 lsmod | grep -E 'kvm|vfio' # 3. 确认内核版本支持所需特性 uname -r # 4. 看 IOMMU 是否开启(需要设备直通时才用) dmesg | grep -i -e DMAR -e IOMMU | head

如果第一项是 0,去固件设置里找虚拟化相关的开关打开,这一步绕不过去。模块没加载就手动modprobe kvm,再根据 CPU 类型加载对应的子模块。装管理工具时按发行版系别来:deb 系用apt装qemu-system、libvirt-daemon-system、virtinst这一套,rpm 系用dnf装对应的包组。装完把当前用户加到 libvirt 相关组里,然后systemctl enable --now libvirtd并检查virsh list --all有没有正常响应。

提示:如果你打算跑桥接网络,提前把网桥规划好,不要等虚拟机装完了再改网络。改网络的代价是全部虚拟机重启,而且容易配错导致失联。

4.2 用 libvirt 创建并调优一台虚拟机

命令行建机器比图形界面可控得多,参数写清楚,以后复现也方便。

# 创建一块 qcow2 磁盘,40G 容量,元数据预分配 qemu-img create -f qcow2 -o preallocation=metadata /var/lib/libvirt/images/demo01.qcow2 40G # 建虚拟机:4 vCPU / 8G 内存 / virtio 磁盘与网卡 virt-install \ --name demo01 \ --memory 8192 \ --vcpus 4,sockets=1,cores=2,threads=2 \ --cpu host-passthrough \ --disk path=/var/lib/libvirt/images/demo01.qcow2,format=qcow2,bus=virtio,cache=none \ --network network=default,model=virtio \ --graphics none \ --console pty,target_type=serial \ --cdrom /var/lib/libvirt/images/install.iso \ --os-variant detect=on

几个参数值得单独解释。--cpu host-passthrough让客户机直接看到宿主的 CPU 特性,性能最好,代价是这台机器不能跨不同型号的宿主迁移;如果要做集群迁移,改成具体的 CPU 模型名。cache=none绕过宿主页缓存,把缓存交给客户机自己管,写密集场景更稳,但要配合io=native才有意义。--vcpus后面那串拓扑不是装饰,它决定了客户机内核怎么组织调度域,跟宿主拓扑对齐能减少跨 NUMA 访问。装完之后用virsh dominfo、virsh domblklist、virsh domiflist逐项核对,别凭感觉认为配对了。

日常操作就那么几条:virsh start、virsh shutdown、virsh reboot、virsh snapshot-create-as打快照、virsh edit改配置(改完要关机重启才生效,热插拔是另一套流程)。快照用多了要注意后端文件链不能太长,超过十几层之后读写性能会明显下降,定期做合并。

4.3 容器运行时的资源限制实操

容器侧的关键是把限制写死在启动参数里,而不是靠"它应该不会用那么多"。

# 限制 CPU 半个核、内存 512M、进程数 100、根文件系统只读 docker run -d --name api01 \ --cpus="0.5" \ --memory="512m" --memory-swap="512m" \ --pids-limit 100 \ --read-only --tmpfs /tmp \ --cap-drop ALL --cap-add NET_BIND_SERVICE \ --security-opt no-new-privileges \ -p 8080:8080 \ registry.local/api:1.4.2

--memory和--memory-swap设成一样大,意思是完全禁用交换,超限直接杀,行为可预期;如果 swap 留了余量,进程会在被杀之前先卡住一段时间,反而更难排查。--pids-limit是我踩过坑之后每次都会加的,某个服务线程池配置错误疯狂创建线程,直接把宿主的进程号耗尽,连带整台机器的新进程都起不来。--read-only加--tmpfs的组合能让大部分应用正常跑,同时把"运行时偷偷往镜像里写文件"的坏习惯堵死。

需要校验限制有没有真的生效,别只看docker stats的估算值,直接读 cgroup 更可靠:

# 找到容器对应的 cgroup 路径并查看实际上限 cat /sys/fs/cgroup/system.slice/docker-<容器ID>.scope/memory.max cat /sys/fs/cgroup/system.slice/docker-<容器ID>.scope/cpu.max # 查看内存事件计数,oom 次数是硬指标 cat /sys/fs/cgroup/system.slice/docker-<容器ID>.scope/memory.events

4.4 混合部署时的资源预留

虚拟机跑在容器宿主的同一台机器上时,最怕的是互相踩踏。我的做法是给虚拟机做整体预留:用 cgroup 或 systemd 切片把 QEMU 进程组的内存下限和 CPU 权重固定住,保证虚拟机不会因为宿主上某个容器突发而被饿死。反过来,容器宿主自身也要留出余量,kube-reserved那套思路在这里同样适用——给系统进程预留 5% 到 10% 的 CPU 和 2G 左右内存,是让整机在压力下还能 SSH 进去救命的最低保障。

5. 常见问题与排查实录

5.1 虚拟机启动失败与运行卡顿

启动失败先看日志,路径通常是/var/log/libvirt/qemu/<机器名>.log,QEMU 的报错原文全在里面。最常见的三类:一是权限问题,磁盘文件属主或 SELinux 上下文不对,日志里会明确写 permission denied;二是资源不足,内存分配失败,这时候要检查宿主剩余内存和 overcommit 设置;三是镜像损坏,qemu-img check跑一遍能确认。

运行卡顿的排查顺序我固定成四步。先看宿主vmstat 1的r列和sy列,运行队列长期超过核数说明 CPU 不够;再进客户机看%st,是超分过头的信号;然后看iostat -x 1的await和%util,磁盘打满时延迟会成倍上升;最后看宿主dmesg有没有内存回收或 OOM 的痕迹。这个顺序的好处是由外向内,先排除宿主问题再怀疑客户机配置。

5.2 容器被杀与内存水位定位

容器突然消失,docker ps -a显示退出码 137,基本可以断定是被 OOM 杀了。但"谁是凶手"要看两层:如果memory.events里oom_kill计数增加,是容器自身超限;如果宿主全局内存吃紧,内核可能直接挑一个进程下手,这时候容器是被殃及。

dmesg -T | grep -i -E 'oom|killed process' | tail -20 cat /sys/fs/cgroup/system.slice/docker-<ID>.scope/memory.events cat /sys/fs/cgroup/system.slice/docker-<ID>.scope/memory.current

还有一种情况是没被杀但响应极慢,多半是撞到了memory.high的软限速,或者是页缓存被反复回收。我遇到过一例,某运行时应用设了 512M 上限,JVM 默认按物理机内存算堆大小,一启动就逼近上限,GC 疯狂触发。解决办法是显式传-XX:MaxRAMPercentage=70,让堆大小跟着容器配额走,而不是跟着宿主机走。这个坑几乎每个 JVM 容器化项目都会踩一遍。

5.3 网络不通的分层排查

容器或虚拟机的网络问题,别一上来就改配置,按层往下捋能省很多时间。第一层看链路和地址:容器里ip addr、ip route有没有正确网关;第二层看宿主转发,相关内核参数是否开启,转发规则和地址转换规则是否命中;第三层看域名解析,容器内的解析配置是从宿主继承还是被覆盖过;第四层看 MTU,特别是走隧道或叠加网络时,MTU 不一致会导致小包通、大包不通这种诡异现象。

我见过最隐蔽的一次是"能 ping 通但 HTTP 请求超时",最后定位到是 MTU 1500 碰上链路 1450,握手包能过,数据包被丢。验证方法很简单,用ping -M do -s 1472逐级往下试,找到临界值就能确认。

5.4 排障速查表

现象优先检查点常用命令
虚拟机起不来QEMU 日志、磁盘权限、SELinux 上下文virsh start --console、tail -f /var/log/libvirt/qemu/*.log
虚拟机卡顿steal time、IO 等待、内存回收virsh domstats、vmstat 1、iostat -x 1
容器退出码 137cgroup 内存上限、全局 OOMdmesg -T、memory.events
容器 CPU 被压cpu.max配额、线程数cat .../cpu.max、top -H
端口不通监听地址、端口映射、转发规则ss -lntp、conntrack -L
大包不通MTU 不一致ping -M do -s 1472
镜像拉取慢本地缓存、镜像层数docker image history
磁盘爆满镜像层、日志、可写层、快照链du -sh、virsh snapshot-list

6. 性能与安全的实践取舍

6.1 性能开销:数字比感觉可靠

关于"虚拟化损耗多少"这个问题,我的经验是分场景看,别迷信单一数字。纯 CPU 计算型负载,在硬件辅助虚拟化加 virtio 的配合下,损耗通常落在个位数百分比,跑基准测试和物理机差距很小;网络 IO 的损耗相对明显,小包高并发场景下能到 20% 以上,用 virtio 加多队列能把差距拉回来一部分;磁盘 IO 受影响最大的是随机小写,尤其在不预分配、快照链很长的情况下,延迟能翻倍。

容器侧的开销主要来自两处:一是系统调用被 seccomp 过滤或被安全模块拦截带来的额外判断,二是叠加文件系统在小文件写入时的复制上抬。我做过对比,同样一个写日志密集的服务,容器里把日志写到挂载卷比写可写层,吞吐能高出一截。所以性能这件事的最优解往往不是调内核参数,而是先把"数据写到哪"这个决策做对。

要自己测就测最小路径:CPU 用计算密集型基准工具跑同规模任务比对耗时,磁盘用随机读写工具测 4K 和 1M 两档,网络用打流工具测小包和大包两档。每次只改一个变量,否则测出来的数字没法归因。

6.2 容器安全的最小实践清单

容器不构成安全边界这句话,值得贴在每个人的显示器上。共享内核意味着内核漏洞是所有容器共同的风险面,所以下面这几条我建议变成强制规范:基础镜像只从内部可信仓库拉,并且锁摘要而不是标签;容器以非 root 用户运行,必要时用用户命名空间做映射;启动时统一--cap-drop ALL再按需加回,绝大多数业务一个都不需要;根文件系统只读,需要写的目录显式挂临时卷;禁止挂载运行时套接字进容器,那等于把宿主的控制权交出去;给所有容器加上禁止提权的选项。

再往上还有两层加固:系统调用白名单,把不需要的调用直接拒掉;强制访问控制,用宿主的安全模块给容器进程打上标签。这两层配置成本高一些,但对外暴露的服务值得做。虚拟化在这里的优势就很明显了,它有真正的硬件边界,同样是被攻破,影响面差一个量级。

6.3 混合架构里我实际踩过的边界

最后说几个只有实际跑起来才会遇到的问题。第一,时间同步。虚拟机里如果没配好时钟源,长时间运行会漂移,容器镜像构建和日志排序都会受影响,统一走宿主的时间同步服务最省事。第二,磁盘水位。虚拟机磁盘是稀疏文件,看着 40G 实际占用可能只有 5G,du和df的数字对不上很常见,监控要盯实际占用而不是表观大小。第三,快照别当备份用,快照和原盘在同一块存储上,存储坏了两个一起没。第四,容器里跑的服务要显式处理优雅退出,否则每次发版都会有一批请求被硬切断,这在滚动更新的场景下会被放大成用户可感知的故障。

资源规划上还有个反直觉的点:高密度容器宿主不是越密越好。我见过一台机器上塞了两百多个容器,单看 CPU 和内存都还有余量,但内核的网络栈和追踪表在高并发下成了瓶颈,表现为偶发的连接建立失败。把密度降下来,问题自己就没了。虚拟化和容器化都是工具,能不能跑稳,取决于你有没有在规划阶段就把边界和余量想清楚。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进