Container Principle
Linux 容器技术的基础原理
容器技术是现代云原生架构的基石。让我深入讲解 Linux 容器的核心原理。
🎯 容器的本质
容器 = 特殊的进程
容器不是虚拟机,它本质上是宿主机上的一个受限进程,通过 Linux 内核的三大核心技术实现隔离和资源限制:
┌─────────────────────────────────────────┐
│ Linux 容器三大支柱 │
├─────────────────────────────────────────┤
│ 1. Namespace (命名空间) - 隔离 │
│ 2. Cgroups (控制组) - 资源限制 │
│ 3. Union FS (联合文件系统) - 镜像分层 │
└─────────────────────────────────────────┘🔒 Namespace (命名空间) - 视图隔离
Namespace 是 Linux 内核提供的一种资源隔离机制,让进程只能看到属于自己的资源。
七种 Namespace
| Namespace | 隔离内容 | 内核版本 | 示例 |
|---|---|---|---|
| PID | 进程 ID | 2.6.24 | 容器内 PID 1 = 宿主机 PID 12345 |
| Network | 网络栈 | 2.6.29 | 独立的 IP、端口、路由表 |
| Mount | 文件系统挂载点 | 2.4.19 | 独立的根目录 |
| UTS | 主机名和域名 | 2.6.19 | 容器有自己的 hostname |
| IPC | 进程间通信 | 2.6.19 | 消息队列、信号量、共享内存 |
| User | 用户和组 ID | 3.8 | 容器内 root ≠ 宿主机 root |
| Cgroup | Cgroup 根目录 | 4.6 | 隔离 cgroup 视图 |
1️⃣ PID Namespace (进程隔离)
原理
每个容器有独立的进程树,容器内看不到宿主机或其他容器的进程。
演示
# 在宿主机上查看进程
ps aux | grep nginx
# root 12345 nginx: master process
# 进入容器
docker exec -it my-container bash
# 在容器内查看进程
ps aux
# PID USER COMMAND
# 1 root nginx: master process ← 容器内看到的 PID 是 1
# 25 root nginx: worker process
# 实际上宿主机上这个进程的真实 PID 是 12345手动创建 PID Namespace
// C 代码示例
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int child_func(void* arg) {
printf("Child PID: %d\n", getpid()); // 输出: 1
sleep(100);
return 0;
}
int main() {
printf("Parent PID: %d\n", getpid()); // 输出: 真实 PID
// 创建新的 PID namespace
char stack[1024*1024];
int flags = CLONE_NEWPID;
pid_t pid = clone(child_func, stack + sizeof(stack), flags | SIGCHLD, NULL);
waitpid(pid, NULL, 0);
return 0;
}核心特点
- 容器内第一个进程 PID = 1 (init 进程)
- 父进程(宿主机)可以看到子进程的真实 PID
- 子进程(容器)看不到父进程和其他容器的进程
2️⃣ Network Namespace (网络隔离)
原理
每个容器有独立的网络栈:独立的 IP、端口、路由表、防火墙规则。
架构图
宿主机网络栈
├─ eth0 (物理网卡)
├─ docker0 (网桥)
└─ veth pairs (虚拟网卡对)
├─ vethXXX (宿主机端) ←→ eth0 (容器端)
└─ vethYYY (宿主机端) ←→ eth0 (容器端)演示
# 创建新的 network namespace
ip netns add myns
# 列出所有 namespace
ip netns list
# 在新 namespace 中执行命令
ip netns exec myns ip addr
# 输出: 只有 loopback,没有 eth0
# 创建 veth pair (虚拟网卡对)
ip link add veth0 type veth peer name veth1
# 将 veth1 移到新 namespace
ip link set veth1 netns myns
# 配置 IP
ip addr add 192.168.1.1/24 dev veth0
ip netns exec myns ip addr add 192.168.1.2/24 dev veth1
# 启动网卡
ip link set veth0 up
ip netns exec myns ip link set veth1 up
ip netns exec myns ip link set lo up
# 测试连通性
ping 192.168.1.2容器网络模式
Bridge 模式(默认)
Container A Container B
│ │
[eth0] [eth0]
│ │
vethA ←─────┬─────────→ vethB
│
[docker0 网桥]
│
[iptables NAT]
│
[宿主机 eth0]
│
外部网络Host 模式
Container
│
└─ 直接使用宿主机网络栈 (没有网络隔离)3️⃣ Mount Namespace (文件系统隔离)
原理
每个容器有独立的挂载点视图,看到不同的文件系统树。
演示
# 创建隔离的挂载环境
unshare --mount /bin/bash
# 在新 namespace 中挂载
mount -t tmpfs tmpfs /tmp
# 查看挂载点
mount | grep tmpfs
# 这个挂载只在当前 namespace 可见
# 退出后,宿主机看不到这个挂载
exit
mount | grep tmpfs # 找不到容器的根文件系统
# Docker 使用 chroot + pivot_root 切换根目录
# 容器内 / 实际是宿主机的某个目录
# 查看容器的根文件系统位置
docker inspect my-container | grep MergedDir
# "MergedDir": "/var/lib/docker/overlay2/xxx/merged"
# 在宿主机上访问容器文件系统
ls /var/lib/docker/overlay2/xxx/merged
# bin boot dev etc home lib ...4️⃣ UTS Namespace (主机名隔离)
演示
# 在宿主机
hostname
# host-machine
# 创建新 UTS namespace
unshare --uts /bin/bash
# 修改主机名
hostname my-container
# 查看主机名
hostname
# my-container
# 退出后,宿主机主机名不变
exit
hostname
# host-machine5️⃣ IPC Namespace (进程间通信隔离)
原理
隔离 System V IPC 和 POSIX 消息队列。
演示
# 在宿主机创建消息队列
ipcmk -Q
# Message queue id: 0
# 查看消息队列
ipcs -q
# ------ Message Queues --------
# key msqid owner
# 0x52020055 0 root
# 进入容器
docker exec -it my-container bash
# 在容器内查看消息队列
ipcs -q
# ------ Message Queues --------
# (空,看不到宿主机的消息队列)6️⃣ User Namespace (用户隔离)
原理
容器内的 root 用户可以映射到宿主机的普通用户,增强安全性。
配置示例
# 启用 User Namespace 的容器
docker run --userns-remap=default -it ubuntu bash
# 容器内
whoami
# root
id
# uid=0(root) gid=0(root) groups=0(root)
# 但在宿主机上,这个进程实际运行在普通用户下
ps aux | grep bash
# 100000 12345 bash ← UID 100000,不是 rootUID 映射配置
# /etc/subuid 和 /etc/subgid
cat /etc/subuid
# dockremap:100000:65536
# 表示将容器内的 UID 0-65535 映射到宿主机的 100000-165535📊 Cgroups (Control Groups) - 资源限制
Cgroups 用于限制、记录、隔离进程组的资源使用(CPU、内存、磁盘 I/O 等)。
Cgroups 子系统
| 子系统 | 功能 | 示例 |
|---|---|---|
| cpu | 限制 CPU 使用率 | 容器最多用 50% CPU |
| cpuset | 绑定特定 CPU 核心 | 容器只能用 CPU 0-3 |
| memory | 限制内存使用 | 容器最多用 512MB 内存 |
| blkio | 限制块设备 I/O | 容器磁盘读写 100MB/s |
| devices | 控制设备访问 | 容器不能访问 /dev/sda |
| net_cls | 网络流量分类 | 为容器流量打标签 |
| pids | 限制进程数量 | 容器最多创建 100 个进程 |
CPU 限制
原理
使用 CFS (Completely Fair Scheduler) 调度器限制 CPU 时间。
关键参数
cpu.cfs_period_us # 周期时间(默认 100ms = 100000us)
cpu.cfs_quota_us # 配额时间
# CPU 使用率 = quota / period
# 例如: 50000 / 100000 = 50% CPUDocker 示例
# 限制容器使用 0.5 个 CPU 核心
docker run --cpus=0.5 nginx
# 等价于
docker run --cpu-period=100000 --cpu-quota=50000 nginx
# 查看 cgroup 配置
cat /sys/fs/cgroup/cpu/docker/<container-id>/cpu.cfs_quota_us
# 50000手动配置 Cgroups
# 创建 cgroup
mkdir -p /sys/fs/cgroup/cpu/mycontainer
# 设置 CPU 限制为 50%
echo 50000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_period_us
# 将进程加入 cgroup
echo $$ > /sys/fs/cgroup/cpu/mycontainer/cgroup.procs
# 运行 CPU 密集任务
yes > /dev/null &
# 在另一个终端查看 CPU 使用率
top -p $(pgrep yes)
# CPU 使用率被限制在 50% 左右内存限制
关键参数
memory.limit_in_bytes # 硬限制
memory.soft_limit_in_bytes # 软限制
memory.oom_control # OOM 行为控制
memory.usage_in_bytes # 当前使用量Docker 示例
# 限制容器使用最多 512MB 内存
docker run -m 512m nginx
# 查看内存限制
cat /sys/fs/cgroup/memory/docker/<container-id>/memory.limit_in_bytes
# 536870912 (512MB)
# 查看当前内存使用
cat /sys/fs/cgroup/memory/docker/<container-id>/memory.usage_in_bytesOOM (Out of Memory) 行为
# 当容器超过内存限制时
# 1. 内核触发 OOM Killer
# 2. 杀死容器内的进程(通常是内存占用最大的)
# 3. 容器退出,状态码 137
docker ps -a
# CONTAINER ID STATUS
# abc123 Exited (137) 1 minute ago ← OOM killed避免 OOM 的策略
# 设置 OOM Score Adjustment
docker run --oom-score-adj=-500 nginx
# 数值越低,越不容易被 OOM Killer 杀死
# 禁用 OOM Killer (不推荐生产环境)
docker run --oom-kill-disable nginx磁盘 I/O 限制
Docker 示例
# 限制读取速度为 10MB/s
docker run --device-read-bps /dev/sda:10mb nginx
# 限制写入速度为 5MB/s
docker run --device-write-bps /dev/sda:5mb nginx
# 限制 IOPS
docker run --device-read-iops /dev/sda:100 nginx
docker run --device-write-iops /dev/sda:50 nginx测试 I/O 限制
# 在容器内测试写入速度
docker exec -it my-container bash
dd if=/dev/zero of=/tmp/test bs=1M count=100
# 写入速度会被限制在 5MB/s📦 Union FS (联合文件系统) - 镜像分层
Union FS 允许多个文件系统分层叠加,实现镜像的复用和高效存储。
核心概念
容器可写层 (Read-Write Layer) ← 容器运行时的修改
─────────────────────────────────
镜像层 4 (Image Layer 4) ← 只读
镜像层 3 (Image Layer 3) ← 只读
镜像层 2 (Image Layer 2) ← 只读
镜像层 1 (Base Layer) ← 只读
─────────────────────────────────
统一挂载点
(Union Mount Point)常见实现
| 文件系统 | 特点 | 使用情况 |
|---|---|---|
| OverlayFS | 性能好,内核原生支持 | Docker 默认(推荐) |
| AUFS | 成熟稳定,但不在主线内核 | 早期 Docker 默认 |
| Btrfs | 支持快照,写时复制 | 适合大规模存储 |
| ZFS | 企业级功能,但有许可问题 | 高级用户 |
| Device Mapper | 块级存储 | Red Hat 系列 |
OverlayFS 原理
目录结构
/var/lib/docker/overlay2/<image-id>/
├── diff/ # 当前层的文件变更
├── link # 短链接名称
├── lower # 指向下层的链接
├── merged/ # 最终挂载点(容器看到的)
└── work/ # 工作目录(临时文件)实际演示
# 查看镜像的层结构
docker image inspect nginx:latest | jq '.[0].RootFS.Layers'
# [
# "sha256:abc123...", ← Layer 1
# "sha256:def456...", ← Layer 2
# "sha256:ghi789..." ← Layer 3
# ]
# 启动容器
docker run -d --name web nginx
# 查看容器的文件系统
docker inspect web | grep MergedDir
# "MergedDir": "/var/lib/docker/overlay2/xxx/merged"
# 查看挂载信息
mount | grep overlay
# overlay on /var/lib/docker/overlay2/xxx/merged type overlay (rw,lowerdir=...,upperdir=...,workdir=...)文件操作的 Copy-on-Write (写时复制)
# 1. 读取文件(从镜像层)
docker exec web cat /etc/nginx/nginx.conf
# 直接从只读的镜像层读取,无需复制
# 2. 修改文件
docker exec web bash -c "echo 'test' >> /etc/nginx/nginx.conf"
# 触发 Copy-on-Write:
# - 从下层复制文件到容器可写层
# - 在可写层修改文件
# - 下次读取时,从可写层读取(覆盖下层)
# 3. 删除文件
docker exec web rm /var/log/nginx/access.log
# 创建 whiteout 文件,标记删除
# 文件在镜像层仍存在,但容器内看不到Whiteout 文件(删除标记)
# 在容器可写层
ls -la /var/lib/docker/overlay2/xxx/diff/var/log/nginx/
# c--------- 1 root root 0, 0 Oct 11 10:00 .wh.access.log
# 字符设备文件,主次设备号都是 0,表示删除标记镜像分层的优势
1. 共享层,节省空间
# 假设有 10 个基于 ubuntu:20.04 的镜像
# 不使用分层:10 × 100MB = 1GB
# 使用分层:100MB (ubuntu base) + 10 × 10MB (应用层) = 200MB
# 节省空间:80%2. 快速构建
FROM ubuntu:20.04 # Layer 1 (缓存)
RUN apt-get update # Layer 2 (缓存)
RUN apt-get install -y nginx # Layer 3 (缓存)
COPY app.conf /etc/nginx/ # Layer 4 (需要重建)
COPY app.js /var/www/ # Layer 5 (需要重建)
# 如果只修改 app.js,只需要重建 Layer 5
# 前面的层都从缓存读取3. 快速分发
# 拉取镜像时,只下载本地没有的层
docker pull nginx:1.21
# Already exists: Layer 1 (ubuntu base)
# Downloading: Layer 2 (nginx files)
# Downloading: Layer 3 (config)🔗 容器技术完整流程
Docker 创建容器的完整过程
docker run -d --name web \
--cpus=0.5 \
-m 512m \
-p 8080:80 \
nginx:latest内部执行流程
1. 拉取镜像(如果本地没有)
└─ 下载各层,存储到 /var/lib/docker/overlay2/
2. 创建 Namespace
├─ PID Namespace (隔离进程)
├─ Network Namespace (隔离网络)
├─ Mount Namespace (隔离文件系统)
├─ UTS Namespace (隔离主机名)
├─ IPC Namespace (隔离进程间通信)
└─ User Namespace (隔离用户)
3. 配置 Cgroups
├─ cpu.cfs_quota_us = 50000 (50% CPU)
└─ memory.limit_in_bytes = 536870912 (512MB)
4. 挂载文件系统 (OverlayFS)
├─ lowerdir: 镜像只读层
├─ upperdir: 容器可写层
├─ workdir: 工作目录
└─ merged: 统一视图挂载点
5. 配置网络
├─ 创建 veth pair
├─ 一端连接到容器的 Network Namespace
├─ 另一端连接到 docker0 网桥
├─ 分配 IP 地址
└─ 配置 iptables NAT 规则 (端口映射)
6. 切换根目录
├─ chroot 或 pivot_root
└─ 容器内看到的 / 是 merged 目录
7. 启动容器进程
├─ 在新的 Namespace 中
├─ 受 Cgroups 限制
└─ 使用新的根文件系统
└─ 执行 ENTRYPOINT/CMD
8. 容器运行中
└─ containerd-shim 监控进程🛠️ 手动创建容器(无 Docker)
完整示例:从零创建容器
#!/bin/bash
# 手动创建一个简单的容器
# 1. 准备根文件系统
mkdir -p /tmp/mycontainer/rootfs
cd /tmp/mycontainer/rootfs
# 下载 busybox 作为基础系统
wget https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox
chmod +x busybox
./busybox --install -s .
# 创建必要的目录
mkdir -p bin sbin etc proc sys tmp dev
# 2. 创建启动脚本
cat > /tmp/mycontainer/start.sh <<'EOF'
#!/bin/bash
# 创建新的 namespace
unshare --pid --net --mount --uts --ipc --fork /bin/bash -c '
# 挂载 proc
mount -t proc proc /proc
# 设置主机名
hostname mycontainer
# 启动 shell
/bin/sh
'
EOF
chmod +x /tmp/mycontainer/start.sh
# 3. 启动容器
chroot /tmp/mycontainer/rootfs /tmp/mycontainer/start.sh配置 Cgroups 限制
# 创建 cgroup
mkdir -p /sys/fs/cgroup/memory/mycontainer
mkdir -p /sys/fs/cgroup/cpu/mycontainer
# 设置内存限制 256MB
echo 268435456 > /sys/fs/cgroup/memory/mycontainer/memory.limit_in_bytes
# 设置 CPU 限制 50%
echo 50000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_period_us
# 将容器进程加入 cgroup
echo $CONTAINER_PID > /sys/fs/cgroup/memory/mycontainer/cgroup.procs
echo $CONTAINER_PID > /sys/fs/cgroup/cpu/mycontainer/cgroup.procs🔍 容器 vs 虚拟机
架构对比
虚拟机架构:
┌─────────────────────────────────────┐
│ App A │ App B │ App C │
├─────────┼─────────┼─────────────────┤
│ Bins/Libs│ Bins/Libs│ Bins/Libs │
├─────────┼─────────┼─────────────────┤
│ Guest OS│ Guest OS│ Guest OS │ ← 每个 VM 都有完整 OS
├─────────┴─────────┴─────────────────┤
│ Hypervisor (VMware/KVM) │
├─────────────────────────────────────┤
│ Host Operating System │
├─────────────────────────────────────┤
│ Hardware │
└─────────────────────────────────────┘
容器架构:
┌─────────────────────────────────────┐
│ App A │ App B │ App C │
├─────────┼─────────┼─────────────────┤
│ Bins/Libs│ Bins/Libs│ Bins/Libs │
├─────────────────────────────────────┤
│ Docker Engine / containerd │
├─────────────────────────────────────┤
│ Host Operating System (Linux) │ ← 共享内核
├─────────────────────────────────────┤
│ Hardware │
└─────────────────────────────────────┘性能对比
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 启动时间 | 分钟级 | 秒级 |
| 资源占用 | GB 级内存 | MB 级内存 |
| 性能开销 | 5-10% | < 1% |
| 隔离程度 | 完全隔离(硬件级) | 进程隔离(OS 级) |
| 安全性 | 更高(独立内核) | 较低(共享内核) |
| 密度 | 每台物理机 10-50 个 | 每台物理机 100-1000 个 |
⚠️ 容器的安全性考虑
1. 共享内核的风险
# 容器逃逸:如果内核有漏洞,容器可能逃逸到宿主机
# 缓解措施:
# - 使用 User Namespace
# - 运行容器为非 root 用户
# - 使用 Seccomp 限制系统调用
# - 使用 AppArmor/SELinux2. 特权容器的危险
# 特权容器可以访问宿主机所有设备
docker run --privileged ...
# ❌ 危险:容器内可以:
# - 加载内核模块
# - 访问宿主机所有设备
# - 修改宿主机网络配置
# - 读写宿主机任意文件
# ✅ 最佳实践:避免使用特权容器3. Capability 控制
# 只授予容器必要的权限
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx
# 默认 Docker 授予的 Capabilities:
# - CHOWN, DAC_OVERRIDE, FOWNER, FSETID
# - KILL, SETGID, SETUID, SETPCAP
# - NET_BIND_SERVICE, NET_RAW
# - SYS_CHROOT, MKNOD, AUDIT_WRITE, SETFCAP💡 关键要点总结
容器 = Namespace + Cgroups + Union FS
Namespace (隔离)
- PID: 进程隔离
- Network: 网络隔离
- Mount: 文件系统隔离
- UTS: 主机名隔离
- IPC: 进程间通信隔离
- User: 用户隔离
Cgroups (限制)
- CPU: 限制处理器使用
- Memory: 限制内存使用
- Block I/O: 限制磁盘 I/O
- Network: 限制网络带宽
Union FS (分层)
- 镜像分层存储
- Copy-on-Write
- 节省空间和带宽
容器不是虚拟机
- ✅ 容器是特殊的进程
- ✅ 共享宿主机内核
- ✅ 启动快、资源占用少
- ⚠️ 隔离性不如虚拟机
- ⚠️ 需要注意安全配置