Linux 容器与隔离:namespace、cgroup 与容器运行时原理

深入 Linux 容器隔离原理:namespaces(PID/网络/挂载等)、cgroup 资源限制、chroot 到容器的演进、容器运行时(runc/containerd)、镜像与分层、手写最小容器。

引言

「容器是什么?」——答案是 Linux 内核的两个特性:namespaces(隔离视图) + cgroups(限制资源)。Docker 只是给这套内核机制加了便捷的打包与分发层。本文从内核视角讲透容器:先拆解 6 大 namespaces 各自隔离什么,再讲 cgroup 如何限 CPU/内存,接着梳理从 chroot → 容器 → Docker 的演进与镜像分层原理,最后用脚本「手搓一个最小容器」,让你真正理解容器运行时的本质。

前置:/linux-process-management/(进程模型)、/linux-systemd-services/(cgroup 资源控制)。容器操作见 [[docker]]。


目录


1. 容器 = namespaces + cgroup

一句话定义:

容器 = namespaces(隔离视图,假装独占系统)
     + cgroups(限制资源,不许抢占机器)
     + 镜像(打包应用 + 依赖)

内核层面容器与普通进程的本质差异:

维度普通进程容器进程
看到进程列表全部只有自己的 namespace
看到网络全部网卡自己的网络栈
资源使用不限cgroup 限 CPU/内存
文件系统主机根镜像根目录

心智:容器进程「以为自己是唯一」——namespace 给它假象,cgroup 给它笼子,镜像给它行李。


2. namespaces:隔离「视图」

Linux 的 8 大 namespaces,各隔离一类资源:

Namespace隔离什么作用
PID进程编号容器内 PID 1 是它自己
Network网络栈独立网卡/IP/防火墙
Mount挂载点独立文件系统视图
UTS主机名容器有自己的 hostname
User用户映射容器内 root 可映射普通用户
IPC消息队列隔离进程间通信
cgroupcgroup 根隔离资源限制视图
Time时钟(新)隔离时间

举例:PID namespace——容器里的进程 ID 独立编号:

# 容器内看进程
docker exec <container> ps aux
# PID 1 是容器自己的主进程,看不到宿主机进程

User namespace 的安全价值:

容器内 uid=0(root)→ 映射到宿主机的非 root uid
→ 容器里的「root」没有宿主机的 root 权限
→ 即使被攻破,也只是宿主机的普通用户

记忆:namespace 是「掩眼法」——PID/网络/挂载各自造一个小世界,User namespace 则让容器内的 root 是「假 root」。


3. cgroup:限制「资源」

cgroup v2 用树形结构限制进程资源:

# 查看 cgroup 文件系统挂载
mount | grep cgroup
cat /sys/fs/cgroup/cpu.max        # CPU 配额(如 100000 100000 = 1核)
cat /sys/fs/cgroup/memory.max     # 内存上限

cgroup 限制维度:

cgroup 控制器限制什么文件
cpuCPU 配额/权重cpu.max / cpu.weight
memory内存上限/软限memory.max / memory.high
io磁盘 IOio.max / io.weight
pids线程数pids.max

临时给进程限资源(systemd 视角见 /linux-systemd-services/):

# 用 systemd-run 起一个限内存进程
systemd-run --user --property=MemoryMax=256M --pty bash
# 在容器内验证
cat /proc/self/cgroup

cgroup 与 OOM:

内存超限 → 内核 OOM Killer 杀掉超限进程组
memory.max 是硬墙,memory.high 是软压(触发回收,不杀)

记忆:cgroup 是「资源配额」——cpu.max 限核、memory.max 限内存、pids.max 限线程,超了要么限流要么 OOM。


4. 从 chroot 到容器的演进

容器不是新东西,是逐步演化:

chroot(1979)   → 只换根目录,无资源限制,无网络隔离
BSD jail(2000) → chroot + 独立网络 + 用户限制
Linux VServer / OpenVZ → 内核分区
LXC(2008)     → namespaces + cgroup 组合 → 现代容器雏形
Docker(2013)  → 加上「镜像分层 + 便捷 CLI」→ 引爆容器化

chroot 的最小示例:

# 用 debootstrap 造一个最小根文件系统,chroot 进去
debootstrap --arch amd64 jammy /opt/rootfs http://archive.ubuntu.com/ubuntu/
chroot /opt/rootfs /bin/bash
# 现在你「以为」自己在最小系统里,但网络/进程/资源毫无隔离

chroot vs 容器:

维度chroot容器(namespace+cgroup)
根目录隔离隔离
进程共享隔离(PID ns)
网络共享隔离(Network ns)
资源不限cgroup 限制
安全弱强得多

心智:容器 = chroot 的「全面隔离加强版」——chroot 只藏文件,容器连进程、网络、资源全隔离。


5. 容器运行时:runc 与 containerd

Docker 的运行时栈:

docker CLI
  └─ dockerd(守护进程)
       └─ containerd(容器生命周期管理)
            └─ runc(真正创建容器:调用 namespace/cgroup)

各组件职责:

组件职责
runc按 OCI 标准创建/运行容器(最底层)
containerd管理容器生命周期、镜像、快照
dockerd提供 Docker API 与 CLI
CRI(k8s)通过 containerd 的 CRI 接口集成

OCI 标准:config.json 描述容器配置(namespace、cgroup、命令):

{
  "ociVersion": "1.0.0",
  "process": { "args": ["/bin/sh"] },
  "root": { "path": "rootfs" },
  "linux": {
    "namespaces": [
      { "type": "pid" },
      { "type": "network" },
      { "type": "mount" }
    ]
  }
}

记忆:docker → dockerd → containerd → runc 是一条命令链——runc 是真正碰内核的,其余是管理壳。


6. 镜像与分层:Union FS

镜像 = 只读分层(overlayfs):

镜像分层:
  layer1: base(Ubuntu 根文件系统)
  layer2: + python
  layer3: + 你的代码
  layer4: 容器可写层(容器运行时才产生)

overlayfs 的读写原理:

读:下钻只读层
写:拷贝到最顶层可写层(copy-on-write)
删:白名单隐藏底层文件

分层的好处:

特性说明
复用相同 base 只存一份
增量只传变更层
缓存层命中可缓存
快构建/拉取增量

记忆:镜像分层 + copy-on-write = 容器镜像「又小又快又复用」——这是 Docker 席卷世界的关键工程创新。


7. 手写最小容器:动手理解本质

用系统调用实现一个最小容器(概念演示):

// 最小容器:unshare 进入新 PID/UTS/挂载 namespace
package main

import (
	"os"
	"os/exec"
	"syscall"
)

func main() {
	cmd := exec.Command("/bin/sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWPID |
			syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWNS |
			syscall.CLONE_NEWNET,   // 新的 PID/主机名/挂载/网络
	}
	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
	cmd.Run()
}
# 运行后,sh 的 PID 从 1 开始(宿主机的 PID 它看不到)
go run container.go
# 在里面执行 ps → 只有自己(PID namespace 隔离生效)

验证隔离:

ps aux               # 容器内只看得到容器进程
hostname foo         # 改主机名不影响宿主机(UTS ns)
mount | head         # 挂载视图隔离(Mount ns)

记忆:容器不是神秘技术,就是 unshare/clone 打开几个 flag——理解了这几个系统调用,你就理解了容器本质。


8. 安全边界:为什么容器不是 VM

容器共享宿主内核,VM 有独立内核——这是安全差异的根本:

维度容器虚拟机
内核共享宿主独立 guest 内核
隔离namespace/cgroup(软隔离)硬件虚拟化(硬隔离)
启动秒级分钟级
开销极小大(虚拟硬件)
逃逸风险内核漏洞可穿透需突破 hypervisor
适用微服务/快速迭代强隔离/异构系统

容器安全加固要点:

1. User namespace 映射(假 root)
2. 只读根文件系统(--read-only)
3. 丢弃能力(--cap-drop ALL --cap-add 最小集)
4. seccomp 限制系统调用
5. 不用 privileged 模式
6. 镜像扫描漏洞

铁律:容器是「软隔离」,永远别拿它当 VM 用——同一宿主上跑不可信工作负载前,先评估逃逸风险。


9. 容器排错与调试

排查思路(结合 [[docker]] 操作):

1. 起不来?  → 看日志 / 前台运行定位
2. 能起但错?→ exec 进去看 / 复制文件出来
3. 资源被限?→ 看 cgroup 统计
4. 网络不通?→ 看网络 namespace 与端口映射

实用命令:

# 进入容器调试(临时加调试工具)
docker run -it --rm myimage sh

# 查看 cgroup 使用
cat /sys/fs/cgroup/memory.current

# 看容器底层 PID(宿主机视角)
docker inspect -f '{{.State.Pid}}' <container>

# 用宿主机视角看容器进程
ps -ef | grep <容器主进程>

记忆:容器排错两条腿——exec 进容器看、宿主机视角看 PID——别忘了容器只是「带着 namespace 的进程」。


10. 速查表

需求做法
容器本质namespaces(隔离视图)+ cgroup(限资源)
PID 隔离PID namespace
网络隔离Network namespace
限内存memory.max
限 CPUcpu.max
假 rootUser namespace 映射
运行时链dockerd → containerd → runc
镜像复用overlayfs 分层 + copy-on-write
手写容器clone/unshare + flag
安全只读根 + 丢能力 + seccomp
排错exec 进容器 / 宿主机看 PID

一句话记忆:容器 = namespaces 造「以为独占」的假象 + cgroup 限资源 + 镜像分层打包;Docker 只是壳,runc 才是内核里的执行者;chroot 到容器是隔离的进化史;容器共享内核软隔离、VM 独立内核硬隔离——理解这四层,容器对你无秘密。


延伸阅读

  • /linux-systemd-services/ — cgroup 资源控制与安全指令
  • /linux-process-management/ — 进程与 PID
  • [[docker]] — 容器实战与编排
  • [[os]] — 操作系统原理
  • [[security]] — 容器安全与逃逸

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「linux」更多文章

  1. Linux 高级文件系统:XFS、Btrfs、ZFS 与存储进阶
  2. Linux 高可用与负载均衡:HAProxy、Keepalived 与集群方案
  3. Linux 防火墙与 nftables:规则集、链、NAT 与网络安全防护