HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • 手撸容器系统

    • 完整手撸容器技术文档系列
    • 01-容器本质与基础概念
    • 02-Namespace隔离机制
    • 03-CGroup资源控制
    • 04-Capabilities与安全机制
    • 05-容器网络原理
    • 06-网络模式与实现
    • 07-CNI插件开发
    • 08-RootFS与文件系统隔离
    • 09-OverlayFS镜像分层
    • 10-命令行手撸容器
    • 11-Go实现最小容器
    • 12-Go实现完整容器
    • 13-容器生命周期管理
    • 14-调试技术与工具
    • 15-OCI规范与标准化
    • 16-进阶场景与优化
    • 常见问题与故障排查
    • 参考资料与延伸阅读

02-Namespace隔离机制

学习目标

  • 深入理解 7 种 Linux Namespace 的作用和原理
  • 掌握 clone() 和 unshare() 系统调用的使用
  • 能够通过命令行演示各种隔离效果
  • 理解 Namespace 在内核中的实现机制

前置知识

  • Linux 进程和线程基础
  • 系统调用概念
  • 文件系统基础
  • 网络基础概念

一、Namespace 概述

1.1 什么是 Namespace?

Namespace 是 Linux 内核提供的一种进程隔离机制,它让不同的进程组拥有独立的系统资源视图。

graph TD
    A[Linux 内核] --> B[全局资源表]
    B --> C[UTS Namespace]
    B --> D[PID Namespace]
    B --> E[MNT Namespace]
    B --> F[NET Namespace]
    B --> G[IPC Namespace]
    B --> H[USER Namespace]
    B --> I[CGROUP Namespace]
    
    C --> C1[主机名/域名]
    D --> D1[进程ID空间]
    E --> E1[挂载点表]
    F --> F1[网络协议栈]
    G --> G1[IPC对象]
    H --> H1[用户/组ID]
    I --> I1[控制组]

1.2 7 种 Namespace 详解

Namespace隔离内容常用命令典型应用
UTS主机名和域名unshare -u容器独立主机名
PID进程ID空间unshare -p容器独立进程树
MNT文件系统挂载点unshare -m容器独立文件系统
NET网络协议栈unshare -n容器独立网络
IPC进程间通信unshare -i容器间通信隔离
USER用户和组IDunshare -U用户权限隔离
CGROUP控制组unshare -C资源控制隔离

二、UTS Namespace - 主机名隔离

2.1 原理说明

UTS (Unix Timesharing System) Namespace 隔离了系统的主机名和域名。

graph LR
    A[宿主机] --> A1[hostname: ubuntu]
    B[容器1] --> B1[hostname: web-server]
    C[容器2] --> C2[hostname: db-server]
    
    A1 -.-> D[内核 UTS 表1]
    B1 -.-> E[内核 UTS 表2]
    C2 -.-> F[内核 UTS 表3]

2.2 实战演示

# 1. 查看当前主机名
hostname
# 输出: ubuntu

# 2. 创建新的 UTS namespace
unshare -u bash

# 3. 在新 namespace 中修改主机名
hostname container-1
hostname
# 输出: container-1

# 4. 在另一个终端查看宿主机主机名
hostname
# 输出: ubuntu (未改变)

2.3 内核实现原理

// 内核中的 UTS namespace 结构
struct uts_namespace {
    struct kref kref;
    struct new_utsname name;  // 存储主机名和域名
    struct user_namespace *user_ns;
    struct ucounts *ucounts;
    struct ns_common ns;
};

三、PID Namespace - 进程隔离

3.1 原理说明

PID Namespace 为进程提供了独立的进程ID空间,每个容器都有自己的 PID 1。

graph TD
    A[宿主机进程树] --> A1[init (PID 1)]
    A1 --> A2[systemd (PID 2)]
    A2 --> A3[其他进程...]
    
    B[容器1进程树] --> B1[容器init (PID 1)]
    B1 --> B2[容器进程 (PID 2)]
    B2 --> B3[容器进程 (PID 3)]
    
    C[容器2进程树] --> C1[容器init (PID 1)]
    C1 --> C2[容器进程 (PID 2)]

3.2 实战演示

# 1. 查看当前进程树
ps aux | head -10

# 2. 创建新的 PID namespace
unshare -p -f bash

# 3. 在新 namespace 中查看进程
ps aux
# 注意:可能显示错误,因为 /proc 没有重新挂载

# 4. 重新挂载 /proc
mount -t proc proc /proc

# 5. 再次查看进程
ps aux
# 现在可以看到独立的进程树,PID 从 1 开始

3.3 重要特性

  • PID 1 特权:容器中的 PID 1 进程有特殊地位
  • 僵尸进程处理:PID 1 需要处理子进程的 SIGCHLD 信号
  • 进程可见性:只能看到同一 namespace 内的进程

四、MNT Namespace - 文件系统隔离

4.1 原理说明

MNT (Mount) Namespace 隔离了文件系统挂载点,每个容器可以有独立的文件系统视图。

graph TD
    A[宿主机挂载点] --> A1[/]
    A1 --> A2[/proc]
    A1 --> A3[/sys]
    A1 --> A4[/dev]
    
    B[容器1挂载点] --> B1[/]
    B1 --> B2[/proc - 独立]
    B1 --> B3[/sys - 独立]
    B1 --> B4[/dev - 独立]
    
    C[容器2挂载点] --> C1[/]
    C1 --> C2[/proc - 独立]
    C1 --> C3[/sys - 独立]
    C1 --> C4[/dev - 独立]

4.2 实战演示

# 1. 查看当前挂载点
mount | grep proc
# 输出: proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)

# 2. 创建新的 MNT namespace
unshare -m bash

# 3. 在新 namespace 中查看挂载点
mount | grep proc
# 输出: (可能为空,因为 /proc 还没有挂载)

# 4. 挂载新的 /proc
mount -t proc proc /proc

# 5. 验证隔离效果
mount | grep proc
# 输出: proc on /proc type proc (rw,relatime)

4.3 关键概念

  • 挂载传播:子 namespace 可以继承父 namespace 的挂载点
  • 私有挂载:使用 MS_PRIVATE 标志创建私有挂载
  • 共享挂载:使用 MS_SHARED 标志创建共享挂载

五、NET Namespace - 网络隔离

5.1 原理说明

NET Namespace 隔离了网络协议栈,每个容器有独立的网络接口、IP地址、路由表等。

graph LR
    A[宿主机网络] --> A1[eth0: 192.168.1.100]
    A1 --> A2[lo: 127.0.0.1]
    
    B[容器1网络] --> B1[veth0: 10.0.0.2]
    B1 --> B2[lo: 127.0.0.1]
    
    C[容器2网络] --> C1[veth1: 10.0.0.3]
    C1 --> C2[lo: 127.0.0.1]

5.2 实战演示

# 1. 查看当前网络接口
ip addr show

# 2. 创建新的 NET namespace
unshare -n bash

# 3. 在新 namespace 中查看网络接口
ip addr show
# 输出: 只有 lo 接口

# 4. 启动 lo 接口
ip link set lo up

# 5. 创建 veth pair (需要 root 权限)
ip link add veth0 type veth peer name veth1

# 6. 将 veth1 移到新 namespace
ip link set veth1 netns $BASHPID

# 7. 配置 IP 地址
ip addr add 10.0.0.2/24 dev veth0
ip link set veth0 up

# 8. 在 namespace 内配置 veth1
ip addr add 10.0.0.3/24 dev veth1
ip link set veth1 up

六、IPC Namespace - 进程间通信隔离

6.1 原理说明

IPC Namespace 隔离了进程间通信对象,包括共享内存、信号量、消息队列等。

graph TD
    A[宿主机IPC] --> A1[共享内存段]
    A --> A2[信号量]
    A --> A3[消息队列]
    
    B[容器1IPC] --> B1[独立共享内存]
    B --> B2[独立信号量]
    B --> B3[独立消息队列]
    
    C[容器2IPC] --> C1[独立共享内存]
    C --> C2[独立信号量]
    C --> C3[独立消息队列]

6.2 实战演示

# 1. 创建新的 IPC namespace
unshare -i bash

# 2. 查看当前 IPC 对象
ipcs -m  # 共享内存
ipcs -s  # 信号量
ipcs -q  # 消息队列

# 3. 创建测试共享内存
ipcmk -M 1024

# 4. 查看创建的共享内存
ipcs -m

七、USER Namespace - 用户隔离

7.1 原理说明

USER Namespace 隔离了用户和组ID空间,允许在容器内以 root 身份运行,但在宿主机上以普通用户身份运行。

graph LR
    A[宿主机用户空间] --> A1[uid=0: root]
    A1 --> A2[uid=1000: user]
    
    B[容器用户空间] --> B1[uid=0: root (映射到宿主机 1000)]
    B1 --> B2[uid=1: daemon (映射到宿主机 1001)]

7.2 实战演示

# 1. 创建新的 USER namespace
unshare -U bash

# 2. 查看当前用户ID
id
# 输出: uid=65534(nobody) gid=65534(nogroup)

# 3. 映射用户ID (需要 root 权限)
echo '0 1000 1' > /proc/$$/uid_map
echo '0 1000 1' > /proc/$$/gid_map

# 4. 再次查看用户ID
id
# 输出: uid=0(root) gid=0(root)

️ 八、CGROUP Namespace - 控制组隔离

8.1 原理说明

CGROUP Namespace 隔离了控制组视图,让容器只能看到自己的 cgroup 层次结构。

graph TD
    A[宿主机CGROUP] --> A1[/sys/fs/cgroup]
    A1 --> A2[system.slice]
    A1 --> A3[user.slice]
    A1 --> A4[docker]
    
    B[容器CGROUP] --> B1[/sys/fs/cgroup]
    B1 --> B2[container-1]
    B2 --> B3[只能看到自己的cgroup]

8.2 实战演示

# 1. 创建新的 CGROUP namespace
unshare -C bash

# 2. 查看 cgroup 信息
cat /proc/self/cgroup

# 3. 查看 cgroup 文件系统
ls /sys/fs/cgroup/

️ 九、系统调用详解

9.1 clone() 系统调用

clone() 是创建新进程的核心系统调用,通过 flags 参数指定要创建的 namespace。

#include <sched.h>

int clone(int (*fn)(void *), void *child_stack,
          int flags, void *arg, ...);

常用 flags:

  • CLONE_NEWUTS - 创建新的 UTS namespace
  • CLONE_NEWPID - 创建新的 PID namespace
  • CLONE_NEWNS - 创建新的 MNT namespace
  • CLONE_NEWNET - 创建新的 NET namespace
  • CLONE_NEWIPC - 创建新的 IPC namespace
  • CLONE_NEWUSER - 创建新的 USER namespace

9.2 unshare() 系统调用

unshare() 将当前进程移到新的 namespace 中。

#include <sched.h>

int unshare(int flags);

9.3 setns() 系统调用

setns() 将当前进程加入到已存在的 namespace 中。

#include <sched.h>

int setns(int fd, int nstype);

十、综合实战:创建完整隔离环境

10.1 命令行方式

#!/bin/bash
# 创建完整隔离的容器环境

echo "=== 创建完整隔离环境 ==="

# 1. 创建所有 namespace
unshare -U -p -m -n -i -C -f bash << 'EOF'
echo "=== 进入隔离环境 ==="

# 2. 设置主机名
hostname container-demo

# 3. 重新挂载 /proc
mount -t proc proc /proc

# 4. 启动网络接口
ip link set lo up

# 5. 查看隔离效果
echo "=== 主机名 ==="
hostname

echo "=== 进程树 ==="
ps aux | head -5

echo "=== 网络接口 ==="
ip addr show

echo "=== 挂载点 ==="
mount | grep -E "(proc|sys|dev)"

echo "=== 用户信息 ==="
id

echo "=== 隔离环境创建完成 ==="
echo "输入 'exit' 退出隔离环境"
EOF

10.2 Go 代码实现

package main

import (
    "fmt"
    "os"
    "os/exec"
    "syscall"
)

func main() {
    // 创建所有 namespace 的 flags
    flags := syscall.CLONE_NEWUTS |
             syscall.CLONE_NEWPID |
             syscall.CLONE_NEWNS |
             syscall.CLONE_NEWNET |
             syscall.CLONE_NEWIPC |
             syscall.CLONE_NEWUSER |
             syscall.CLONE_NEWCGROUP

    // 准备子进程命令
    cmd := exec.Command("/bin/bash")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: flags,
    }

    // 设置标准输入输出
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    // 启动子进程
    if err := cmd.Run(); err != nil {
        fmt.Printf("Error: %v\n", err)
        os.Exit(1)
    }
}

十一、调试技巧

11.1 查看 Namespace 信息

# 查看当前进程的 namespace
ls -la /proc/self/ns/

# 查看特定进程的 namespace
ls -la /proc/<PID>/ns/

# 使用 nsenter 进入特定 namespace
nsenter -t <PID> -n bash  # 进入网络 namespace
nsenter -t <PID> -p bash  # 进入进程 namespace

11.2 常见问题排查

  1. 权限问题:某些 namespace 需要 root 权限
  2. 挂载问题:MNT namespace 需要重新挂载 /proc
  3. 网络问题:NET namespace 需要手动配置网络接口
  4. 用户映射问题:USER namespace 需要正确配置 ID 映射

十二、验证检查清单

基础理解

  • [ ] 理解 7 种 Namespace 的作用和区别
  • [ ] 掌握 clone() 和 unshare() 系统调用
  • [ ] 能够通过命令行创建各种隔离环境
  • [ ] 理解 Namespace 在内核中的实现原理

实践能力

  • [ ] 能够创建 UTS namespace 并修改主机名
  • [ ] 能够创建 PID namespace 并查看独立进程树
  • [ ] 能够创建 MNT namespace 并重新挂载文件系统
  • [ ] 能够创建 NET namespace 并配置网络接口
  • [ ] 能够创建完整的隔离环境

调试技能

  • [ ] 掌握 namespace 调试工具的使用
  • [ ] 能够排查常见的 namespace 问题
  • [ ] 理解 namespace 的生命周期管理

相关链接

  • 01-容器本质与基础概念 - 容器技术基础
  • 03-CGroup资源控制 - 资源限制技术
  • 10-命令行手撸容器 - 综合实践应用

下一步:让我们学习 CGroup 资源控制技术,这是容器资源管理的核心!

Prev
01-容器本质与基础概念
Next
03-CGroup资源控制