Docker 底层关键技术-namespace && cgroup
namespace
Linux Namespace 是 Linux 内核提供的一种内核级别环境隔离机制。它的主要目的是将全局的系统资源包装在一个抽象的隔离空间中,使得从每个 Namespace 内部看来,它都拥有自己独立的全局资源实例。
你可以把它想象成一种“障眼法”:它对进程“撒谎”,让一组进程“看到”一套独立的系统资源,而另一组进程“看到”另一套完全不同的资源。这些被隔离的资源包括进程 ID、主机名、用户 ID、网络接口、文件系统挂载点等。
核心目标:实现轻量级虚拟化(容器化),让一个进程或一组进程在一个隔离的运行环境中运行,而无需启动完整的虚拟机。这正是 Docker、LXC、Kubernetes 等容器技术的基石。
dd 命令
dd 是一个命令行工具,用于在 Unix 和类 Unix 操作系统(如 Linux)上转换和复制文件。它的名字源于 IBM 的 Job Control Language (JCL) 语句 “Data Definition”,但其功能更像是“磁盘转储”(Disk Dump)或“数据复制器”。
它的核心功能是按块从输入文件(if)读取数据,进行可能的转换,然后写入输出文件(of)。由于其底层操作的特性和灵活性,它成为了系统管理员、开发者和高级用户进行各种低级磁盘操作的瑞士军刀。
命令语法
基本语法非常简单:
dd if=<输入文件> of=<输出文件> [选项]
if=: input file,输入文件。默认为标准输入(stdin)。of=: output file,输出文件。默认为标准输出(stdout)。
核心常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
bs= |
block size: 同时设置输入和输出的块大小。这是最重要的选项之一,极大影响性能。 | bs=4M |
count= |
要复制的块的数量。 | count=1 |
skip= |
从输入文件开头跳过的块数。 | skip=1 |
seek= |
在输出文件开头跳过的块数。 | seek=1 |
status= |
显示进度信息的级别。progress 显示周期性进度,none 不显示。 |
status=progress |
conv= |
转换参数,用逗号分隔多个。最常用的是 fsync(确保数据物理写入)和 nocreat(不创建输出文件)。 |
conv=fsync |
status 选项的作用
status=progress 的作用就像一个实时仪表盘,它的核心价值是提供反馈。
当你运行一个需要很长时间的 dd 命令时(比如复制整个硬盘),终端会看起来完全卡住,没有任何输出,只有一个闪烁的光标。
sudo dd if=/dev/sda of=/dev/sdb bs=4M
# 按下回车后... 一片寂静,可能持续几十分钟甚至几个小时
用户的感受:
- “它还在工作吗?”
- “是死机了吗?”
- “我要不要强制终止它?”
- “已经复制了多少?还剩多少时间?”
这种不确定性非常令人焦虑,尤其是在处理重要数据时。
加上这个选项后,dd 会定期(通常是每秒)向标准错误输出(stderr)打印一行进度信息。
sudo dd if=/dev/sda of=/dev/sdb bs=4M status=progress
输出示例:
987425382 bytes (987 MB, 942 MiB) copied, 15 s, 65.8 MB/s
或者更现代的格式:
467854336 bytes (468 MB, 446 MiB, 112384 sectors) copied, 9.12321 s, 51.3 MB/s
再或者(使用 oflag=direct 等时):
2838312960 bytes (2.8 GB, 2.6 GiB) copied, 12.4323 s, 228 MB/s
467854336 bytes (468 MB, 446 MiB, 112384 sectors) copied, 9.12321 s, 51.3 MB/s
再或者(使用 oflag=direct 等时):
2838312960 bytes (2.8 GB, 2.6 GiB) copied, 12.4323 s, 228 MB/s
这行信息包含了极其有用的情报:
987425382 bytes: 已经复制了多少数据。你知道工作正在推进。15 s: 已经运行了多长时间。65.8 MB/s: 当前的传输速率。这是性能调试的关键指标。如果速度远低于你的磁盘性能,可能意味着遇到了瓶颈(比如加密、压缩、网络等)。- (942 MiB): 同时用二进制单位(MiB/GiB)显示,更直观。
所以,虽然 status=progress 不改变 dd 命令的实质,但它极大地改善了使用 dd 命令的体验和可观测性。强烈建议在任何需要等待的 dd 命令中都加上它。
经典实用示例
1. 制作启动盘(最经典的用途)
将 ISO 镜像文件写入 U 盘 (/dev/sdb)。警告:操作前务必确认目标设备,否则可能导致数据丢失!
sudo dd if=ubuntu-22.04.iso of=/dev/sdb bs=4M status=progress oflag=sync
if=ubuntu-22.04.iso: 输入是 ISO 镜像文件。of=/dev/sdb: 输出是整个 U 盘设备(不是分区,如/dev/sdb1)。bs=4M: 使用 4MiB 的大块进行读写,提高速度。status=progress: 显示复制进度和速度。oflag=sync: 确保每次写入都同步到物理介质,保证数据完整性。
2. 磁盘/分区备份与还原
备份整个磁盘(例如 /dev/sda)到一个镜像文件:
sudo dd if=/dev/sda of=/path/to/backup.img bs=4M status=progress
从镜像文件还原到磁盘:
sudo dd if=/path/to/backup.img of=/dev/sda bs=4M status=progress
注意: 对于包含大量空块的磁盘(如虚拟机磁盘),使用 gzip 压缩会更高效:
# 备份并压缩
sudo dd if=/dev/sda bs=4M | gzip > /path/to/backup.img.gz
# 解压并还原
gzip -dc /path/to/backup.img.gz | sudo dd of=/dev/sda bs=4M status=progress
3. 测试磁盘读写速度
测试写入速度:
创建一个 1GB 的文件,测试写入性能。
dd if=/dev/zero of=./testfile bs=1G count=1 oflag=direct
if=/dev/zero: 提供无限的空字符(0x00)。of=./testfile: 输出到当前目录的 testfile。bs=1G count=1: 写一个 1GiB 的块。oflag=direct: 使用直接 I/O,绕过系统缓存,得到更真实的磁盘速度。
测试读取速度:
读取刚才创建的文件。
dd if=./testfile of=/dev/null bs=1G count=1 iflag=direct
of=/dev/null: 相当于一个数据黑洞,丢弃所有写入的数据。iflag=direct: 使用直接 I/O 读取。
4. 安全擦除磁盘数据
用随机数据覆盖整个磁盘,使其难以恢复。
sudo dd if=/dev/urandom of=/dev/sdX bs=4M status=progress
if=/dev/urandom: 提供高质量的随机数据。of=/dev/sdX: 目标设备。
(对于快速擦除,用零覆盖一次通常就足够了):
sudo dd if=/dev/zero of=/dev/sdX bs=4M status=progress
5. 转换文件内容
将文件转换为大写:
dd if=input.txt of=output.txt conv=ucase
将 EBCDIC 编码的文件转换为 ASCII:
dd if=ebcdic_file.txt of=ascii_file.txt conv=ascii
6. 仅复制磁盘的 MBR(主引导记录)
MBR 通常位于磁盘的第一个扇区(512 字节)。
# 备份 MBR
sudo dd if=/dev/sda of=mbr_backup.bak bs=512 count=1
# 还原 MBR
sudo dd if=mbr_backup.bak of=/dev/sda bs=512 count=1
7. 创建特定大小的空文件
快速创建一个充满零的 100MB 文件,用于测试或占位。
dd if=/dev/zero of=test.file bs=1M count=100
注意事项和警告
- 极具破坏性:
dd命令不会询问确认。如果你将of=(输出目标)指定错了(比如是你的系统盘),它就会毫不犹豫地覆盖并销毁所有数据。“Double-check before you press Enter!”(按下回车前务必双重检查!) - 理解设备名: 在操作
/dev/sdX这样的块设备时,务必使用lsblk或fdisk -l命令确认设备标识符。 - 性能优化:
bs(块大小)的设置对性能影响巨大。太小的值(如 512Bytes)会导致大量 I/O 操作,降低速度。太大的值可能会超出系统缓存。通常1M到8M是一个很好的起点。使用status=progress来观察速度。 - 数据完整性: 在写入重要数据(如制作启动盘)后,使用
oflag=sync或conv=fsync来确保所有数据都从内存缓冲区刷新到了物理设备上。
mkfs 命令
mkfs 是 MaKe FileSystem 的缩写。它的作用就是在块设备(如硬盘分区、U 盘、软盘)或镜像文件上创建(格式化)一个文件系统。
您可以把它想象成:在一块已经划分好的“土地”(分区)上,按照特定的“规划和布局”(文件系统类型,如 ext4, FAT32)修建一座“城市”的基础设施(文件系统结构)。在这之后,您才能在上面“存放人员和物资”(存储文件)。
命令格式与工作原理
mkfs 其实是一个前端工具,它本身并不直接完成所有工作,而是会去调用相应的特定文件系统创建工具。
基本语法:
mkfs [选项] [-t 文件系统类型] [设备名] [块数]
-t 文件系统类型: 指定要创建的文件系统,如ext4,xfs,vfat等。设备名: 要在哪个设备上创建文件系统,例如/dev/sdb1。块数: 很少手动指定,通常使用整个设备。
例如,当你执行:
sudo mkfs -t ext4 /dev/sdb1
mkfs 会实际上调用更专门的程序 mkfs.ext4 来完成工作。这就是为什么你也会经常看到直接调用 mkfs.<类型> 的命令格式。
常用文件系统类型
| 类型 | 命令 | 描述 | 适用场景 |
|---|---|---|---|
| ext4 | mkfs.ext4 |
Linux 最主流、最稳定的日志文件系统。 | Linux 系统根分区、家用、通用服务器。 |
| XFS | mkfs.xfs |
高性能日志文件系统,擅长处理大文件。 | 大型服务器、数据库、视频编辑。 |
| Btrfs | mkfs.btrfs |
先进的功能性文件系统,支持写时复制、快照、压缩等。 | 需要高级功能的桌面用户或服务器。 |
| vFAT | mkfs.vfat |
兼容性最好的文件系统,无权限设置。 | U 盘、SD 卡,需要在 Windows/Linux/Mac 间交叉使用。 |
| NTFS | mkfs.ntfs |
Windows 现代文件系统,支持大文件和 ACL。 | 主要用于 Windows 系统分区,Linux 也可读写。 |
| exFAT | mkfs.exfat |
微软为闪存设备设计的文件系统,比 FAT32 支持更大文件。 | 大容量 U 盘、移动硬盘,用于跨平台交换大文件。 |
常用选项
这些选项通常用于直接调用 mkfs.<类型> 命令时。
| 选项 | 示例 | 描述 |
|---|---|---|
-L |
-L MYDATA |
为文件系统设置一个卷标,便于用标签挂载而非设备名。 |
-c |
-c |
在创建文件系统前检查设备是否有坏块。 |
-q |
-q |
安静模式,减少输出信息。 |
-f |
-f |
强制创建,即使参数可能有问题也会尝试。 |
-N |
-N 10000 |
显式指定 inode 的数量(文件数量的上限)。 |
重要警告与前提
`mkfs 命令会永久销毁目标设备上的所有现有数据!
格式化之前,必须:
- 确认设备名: 使用
lsblk、fdisk -l等命令双重、三重确认你要格式化的设备是否正确。 - 卸载设备: 确保该设备没有被系统挂载 (
umount /dev/sdb1)。对已挂载的设备进行格式化会导致严重错误。
实用示例
示例 1:将 U 盘格式化为 FAT32(最大兼容性)
假设 U 盘被识别为 /dev/sdb1(请务必确认!)
# 先卸载
sudo umount /dev/sdb1
# 再格式化为 FAT32,并设置卷标为“MYUSB”
sudo mkfs -t vfat -n MYUSB /dev/sdb1
# 或者直接使用 mkfs.vfat
sudo mkfs.vfat -n MYUSB /dev/sdb1
示例 2:将分区格式化为 Linux 常用的 ext4
假设要格式化的分区是 /dev/sdc1。
sudo umount /dev/sdc1
sudo mkfs -t ext4 -L BackupDrive /dev/sdc1
# 更常见的直接用法
sudo mkfs.ext4 -L BackupDrive /dev/sdc1
示例 3:创建 XFS 文件系统
XFS 是很多企业级应用的首选。
sudo umount /dev/sdd1
sudo mkfs.xfs -f -L Database /dev/sdd1
# `-f` 强制创建,如果设备上已有文件系统则需要此选项
示例 4:检查坏块后再格式化
这是一个好习惯,尤其对于新旧不明的硬盘。
sudo mkfs.ext4 -c /dev/sde1
# 这个过程会很久,因为它会先扫描整个设备的坏道
示例 5:格式化一个镜像文件
你不仅可以格式化设备,也可以格式化一个文件(通常需要先关联到 loop 设备)。
# 创建一个 1G 的空文件
dd if=/dev/zero of=my-disk.img bs=1M count=1000
# 将这个文件格式化为 ext4
mkfs.ext4 my-disk.img
# 挂载这个镜像文件来使用它
sudo mount -o loop my-disk.img /mnt
mkfs 执行成功后你会看到什么?
运行 sudo mkfs.ext4 /dev/sdb1 后,你会看到类似如下输出:
mke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 2621440 4k blocks and 655360 inodes
Filesystem UUID: 6e5cff33-5f3d-4a86-b965-85c821a9e6b7
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632
Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
这些信息告诉你文件系统的基本参数:块数、inode 数、UUID、超级块备份位置等。
mkfs是用于创建文件系统的命令,也就是高级格式化。- 它是一个前端,实际调用的是
mkfs.ext4、mkfs.xfs等具体工具。 - 使用前务必确认设备未挂载且设备名正确,否则会导致数据丢失。
- 根据使用场景(Linux 专用、跨平台、大文件性能)选择合适的文件系统类型。
- 常用的选项包括设置卷标
-L和检查坏块-c。
df 命令
df 是 Disk Filesystem 的缩写。它的主要功能是报告文件系统的磁盘空间使用情况。简单来说,它告诉你:
- 系统上有哪些磁盘/分区(文件系统)已经被挂载了。
- 每个磁盘/分区总共有多少空间。
- 已经使用了多少空间。
- 还剩下多少可用空间。
- 它们被挂载到了哪个目录(挂载点)。
它是监控磁盘空间、防止磁盘被写满导致系统问题的首选工具。
命令语法
df [选项] [文件或目录]
- 如果不加任何参数,
df会显示所有已挂载的文件系统的使用情况。 - 如果指定了
文件或目录,df会报告该文件或目录所在的文件系统的空间情况。
核心常用选项
| 选项 | 描述 |
|---|---|
-h |
人类可读模式。以易读的单位(K, M, G, T)显示大小,而不是单纯的块数。这是最常用的选项。 |
-T |
显示文件系统类型。会在输出中增加一列 Type,显示是 ext4, xfs, tmpfs 还是其他类型。 |
-i |
显示 inode 信息而不是块使用情况。可以查看文件数量是否达到上限。 |
-x [类型] |
排除指定类型的文件系统。例如 -x tmpfs 不显示临时文件系统。 |
--total |
在输出结果的末尾添加一行总计信息。 |
-a |
显示所有文件系统,包括那些大小为 0 的伪文件系统(如 /proc, /sys)。 |
输出列解读
当你运行 df -h,你会看到类似这样的输出:
文件系统 容量 已用 可用 已用% 挂载点
udev 3.9G 0 3.9G 0% /dev
tmpfs 787M 2.1M 785M 1% /run
/dev/nvme0n1p2 234G 65G 157G 30% /
tmpfs 3.9G 87M 3.8G 3% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
/dev/nvme0n1p1 511M 6.1M 505M 2% /boot/efi
tmpfs 787M 88K 787M 1% /run/user/1000
各列的含义如下:
- 文件系统: 磁盘设备的名称(如
/dev/nvme0n1p2)或特殊文件系统(如tmpfs,udev)。 - 容量: 该文件系统的总大小。
- 已用: 已经使用了多少空间。
- 可用: 剩余可用的空间。这是最需要关注的列,防止它变为 0。
- 已用%: 已用空间占总空间的百分比。一眼就能看出哪个盘快满了。
- 挂载点: 该文件系统被挂载到了哪个目录。例如,根分区
/是系统最重要的挂载点。
实用示例
示例 1:最基本用法(人类可读格式)
这是最常用的命令,没有之一。
df -h
输出易于阅读,快速查看所有磁盘的使用情况。
示例 2:查看特定目录所在分区的空间
比如你想知道 /home 目录还有多少空间。
df -h /home
输出示例:
文件系统 容量 已用 可用 已用% 挂载点
/dev/sda1 200G 50G 150G 25% /home
示例 3:显示文件系统类型
想知道各个分区都是什么格式(ext4? NTFS?)。
df -hT
输出示例:
文件系统 类型 容量 已用 可用 已用% 挂载点
/dev/sda1 ext4 234G 65G 157G 30% /
/dev/sdb1 vfat 30G 5G 25G 17% /media/myusb
示例 4:检查 inode 使用情况
有时候磁盘空间明明还有剩余,但系统却报错“No space left on device”。这通常是 inode 用尽了(创建了太多小文件)。这个命令可以诊断这个问题。
df -i
输出示例:
文件系统 Inode 已用(I) 可用(I) 已用%(I) 挂载点
/dev/sda1 5.8M 1.1M 4.7M 19% /
如果 可用(I) 接近 0 或 已用%(I) 是 100%,说明 inode 耗尽,需要清理文件。
示例 5:排除特定类型的文件系统
例如,不想看那些临时的 tmpfs 文件系统,只关心真实的硬盘。
df -h -x tmpfs
示例 6:查看所有文件系统(包括伪文件系统)
显示包括 /proc, /sys 在内的所有信息。
df -a
(这个输出通常会很长,包含很多内核虚拟的文件系统。)
需要注意的要点
- “可用”空间: 通常,根文件系统 (
/) 会保留约 5% 的磁盘空间(可由tune2fs -m调整)仅供 root 用户使用,以防止系统完全崩溃。所以普通用户看到的“可用”空间可能比实际计算的少一点点。 - 伪文件系统: 像
tmpfs,udev,proc这些并不是真正的物理磁盘,它们是内核在内存中创建的虚拟文件系统,用于管理设备、进程信息等。它们的空间使用是动态变化的。 - 网络文件系统:
df也会显示通过网络挂载的文件系统,如 NFS 或 Samba 共享。 - 挂载点: 理解挂载点的概念至关重要。
/home目录可能是一个独立的大分区,而/boot通常是一个较小的独立分区。
总结
df是查看磁盘空间总体使用情况的命令。du是查看某个目录或文件具体占用多少空间的命令。它们两个通常配合使用:先用df发现哪个盘空间紧张,再用du去那个盘下找是哪个目录或文件占用了大量空间。df -h是你最应该记住的命令。- 如果遇到磁盘空间满的错误,但
df -h显示还有空间,记得用df -i检查是否是 inode 用尽了。
掌握 df 命令是每个 Linux 用户和管理员进行系统监控和存储管理的基本功。
mount 命令
mount 命令用于将存储设备(如硬盘分区、U 盘、光盘、ISO 镜像)上的文件系统附加到 Linux 目录树的某个挂载点上,从而使该设备中的文件和目录可以被访问。
反之,umount 命令则用于将其分离,断开访问通道。
核心思想:它是在物理存储设备和用户可见的目录路径之间搭建一座桥梁。
命令语法
mount [-选项] [-o 挂载选项] <设备名> <挂载点>
umount <挂载点或设备名>
基本用法示例
1. 查看所有已挂载的文件系统
不加任何参数直接运行 mount,会显示系统中所有已挂载的文件系统列表,信息非常详细。
mount
输出会包括设备、挂载点、文件系统类型、挂载选项等,类似于 df -h 但更详细。
2. 挂载一个 U 盘(手动挂载)
假设系统将 U 盘识别为 /dev/sdb1,我们要将它挂载到 /mnt/usb。
# 第一步:创建挂载点目录 (如果不存在)
sudo mkdir -p /mnt/usb
# 第二步:执行挂载
sudo mount /dev/sdb1 /mnt/usb
# 第三步:访问U盘内容
ls /mnt/usb
3. 卸载设备
操作完成后,必须卸载才能安全移除设备。
# 通过挂载点卸载
sudo umount /mnt/usb
# 或者通过设备名卸载
sudo umount /dev/sdb1
重要提示:umount 而不是 unmount。
常用选项和高级用法
-t:指定文件系统类型
如果系统无法自动识别设备类型,或者你想明确指定,可以使用 -t 选项。
# 明确指定文件系统类型为 vfat (FAT32)
sudo mount -t vfat /dev/sdb1 /mnt/usb
# 挂载光盘,类型通常是 iso9660
sudo mount -t iso9660 /dev/sr0 /mnt/cdrom
# 挂载 exFAT 格式的U盘 (可能需要先安装 exfat-fuse)
sudo mount -t exfat /dev/sdc1 /mnt/usb
-o:指定挂载选项(非常强大)
这是 mount 命令最灵活的部分,允许你精细控制挂载行为。多个选项用逗号分隔。
常用挂载选项:
| 选项 | 描述 | 示例 |
|---|---|---|
ro / rw |
只读 / 读写 挂载。挂载光盘或只读镜像时用 ro。 |
-o ro |
remount |
重新挂载一个已挂载的文件系统。常用于将只读挂载改为读写。 | -o remount,rw / |
loop |
挂载镜像文件。将普通文件(如 .iso)当作块设备来挂载。 |
-o loop ubuntu.iso /mnt/iso |
defaults |
使用默认选项:rw, suid, dev, exec, auto, nouser, async。 |
-o defaults |
noexec |
禁止执行该文件系统上的二进制程序。增加安全性。 | -o noexec |
nosuid |
忽略 suid 和 sgid 权限位。增加安全性。 |
-o nosuid |
user |
允许普通用户挂载设备。 | -o user |
uid, gid |
设定挂载后文件所有者的用户 ID 和组 ID,解决权限问题。 | -o uid=1000,gid=1000 |
username, password |
挂载 Windows 共享 (CIFS/SMB) 时使用的认证信息。 | -o username=john,password=secret |
示例:
# 以只读方式挂载光盘
sudo mount -t iso9660 -o ro /dev/sr0 /mnt/cdrom
# 挂载ISO镜像文件
sudo mount -o loop ubuntu-22.04.iso /mnt/iso
# 挂载NTFS分区并赋予当前用户读写权限 (需安装ntfs-3g)
sudo mount -t ntfs-3g -o uid=1000,gid=1000 /dev/sdd1 /mnt/ntfs
# 重新挂载根目录为读写模式 (在救援模式中常用)
sudo mount -o remount,rw /
注意事项
- 卸载失败: 如果
umount失败并提示device is busy,表示有进程正在使用该挂载点下的文件。你可以:- 使用
lsof /mnt/usb查看是哪个进程在占用。 - 切换到其他目录,再尝试卸载。
- 使用
umount -l(lazy unmount)延迟卸载,等设备不再繁忙时再断开。
- 使用
- 权限问题: 挂载的设备可能因为权限问题导致当前用户无法读写。通常用
-o uid=...,gid=...选项解决。 - 安全卸载: 永远在拔出 U 盘或移动硬盘前执行
umount,否则可能导致数据损坏。
总结
| 命令 | 作用 |
|---|---|
mount |
挂载文件系统到目录树 |
umount |
卸载已挂载的文件系统 |
mount -a |
挂载 /etc/fstab 中定义的所有文件系统 |
mount -o loop |
挂载镜像文件 |
mount -o remount |
重新挂载,改变挂载参数 |
mount 命令是 Linux 系统管理的瑞士军刀,它将物理存储、网络资源与系统的逻辑目录树无缝连接起来,体现了 Linux “一切皆文件” 的设计哲学。
unshare 命令允许一个已经存在的进程从其父进程“脱离”一个或多个 Namespace,从而进入一个新的、隔离的上下文中运行。
简单来说: 它能让你的当前 shell 或命令“脱离”原来的网络、进程树、挂载点等视图,获得一个全新的、隔离的环境,而不需要启动一个新的容器或虚拟机。
与 clone() 系统调用的区别
要理解 unshare,最好先知道它的“兄弟”——clone()。
clone(): 在创建新进程的同时就将其放入新的 Namespace。docker run、podman run等容器命令在底层最终调用的是clone()。(出生即隔离)unshare(): 让一个已经运行着的进程脱离它当前的 Namespace。(后天改造隔离)
unshare 命令就是这个系统调用的命令行包装。
命令语法
unshare [选项] [<程序> [<参数>...]]
- 选项: 指定要脱离哪种类型的 Namespace。
- 程序: 可选参数。如果指定了,则在新创建的 Namespace 中直接运行这个程序。如果没指定,则默认会在新 Namespace 中启动一个新的 shell。
核心选项(指定要脱离的 Namespace)
| 选项 | 对应的 Namespace | 效果 |
|---|---|---|
--mount or -m |
Mount (CLONE_NEWNS) |
拥有独立的文件系统挂载点视图。 |
--uts or -u |
UTS (CLONE_NEWUTS) |
可以设置独立的主机名和域名。 |
--ipc or -i |
IPC (CLONE_NEWIPC) |
拥有独立的 System V IPC 和 POSIX 消息队列。 |
--net or -n |
Network (CLONE_NEWNET) |
拥有独立的网络栈(接口、路由、防火墙等)。 |
--pid or -p |
PID (CLONE_NEWPID) |
拥有独立的进程 ID 空间。需要与 --fork 一起使用。 |
--user or -U |
User (CLONE_NEWUSER) |
拥有独立的用户和用户组 ID 映射。 |
--cgroup or -C |
Cgroup (CLONE_NEWCGROUP) |
拥有独立的 cgroup 根目录。 |
--time or -T |
Time (CLONE_NEWTIME) |
允许独立设置系统时钟偏移量。 |
常用辅助选项:
| 选项 | 描述 |
|---|---|
--fork or -f |
在启动指定程序前先 fork 一个子进程。与 --pid 必须一起使用,否则新 Namespace 中第一个进程的 PID 会是 1,它退出后整个 Namespace 就销毁了。 |
--mount-proc |
将 /proc 文件系统重新挂载到新的 Mount Namespace 中。与 --pid 一起使用时是必须的,否则 ps, top 等命令无法正常工作(它们仍然会看到宿主机的所有进程)。 |
--map-root-user |
在新的 User Namespace 中,将当前用户映射为 root (uid 0)。这允许你在没有外部 root 权限的情况下,在内部拥有 root 权限。 |
--root=<目录> |
将指定的目录设置为新的根目录(类似 chroot)。 |
实用示例
示例 1:创建一个新的 Network Namespace
创建一个拥有独立网络环境的 shell。在这个 shell 里,你看不到宿主机的网络接口(除了 loopback)。
sudo unshare --net /bin/bash
# 进入新的bash后,查看网络,只有lo接口
ip a
示例 2:创建一个新的 PID 和 Mount Namespace(最像迷你容器)
这是最经典的用法,创建一个进程隔离的环境。
sudo unshare --pid --fork --mount-proc /bin/bash
--pid: 创建新的 PID Namespace。--fork: 让unshare先 fork,再执行/bin/bash,这样 bash 的 PID 就是 1。--mount-proc: 重新挂载/proc,使得ps aux等命令只显示这个新 Namespace 内的进程(实际上只有几个)。
现在,你运行ps aux,会发现看不到宿主机上的其他进程,仿佛进入了一个崭新的系统。
示例 3:在新的 Namespace 中改变主机名(UTS)
# 先查看当前主机名
hostname
# 创建新的 UTS Namespace 并运行 bash
sudo unshare --uts /bin/bash
# 在新环境中修改主机名
hostname my-new-container
# 再次查看,主机名已变
hostname
# 打开另一个终端,查看原主机名,发现并未改变。证明隔离成功。
exit # 退出后,主机名恢复
示例 4:普通用户创建“伪 root”环境(User Namespace)
这是非常强大的一点:普通用户可以不需 root 权限就创建新的 User Namespace,并在其中拥有 root 权限。
# 不需要 sudo!
unshare --user --map-root-user --pid --fork --mount-proc /bin/bash
whoami # 输出:root
id # 输出:uid=0(root) gid=0(root) groups=0(root)
--user和--map-root-user: 让你在内部成为 root。- 注意:这个内部的 root 权限仅限于这个新的 Namespace 之内,例如你可以挂载文件系统、设置能力(capabilities),但对宿主机的影响非常有限,安全性很高。
示例 5:直接运行一个程序
不启动交互式 shell,而是直接在新 Namespace 中运行一个一次性命令。
sudo unshare --net --pid --fork --mount-proc sleep 300
# 这个 `sleep` 进程在一个隔离的 PID 和 Network Namespace 中运行300秒
unshare 是 Docker/容器技术的底层基础之一。
- 当你运行
docker run -it ubuntu bash时,Docker 引擎在底层做的事情,就类似于一个超级加强版的unshare命令:它一次性调用了几乎所有--mount --uts --ipc --net --pid --user --cgroup选项,并在此基础上增加了 Cgroups(资源限制)、镜像根文件系统等更多功能。 unshare让你能够手动地、一步一步地体验和理解容器是如何实现隔离的。
mount 隔离
在 Linux 容器技术中,Mount Namespace 是实现文件系统隔离的核心机制,它允许每个容器拥有自己独立的文件系统挂载点视图,仿佛运行在一个完全独立的系统之上。这意味着在一个容器内部进行的挂载或卸载操作,不会影响到宿主机器或其他容器,反之亦然。
核心原理:挂载传播与命名空间
Mount Namespace 的实现并非简单的“屏蔽”或“过滤”,而是依赖于 Linux 内核一项更为精巧的设计:挂载传播(Mount Propagation)。
每个挂载点都有一个传播类型属性,它决定了挂载事件如何在不同的命名空间之间通知和共享。当一个新 Mount Namespace 被创建时(例如通过 unshare --mount 或 clone() 系统调用),它会复制当前命名空间的挂载点列表。然而,这些复制过来的挂载点的默认传播类型被设置为 MS_PRIVATE(私有)。
MS_PRIVATE 的含义是:在该挂载点下发生的任何挂载或卸载事件,都被视为当前 Mount Namespace 的“私事”。内核不会将此事件通知给任何其他命名空间,其他命名空间也不会将它们的挂载变更传播过来。这就像在两者之间设置了一道双向不可见的单向玻璃,完美实现了隔离。
实践演示:私有挂载实验
通过一个简单的命令,我们可以清晰地观察到 Mount Namespace 的隔离效果:
创建隔离环境:首先,我们使用
unshare命令创建一个新的 Mount Namespace(--mount)并启动一个 shell。--mount-proc选项不仅会挂载一个新的/proc,其内部也会调用unshare(CLONE_NEWNS)来创建新的 Mount Namespace。sudo unshare --mount --fork --mount-proc /bin/bash准备存储介质:在新创建的隔离 shell 中,我们创建一个空的镜像文件并将其格式化为 ext4 文件系统,模拟一块虚拟磁盘。
dd if=/dev/zero of=./my-disk.img bs=1M count=100 mkfs.ext4 ./my-disk.img执行挂载操作:将格式化好的镜像文件挂载到一个目标目录(如
./mnt)。mkdir ./mnt mount ./my-disk.img ./mnt此时,在隔离环境内使用
df -h或mount命令,可以清晰地看到该挂载点。验证隔离性:这是最关键的一步。 此时,在宿主系统或其他任何终端窗口中使用
mount或df -h命令查看,都无法发现my-disk.img被挂载到了./mnt。这个挂载操作完全被限制在了新创建的 Mount Namespace 内部,对外部系统不可见。
cgroup
cgroup(Control Groups)是 Linux 内核提供的一种机制,用于对一组进程进行统一的资源限制、隔离和统计。它是容器化技术(如 Docker 和 Kubernetes)最核心的基石之一。
与早期的 cgroup v1 相比,cgroup v2 带来了更清晰统一的管理模型、更一致的组织结构以及更强大的功能(如原子化资源分配)。目前主流的 Linux 发行版均已默认采用 cgroup v2。
其核心思想可以通过下图来理解:
flowchart TD
subgraph Host[宿主机系统]
subgraph CgroupRoot[统一层级结构 /sys/fs/cgroup/]
direction TB
Root[/根控制组
控制所有系统资源/]
Root --> A[控制组 A
e.g. 数据库]
Root --> B[控制组 B
e.g. Web 服务]
Root --> C[控制组 C
e.g. 备份任务]
A --> A1[控制组 A1
e.g. 数据库子任务]
end
end
A -.->|施加 CPU、内存等限制| LA[进程列表 A]
B -.->|施加 CPU、内存等限制| LB[进程列表 B]
C -.->|施加 CPU、内存等限制| LC[进程列表 C]
A1 -.->|继承父组限制并可叠加| LA1[进程列表 A1]
cgroup v2 通过一个统一的层级结构(通常挂载在 /sys/fs/cgroup/)来管理所有资源控制器(cpu、memory、io 等)。每个子目录都是一个控制组,可以为其配置资源限制,并将进程纳入其中进行管理。
管理基础:挂载与目录操作
cgroup v2 的管理接口通过虚拟文件系统(通常为 cgroup2fs)暴露。首先需要挂载,但现代系统通常已自动完成:
# 检查 cgroup2 挂载点
mount | grep cgroup2
# 输出示例:cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
管理 cgroup 的本质就是操作 /sys/fs/cgroup/ 下的目录和文件:
- 创建控制组:使用
mkdir创建一个子目录。 - 配置限制:向该目录下的特定文件(如
cpu.max)写入配置值。 - 加入进程:将进程的 PID 写入该目录下的
cgroup.procs文件。 - 删除控制组:使用
rmdir删除空目录。
实战:使用 cgroup v2 限制 CPU 使用率
本示例将演示如何创建一个 cgroup,并将其内所有进程的 CPU 使用率限制为 单个核心的 20%。
第 1 步:创建控制组
我们创建一个名为 cpu_demo 的控制组。创建目录即创建了 cgroup。
# 切换到 cgroup 根目录
cd /sys/fs/cgroup/
# 创建新的控制组
sudo mkdir cpu_demo
创建后,系统会自动在该目录下生成所有控制文件(cpu.max, cgroup.procs 等)。
第 2 步:配置 CPU 限制
cgroup v2 通过 cpu.max 文件来设置 CPU 配额,其格式为:$MAX $PERIOD。
$PERIOD: 一个周期的时间长度(微秒)。通常固定为100000(即 100 毫秒)。$MAX: 在上述一个周期内,该 cgroup 中所有进程最多能使用的 CPU 时间(微秒)。计算公式为:MAX = PERIOD * desired_cpu_percentage。
我们的目标是 20%,所以计算:100000 * 0.2 = 20000。
# 设置 CPU 配额:100ms 周期内最多使用 20ms 的 CPU 时间
echo "20000 100000" | sudo tee /sys/fs/cgroup/cpu_demo/cpu.max
这表示该 cgroup 中的进程在任何 100ms 的时间窗口内,最多只能运行 20ms,即 20% 的 CPU 时间。
第 3 步:验证限制效果
为了验证限制,我们需要一个能消耗大量 CPU 的程序。这里使用 stress 工具。
将当前 Shell 加入控制组:
当前 Shell 中启动的进程会自动继承其 cgroup。我们将当前 Shell 加入cpu_demo。# 将当前 shell 的 PID 写入 cgroup.procs echo $$ | sudo tee /sys/fs/cgroup/cpu_demo/cgroup.procs启动压力测试:
在新终端中,使用pidstat监控 CPU。在原终端中启动stress,它会尝试占满一个核心。# 启动一个消耗 CPU 的 worker stress -c 1 &监控 CPU 使用率:
在另一个终端,使用pidstat监控stress进程。pidstat -p $(pgrep -o stress) -u 1 # 输出示例: # 06:14:48 PM UID PID %usr %system %guest %wait %CPU CPU Command # 06:14:49 PM 0 1234 20.0 0.0 0.0 80.0 20.0 1 stress结果分析:可以看到,
%CPU列被稳定地限制在了 ~20%。同时,%wait值很高,表明进程大部分时间在等待 CPU 时间片,这正是 cgroup 通过内核调度器进行“节流”限制的直观表现。
第 4 步:清理
实验完成后,务必进行清理:
# 1. 终止测试进程
pkill stress
# 2. 将当前 shell 移回根 cgroup
echo $$ | sudo tee /sys/fs/cgroup/cgroup.procs
# 3. 删除创建的 cgroup (必须确保组内无进程)
sudo rmdir /sys/fs/cgroup/cpu_demo
核心特性与优势
通过上述演示,我们可以看到 cgroup v2 的一些核心优势:
- 统一层级结构:所有控制器(CPU、内存、IO 等)在同一目录下管理,结构清晰。
- 接口简单直观:通过读写文件即可完成所有配置,易于理解和自动化。
- 限制精确有效:在内核层面进行资源调度和隔离,效果稳定可靠。
- 继承性:子 cgroup 继承父 cgroup 的限制,并可以进一步收紧,提供了灵活的资源配置能力。
cgroup v2 是理解和实现现代 Linux 系统资源管理的基础,从简单的进程限制到复杂的容器编排,都离不开它的强大功能。