阶段一 · Linux 与容器底层

容器技术的前置知识

为什么需要这篇

后续的 Namespace,Cgroups,OverlayFS 都是 Linux 内核特性.如果你不清楚"进程是怎么创建的""/proc 是什么""网络接口为什么能配 IP",后续章节会频繁出现认知断层.本篇目标是用最短篇幅补齐这些前置概念,不求精通,但求理解"是什么,为什么".

一,进程模型

Linux 中一切工作都由进程完成.你在终端敲的每条命令,运行的每个程序,容器里的每个应用,都是一个进程.理解进程的创建方式是理解容器的第一步.

fork 与 exec - 进程的诞生方式

Linux 创建新进程只有一种途径:fork(分裂).

操作 含义 类比
fork() 当前进程复制一份自己,产生一个子进程(内容完全相同) 细胞分裂--分裂后两个细胞一模一样
exec() 用一个新程序替换 当前进程的代码和数据 换魂--壳还是那个壳,里面的灵魂换了

典型流程(你在 shell 里输入 ls 时发生的事):

bash 进程(PID 100)

├─ fork() → 子进程(PID 101),内容和 bash 完全一样

└─ 子进程调用 exec("ls") → PID 101 变成了 ls 程序
→ ls 执行完毕,退出
→ bash 继续等待下一条命令

为什么不直接"创建"一个全新进程?因为 Linux 的设计哲学是复制比从零构建更简单--先 fork 出一份副本,再按需替换内容(exec).这个"先复制再替换"的模式贯穿整个系统.

PID - 进程的身份证号

PID(Process ID)是内核分配给每个进程的唯一编号:

  • PID 1 = init(或 systemd),系统启动后的第一个用户态进程,是所有进程的祖先
  • 每个进程都有一个父进程(PPID,Parent PID)
  • 进程退出后,PID 可以被回收复用
# 查看当前 shell 的 PID
echo $$

# 查看进程的父进程
ps -o pid,ppid,comm -p $$

进程树 - 所有进程的族谱

由于每个进程都由 fork 产生,所有进程形成一棵:

# 查看进程树
pstree -p

# 输出示例(精简):
# systemd(1)─┬─sshd(800)───sshd(1200)───bash(1201)───pstree(1234)
# ├─dockerd(900)───containerd(901)
# └─nginx(1000)─┬─nginx(1001)
# └─nginx(1002)

与容器的关系

容器里的进程也在这棵树上--它只是一个普通的子进程.但通过 PID Namespace(第 02 篇),容器内的进程"以为"自己的 PID 是 1,看不到树的其他部分.隔离的是视图,不是进程本身.

二,Linux 文件系统基础

在讨论容器技术之前,必须先把 Linux 文件系统的基本概念理清.很多人对"文件系统"的理解停留在"文件夹和文件"的层面--这在 Windows 的日常使用中够用,但在 Linux 里不够.下面我们从最核心的一条设计哲学讲起,逐层建立认知.

2.1 "一切皆文件":Linux 的核心设计哲学

Linux 有一条贯穿整个系统的设计理念:一切皆文件(Everything is a file).这句话不是比喻,而是字面意义上的事实.

什么是"文件"?

在 Linux 中,"文件"不是一个狭义概念(只有 Word 文档,图片才叫文件).文件是一个抽象接口--只要一个东西支持以下四个基本操作,它在 Linux 眼里就是"文件":

系统调用 含义 类比
open() 打开一个文件,获得文件描述符(file descriptor) 拿起电话听筒
read() 从文件中读取数据 听对方说话
write() 向文件中写入数据 对着话筒说话
close() 关闭文件,释放文件描述符 挂断电话

关键是:读一个 .txt 文件,读键盘输入,读网络数据,读进程信息,用的都是同一套 open → read → write → close 系统调用.程序员不需要为"不同类型的 IO"写完全不同的代码,这是巨大的简化.

哪些东西在 Linux 里是"文件"?

类型 例子 你平时就在这里看到它
普通文件(Regular File) 源代码,日志,图片,二进制程序 ~/project/main.go
目录(Directory) 文件夹--本质上是一个存有"文件名→inode 编号"映射关系的特殊文件 任何一个文件夹
设备文件(Device File) 硬盘,键盘,鼠标,终端 /dev/sda(硬盘),/dev/tty(终端)
管道(Pipe / FIFO) 进程间单向数据流 ls | grep foo
套接字(Socket) 网络连接端点,也可以是本机进程间通信 /var/run/docker.sock
符号链接(Symlink) 指向另一个文件路径的"快捷方式" /usr/bin/python → python3
虚拟文件(Virtual File) 内核实时生成的"假"文件,不存在于磁盘 /proc/cpuinfo,/sys/class/net/eth0/address

一切皆文件 = 一切都有统一的读写接口.想象一个万能插座:不管你插的是台灯,电视,电风扇,手机充电器,插座提供的都是 220V 交流电.Linux 的"文件接口"就是这个万能插座--不论底层是硬盘,键盘,网卡还是内核数据,上层都用同一套 open/read/write/close 来操作.

一个具体例子来感受

假设你想获取当前系统有几颗 CPU 核心.在 Linux 里,你不需要调用特殊 API,直接"读文件"就行:

# 读取 /proc/cpuinfo -- 就像读一个普通文本文件一样
cat /proc/cpuinfo | grep 'processor' | wc -l

# 和读普通文件完全相同的系统调用链:
# open("/proc/cpuinfo") → read() → write() → close()

再比如,你想限制一个进程的 CPU 使用量(第 03 篇 Cgroups 的核心操作),不需要特殊工具,直接"写文件":

# 往一个"文件"里写个数字,内核就帮你限制了这个 Cgroup 的 CPU
echo 50000 > /sys/fs/cgroup/cpu/mygroup/cpu.cfs_quota_us

读文件 = 查询信息,写文件 = 修改配置. 这就是 Linux 把复杂性隐藏在统一接口背后的方式.

关键认知

"一切皆文件"不是 Linux 的专利(Unix 首创),但 Linux 把这条哲学贯彻得最彻底.理解这一点后,你会发现:在 Linux 里,"探索系统"就是"浏览文件"--你用 lscat 就能窥探内核的内部状态,不需要特殊工具.这是 Linux 作为学习平台的巨大优势.

2.2 统一目录树:没有 C 盘 D 盘的世界

Windows 用户习惯了 C:\,D:\,E:\ 这样的盘符体系--每个分区是一个独立的"根",各自拥有一棵目录树:

Windows(多根):
C:\ D:\ E:\
├─ Windows ├─ 游戏 ├─ 电影
├─ Program Files ├─ 照片 └─ 音乐
└─ Users └─ 文档

Linux 的设计完全不同:整个系统只有一棵目录树,根就是 /.不管你插了几个 U 盘,挂了几块硬盘,它们全部"接入"到这棵树的某个分支上:

Linux(单根):
/
├─ bin/ ← 基本命令(ls, cat, cp...)
├─ etc/ ← 系统配置
├─ home/
│ └─ yutong/ ← 你的个人文件
├─ mnt/
│ ├─ usb/ ← U 盘的内容出现在这里(通过 mount 接入)
│ └─ data/ ← 另一块硬盘的内容出现在这里
├─ proc/ ← 内核进程信息(虚拟,不存在于磁盘)
├─ sys/ ← 内核子系统信息(虚拟)
├─ dev/ ← 设备文件
└─ var/ ← 日志,缓存等可变数据

Windows 盘符 = 独立的岛屿,每个岛有自己的地图系统.C 岛和 D 岛之间没有天然的层次关系.Linux 目录树 = 一棵大树,/ 是树干,所有存储设备和文件系统都是长在这棵树上的树枝.你要访问 U 盘?它就在 /mnt/usb 这个树枝上--和访问 /home/yutong 没有任何语法差异.

这种设计为什么更好?

  • 位置透明:程序不需要关心数据在哪个物理设备上.读 /home/yutong/file.txt 就行了--它可能在本地 SSD 上,也可能是网络挂载的 NFS 远程文件系统,对程序来说没有区别.
  • 灵活挂载:任何目录都可以成为"接入点".你可以把一整块硬盘挂到 /home/yutong/projects,让那个目录拥有独立的存储空间--不需要给它单独的盘符.
  • 容器的基础:容器的 rootfs 就是给容器进程"换一个根"--让容器进程看到的 / 不是宿主机的 /,而是一个隔离的目录树(通过 pivot_rootchroot 实现).没有"统一树"的设计,容器文件系统隔离会复杂得多.

2.3 inode:文件在磁盘上的"身份证"

有了"一切皆文件"和"统一目录树"的认知后,还有一个关键概念需要理解:文件名和文件内容是两个不同的东西,它们通过 inode 关联.

什么是 inode?

inode(Index Node,索引节点)是文件系统内部的一个数据结构,每个文件(或目录)都对应唯一一个 inode.inode 里存的是:

inode 存储的内容 说明
文件类型与权限 是普通文件,目录还是符号链接?rwx 权限是什么?
所有者信息 UID(属主),GID(属组)
大小 文件占多少字节
时间戳 最后访问时间(atime),最后修改时间(mtime),最后属性变更时间(ctime)
数据块指针 文件的实际数据存在磁盘的哪些位置
链接计数 有多少个"文件名"指向这个 inode

注意 inode 里不存什么:不存文件名.文件名存在哪里?存在目录里.目录本质上是一个"文件名 → inode 编号"的映射表:

目录 "/home/yutong" 的内部结构(概念示意):

文件名 → inode 编号
. → 524288 (指向目录自身)
.. → 262144 (指向父目录 /home)
notes.txt → 789012 (指向 notes.txt 的数据)
project/ → 890123 (指向 project 子目录)

inode = 房产证,记录着"这块地(数据块)的面积,位置,产权人".文件名 = 门牌号,只是贴在门口的标签.房产证(inode)只有一份,但一栋房子可以有多个门牌号(多个文件名指向同一个 inode)--这就是 硬链接(hard link) 的本质.

这解释了哪些日常现象?

为什么 mv (移动/重命名)几乎瞬间完成? 因为 mv 只修改了目录里的"文件名 → inode 编号"映射,没有碰实际数据.只要源和目标在同一个文件系统内(inode 编号空间一致),mv 就是改一行记录而已.

# 查看文件的 inode 编号
ls -i notes.txt
# 输出: 789012 notes.txt

# mv 改名后,inode 编号不变
mv notes.txt notes_v2.txt
ls -i notes_v2.txt
# 输出: 789012 notes_v2.txt ← 还是同一个 inode

为什么删除大文件很快,但删除很多小文件很慢? 删除文件本质是减少 inode 的链接计数 + 标记数据块为可用.大文件只有一个 inode 要处理;100 万个小文件有 100 万个 inode 要逐个处理.

为什么硬链接不能跨文件系统? 因为 inode 编号只在单个文件系统内有意义.文件系统 A 的 inode 789012 和文件系统 B 的 inode 789012 是完全不同,毫无关系的两个文件.

# 创建硬链接--两个文件名,同一个 inode
ln source.txt hardlink.txt
ls -li source.txt hardlink.txt
# 输出同样的 inode 编号,链接计数 +1

# 符号链接就不一样了--它是一个"指向路径"的特殊文件,有自己的 inode
ln -s source.txt softlink.txt
ls -li source.txt softlink.txt
# 输出不同的 inode 编号

inode 与容器的关系

OverlayFS(第 04 篇)的分层机制本质上是在不同层之间管理 inode 的指向关系.写时复制(Copy-on-Write)就是:当你要修改 lower 层的一个文件时,先把它的 inode 指向的数据块复制到 upper 层,然后修改 upper 层的副本--所有这些操作对容器进程透明.

2.4 深入理解 mount:把文件系统"接入"目录树

现在你理解了"统一目录树"和"inode",挂载就好理解了.在讲命令之前,先建立直觉.

挂载的本质是什么?

一块磁盘(或一个虚拟文件系统)在被格式化后,上面有一个独立的文件系统--它有自己的目录结构,自己的 inode 编号空间.但在挂载之前,这个文件系统和 Linux 的目录树是彼此隔离的,你无法访问它.

挂载(mount)就是把一个独立的文件系统"接入"到 Linux 目录树的某个节点上,让那个节点成为访问这个文件系统的入口.

挂载前:
Linux 目录树 独立文件系统(比如 U 盘)
/ /(U 盘自己的根)
├─ home/ ├─ 照片/
├─ mnt/ ← 空目录 ├─ 文档/
└─ var/ └─ 音乐/

挂载后:sudo mount /dev/sdb1 /mnt/usb

Linux 目录树
/
├─ home/
├─ mnt/
│ └─ usb/ ← 现在访问这里,看到的是 U 盘的内容
│ ├─ 照片/ ← U 盘的内容"长"到了这棵树上
│ ├─ 文档/
│ └─ 音乐/
└─ var/

挂载 = 把 U 盘插到目录上. Linux 的目录树是一个插线板,每个目录节点都可以是一个"插座".当你把 U 盘 mount 到 /mnt/usb,就等于把 U 盘插进了这个插座--之后电流(数据访问)就通了.当你 umount,就是拔掉插头--U 盘还是那个 U 盘,但不再能通过目录树访问到了.

为什么需要挂载?为什么不直接"看到"所有设备?

回到"统一目录树"的设计:因为 Linux 只有一棵树,所以任何存储资源要可访问,就必须成为这棵树的一部分.挂载就是"成为这棵树的一部分"的操作.

Windows 其实也有挂载--只是把挂载点固定在盘符(C:,D:)上,你感知不到"挂载"这个动作.Linux 把这个权力完全交给用户:你想把 U 盘挂到哪个目录都行.

挂载不只是接 U 盘

在前面的"一切皆文件"小节你已经看到:硬盘上的 ext4/xfs 文件系统需要挂载,虚拟文件系统同样需要挂载.实际上,Linux 系统启动时,内核会先挂载一个临时 rootfs(在内存中),然后再把真正的根文件系统挂载到 /:

# 查看当前所有挂载点--你会看到各种类型
mount | head -20

# 典型输出解读:
# /dev/sda1 on / type ext4 ← 根文件系统,磁盘上的 ext4,挂载到 /
# proc on /proc type proc ← /proc 是虚拟文件系统,挂载到 /proc
# sys on /sys type sysfs ← /sys 是虚拟文件系统,挂载到 /sys
# tmpfs on /run type tmpfs ← 内存文件系统,挂载到 /run
# /dev/sdb1 on /mnt/usb type vfat ← U 盘(FAT32),挂载到 /mnt/usb
文件系统类型 存储在哪里 典型挂载点 容器场景
ext4 / xfs 磁盘 /,/home 容器镜像的底层存储
tmpfs 内存(掉电丢失) /tmp,/run 容器的 tmpfs 挂载
proc 内核虚拟生成 /proc 每个容器需要独立的 /proc
sysfs 内核虚拟生成 /sys Cgroup 路径依赖 /sys 挂载
overlay 基于多个下层目录构建 容器的 rootfs Docker 镜像分层(第 04 篇)
bind mount 把现有目录"映射"到另一个位置 任意 docker run -v /host/path:/container/path

常用挂载命令

# 查看所有挂载点
mount
# 也可以用 findmnt 获得更清晰的树状输出
findmnt

# 挂载磁盘分区到目录
sudo mount /dev/sdb1 /mnt/usb

# 挂载时指定文件系统类型
sudo mount -t ext4 /dev/sdc1 /mnt/data

# 挂载虚拟文件系统(proc,tmpfs 等)
sudo mount -t proc proc /some/dir/proc
sudo mount -t tmpfs tmpfs /tmp

# 绑定挂载(bind mount)--让一个目录同时出现在两个位置
sudo mount --bind /original/path /another/path

# 卸载
sudo umount /mnt/usb
# 如果提示 "target is busy",说明有进程在使用该挂载点
# 用 lsof /mnt/usb 查看是谁,关闭后就能卸载

# 强制卸载(当普通卸载失败时)
sudo umount -l /mnt/usb # lazy unmount,等没人用了再真正卸载

挂载与容器的关系--这是理解容器文件系统的关键

容器的文件系统隔离通过 Mount Namespace(第 02 篇)实现.每个容器拥有独立的挂载表(mount table):

  • 宿主机看不到容器内部的挂载操作
  • 容器看不到宿主机的挂载表全貌(除非显式共享)
  • Docker 的 -v /host:/container 本质上就是一个 bind mount,把宿主机的目录"映射"到容器内部
  • Docker 镜像的分层文件系统(OverlayFS)也是通过 mount 实现把多个层合并挂载为容器的 /

一个帮助你记住的直觉

你可以把 Linux 的文件系统想成一棵树 + 很多个"挂件".树干和主要树枝是根文件系统(ext4/xfs 在磁盘上),而 /proc,/sys,U 盘,OverlayFS 都是"挂件"--它们通过 mount 挂在树的某个节点上.内核负责让这一切看起来像是一棵完整的,无缝的树,这就是 VFS(Virtual File System)层的工作.

2.5 虚拟文件系统的实例:/proc 与 /sys

前面讲了很多抽象概念,现在来看两个最重要的虚拟文件系统实例.它们完美体现了"一切皆文件"哲学.

/proc - 进程信息的窗口

/proc 是一个虚拟文件系统--它不占磁盘空间,内容由内核实时生成.每次你读取 /proc 下的文件,内核都会现场计算并返回当前状态.

路径 内容 谁在用
/proc/[PID]/ 某个进程的详细信息 ps,top,htop
/proc/[PID]/ns/ 进程所属的各个 Namespace nsenter,docker exec
/proc/[PID]/cgroup 进程所属的 Cgroup Cgroup 管理工具
/proc/meminfo 系统内存使用情况 free 命令
/proc/cpuinfo CPU 信息 lscpu
# /proc 中每个数字目录就是一个进程
ls /proc | grep '^[0-9]' | head -5

# 查看当前 bash 进程的信息
ls /proc/$$/
# 输出: cmdline cwd environ exe fd maps mem ns status ...

# 查看某进程的命令行
cat /proc/$$/cmdline | tr '\0' ' '

# 查看系统内存(free 命令的数据来源就是这个文件)
cat /proc/meminfo | head -5

关键认知

ps 命令不是"直接问内核有哪些进程",而是遍历 /proc 目录,读取每个数字子目录的信息拼装输出.所以在容器中如果 /proc 没有重新挂载(还是宿主机的),ps 就会显示宿主机所有进程--这就是 Namespace 篇中 --mount-proc 参数的意义.

/sys - 内核子系统的控制面板

/sys(sysfs)也是虚拟文件系统,主要暴露内核子系统和设备 的信息:

  • /sys/fs/cgroup/ - Cgroup 文件系统的入口(第 03 篇核心内容)
  • /sys/class/net/ - 所有网络接口的信息
  • /sys/block/ - 块设备信息(磁盘)
# 查看所有网络接口
ls /sys/class/net/
# 输出: eth0 lo docker0 veth1234

# 通过 sysfs 查看某网卡的 MAC 地址
cat /sys/class/net/eth0/address

/proc 偏向"进程视角"(每个进程的状态),/sys 偏向"设备/子系统视角"(硬件和内核模块的状态).两者都是内核暴露信息的窗口,容器技术大量依赖它们--Cgroup 通过写文件限制资源,Namespace 通过读文件查看隔离状态.

三,网络基础

网络接口(Network Interface)

网络接口是 Linux 收发网络数据的端点,可以是物理的也可以是虚拟的:

接口 类型 用途
eth0 物理网卡 连接外部网络
lo 回环(loopback) 本机自己跟自己通信(127.0.0.1)
docker0 虚拟网桥 Docker 容器的默认网关
vethXXXX 虚拟以太网 连接容器和网桥

对内核来说,物理网卡和虚拟网卡没有本质区别--都是"网络接口"这个抽象的实例,都能配 IP,收发数据包.这是容器网络的基础.

# 查看所有网络接口及其 IP
ip addr show

# 简写
ip a

IP 地址与子网

私有地址(内网地址)

RFC 1918 规定了三段永远不会出现在公网 的地址,任何人都可以在内网随意使用:

网段 范围 常见用途
10.0.0.0/8 10.0.0.0 ~ 10.255.255.255 大型企业内网,云厂商 VPC,容器网络
172.16.0.0/12 172.16.0.0 ~ 172.31.255.255 Docker 默认网段(172.17.0.0/16)
192.168.0.0/16 192.168.0.0 ~ 192.168.255.255 家庭路由器,小型办公网络

CIDR 表示法

IP 地址 32 位,/数字 表示前多少位是网络号,剩余是主机号:

10.0.0.1/24

网络号:10.0.0.___ (前 24 位,标识"哪个子网")
主机号:_______.1 (后 8 位,标识"子网中第几台")

该子网范围:10.0.0.0 ~ 10.0.0.255(可用主机地址 .1 ~ .254)

类比邮政地址:网络号 = 街道名,主机号 = 门牌号.同一条街(同一子网)的住户可以直接喊话通信,不同街道需要通过邮局(路由器)转发./24 就是说"街道名占了地址的前 24 位".

快速记忆:

  • /24 = 256 个地址(一个"小区"),最常用
  • /16 = 65536 个地址(一个"城区")
  • /8 = 约 1677 万个地址(一个"城市")
  • 数字越大,子网越小

veth pair - 虚拟网线

veth= Virtual Ethernet.veth pair 是成对出现的虚拟网络接口--数据从一端进,必定从另一端出:

┌─────────────────┐              ┌─────────────────┐
│ Namespace A │ │ Namespace B │
│ │ │ │
│ [veth-a] ●━━━━━━ 虚拟网线 ━━━━━━● [veth-b] │
│ │ │ │
└─────────────────┘ └─────────────────┘

从 veth-a 发出的数据 → 立刻出现在 veth-b
从 veth-b 发出的数据 → 立刻出现在 veth-a

物理世界里,两台电脑用一根网线直连就能通信.veth pair 就是这根网线的虚拟版.Network Namespace 隔离后两个空间完全不通(像两个密封的房间),veth pair 就是在墙上打一个洞穿一根线,让两边能互通数据.

# 创建一对 veth
sudo ip link add veth-a type veth peer name veth-b

# 查看--两个接口同时出现
ip link show type veth

# 给 veth-a 配 IP 并启动
sudo ip addr add 10.0.0.1/24 dev veth-a
sudo ip link set veth-a up

# 给 veth-b 配 IP 并启动
sudo ip addr add 10.0.0.2/24 dev veth-b
sudo ip link set veth-b up

# 测试连通
ping -c 2 10.0.0.2

# 清理
sudo ip link del veth-a # 删一端,另一端自动消失

网桥(Bridge)- 虚拟交换机

veth pair 是一对一的连接.当有多个容器需要互通时,不可能两两直连(N 个容器需要 N*(N-1)/2 根线).这时需要网桥(Bridge):

┌──────────┐    ┌──────────┐  ┌──────────┐
│ 容器 A │ │ 容器 B │ │ 容器 C │
│ [veth-a] │ │ [veth-b] │ │ [veth-c] │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━┷━━━━━━━━ docker0 网桥

[宿主机网络]
物理世界 虚拟世界
交换机(Switch) Linux Bridge(如 docker0)
网线插到交换机端口 veth 一端连到 Bridge
交换机转发数据帧给目标端口 Bridge 按 MAC 地址转发给目标 veth
# 创建网桥
sudo ip link add br0 type bridge
sudo ip link set br0 up

# 创建两对 veth,一端接入网桥
sudo ip link add veth1 type veth peer name veth1-br
sudo ip link add veth2 type veth peer name veth2-br

# 把 "网桥侧" 的端接入 br0
sudo ip link set veth1-br master br0
sudo ip link set veth2-br master br0
sudo ip link set veth1-br up
sudo ip link set veth2-br up

# 给 "容器侧" 配 IP
sudo ip addr add 10.0.0.1/24 dev veth1
sudo ip addr add 10.0.0.2/24 dev veth2
sudo ip link set veth1 up
sudo ip link set veth2 up

# veth1 和 veth2 现在可以通过 br0 互通
ping -c 2 -I veth1 10.0.0.2

Docker 网络的本质

安装 Docker 后系统多出一个 docker0 网桥.每创建一个容器,Docker 就生成一对 veth--一端放进容器的 Network Namespace(容器里看到的 eth0),另一端接到 docker0 网桥.所有容器通过这个网桥互通,再通过 NAT/iptables 规则与外网通信.

小结

本篇要点回顾

模块 核心认知 后续关联
进程模型 fork 复制 + exec 替换;PID 是进程身份证;所有进程形成树 PID Namespace 让容器看到独立的进程树(第 02 篇)
文件系统基础 一切皆文件(统一 open/read/write/close 接口);统一目录树(单根 /);inode 是文件身份证(文件名≠文件内容) 容器 rootfs 就是一棵独立的目录树;OverlayFS 的分层本质是 inode 指向的切换(第 04 篇)
mount 挂载 所有文件系统必须通过 mount 接入目录树才能访问;不只是接 U 盘,proc/sys/overlay 等虚拟文件系统也通过 mount 接入 Mount Namespace 隔离挂载表;Docker -v 本质是 bind mount;OverlayFS 通过 mount 实现分层合并(第 02,04 篇)
/proc 与 /sys 虚拟文件系统,不占磁盘,内核实时生成;ps 命令读的是 /proc 而非直接问内核 Namespace 通过 /proc/PID/ns/ 暴露;Cgroups 通过 /sys/fs/cgroup/ 控制资源(第 03 篇)
网络接口 物理网卡和虚拟网卡对内核无区别,都能配 IP 收发数据 Network Namespace 给容器一套独立的网络接口(第 02 篇)
IP/子网/CIDR 10.x / 172.16~31.x / 192.168.x 是私有地址;/24 表示子网大小 容器网络全部使用私有地址
veth pair 成对的虚拟网线,数据从一端进另一端出 连接容器 NS 与宿主机网桥(第 02 篇 Network NS)
网桥 虚拟交换机,多个 veth 接入后可互通 docker0 就是一个网桥,所有容器通过它互联

动手练习

  1. 运行 pstree -p 观察你的系统进程树,找到当前 shell 的位置
  2. 创建一个临时文件,用 ls -li 查看它的 inode 编号;mv 重命名后再次查看,验证 inode 不变;创建一个硬链接,观察两个文件名是否共享同一个 inode
  3. ls /proc/$$/ 探索你当前 bash 进程的信息,尝试读取 cmdline,status,ns/
  4. 运行 mount | grep -E 'proc|sys|cgroup',看看系统中有哪些虚拟文件系统被挂载
  5. ip addr 查看你系统上有哪些网络接口,哪些是物理的,哪些是虚拟的
  6. 创建一对 veth,配上 IP,用 ping 验证连通性后删除