一、阶段 1:没有隔离的 “假容器”(先看问题,再看解决)

1. 实验核心动作:导出 Alpine 容器文件系统,手工解压
  • 操作:用 docker export conns -o dockercontainer.tar 导出一个运行中的 Alpine 容器,解压到 rootfs 目录。

  • 目的:我们要手工造一个容器的根文件系统(Rootfs)。这是容器能跑的基础,相当于把 “操作系统的根目录” 单独拿出来了。

  • 效果rootfs 里包含了 binetclib 等所有 Linux 基础目录,看起来像一个独立的 Linux 系统。

2. 关键测试:用 chroot 切换根目录
sudo chroot $PWD/rootfs ash
  • 现象:你进入了一个新的 Shell,执行 ls 能看到 bindev 等目录。

  • 问题:

    虽然你看到了独立的文件系统,但你并没有被隔离!

    • 执行 ip link:能看到宿主机的网卡(ens33、docker0),网络没隔离;

    • 执行 ps -ef:能看到宿主机的所有进程(systemd、kthreadd),进程没隔离;

    • 执行 id:虽然你在容器里显示是 root,但实际你还是宿主机的 user1 用户,权限没有隔离。

结论:

chroot 只是改变了当前目录的根视图,没有做任何隔离。 这是早期的简陋容器技术,安全性极差。


二、阶段 2:真正的隔离核心 —— unshare 命令(Namespace 隔离)

1. 核心概念:Linux Namespace(命名空间)

容器之所以叫 “容器”,核心就是 Namespace。它相当于给进程加了 “视觉滤镜”,让进程只能看到属于自己的那部分资源。

6 种最核心的隔离类型(对应 unshare 参数):

Namespace 类型 隔离内容 图中对应参数 大白话解释
PID 进程 ID --pid 容器里的 PID 是 1,看不到宿主机进程
NET 网络栈 --net 容器有独立的 IP、网卡,看不到宿主机网卡
UTS 主机名、域名 --uts 容器可以有独立的主机名,不影响宿主机
IPC 进程间通信 --ipc 容器内进程只能和自己通信
MNT 挂载点 --mount 容器有独立的文件系统挂载视图
USER 用户 / 组 ID --user 容器内的 root 映射到宿主机的普通用户,更安全
2. 实验动作:用 unshare 创建隔离环境
unshare --mount --uts --ipc --net --pid --fork --map-root-user /bin/bash
  • --fork:创建一个子进程来执行 bash;

  • --map-root-user:把容器内的 UID 0(root)映射到宿主机的 UID,虽然容器内是 root,实际宿主机看是普通用户,安全隔离

  • 参数组合:同时开启了 5 种隔离,这就是容器的核心底层。

3. 效果验证:此时你进入了一个真正的隔离环境
  • 网络隔离(NET):执行 ip link,只能看到 lo(本地回环),看不到宿主机的 ens33/docker0 了!

  • 进程隔离(PID):执行 ps -ef,只能看到当前的 bash 进程(PID 1),看不到宿主机的 systemd 等进程了!

  • 用户隔离(USER):执行 id,显示 uid=0 (root),但是宿主机上看这个进程,它还是 user1 用户执行的。


三、阶段 3:完善 Rootfs 与资源隔离(补全最后两块拼图)

光有隔离(Namespace)还不够,容器还要有独立的文件视图网络设备,这才是完整的 “手工容器”。

1. 进程隔离的最后一块拼图:Proc 文件系统

你会发现,虽然用了 --pid 隔离,但如果不挂载新的 proc,执行 ps -ef 还是能看到宿主机进程。

  • 原理/proc 目录是内核给进程看的资源列表。

  • 操作:

    mount -t proc proc /proc
    
  • 效果:重新执行

    ps -ef

    你只能看到自己的 bash 进程了!

    PID 命名空间彻底生效。

    “进程本质上是 /proc 挂载点中的文件描述符”。

2. 网络隔离的最后一块拼图:Veth Pair(虚拟网卡对)

容器需要独立的网络,但又要能和宿主机通信。所以用 Veth Pair(虚拟网线)。

  • 原理:Veth Pair 就像一根虚拟的网线,两端分别连接 “容器” 和 “宿主机网桥”。

  • 操作:

    # 1. 创建一对虚拟网卡
    sudo ip link add vethLocal type veth peer name vethContainer
    # 2. 把其中一端放入容器的网络命名空间
    sudo ip link set vethContainer netns 3585  # 3585是unshare进程的PID
    # 3. 在容器内查看
    ip link
    
  • 效果:在容器里执行

    ip link

    ,会看到

    vethContainer

    这个新网卡出现了。

    这就是容器的网络结构:一端在容器(vethContainer),一端在宿主机(vethLocal),通过这根 “虚拟线” 连通。


四、完整总结:手工容器 vs Docker 容器

实验步骤(手工) Docker 容器(实际) 核心作用
解压 rootfs 镜像层(layer.tar) 提供容器的根文件系统(Rootfs)
chroot 切换根 Overlay2 挂载 让容器看到独立的文件视图
unshare --mount/uts/ipc/net/pid Linux Namespace 核心隔离! 让进程看不到宿主机资源
挂载 proc Docker 自动挂载 ps 命令只显示当前容器进程
ip link add veth pair Docker 网络 Bridge 给容器配独立网卡,连通宿主机
chroot 启动进程 runc 启动容器 最终生成一个独立运行的容器进程

容器本质上不是虚拟机,容器本质就是一个被 unshare 命令隔离了资源(PID/Net/IPC 等),并且有独立 Rootfs 的普通 Linux 进程。

Docker 只是把这些复杂的 Linux 命令(unshare、chroot、mount)封装成了一个好用的工具,让我们不用手工敲命令就能跑容器而已。


五、常见误区与核心考点

  1. 为什么不直接用 chroot 而要用 unshare?

    • 因为 chroot 只是改了目录,没有隔离。黑客如果突破了 chroot 根目录,就能直接访问宿主机文件;而 unshare 生成的新命名空间,是真正的独立环境,突破难度极大。

  2. PID 1 的含义

    • 在手工容器里,ps -ef 看到的第一个进程是 PID 1。这就是容器的 “init 进程”,如果这个进程挂了,容器就会退出。K8s 的 Pod 管理,本质就是管理这组 PID 命名空间下的进程。

  3. Veth Pair 的作用

    • 它是连接容器和宿主机的桥梁。没有 Veth Pair,容器就是孤岛(只有 lo 接口),连不上网;有了它,容器就能通过 docker0 网桥与宿主机通信。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐