LXC (Linux 容器) 工作原理详解
LXC (Linux Containers) 是一种操作系统级别的虚拟化技术,它允许在单个 Linux 内核上运行多个隔离的 Linux 系统(容器)。与虚拟机(VM)不同,LXC 不需要模拟硬件,因此它非常轻量级且启动速度快。
LXC 的核心是利用 Linux 内核的两个关键特性:命名空间 (Namespaces) 和 控制组 (Cgroups)。
- Namespaces:负责隔离,确保一个容器中的进程看不到或影响到另一个容器或宿主机的进程、网络、文件系统等。
- Cgroups:负责资源限制和审计,确保每个容器只能使用分配给它的 CPU、内存、I/O 等资源。
本文将重点详细介绍几个关键的命名空间。
1. PID 命名空间 (PID Namespace)
PID (Process ID) 命名空间用于隔离进程 ID。
原理
- 隔离进程树:每个 PID 命名空间都有一套独立的进程 ID,从 1 开始。
- 容器的
init进程:在一个新的 PID 命名空间中创建的第一个进程会成为该空间的 “init” 进程,其 PID 为 1。这个进程负责管理容器内的所有其他进程(例如,处理孤儿进程)。如果这个 PID 为 1 的进程终止,内核将终止该命名空间中的所有其他进程。 - 内外 PID 映射:容器内的进程在容器内部有自己的 PID(例如,PID 1, 2, 3…),同时在宿主机上也有一个全局唯一的 PID。这意味着从宿主机看,所有容器的进程都是普通的进程,只是被 PID 命名空间隔离开来。
示例
假设我们在宿主机上启动一个 LXC 容器,并在容器内运行 bash。
-
容器内视角:
1 2 3 4 5 6# 在容器内执行 ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.1 0.0 2384 1596 ? Ss 10:00 0:00 /sbin/init root 15 0.0 0.0 4372 3480 pts/0 Ss 10:01 0:00 bash root 25 0.0 0.0 5924 1788 pts/0 R+ 10:02 0:00 ps aux在这里,
init进程的 PID 是 1,bash的 PID 是 15。 -
宿主机视角:
1 2 3 4# 在宿主机执行 ps aux | grep bash # 输出可能像这样 root 12345 0.0 0.0 4372 3480 pts/0 Ss 10:01 0:00 bash在宿主机上,同一个
bash进程的 PID 可能是 12345。这种隔离使得容器内的进程管理与宿主机完全分离。
2. 网络命名空间 (Network Namespace)
网络命名空间为每个容器提供了一个完全独立的网络协议栈。
原理
- 独立网络栈:每个网络命名空间都有自己独立的网络设备(如
lo,eth0)、IP 地址、路由表、iptables 防火墙规则、端口号等。一个容器默认无法访问另一个容器或宿主机的网络。 veth设备对:为了让容器能与外部通信,LXC 通常使用veth(Virtual Ethernet) 设备对。veth设备总是成对出现,像一根虚拟网线。数据从一端进入,会从另一端出来。- 连接过程:
- 创建一对
veth设备,例如veth_host和veth_container。 - 将
veth_host留在宿主机的网络命名空间中。 - 将
veth_container“移动” 到容器的网络命名空间中,并将其重命名为eth0。 - 在宿主机上,通常会创建一个网桥(例如
lxcbr0),并将veth_host端连接到这个网桥上。 - 为容器内的
eth0分配 IP 地址。
- 创建一对
- 数据流: 容器内
eth0发出的网络包 -> 通过veth对到达宿主机的veth_host-> 进入宿主机的网桥lxcbr0-> 通过宿主机的物理网卡和路由规则与外部网络通信。
这种结构使得每个容器都像一台独立的机器连接到了一个虚拟交换机(网桥)上,实现了网络隔离和互联。
3. 文件系统命名空间 (Mount Namespace)
文件系统(挂载)命名空间允许每个容器拥有自己独立的文件系统视图,尤其是独立的根目录 (/)。
原理
- 隔离挂载点:每个挂载命名空间维护着一个独立的挂载点列表。在一个命名空间中的
mount()和umount()操作不会影响到其他命名空间。 - 独立的根文件系统 (
rootfs):LXC 利用这个特性为每个容器创建一个独立的根文件系统。容器进程看到的文件系统层次结构与宿主机完全不同。 - 实现方式:
- 准备
rootfs:首先,需要为容器准备一个目录,其中包含一个完整的 Linux 系统所需的文件和目录(如/bin,/etc,/lib,/usr等)。这通常通过复制一个最小化的系统模板来完成。 pivot_root系统调用:为了将容器的根目录切换到准备好的rootfs,LXC 使用pivot_root系统调用(或者在某些情况下使用chroot,但pivot_root更强大、更安全)。pivot_root会将当前进程的根文件系统切换到一个新的挂载点,同时将旧的根文件系统挂载到新根下的一个指定目录中,之后可以将其卸载。- 隔离挂载:在容器启动后,它可以在自己的文件系统命名空间内自由地挂载其他设备或文件系统(如
proc,sysfs,tmpfs),而这些挂载对宿主机是不可见的。
- 准备
通过这种方式,容器内的进程被"囚禁"在其自己的文件系统视图中,无法访问或修改宿主机的文件系统(除非特别配置了绑定挂载 bind mount)。
其他命名空间
除了以上三个,LXC 还使用了其他命名空间来实现全方位隔离:
- UTS Namespace: 隔离主机名和域名。
- IPC Namespace: 隔离进程间通信资源,如 System V IPC 和 POSIX 消息队列。
- User Namespace: 隔离用户和组 ID。允许容器内的
root用户(UID 0)映射为宿主机上的一个非特权用户,极大地提升了安全性。 - Cgroup Namespace: 隔离控制组视图。
总结
LXC 通过精巧地组合使用 Linux 内核的 Namespaces 和 Cgroups 特性,为用户提供了一个轻量级、高效且隔离性良好的容器环境。PID、网络和文件系统命名空间是实现这种隔离的基础,它们分别创建了独立的进程树、网络协议栈和文件系统视图,使得容器内的环境看起来就像一个独立的操作系统。