概念總覽#
容器(Container)能夠「看起來像一台獨立的機器」,最關鍵的核心機制就是 Linux 命名空間(Namespaces)。它將原本全域的核心資源切成多個獨立的視圖(view),讓不同的行程(Process)在各自的命名空間中以為自己擁有整個系統。
- 命名空間並不是「虛擬機」,所有行程仍共用同一個 Kernel
- 每個命名空間只隔離特定一類資源(例如 PID、網路、掛載點)
- 一個行程同時屬於每一類命名空間中的某一個
七種主要 Namespaces#
Linux Kernel 目前提供以下幾種主要的命名空間:
- PID Namespace:隔離行程編號(Process ID),每個 PID Namespace 都有自己的 PID 1
- Mount Namespace:隔離掛載點(mount points),讓不同容器看到不同的檔案系統樹
- Network Namespace:隔離網路裝置、IP、路由表、iptables 規則
- User Namespace:隔離使用者與群組 ID,可讓容器內的 root 對應到主機上的非特權使用者
- UTS Namespace:隔離主機名稱(hostname)與 NIS domain name
- IPC Namespace:隔離 System V IPC 與 POSIX message queue
- Cgroup Namespace:隔離行程在 cgroup 階層中的視圖
Time Namespace 是較新加入的成員,用於隔離
CLOCK_MONOTONIC與CLOCK_BOOTTIME,但容器執行環境(runtime)目前較少使用。
三個關鍵的 Syscall#
操作命名空間主要透過三個系統呼叫:
clone(2):建立新行程的同時,可透過 flags(如CLONE_NEWPID、CLONE_NEWNS)讓子行程進入新的命名空間unshare(2):讓「目前」這個行程脫離原本的命名空間,進入新建立的命名空間setns(2):讓行程加入「已經存在」的命名空間(例如另一個容器的 PID Namespace)
對照表:
| 動作 | Syscall | 典型使用場景 |
|---|---|---|
| 建立新行程並進入新 namespace | clone(2) | 容器執行環境啟動容器主行程 |
| 自己離開舊 namespace 進入新 namespace | unshare(2) | unshare 指令、容器初始化前置 |
| 加入已存在 namespace | setns(2) | nsenter、docker exec |
從指令觀察#
可以用以下指令初步觀察命名空間:
# 列出目前 shell 屬於哪些 namespace
ls -l /proc/self/ns/
# 建立一個新的 UTS namespace 並修改 hostname
sudo unshare --uts /bin/bash
hostname demo每個 /proc/<pid>/ns/<type> 都是一個 symbolic link,連結目標形如 pid:[<inode>],inode 相同代表屬於同一個命名空間。
命名空間與容器的關係#
容器(例如 Docker、containerd 啟動的容器)在啟動時,runtime 會:
- 透過
clone(2)建立新行程,並一次帶入多個CLONE_NEW*flag - 在新行程中設定根目錄(pivot_root 或 chroot)、掛載 procfs
- 設定 cgroup 限制與網路裝置
- 執行容器內真正的入口程式(entrypoint)
理解 Namespaces 是後續理解容器隔離、容器除錯、以及 docker exec / nsenter 等工具的基礎。
延伸閱讀#
man 7 namespaces(Linux man-pages)- Linux Kernel 文件:https://www.kernel.org/doc/html/latest/admin-guide/namespaces/ ↗
- Michael Kerrisk, “Namespaces in operation” 系列文章(LWN.net)