09|容器与虚拟化
虚拟化让一台物理机变成多台”虚拟机”,容器让一个操作系统里跑出多个”隔离环境”。本章将带你理解虚拟化的原理,深入 Namespace、Cgroups 这两大容器基石,以及 Docker 的底层实现机制。
📋 开篇自测:你已经知道多少?
- 虚拟机和容器的本质区别是什么?各自的优劣势是什么?
- Linux Namespace 提供了哪些隔离维度?
- Cgroups 解决了什么问题?它能限制哪些资源?
一、虚拟化:从一变多
1.1 为什么需要虚拟化
一台高配服务器可能有 128 核 CPU、512GB 内存,但大多数应用只需要 4 核 8GB。如果一台服务器只跑一个应用,资源利用率可能只有 5-10%。虚拟化的目标就是让一台物理机同时运行多个操作系统实例,把利用率提升到 60-80%。
1.2 虚拟化的三种层次
+------------------------------------------------------------------+
| 虚拟化类型对比 |
+------------------------------------------------------------------+
| |
| 类型1: 全虚拟化 (Full Virtualization) |
| |
| VM1 VM2 VM3 |
| +------+ +------+ +------+ |
| |Guest | |Guest | |Guest | <-- 完整的客户操作系统 |
| | OS | | OS | | OS | |
| +------+ +------+ +------+ |
| +---------------------------+ |
| | Hypervisor (VMM) | <-- 虚拟机监控器 |
| +---------------------------+ |
| +---------------------------+ |
| | Host OS (可选) | |
| +---------------------------+ |
| | 硬件 | |
| +---------------------------+ |
| |
| 类型2: 半虚拟化 (Paravirtualization) |
| 客户OS知道自己在虚拟环境中, 主动调用Hypervisor接口 |
| 代表: Xen |
| |
| 类型3: 硬件辅助虚拟化 (Hardware-Assisted) |
| CPU 提供虚拟化指令集 (Intel VT-x, AMD-V) |
| 代表: KVM + QEMU |
| |
+------------------------------------------------------------------+
1.3 Type-1 vs Type-2 Hypervisor
Type-1 (裸金属): Type-2 (托管型):
直接运行在硬件上 运行在宿主操作系统上
VM1 VM2 VM3 VM1 VM2 VM3
+---+ +---+ +---+ +---+ +---+ +---+
| | | | | | | | | | | |
+---+ +---+ +---+ +---+ +---+ +---+
+-----------------------+ +-----------------------+
| Hypervisor | | Hypervisor |
+-----------------------+ +-----------------------+
| 硬件 | | 宿主操作系统 |
+-----------------------+ +-----------------------+
| 硬件 |
代表: VMware ESXi +-----------------------+
Microsoft Hyper-V
KVM (Linux内核模块) 代表: VirtualBox
VMware Workstation
1.4 KVM:Linux 的虚拟化方案
KVM(Kernel-based Virtual Machine)是 Linux 内核的一个模块,把 Linux 变成了一个 Type-1 Hypervisor:
KVM + QEMU 架构:
用户空间:
+--------+ +--------+ +--------+
| QEMU | | QEMU | | QEMU | 每个VM一个QEMU进程
| 进程 | | 进程 | | 进程 | 负责设备模拟
+--------+ +--------+ +--------+
| | |
-----+-----系统调用-----------+------
| | |
内核空间:
+----------------------------------------+
| KVM 模块 (/dev/kvm) |
| - 提供 CPU 虚拟化 (利用 VT-x/AMD-V) |
| - 提供内存虚拟化 (EPT/NPT) |
| - 提供中断虚拟化 |
+----------------------------------------+
| Linux 内核 |
+----------------------------------------+
| 硬件 (CPU支持 VT-x/AMD-V) |
+----------------------------------------+
工作流程:
1. QEMU 通过 ioctl(/dev/kvm) 创建虚拟机
2. 设置虚拟CPU、内存、设备
3. 进入 Guest 模式执行客户OS代码
4. 遇到特权指令 -> VM Exit -> KVM处理 -> VM Entry -> 继续
二、容器:轻量级的隔离
2.1 容器 vs 虚拟机
容器不需要运行一个完整的操作系统内核,它们共享宿主机的内核,只隔离用户空间的资源。
虚拟机: 容器:
+-----+ +-----+ +-----+ +-----+ +-----+ +-----+
|App A| |App B| |App C| |App A| |App B| |App C|
+-----+ +-----+ +-----+ +-----+ +-----+ +-----+
|Bins | |Bins | |Bins | |Bins | |Bins | |Bins |
|Libs | |Libs | |Libs | |Libs | |Libs | |Libs |
+-----+ +-----+ +-----+ +-----+-+-----+-+-----+
|Guest| |Guest| |Guest| 容器引擎
| OS | | OS | | OS | (Docker/containerd)
+-----+ +-----+ +-----+ +---------------------+
+-----------------------+ | 宿主操作系统 |
| Hypervisor | +---------------------+
+-----------------------+ | 硬件 |
| 宿主操作系统/硬件 | +---------------------+
+-----------------------+
虚拟机: 每个VM一个完整OS 容器: 共享宿主OS内核
(GB级, 分钟级启动) (MB级, 秒级启动)
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 隔离级别 | 硬件级(最强) | 进程级(较弱) |
| 启动速度 | 分钟级 | 秒级甚至毫秒级 |
| 资源开销 | 大(完整OS) | 小(只有应用和依赖) |
| 镜像大小 | GB 级 | MB 级 |
| 安全性 | 高 | 较低(共享内核) |
| 密度 | 一台宿主机十几个VM | 一台宿主机数百个容器 |
2.2 容器的两大基石
容器并不是一种新的虚拟化技术,它只是 Linux 内核已有功能的组合应用。容器的核心依赖两个内核特性:
容器 = Namespace (隔离) + Cgroups (限制)
Namespace: "让你看到的世界不同"
- 每个容器有自己的 PID 空间
- 每个容器有自己的网络栈
- 每个容器有自己的文件系统视图
- ...
Cgroups: "限制你能用多少资源"
- 限制 CPU 使用量
- 限制内存使用量
- 限制磁盘 I/O 带宽
- 限制网络带宽
- ...
三、Namespace:资源隔离
3.1 八种 Namespace
Linux 提供了以下 Namespace 来隔离不同维度的资源:
+------------------+------+------------------------------------------+
| Namespace | 标志 | 隔离的资源 |
+------------------+------+------------------------------------------+
| Mount (mnt) | CLONE_NEWNS | 文件系统挂载点 |
| PID | CLONE_NEWPID | 进程 ID |
| Network (net) | CLONE_NEWNET | 网络设备、IP、端口、路由表 |
| UTS | CLONE_NEWUTS | 主机名和域名 |
| IPC | CLONE_NEWIPC | 信号量、消息队列、共享内存 |
| User | CLONE_NEWUSER | 用户 ID 和组 ID |
| Cgroup | CLONE_NEWCGROUP| Cgroup 根目录 |
| Time | CLONE_NEWTIME | 系统时钟 (Linux 5.6+) |
+------------------+------+------------------------------------------+
3.2 PID Namespace 详解
宿主机视角: 容器内视角:
PID=1 (systemd) PID=1 (容器init进程)
| |
+-- PID=1000 (dockerd) +-- PID=2 (nginx master)
| | | |
| +-- PID=2000 (容器进程) | +-- PID=3 (nginx worker)
| | | | +-- PID=4 (nginx worker)
| | +-- PID=2001 |
| | +-- PID=2002 容器里只能看到自己的进程
| | +-- PID=2003 PID 从 1 开始编号
| |
| +-- PID=3000 (另一个容器)
在容器内部看到的 PID=1 进程,在宿主机上可能是 PID=2000。这种映射由 PID Namespace 维护。
3.3 Network Namespace 详解
容器网络隔离:
宿主机 容器1 容器2
+--------------------------+
| eth0 (192.168.1.100) |
| docker0 (172.17.0.1) |
| | |
| +-- veth-pair -------+-- eth0 |
| | (虚拟网线) | (172.17.0.2) |
| | +---------------------+
| |
| +-- veth-pair -------+---------------------+
| (虚拟网线) | eth0 |
| | (172.17.0.3) |
+--------------------------+---------------------+
每个容器有独立的:
- 网络接口 (eth0)
- IP 地址
- 路由表
- iptables 规则
- 端口空间 (每个容器都能监听 80 端口)
3.4 Mount Namespace 与 Union FS
容器看到的文件系统是通过 Mount Namespace + Union 文件系统(OverlayFS)实现的:
OverlayFS 分层结构:
容器可写层 (upperdir) <-- 容器运行时修改的文件
+-----------------------------+
| modified_config.yaml |
| new_log_file.log |
+-----------------------------+
|
v 合并视图 (merged)
+-----------------------------+
| /bin /sbin /lib /etc ... | <-- 容器内看到的完整文件系统
| + modified_config.yaml |
| + new_log_file.log |
+-----------------------------+
^
|
镜像只读层 (lowerdir) <-- Docker 镜像的各层
+-----------------------------+
| Layer 3: app code | FROM node:18
+-----------------------------+ COPY . /app
| Layer 2: npm install | RUN npm install
+-----------------------------+
| Layer 1: node:18 base | 基础镜像
+-----------------------------+
关键特性:
- 镜像层只读,多个容器共享
- 修改文件时自动复制到可写层 (COW)
- 删除文件在可写层创建 "whiteout" 标记
🤔 想一想 如果容器内的 PID=1 进程退出了,整个容器会怎样?这和宿主机上 PID=1 的 init 进程退出有什么相似之处?
四、Cgroups:资源限制
4.1 Cgroups 是什么
Cgroups(Control Groups)是 Linux 内核用于限制、记录和隔离进程组所使用的资源(CPU、内存、磁盘 I/O、网络等)的机制。
Cgroups 有两个版本:v1 为每种资源类型(cpu、memory、blkio 等)维护独立的层级树,配置分散;v2 采用统一的单层级结构,所有资源控制器挂载在同一棵树上,接口更简洁。当前主流发行版(Fedora 31+、Ubuntu 22.04+、RHEL 9+)已默认使用 Cgroups v2。
Cgroups v1 层级结构(每种资源一棵独立的树):
/sys/fs/cgroup/
├── cpu/
│ ├── docker/
│ │ ├── container-abc123/
│ │ │ ├── cpu.cfs_quota_us # CPU 配额
│ │ │ ├── cpu.cfs_period_us # CPU 周期
│ │ │ └── tasks # 该组的进程列表
│ │ └── container-def456/
│ │ └── ...
│ └── ...
├── memory/
│ ├── docker/
│ │ ├── container-abc123/
│ │ │ ├── memory.limit_in_bytes # 内存上限
│ │ │ ├── memory.usage_in_bytes # 当前使用
│ │ │ └── memory.oom_control # OOM 控制
│ │ └── ...
│ └── ...
├── blkio/ # 块设备I/O限制
├── pids/ # 进程数限制
└── ...
Cgroups v2 统一层级(所有资源在同一棵树上):
/sys/fs/cgroup/
├── cgroup.controllers # 可用的控制器列表
├── cgroup.subtree_control # 已启用的控制器
├── docker/
│ ├── container-abc123/
│ │ ├── cpu.max # CPU 限制 (quota period)
│ │ ├── memory.max # 内存上限
│ │ ├── io.max # I/O 限制
│ │ └── cgroup.procs # 进程列表
│ └── ...
└── ...
4.2 CPU 限制
# 限制容器使用最多 2 个 CPU 核心
# 在 Cgroups v1 中:
echo 200000 > /sys/fs/cgroup/cpu/mygroup/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/mygroup/cpu.cfs_period_us
# 200000/100000 = 2 核
# Docker 方式:
docker run --cpus=2 nginx # 限制使用 2 核
docker run --cpu-shares=512 nginx # 相对权重 (默认1024)
# Cgroups v2 (更统一的接口):
echo "200000 100000" > /sys/fs/cgroup/mygroup/cpu.max
4.3 内存限制
# 限制容器使用最多 512MB 内存
echo 536870912 > /sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes
# Docker 方式:
docker run --memory=512m nginx
# 内存超限时:
# 1. 内核先尝试回收缓存
# 2. 回收不够则触发 OOM Killer 杀掉容器中的进程
# 3. 可以通过 memory.oom_control 禁用 OOM (进程会被挂起)
# 查看容器内存使用情况:
cat /sys/fs/cgroup/memory/docker/<container-id>/memory.usage_in_bytes
cat /sys/fs/cgroup/memory/docker/<container-id>/memory.stat
4.4 I/O 限制
# 限制容器的磁盘读写速率
# Docker 方式:
docker run --device-read-bps /dev/sda:10mb nginx # 读限速10MB/s
docker run --device-write-bps /dev/sda:10mb nginx # 写限速10MB/s
docker run --device-read-iops /dev/sda:1000 nginx # 读限制1000 IOPS
五、Docker 底层机制
5.1 Docker 架构
Docker 架构:
docker CLI Docker Desktop (GUI)
| |
v v
Docker Daemon (dockerd)
|
v
containerd (容器运行时管理)
|
v
runc (OCI标准容器运行时)
|
v
+---Namespace---+---Cgroups---+---OverlayFS---+
| PID隔离 | CPU限制 | 分层文件系统 |
| Network隔离 | 内存限制 | 写时复制 |
| Mount隔离 | I/O限制 | |
+--------------+-------------+---------------+
|
v
Linux Kernel
5.2 容器的生命周期
docker run 的幕后:
1. docker CLI 发送请求到 dockerd
2. dockerd 请求 containerd 创建容器
3. containerd 调用 runc 设置 namespace + cgroups
4. runc 执行以下步骤:
a. clone() 创建新进程 (带 CLONE_NEWPID | CLONE_NEWNS | ...)
b. 在新 namespace 中:
- 挂载 OverlayFS (Union Mount)
- 配置网络 (veth pair + bridge)
- 设置 hostname
- 挂载 /proc, /sys
c. 将进程加入 cgroup
- 写入 CPU 限制
- 写入内存限制
d. execve() 执行容器入口命令 (如 nginx)
5. runc 退出,containerd 接管容器生命周期管理
5.3 容器网络模式
Docker 四种网络模式:
1. bridge (默认):
容器通过 docker0 网桥互联,NAT 访问外网
docker run nginx
2. host:
容器直接使用宿主机网络栈,无隔离
docker run --network=host nginx
3. none:
无网络,完全隔离
docker run --network=none nginx
4. container:
共享另一个容器的网络栈
docker run --network=container:web1 sidecar
bridge 模式详细:
宿主机:
+-----------+
| eth0 | (对外)
| 10.0.0.5 |
+-----+-----+
|
+-----+-----+
| docker0 | (网桥)
| 172.17.0.1|
+--+-----+--+
| |
+--+--+ +--+--+
|veth0| |veth1|
+--+--+ +--+--+
| |
容器1 容器2
172. 172.
17.0.2 17.0.3
六、容器安全
6.1 容器的安全边界
容器共享宿主机内核,因此容器的安全边界比虚拟机弱得多:
安全层次:
最强 +----------------------------------+
^ | 虚拟机 (硬件级隔离) |
| +----------------------------------+
| | 容器 + 安全增强 |
| | (seccomp + AppArmor + rootless) |
| +----------------------------------+
| | 普通容器 (Namespace + Cgroups) |
| +----------------------------------+
最弱 | 进程 (无隔离) |
+----------------------------------+
6.2 增强容器安全的手段
1. seccomp: 限制容器可用的系统调用
Docker 默认禁止了约 44 个危险系统调用
(如 reboot, mount, ptrace 等)
2. AppArmor / SELinux: 强制访问控制
限制容器可访问的文件和操作
3. Rootless Container: 以非root用户运行容器引擎
即使容器内提权成功也只是普通用户
4. Read-only Root FS: 只读根文件系统
docker run --read-only nginx
5. Capabilities: 细粒度的权限控制
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx
🤔 想一想 为什么说”容器逃逸”是容器安全最严重的威胁?一旦成功逃逸,攻击者能做什么?
七、本章总结
+----------------------------------------------------+
| 容器与虚拟化核心知识 |
+----------------------------------------------------+
| |
| 虚拟化: |
| 全虚拟化 / 半虚拟化 / 硬件辅助 |
| KVM = Linux 内核模块 + QEMU 设备模拟 |
| |
| 容器 = Namespace + Cgroups + Union FS |
| |
| Namespace (8种隔离): |
| PID | Network | Mount | UTS | IPC | User |
| Cgroup | Time |
| |
| Cgroups (资源限制): |
| CPU (quota/shares) | Memory (limit) |
| I/O (bps/iops) | PIDs (进程数) |
| |
| Docker: |
| dockerd -> containerd -> runc -> kernel |
| 镜像: OverlayFS 分层 + COW |
| 网络: bridge / host / none / container |
| |
+----------------------------------------------------+
📝 结尾自测:检验你的收获
- 虚拟机和容器的核心区别是什么?画出两者的架构对比图。
- Linux Namespace 提供了哪些隔离维度?PID Namespace 如何实现进程 ID 的隔离?
- Cgroups 如何限制容器的 CPU 和内存使用?超出内存限制会发生什么?
- Docker 镜像的分层存储如何工作?OverlayFS 的上层和下层分别存储什么?
- 容器共享宿主机内核带来了什么安全风险?有哪些缓解措施?
购买课程解锁全部内容
系统底层入门:10 章掌握操作系统核心
¥29.90