cgroup v2 与 namespace 隔离

共 28 题
📑 题目列表 28 题
#
★★★

1. Linux namespace 的 Mount、PID、Network、UTS、IPC、User 六种

列举 Linux 的六种 namespace(Mount、PID、Network、UTS、IPC、User),并说明各自隔离的内容?

  • 理解每种 namespace 的隔离对象
  • 理解 namespace 的创建接口(clone/unshare)
  • 理解容器隔离的基础

Linux 的 namespace 提供进程视角的隔离,使进程拥有独立的资源视图。六种经典 namespace:Mount(mnt)隔离挂载点,使进程看到不同的文件系统挂载树;PID 隔离进程 ID,使进程拥有独立的 PID 命名空间(可见独立的 PID 1);Network(net)隔离网络栈(网卡、IP、路由、socket),使进程拥有独立的网络环境;UTS 隔离主机名与域名(hostname);IPC(ipc)隔离进程间通信(System V IPC、POSIX 消息队列等);User(user)隔离 UID/GID 映射,允许非 root 用户拥有虚拟 root 权限。它们通过 clone(CLONE_NEW*) 或 unshare 创建,通常组合用于容器(如 Docker 组合 mnt/pid/net/uts/ipc/user 等)。加上 Time(5.6)共七种及 cgroup namespace。namespace 是"视图隔离",只改变进程看到的资源,不限制资源用量。

namespace 是"视图隔离"的基石,每种隔离一个资源维度。理解六种 namespace 的职责与创建接口,是理解容器隔离原理的基础。

#
★★

2. cgroup v2 的 cpu、memory、io 三大控制器 v2 形式

说明 cgroup v2 的 cpu、memory、io 三大控制器各自的 v2 形式与核心文件?

  • 理解 cpu 控制器的 cpu.max/cpu.weight
  • 理解 memory 控制器的 memory.max/high/low/current
  • 理解 io 控制器的 io.max/io.weight

cgroup v2 中三大资源控制器以统一层级组织。cpu 控制器:通过 cpu.max(quota/period 绝对上限)、cpu.weight(相对权重)、cpu.stat(统计)控制 CPU 用量与带宽。memory 控制器:通过 memory.max(硬上限)、memory.high(软节流)、memory.low(保护)、memory.current(当前用量)、memory.min(硬保护)、memory.swap.max 等控制内存;memory.events 记录事件。io 控制器:通过 io.max(按设备设置的绝对上限,如 rbps/wbps/riops/wiops)、io.weight(相对权重)、io.stat(统计)、io.latency 控制块设备 IO。三者都遵循 cgroup v2 的"统一层级 + 子树委派"模型,控制器在子 cgroup 默认关闭,需在 parent 的 subtree_control 中启用。三大控制器共同构成容器资源管理的基础。

三大控制器分布在统一层级,各有"上限(绝堆)+权重(比例)"双通道。理解 cpu/memory/io 的 v2 文件与语义,是容器资源治理的核心。

#
★★

3. cpu cgroup 的 cpu.max(quota + period)的带宽限制

解释 cpu cgroup 的 cpu.max(quota + period)如何实现 CPU 带宽限制?

  • 理解 quota/period 的语义
  • 理解 quota 提供绝对上限
  • 理解 throttle 现象

cpu.max 以 "quota period" 两个值限制 cgroup 内所有任务可用的 CPU 带宽。period 是周期长度(微秒,默认 100000us=100ms),quota 是每个周期内可用的 CPU 时间(微秒)。例如 50000 100000 表示每 100ms 周期内最多运行 50ms,即最多使用 50% 的 CPU(一个核)。quota 是绝对上限:即使系统空闲,任务也不能超过该配额。当 cgroup 内任务在周期内用尽 quota 而仍有运行需求时,会被暂停(throttle)直到下一周期开始,表现为 CPU 节流。quota 可以大于 period(允许超一个核,如 200000 100000 表示 2 核),"-1" 表示不限制。通过 cpu.stat 的 nr_throttled 可观察节流次数。cpu.max 用于为容器设定明确的 CPU 上限,防止其占用过多 CPU。

quota/period 是"周期内配额"的带宽控制模型。理解 quota 的绝对上限与 throttle 触发,是容器 CPU 限流与排查 throttle 抖动的基础。

#
★★

4. Mount namespace 的 pivot_root 与容器根文件系统

解释 Mount namespace 中的 pivot_root 系统调用及其在容器根文件系统切换中的作用?

  • 理解 pivot_root 把根文件系统切换到新目录
  • 理解容器启动时切换根目录
  • 理解 pivot_root 与 chroot 的差异

Mount namespace 让容器拥有独立的挂载树,pivot_root 是容器启动时把根文件系统切换为容器镜像根目录的关键系统调用。pivot_root(new_root, put_old) 会把当前根文件系统(原来的根)放到 put_old 指定的挂载点,并把 new_root 设为新的根文件系统,同时维护挂载树的完整性。容器运行时(如 runc)在创建 Mount namespace 后,用 pivot_root 把容器根切换到镜像层,使容器进程看到独立的根文件系统,且原根被挂到 put_old(随后可卸载)。相比 chroot:pivot_root 是内核级、更彻底地切换根,能避免 chroot 逃逸(进程可通过打开已引用的 fd 逃出 chroot),且 pivot_root 要求 new_root 是挂载点;chroot 只是改变根路径,安全性较弱。因此现代容器用 pivot_root 而非 chroot。

pivot_root 是"内核级根切换",配合 Mount namespace 保证容器根隔离且防逃逸。理解它与 chroot 的差异是容器安全与根文件系统管理的核心。

#
★★

5. 如何结合 PSI 与 cgroup 指标判断容器是 CPU 受限、内存受限还是 IO 受限,从而选择扩容维度?

说明如何结合 PSI 与 cgroup 指标判断容器是 CPU 受限、内存受限还是 IO 受限,从而选择扩容维度?

  • 理解 PSI 的 cpu/memory/io 三类压力
  • 理解 cgroup 的 cpu.max/memory/io 指标与 throttle 事件
  • 理解根据压力来源选择扩容维度

判断容器瓶颈来源需结合 PSI 与 cgroup 指标。PSI 提供 cpu、memory、io 三类压力(some/full 比例),位于 /proc/pressure/ 与 cgroup 的 cpu.pressure/memory.pressure/io.pressure。结合 cgroup 指标:若 CPU 受限,看 cpu.stat 的 nr_throttled 与 throttled_time 增长(cpu.max 触发节流)、cpu.pressure 高;若内存受限,看 memory.events 的 high/max/oom 事件、memory.pressure 高、或 swap 活跃;若 IO 受限,看 io.stat 的延迟与 io.pressure 高、io.max 限流。扩容维度选择:CPU 受限(throttle 明显)→ 增加 CPU 配额或核数;内存受限(high/max/OOM + memory pressure)→ 提升 memory.max 或扩容内存;IO 受限(io.pressure 高 + io 延迟)→ 提升 IO 配额或换更快存储。综合各类压力与事件,可准确判断瓶颈是 cpu/memory/io 并定向扩容。

PSI 给出"哪类资源造成停滞",cgroup 事件给出"是否触发了限制"。两者结合可精确定位瓶颈维度,避免盲目扩容(如 CPU 转圈却因内存受限)。

#
★★

6. CPU 带宽控制(cpu.max)在突发负载下为何会出现 throttle 抖动,burst 参数如何缓解?

说明 CPU 带宽控制(cpu.max)在突发负载下为何会 throttle 抖动,以及 burst 参数如何缓解?

  • 理解 quota/period 的刚性限制导致突发节流
  • 理解 burst 借配额机制
  • 理解 burst 的使用与权衡

cpu.max 的 quota/period 是刚性限制:在周期内 quota 用尽后,即使系统空闲,任务也必须等下一周期,导致突发负载(短时间内需要远超平均配额)被 throttle,表现为吞吐抖动与延迟尖刺。burst 参数(cpu.max 的第三个值,Linux 5.15+)通过"借用配额"缓解:允许任务在突发时使用超过 quota 的 CPU,其"借用"的量会从未来的周期中扣除(即降低未来可用配额),从而在 quota 的平均值内平滑突发。例如 cpu.max 50000 100000 200000 表示每周期允许突发到 200ms 的预算、平均仍受 50ms 限制。burst 缓解了突发负载的节流抖动,但代价是"提前透支"未来配额,可能延续后续节流。适合周期性低负载 + 偶发高峰的负载(如编译、批处理),需根据平均利用率与突发量配置。

burst 是"借配额"机制,把刚性 quota 变为允许短期超额、长期平均受限。理解它缓解突发 throttle 的原理与透支权衡,是 CPU 限流精细调优的关键。

#
★★

7. cgroup v2 的 namespace(cgroup namespace),容器内可见的 cgroup 路径隔离

解释 cgroup namespace 的作用,以及它如何隔离容器内可见的 cgroup 路径?

  • 理解 cgroup namespace 提供伪根路径
  • 理解容器内 /proc/self/cgroup 的显示
  • 理解 cgroup namespace 与 cgroup 层级的关系

cgroup namespace 是 Linux 4.6 引入的 namespace,用于隔离进程看到的 cgroup 层级路径。它通过提供一个"伪根"(cgroup root),使 namespace 内的进程看到的 cgroup 路径从该伪根开始,而不是宿主机的绝对路径。例如容器内进程的 /proc/self/cgroup 显示的路径可能只是 "0::/",而不是宿主机上的 "/docker/abc123"。这样既隐藏了宿主机的 cgroup 结构,也让容器内进程无法感知宿主机层级。cgroup namespace 与 cgroup v2 的"统一层级"配合:创建 cgroup namespace 时指定某个 cgroup 作为根,namespace 内进程的 cgroup 视图相对该根。它不改变实际的资源限制,只改变"视图"(路径隔离),主要用于容器与安全(隐藏宿主层级)。cgroup namespace 可单独通过 unshare(CLONE_NEWCGROUP) 创建。

cgroup namespace 是"视图隔离",隐藏宿主 cgroup 路径。理解它不改变限制、只改变路径显示,是正确理解容器 cgroup 视图与安全的关键。

#
★★

8. io cgroup 的 io.max 与 io.stat 的 PSI 指标

解释 io cgroup 的 io.max 与 io.stat 文件,以及它们如何与 PSI 的 io 指标配合?

  • 理解 io.max 按设备设置 IO 上限
  • 理解 io.stat 提供 IO 统计
  • 理解 io.pressure 反映 IO 压力

io 控制器(io)在 cgroup v2 中通过 io.max 限制块设备 IO:io.max 以 "major:minor rbps=... wbps=... riops=... wiops=..." 形式按设备设置读写带宽与 IOPS 上限,提供绝对限制。io.stat 提供该 cgroup 的 IO 统计(读写字节数、IO 次数、延迟等),用于观测实际 IO 用量。配合 PSI 的 io.pressure(/proc/pressure/io 或 cgroup io.pressure),其 some/full 反映"因 IO 阻塞导致任务停滞的比例",可直接判断 IO 是否成为瓶颈。综合分析:io.max 触发限流 / io.pressure 高,说明 IO 受限;io.stat 显示高延迟可定位 IO 压力来源。io.weight 则提供相对权重分配。三者的配合能判断容器是 IO 受限还是其他瓶颈,并决定是否提升 IO 配额或更换存储。

io.max 是 IO 上限,io.stat 是 IO 用量,io.pressure 是 IO 压力。三者结合能定位 IO 瓶颈并指导扩容,是容器 IO 治理的关键。

#
★★

9. User namespace 的 UID/GID 映射(rootless 容器)

解释 User namespace 的 UID/GID 映射机制,以及它如何支撑 rootless 容器?

  • 理解 uid_map/gid_map 的映射
  • 理解容器内 root 与宿主非 root 的映射
  • 理解 rootless 容器的安全原理

User namespace 通过 UID/GID 映射(uid_map/gid_map 文件)把容器内的 UID/GID 映射到宿主机的 UID/GID。映射文件格式为 "inside_start outside_start length",例如 "0 100000 65536" 表示容器内 UID 0-65535 映射到宿主 UID 100000-165535。这样容器内"root"(UID 0)在宿主机上对应一个非 root 的高位 UID,容器进程在宿主上实际没有 root 权限,从而提升安全性。rootless 容器正是基于此:非 root 用户创建 User namespace,并利用映射让自己的容器内拥有虚拟 root 权限(可执行需 root 的操作),但宿主机视角仍是普通用户,无法影响宿主其他进程。User namespace 还允许非特权用户创建网络、mount 等 namespace(配合映射),是实现 rootless 容器(如 podman、Docker rootless)的核心。设置映射需写 uid_map 与 gid_map,且受权限与唯一映射限制。

User namespace 通过 UID/GID 映射实现"容器内 root = 宿主非 root",是 rootless 容器与安全隔离的基础。理解映射方向与权限是掌握容器安全模型的关键。

#
★★

10. Time namespace(Linux 5.6+)的容器内时钟偏移

解释 Time namespace(Linux 5.6+)的作用,以及它如何提供容器内时钟偏移?

  • 理解 Time namespace 隔离系统时钟
  • 理解 boottime 与 monotonic 的偏移
  • 理解容器迁移/时钟一致性场景

Time namespace(Linux 5.6+)允许进程拥有独立的时钟视图,主要是对 boottime(启动时间)与 monotonic(单调时钟)提供偏移。通过偏移,容器内进程看到的时钟与宿主机不同。典型场景:容器迁移(如 CRIU 实时迁移)时,进程的单调时钟若跨主机翻转会破坏基于时间的逻辑(如定时器、超时),Time namespace 通过偏移让迁移后的进程看到连续的时钟。另外可让容器内进程看到"虚拟化"的启动时间。实现上,Time namespace 保存对 boottime/monotonic 的偏移量,进程读取时钟时内核加上偏移。注意:wall clock(REALTIME,如 date 显示的墙上时间)在 Time namespace 中不隔离,仍共享宿主时钟(REALTIME 由其他机制处理)。Time namespace 用于容器时间一致性,与 UTS 的 hostname、Proc 等 namespace 配合。

Time namespace 通过偏移隔离 boottime/monotonic,保证容器(尤其迁移)内时钟一致性。理解它不隔离 REALTIME 的边界,是掌握容器时间语义的关键。

#
★★

11. cgroup v2 的 domain controller 与 threaded controller 有何区别,delegation 机制如何支持容器运行时安全分权?

说明 cgroup v2 的 domain controller 与 threaded controller 的区别,以及 delegation 机制如何支持容器运行时安全分权?

  • 理解 domain 与 threaded 控制器的区别(线程 vs 进程级)
  • 理解 threaded 控制器用于线程制资源
  • 理解 delegation 委派机制

cgroup v2 把控制器分为 domain 与 threaded 两类。domain controller 是"进程级"的,作用于整个 cgroup(以进程为粒度),如 memory、cpu、io 等;它要求 cgroup 没有内部进程("no internal process"约束),只能控制叶子节点。threaded controller 是"线程级"的,作用于 cgroup 内的线程(如 cpu controller 的线程模式、perf_event、pids 在某些配置下),允许在 threaded 层级中控制个别线程,且线程可以在同一线程组内迁移。threaded 控制器通过 cgroup.threaded 标志启用,用于需要对线程粒度资源进行控制(如 CPU affinity 按线程)。delegation(委派)是 cgroup v2 的安全分权机制:父 cgroup 的拥有者(如 root 容器运行时)通过 subtree_control 把某些控制器委派给子 cgroup 的 owner,被委派者(如容器运行时、非特权用户或容器)可以管理其子树内的 cgroup 而无需 root 权限,从而实现"容器运行时在分配好的子树内管理容器资源"的安全分权。delegation 要求委派方与受托方符合权限规则(如 cgroup 的 uid/gid 匹配)。

domain 控制进程级、threaded 控制线程级,delegation 实现资源控制的安全分权。理解二者差异与委派机制,是容器运行时与 systemd 资源管理的基础。

#
★★

12. pids 控制器如何限制容器内进程与线程总数,fork bomb 如何被 pids.max 拦截?

解释 pids 控制器如何限制容器内进程与线程总数,以及 fork bomb 如何被 pids.max 拦截?

  • 理解 pids.max 限制进程/线程总数
  • 理解 pids.current 统计当前数量
  • 理解 fork bomb 被 pids.max 拦截

pids 控制器(pids)用于限制 cgroup 内同时存在的进程与线程总数。pids.max 设置上限,pids.current 显示当前数量,pids.events 记录事件(max 触发次数)。当 cgroup 内进程/线程数达到 pids.max,新的 fork/clone 会失败(返回 EAGAIN),从而阻止进程无限增长。fork bomb(不断 fork 子进程耗尽进程表)正是被 pids.max 拦截:容器内 fork 达到上限后,后续 fork 失败,进程无法继续爆炸,保护了宿主机的 PID 空间与系统稳定性。容器运行时通常为容器设置 pids.max(如 1000),防止容器内进程失控。注意 pids 控制器统计的是活的进程与线程数(含线程),因此也能限制线程总数。pids 控制器是轻量控制器,不依赖 cgroup v2 的其他资源。

pids.max 通过限制进程/线程总数,在 fork 达到上限时让 fork 失败,从而拦截 fork bomb。理解其计数范围与失败行为,是容器进程安全防护的关键。

#
★★

13. memory.high 与 memory.max 在触发回收与触发 OOM 上的语义差异,为何 high 被推荐为软性节流阀?

说明 memory.high 与 memory.max 在触发回收与触发 OOM 上的语义差异,以及为何 high 被推荐为软性节流阀?

  • 理解 memory.max 触发回收与 OOM
  • 理解 memory.high 触发软性节流
  • 理解 high 作为软性节流阀的价值

memory.max 与 memory.high 都触发内存回收,但语义不同。memory.max 是硬性上限:超过后内核强制回收,若回收后仍无法满足则触发 OOM(杀进程),是硬约束。memory.high 是软性上限:超过后内核主动回收并可能对分配施加节流(throttle),但不会立即 OOM,允许短时超过(可运行此后再回收),因此是"软性节流阀"。为何 high 被推荐为软性节流阀:在内存快满但尚未过限时,high 提前触发回收与节流,让内存增长平滑、避免猛然冲到 max 触发灾难性 OOM;同时 throttling 提供 backpressure,让应用感知压力并调整。相比 max 的"一刀切",high 让容器在压力下仍能继续运行(只是更慢),适合作为平时的软性控制,max 作为兜底硬限。生产环境常设置 high 略低于 max,让 high 先平滑节流、max 兜底防 OOM。

核心是"max 触发 OOM、high 触发节流"。high 让内存压力以平滑方式呈现(backpressure),而非直接杀进程,因此是软性节流阀。理解二者语义是容器内存调优的关键。

#
★★

14. cgroup v1 与 v2 的设计哲学差异,单一 hierarchy vs 多 hierarchy

对比 cgroup v1 与 v2 的设计哲学差异,重点说明单一 hierarchy(v2)与多 hierarchy(v1)的区别?

  • 理解 v1 的多 hierarchy 与控制器分散
  • 理解 v2 的统一 hierarchy 与控制器合并
  • 理解 v2 的改进(no internal process、线程模式)

cgroup v1 与 v2 的设计哲学差异显著。v1 采用多 hierarchy(多层级):每个控制器(cpu、memory 等)可以挂载到独立的 hierarchy 树,一个进程可同时属于多个 cgroup 树,控制器可分散在不同层级,这种设计灵活但复杂、易造成不一致(如内存与 CPU 在不同树中难以协同约束),且不同控制器层级不统一。v2 采用单一(统一)hierarchy:所有控制器都挂在同一个层级树上,一个进程严格属于该树的一个 cgroup,控制器在统一的层级树上按需启用(subtree_control),从而保证一致性、简化管理与迁移。v2 还引入"no internal process"约束(非叶子节点不能有进程,只能用于组织)、线程模式(threaded controller)、以及更统一的文件接口(如 cpu.max、memory.max)。v2 想解决 v1 的复杂性、不一致与扩展性问题,是当前容器与 systemd 使用的主流。

v1 多树灵活但复杂,v2 单树统一、一致、易管理。理解二者的哲学差异(多 hierarchy vs 单一 hierarchy)是理解 cgroup 演化的核心。

#
★★

15. cgroup v2 的统一层级(unified hierarchy)与 subtree_control 委派

解释 cgroup v2 的统一层级(unified hierarchy)以及 subtree_control 的委派机制?

  • 理解统一层级的结构
  • 理解 subtree_control 启用/关闭控制器
  • 理解委派与权限控制

cgroup v2 的统一层级(unified hierarchy)把整个系统组织为单棵 cgroup 树,所有控制器都在这一棵树上的各 cgroup 点按需启用。subtree_control 是控制"哪些控制器在 cgroup 的子节点中生效"的文件:通过写入例如 "cpu memory" 来启用指定的控制器,使该 cgroup 的子节点能使用这些控制器,同时它也隐含了委派——非 root 的 cgroup 拥有者可以在其子树中启用/配置控制器,而无需 root 权限。subtree_control 的委派机制是安全分权的核心:父 cgroup 的 owner 决定把哪些控制器委派给子树,子树 owner 据此管理其下资源。统一层级 + subtree_control 解决了 v1 多树不一致的问题,并让容器运行时(在获得委派后)能自主管理其容器子树的资源。注意:控制器在子 cgroup 默认关闭,需在父的 subtree_control 中显式启用。

统一层级保证单树一致性,subtree_control 提供逐层启用的控制器与委派。理解二者是容器运行时与 systemd 管理 cgroup 资源的基础。

#
★★

16. memory cgroup 的 memory.peak 与 memory.current 观测

解释 memory cgroup 的 memory.peak 与 memory.current 的观测与用途?

  • 理解 memory.current 是全量当前用量
  • 理解 memory.peak 记录峰值用量
  • 理解用峰值判断内存上限配置

memory.current 显示 cgroup 当前的内存用量(匿名页 + 页缓存 + 内核内存等,不含 swap),是实时观测值。memory.peak(Linux 5.19+)记录该 cgroup 自创建以来(或自上次重置)达到的内存峰值,用于了解历史最大用量。memory.peak 的用途:判断应用内存的真实峰值,从而合理设置 memory.max/high 上限(避免设太高浪费或设太低导致 OOM);对比当前与峰值,分析内存是否回落或持续增长;在内存压力诊断中,peak 能揭示"是否曾超过预设的 high"而不只是当前值。memory.peak 可通过读 memory.peak 获取,写回可重置(或由 cgroup 生命周期重置)。配合 memory.current 与 memory.events(high/max/oom),可全面掌握容器内存行为。

current 是"现在多少",peak 是"最多用过多少"。peak 帮助设置合理上限并诊断峰值内存压力,是容器内存观测的重要补充。

#
★★

17. unshare 与 clone 新 namespace 的工程应用

说明 unshare 与 clone 创建新 namespace 的差异及工程应用?

  • 理解 clone 在创建子进程时进新 namespace
  • 理解 unshare 让当前进程进新 namespace
  • 理解两者在容器/工具中的应用

unshare 与 clone 是创建 namespace 的两种方式。clone(以及 clone3)在创建子进程时通过 CLONE_NEW* 标志让新子进程进入新的 namespace,进程本身保留在父 namespace,适合"启动一个隔离的新进程"(如容器启动、沙箱)。unshare 则让"当前进程"自己进入新的 namespace(不创建新进程),通过 CLONE_NEW* 标志执行,适合"把当前程序/会话隔离"(如 unshare 命令、解绑 mount;unshare 命令行工具配合 --fork 时才先 fork 出子进程)。工程应用:容器运行时用 clone 创建隔离的容器进程;沙箱与工具(如 unshare -m、unshare -p)用 unshare 把当前 shell 或命令放入新 namespace;systemd-run、nsenter 等工具也涉及。选择:需要新进程隔离用 clone,需要当前进程(或子进程)进入新 namespace 用 unshare。两者都通过 CLONE_NEW* 标志指定要创建的 namespace。

clone 是"子进程进新 namespace",unshare 是"当前进程进新 namespace"。理解二者的适用场景(启动新进程 vs 隔离当前)是容器/沙箱工具设计的基础。

#
★★

18. Network namespace 的独立网络栈与 veth pair

解释 Network namespace 的独立网络栈,以及 veth pair 如何连通不同网络命名空间?

  • 理解 Network namespace 隔离网络栈
  • 理解 veth pair 是一对虚拟网卡
  • 理解容器网络连接(veth + bridge)

Network namespace 让每个命名空间拥有独立的网络栈:独立的网卡、IP 地址、路由表、防火墙(iptables/nftables)、socket 等,使容器拥有"自己的网络"。为让不同 namespace(如容器与宿主机)通信,需要虚拟网络设备。veth pair 是一对虚拟网卡(veth0/veth1),数据从一个端进入会从另一端输出,把两个 namespace 连接起来:一端放在容器 namespace,另一端放在宿主机(或 bridge)。典型容器网络:容器内的 veth 端配 IP,宿主端的 veth 连到 bridge(如 docker0),bridge 再与宿主路由连接,使容器能通信并访问外网。veth pair 是"点对点虚拟网线",常用于容器/虚拟机网络。网络命名空间还支持 bridge、veth、macvlan、ipvlan 等虚拟设备。理解 veth pair 与 bridge 的组合是容器网络的基础。

Network namespace 隔离网络栈,veth pair 充当连通两个 namespace 的"虚拟网线"。理解二者是容器网络(bridge 模式)实现的核心。

#
★★

19. PID namespace 与进程 ID 隔离(独立 PID 1)

解释 PID namespace 的实现,以及容器内为何看到独立的 PID 1?

  • 理解 PID namespace 隔离进程 ID
  • 理解容器内 PID 1 的语义
  • 理解 PID 映射与 init 信号处理

PID namespace 让不同命名空间内的进程拥有独立的进程 ID 视图。容器内的进程从 PID 1 开始编号,容器内的 PID 1 是 PID namespace 的 init 进程;宿主机上看到的进程 ID 与外层 namespace 不同,内核通过层级映射(每层一个 PID 映射表)记录各层看到的 PID。容器内 PID 1 的特殊语义:它是该 namespace 的 init,负责回收孤儿进程(reap),并处理终止信号(如 SIGTERM/SIGKILL);当 PID 1 退出时,整个 namespace 的进程都被终止(namespace 销毁)。PID namespace 还带来进程可见性隔离:容器内看不到宿主机其他进程,只能看到本 namespace 的进程。PID 1 与 init 进程对容器生命周期至关重要(容器退出前 PID 1 需正确回收与处理信号)。PID namespace 通过 CLONE_NEWPID 创建,嵌套时形成层级。

PID namespace 隔离 PID 视图,容器内 PID 1 是 init(负责回收与信号处理)。理解 PID 1 的生命周期职责是容器运维(如 PID 1 僵尸问题)的关键。

#
★★

20. cgroup v2 的 BPF 子系统(cgroup_skb_md)的高级用例

说明 cgroup v2 的 BPF 子系统(cgroup_skb_md)及其高级用例?

  • 理解 BPF 程序可挂载到 cgroup
  • 理解 cgroup_skb_md 上下文
  • 理解 cgroup BPF 的用例(过滤、限速、监控)

cgroup v2 支持将 BPF 程序挂载到 cgroup 上,通过 cgroup_bpf 机制对 cgroup 所属进程的网络包、socket 等执行策略。BPF 程序类型如 BPF_PROG_TYPE_CGROUP_SKB(网络包过滤)、BPF_PROG_TYPE_CGROUP_SOCK(socket 创建/连接)、CGROUP_SOCK_ADDR(bind/connect 地址)、CGROUP_DEVICE(代替 device 控制器)等。cgroup_skb_md 是 cgroup skb 程序收到的上下文(metadata),包含 cgroup 层次、接口、skb 信息等,供程序判断。高级用例:租户网络隔离——按 cgroup 用 BPF 过滤/限制容器流量;内容感知的流量整形与限速(qdisc 与 BPF 结合);容器间的访问控制(防止某容器访问特定目的地址);基于 cgroup 的流量监控与统计;以及替代传统 device/iptables 的部分功能。cgroup BPF 让资源控制与网络策略可编程化、低开销、可动态更新,是云原生网络与安全的重要方向。

cgroup BPF 把"可编程策略"代入 cgroup 层级,实现网络过滤、限速、监控等高级控制。理解 cgroup_skb_md 与挂载机制是掌握现代容器网络与安全的基础。

#
★★

21. cgroup v2 的"委派"(delegation)机制与 systemd 协同

解释 cgroup v2 的 delegation 机制以及它与 systemd 的协同方式?

  • 理解 delegation 委派资源控制权
  • 理解 systemd 在 cgroup 中管理服务
  • 理解 systemd 与容器运行时协同

cgroup v2 的 delegation(委派)机制让上级 cgroup 的 owner 把子树的资源控制权委派给子 owner(如容器运行时、非特权用户),被委派者可在其子树内启用控制器、创建与配置 cgroup,而无需 root。systemd 是 cgroup v2 的核心用法:systemd 把每个服务单元(unit)映射到 cgroup,通过 cgroup 属性(如 MemoryMax、CPUQuota)配置资源,并通过 subtree_control 把控制器委派给服务的 cgroup 子树。systemd 与容器运行时协同:容器运行时(如 systemd-nspawn、Docker、podman)在 systemd 分配的 cgroup 子树内运行,通过 delegation 获得容器资源的自主管理权,同时 systemd 仍在外层管理整体资源与生命周期。systemd 的 Delegate= 属性可显式委派 cgroup 给某 unit。二者协同实现了"systemd 管整体、运行时管容器内资源"的分层治理。

delegation 是"把子树资源权交给子 owner",systemd 利用它管理服务与容器。理解二者的协同是容器编排与系统资源管理的关键。

#
★★

22. namespace 负责“视图隔离”、cgroup 负责“资源限额”,二者的职责边界如何划分?

说明 namespace 与 cgroup 的职责边界:视图隔离 vs 资源限额?

  • 理解 namespace 是视图隔离
  • 理解 cgroup 是资源限额
  • 理解二者关系与互补

namespace 与 cgroup 是两类不同的隔离机制,职责边界清晰。namespace 负责"视图隔离"(view isolation):让进程看到独立的资源视图(挂载点、PID、网络栈、主机名、UTS、用户等),但不限制资源用量——进程在自己的 namespace 里能看到"独立的世界",但用多少资源不受限制。cgroup 负责"资源限额"(resource limitation):限制进程组的 CPU、内存、IO 等资源用量,保证公平与隔离,但不改变进程"看到的"资源视图。举例:容器用 namespace 让进程看到独立的文件系统与网络栈(视图),用 cgroup 限制其最多用几个 CPU、多大内存(资源)。二者互补:namespace 提供"看起来独立",cgroup 提供"用起来受限"。没有 cgroup 的 namespace 无法限制资源(一个容器可耗尽内存),没有 namespace 的 cgroup 无法隔离视图。因此容器通常两者结合使用。

职责边界是"看什么(namespace)vs 用多少(cgroup)"。理解二者互补关系,是正确设计容器隔离与资源治理的基础。

#
★★

23. cpu.weight 比例分配与 cpu.max 绝对上限的关系,两种限制同时存在时如何共同决定 CPU 带宽?

说明 cpu.weight 比例分配与 cpu.max 绝对上限的关系,以及两者同时存在时如何共同决定 CPU 带宽?

  • 理解 cpu.weight 是比例分配
  • 理解 cpu.max 是绝对上限
  • 理解两者共同作用时的带宽决定

cpu.weight 与 cpu.max 是两种并存的 CPU 分配机制。cpu.weight 是相对权重:在 CPU 竞争时,同层 cgroup 按权重比例分配可用 CPU,权重越高分得越多,但总量取决于系统空缺与总需求。cpu.max 是绝对上限:限制 cgroup 最多使用的 CPU 带宽(quota/period),即使系统空闲也不能超过。二者同时存在时,CPU 带宽由"取两者决定的较小者"共同决定:实际可用 CPU = min(按权重分配得到的份额, cpu.max 上限)。即:当系统 CPU 竞争不激烈、空闲充足时,权重分配能给的份额可能超过 cpu.max,此时以 cpu.max 封顶;当竞争激烈、权重给到的份额低于 cpu.max 时,以权重分配为准。因此 cpu.weight 决定了"竞争时能拿多少",cpu.max 决定了"最多能拿多少",最终带宽 = 两者中较小者,兼顾"保底比例"与"封顶上限"。

两者是"比例 share"与"绝对 cap"的叠加,实际带宽取较小者。理解此关系才能正确配置"既有优先级又有上限"的容器 CPU 策略。

#
★★

24. cgroup freezer(cgroup.freeze)如何实现容器快照与批量暂停,与 SIGSTOP 在语义上的差异?

解释 cgroup freezer(cgroup.freeze)如何实现容器快照与批量暂停,以及与 SIGSTOP 的语义差异?

  • 理解 cgroup.freeze 冻结整个 cgroup
  • 理解容器快照/批量暂停场景
  • 理解与 SIGSTOP 的差异

cgroup v2 的 cgroup.freeze 允许冻结(freeze)整个 cgroup 内所有进程,写入 1 冻结、0 解冻,cgroup.events 中 frozen 指示冻结状态。冻结后,cgroup 内所有进程停止运行(不消耗 CPU),但保留状态与内存,可用于容器快照(配合 CRIU 迁移)、批量暂停/恢复、升级维护等场景。与 SIGSTOP 的差异:SIGSTOP 是发给单个进程的信号,暂停单个进程,需逐个处理进程,且会被信号状态、子进程等干扰;cgroup.freeze 是 cgroup 层面的批量冻结,一次冻结整个 cgroup 的所有进程(含线程),粒度更粗、原子性更好,且不改变进程的信号状态,更便于批量管理。另外,cgroup.freeze 不依赖信号传递,能冻结处于不同状态的进程,且对内核线程与不可中断态进程的处理更可控。freeze 适用于容器/系统的整体暂停。

cgroup.freeze 是"cgroup 级批量冻结",比 SIGSTOP 的"单进程信号"更适合容器快照与批量暂停。理解其粒度与语义差异是关键。

#

25. io.weight(BFQ/blkio 权重)与 io.max 分别提供按比例分配与绝对上限,各自的适用与局限是什么?

说明 io.weight 与 io.max 各自提供按比例分配与绝对上限的适用场景与局限?

  • 理解 io.weight 按比例分配 IO
  • 理解 io.max 绝对上限
  • 理解两者的适用与局限

io.weight 与 io.max 是 io 控制器(cgroup v2)的两种 IO 分配方式。io.weight 提供按比例分配:IO 竞争时,同层 cgroup 按权重比例分配 IO 带宽/IOPS,权重高者分得多,适合在 IO 竞争激烈时实现"按比例公平分配",让不同租户按优先级共享 IO。其局限:只影响"竞争时"的分配,当 IO 空闲时权重无法限制某个 cgroup 独占全部 IO(无上限),且权重分配与调度器(如 BFQ、mq-deadline)实现相关。io.max 提供绝对上限:按设备设置读写带宽与 IOPS 的上限,硬性限制某 cgroup 最多使用的 IO,适合"硬性隔离/封顶",防止某容器过度消耗 IO。其局限:配置繁琐(需按设备指明限额),且不随竞争动态调整(无法利用空闲 IO)。适用:io.weight 适合多租户共享存储按比例分配,io.max 适合对关键/隔离容器做硬性 IO 上限。可结合使用(权重定比例、max 定封顶)。

io.weight 是"竞争时的比例",io.max 是"绝对的封顶"。理解二者适用(共享按比例 vs 硬性隔离)与局限(weight 无上限、max 不动态)是容器 IO 治理的关键。

#

26. IOPRIO(io_priority)在 mq-deadline/bfq 调度器下如何影响请求派发,为何在 none 调度器下基本无效?

说明 IOPRIO(io_priority)在 mq-deadline/bfq 调度器下如何影响请求派发,以及为何在 none 调度器下基本无效?

  • 理解 IOPRIO 设置 IO 优先级
  • 理解 mq-deadline/bfq 如何用优先级调度
  • 理解 none 调度器不处理优先级

IOPRIO(io_priority)是进程级 IO 优先级,通过 ioprio_set 设置,分级(RT、BE、IDLE)与优先级数值。它由块设备层的 IO 调度器(scheduler)消费:mq-deadline 调度器按优先级组织请求(读优先、按优先级排序),高优先级请求优先派发;bfq 调度器基于权重与优先级进行公平调度,高优先级进程获得更多带宽与更低延迟。因此 IOPRIO 在 mq-deadline/bfq 下影响请求派发顺序与带宽分配。none 调度器(noop/none)不对请求做排序或优先级处理,只是把 IO 请求直接传递到设备(等待硬件队列/驱动器),因此 IOPRIO 在 none 下基本无效——请求按提交顺序派发,优先级被忽略。这也是为什么现代 NVMe 等使用 none 调度器的设备上,IOPRIO 不生效,需依赖硬件 QoS 或改用 bfq/mq-deadline 才能利用优先级。

IOPRIO 由 IO 调度器消费,mq-deadline/bfq 会按优先级调度,none 直接透传不处理优先级。理解调度器差异是判断 IO 优先级是否生效的关键。

#

27. cgroup v2 的 “no internal process” 约束为何要求控制器只作用于叶子节点,迁移进程时如何遵守?

解释 cgroup v2 的 "no internal process" 约束为何要求控制器只作用于叶子节点,以及迁移进程时如何遵守?

  • 理解 no internal process 约束
  • 理解控制器只作用于叶子节点
  • 理解进程迁移时的约束

cgroup v2 的 "no internal process"(禁止内部进程)约束要求:启用了控制器的 cgroup 中,非叶子节点(有子 cgroup 的节点)不能拥有自己的进程,即进程只能存在于"叶子节点"(没有子 cgroup 的节点)。原因:cgroup v2 的控制器采用"层级聚合"模型,资源限制在叶子节点生效,父节点通过子树聚合统计;若父节点(非叶子)也有进程,则这些进程的资源归属与子树聚合冲突,无法明确归属,导致管理混乱。因此 v2 规定控制器只能作用于叶子节点,父节点只作组织与聚合。迁移进程时遵守:把进程放入 cgroup 时,必须放到底层"叶子" cgroup(无子节点)中;若目标 cgroup 有子 cgroup,则需先创建/选择叶子子 cgroup 放入,或把进程移到某叶子。同时父节点若启用控制器,就不能再直接放进程(否则违反约束)。这一约束简化了资源归属与回收。

no internal process 让"进程只存在于叶子、控制器作用叶子",父节点聚合统计。理解它约束了进程只能放叶子,是正确操作 cgroup v2 层级的关键。

#

28. memory.swap.max 与 memory.max 的协同,swap 上限为何默认与内存上限联动,内存回收时 swap 的优先级如何?

说明 memory.swap.max 与 memory.max 的协同,swap 上限为何需要纳入总量限制,以及内存回收时 swap 的优先级?

  • 理解 memory.swap.max 是 swap 上限
  • 理解 swap 上限默认与内存上限联动
  • 理解回收时 swap 与内存回收的优先级

memory.swap.max 限制 cgroup 可用的 swap 上限,memory.max 限制内存(内存页)上限。v2 中 memory.swap.max 默认值为 0(默认不允许使用 swap),需显式设置才会启用;设置后"内存 + swap"的总上限为 memory.max + memory.swap.max,防止只限制内存却无限 swap 导致总量失控。若想独立控制 swap 用量,可单独设置 memory.swap.max。设计原因:swap 与内存是"同一虚拟内存的两部分",若不在总量上限制 swap,容器可在内存耗尽后把大量数据 swap 出去,绕过内存上限,破坏资源隔离。内存回收时 swap 的优先级:回收时内核优先回收 file-backed 页(可丢弃重读),再按 swappiness 决定是否回收匿名页(swap 出去);swappiness 越高越倾向 swap 匿名页,越低越倾向保留匿名页、回收文件页。总体而言,swap 是匿名页回收的"出口",在 file-backed 页不足时才更多使用 swap。

swap 上限纳入"内存 + swap"总量限制防止绕过内存上限,回收时按代价(先文件页、swap 取决于 swappiness)选择。理解协同与优先级是容器内存与 swap 治理的关键。