容器 A 与容器 B 各自看到 PID 1 进程和自己的回环地址,同时使用守护进程所在环境的同一个 Linux 内核。

容器把进程隔离机制与共享 Linux 内核结合起来

常规 Linux 容器通过守护进程所在宿主的 Linux 内核运行进程,并利用命名空间提供选定的隔离视图。镜像文件提供用户态环境,不会给每个容器单独启动一个内核。在 macOS 的 Docker Desktop 中,守护进程和 Linux 容器位于 Linux 虚拟机里,所以容器共享的是该虚拟机内核,而不是 macOS 内核。讨论“宿主”时必须说清楚这一层。 图示普通独立命名空间配置,容器共享守护进程一侧的 Linux 内核;命名空间、资源限制与安全策略解决不同问题。 坏掉的样子 假定每个容器都有独立操作系统内核,会导致错误诊断。如果守护进程所在 Linux 环境缺少某个内核能力,仅换成包含另一发行版用户态文件的镜像,并不会让该能力出现。反过来,升级镜像内的软件包,也不会升级宿主内核。这个区别影响系统调用、内核模块、安全边界和资源统计,不能用容器里显示的发行版名字代替判断。 命名空间隔离可以配置,不是每次部署都完全相同的固定组合。容器可以加入其他网络命名空间、使用宿主网络、共享卷,或者采用不同的用户命名空间映射。看到容器内 UID 为零,也不能直接判断容器外权限;必须结合能力集合、用户映射、安全策略和暴露资源分析。一个数字或名称不足以描述完整安全边界。 localhost 也是常见误区。在独立网络命名空间下,127.0.0.1 指向调用容器自己的回环空间,不会自动到达兄弟服务或开发者电脑。同样,在一个普通容器的可写层创建文件,不会自动出现在使用同一镜像的另一个容器里。排查访问失败时,应先分清地址和文件究竟属于哪一个视图。 快速验证 取得固定的 Python 镜像后,使用宿主 Python 和 Docker 守护进程运行脚本。实测 Docker 为 29.3.1,Docker Desktop 虚拟机内 Linux 为 6.12.76,镜像内 Python 为 arm64 的 3.11.16。两个容器使用相同镜像、不同主机名,并设置无外部网络。结束时只删除这两个临时容器: ...

十二月 7, 2026 · 1 分钟 · XB Field Notes
三条 cgroup 控制器路径分别表示 CPU 带宽节流、内存回收及可能的 OOM、任务创建拒绝;预算属于同一层次。

cgroup v2 的不同资源预算有不同失败方式

cgroup v2 用统一层次组织资源控制。一个工作负载的多个进程可以共享合计预算,限制还会沿层次继承,而不是只对某个特定 PID 生效。CPU 带宽、内存用量和任务数量由不同控制器管理,执行方式也不同。读取真实 cgroup 文件和失败计数器,比把所有配置上限都当作同一种“到点就停”更能解释实际行为。 坏掉的样子 cpu.max 按周期限制 CPU 带宽。控制组用完配额后,可运行工作可能被节流,但这不会预留一颗专用处理器,也不保证某个请求延迟。多个线程可以很快消耗共享配额。CPU 权重则影响竞争情况下的相对分配;混淆权重、配额和 CPU 亲和性,会让容量规划建立在错误预期上。 内存上限不会统一把大分配变成可由应用干净处理的错误。到达 memory.max 时,内核可以回收内存;无法降低用量时,可能触发控制组 OOM 机制。某些分配路径还有其他失败方式。memory.high 施加回收压力和节流,本身不会调用 OOM,因此不能把这两个设置解释成同义的硬终止阈值。 任务上限也包括线程,不只是监控面板显示的应用进程数量。达到 pids.max 后,即使 CPU 和内存看似充足,fork 或 clone 仍可能失败。层次限制意味着子组配置更大,也不能简单覆盖祖先更紧的预算。解释可见文件时,需要确认对应路径与控制器委派关系,不能只读取一个数值就断定完整有效上限。 CPU 带宽、内存压力与任务计数使用不同上限;退出码 137 不能单独证明 OOM。 快速验证 先取得固定镜像,再用 Python 和采用 cgroup v2 的 Docker 守护进程运行脚本。实测为 Docker 29.3.1、Linux 6.12.76、Python 3.11.16。所有限制只施加到可丢弃实验容器。脚本执行受限的小规模内存耗尽测试,清理自己的容器,不改变宿主全局 cgroup 设置,也不修改其他服务的预算: ...

十一月 10, 2026 · 2 分钟 · XB Field Notes
选定程序输出从较大的构建阶段进入具有自身基础和必要运行文件的运行阶段;编译器与源码不会自动跟随。

多阶段构建需要经过测试的运行边界

多阶段 Dockerfile 可以在一个阶段编译软件,再把选定输出复制到另一个运行阶段。最终阶段继承自己的基础镜像和明确复制的文件,而不会自动继承构建器里的编译器、源码目录和软件包缓存。这建立了有用的边界,但作者仍必须识别程序真正执行时需要的一切。文件复制成功,只能证明文件已进入产物,不能证明运行环境已经完整。 坏掉的样子 即使 ELF 解释器和共享库缺失,COPY 仍可成功把动态链接程序放进 scratch 镜像。构建可能正常结束,因为复制不会执行程序,也不会验证全部运行依赖图。随后启动却可能报告 no such file or directory,尽管配置路径上的可执行文件确实存在。此时需要检查解释器和库,而不是只反复确认程序文件是否被复制。 脚本需要解释器,应用可能需要证书根或时区数据,还有程序只在特定请求路径上加载插件或资源。简单启动测试可能漏掉这些较晚发生的需求。选择小型运行基础镜像,只有在它满足应用真实契约时才有价值,包括受支持架构和必要文件路径。不能因为某个镜像最小,就默认它适合所有应用。 早期阶段不属于最终镜像的继承链,也不意味着它被安全擦除。构建缓存和明确导出的中间镜像,仍可能保留其内容。敏感构建输入需要正确的密钥传递方式,复制输出也必须检查是否意外包含不该分发的材料。多阶段结构控制最终文件系统组成,却不会自动保证整个构建过程的保密性。 窄范围 COPY 控制最终文件系统组成,既不验证运行依赖完整,也不会安全擦除构建缓存。实时软件包来源不能承诺编译器字节永久相同。Docker 示例执行仍受阻,本次未重跑。 快速验证 历史 Docker 示例保持原样;当前没有 Docker 运行环境,因此本次没有重跑。 使用 Python、Docker、固定的 Python 基础镜像,以及供构建阶段安装 Debian 软件包的网络运行脚本。运行容器关闭网络。实测是 Docker 29.3.1、Linux arm64,GCC 为 Debian 12.2.0-14+deb12u1 包提供的 12.2.0。软件仓库是实时仓库,因此实验验证运行组成,不承诺未来永远取得完全相同的编译器字节: ...

九月 25, 2026 · 2 分钟 · XB Field Notes