容器基础概念1

容器技术三剑客:Namespace、Cgroups、UnionFS 的通俗解析

一、Namespace:给进程 “划地盘” 的隔离管家

核心作用:让不同进程以为自己 “独占” 了整个系统资源(如文件目录、网络、进程号等),但实际是在各自的 “虚拟空间” 里活动。

工作原理类比
就像在一间大教室里用隔板(Namespace)隔出多个小房间,每个房间里的学生(进程)以为自己独占了教室的黑板(文件系统)、讲台(网络接口)、座位号(PID),但其实这些资源是共享的,只是通过隔板让他们看不到彼此的存在。

  • 具体隔离场景
    • Mount Namespace:比如你在容器里用 cd / 看到的根目录,其实是容器独有的 “虚拟目录”,和宿主机的 / 完全不同(通过 chroot 切换目录)。
    • PID Namespace:容器里的进程号(如 PID=1)和宿主机的进程号互不干扰,就像两个班各自排学号,1 号学生不是同一个人。
    • Network Namespace:每个容器有独立的网卡、IP 地址、端口,就像每个房间有独立的电话线,互相打不通,只能通过教室的总交换机(宿主机网络)中转。

Linux Namespace 是容器技术的核心隔离机制,它通过内核提供的系统调用和数据结构,让进程只能访问特定的系统资源。以下是其具体实现逻辑的解析:

Linux 通过以下几个关键系统调用实现 Namespace 隔离:

  1.  clone()创建新进程时指定隔离类型,例如:
    • clone(child_func, stack, 
            CLONE_NEWNS |  // Mount Namespace
            CLONE_NEWPID | // PID Namespace
            CLONE_NEWNET | // Network Namespace
            SIGCHLD, arg);
      
    • unshare()
      让当前进程脱离某个 Namespace,加入新的 Namespace:

      unshare(CLONE_NEWNS);  // 脱离当前 Mount Namespace
      

    • setns()
      让进程加入已存在的 Namespace:

      int fd = open("/proc/1234/ns/mnt", O_RDONLY);
      setns(fd, 0);  // 加入 PID 为 1234 的进程的 Mount Namespace
      

Linux 内核目前支持 6 种 Namespace,每种对应不同的系统资源隔离:

Namespace 类型隔离的资源系统调用标志内核版本
Mount (mnt)文件系统挂载点CLONE_NEWNS2.4.19
PID进程 ID 空间CLONE_NEWPID2.6.24
Network (net)网络栈(网卡、IP、端口)CLONE_NEWNET2.6.29
UTS主机名和 NIS 域名CLONE_NEWUTS2.6.19
IPC进程间通信资源CLONE_NEWIPC2.6.19
User (user)用户和组 ID 空间CLONE_NEWUSER3.8
Namespace 的数据结构:
struct task_struct {
    // ...
    struct nsproxy *nsproxy;  // 指向进程所属的 Namespace
    // ...
};

struct nsproxy {
    atomic_t count;             // 引用计数
    struct mnt_namespace *mnt_ns;  // Mount Namespace
    struct uts_namespace *uts_ns;  // UTS Namespace
    struct ipc_namespace *ipc_ns;  // IPC Namespace
    struct pid_namespace *pid_ns;  // PID Namespace
    struct net *net_ns;          // Network Namespace
    struct user_namespace *user_ns; // User Namespace
};
Namespace 的文件系统接口

Linux 通过 /proc 文件系统暴露 Namespace 信息:

/proc/[pid]/ns/ 目录下包含当前进程所属的所有 Namespace 文件:

$ ls -l /proc/self/ns/
lrwxrwxrwx 1 root root 0 Jul  1 10:00 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jul  1 10:00 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jul  1 10:00 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Jul  1 10:00 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jul  1 10:00 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jul  1 10:00 uts -> uts:[4026531838]
  • 这些文件是特殊的 inode,相同 Namespace 的进程指向同一个 inode。
  • 可以通过 setns() 系统调用和这些文件让进程加入其他 Namespace。

二、Cgroups:给进程 “定规矩” 的资源裁判

核心作用:限制进程能使用的资源上限,避免某个进程 “吃太多” 导致系统崩溃。

工作原理类比
好比学校给每个班级分配固定的零食(CPU)、饮用水(内存)份额,每个学生(进程)不能超过班级的配额。如果某个班的学生吃太多零食,裁判(Cgroups)会强制限制,防止他们撑坏肚子(系统资源耗尽)。

  • 具体限制方式
    • CPU 限制:可以规定某个容器最多只能用 2 个 CPU 核心,即使宿主机有 8 个核心,它也只能用 2 个。
    • 内存限制:比如给容器分配 1GB 内存,当它用到 1GB 时就会被强制杀死,避免占用宿主机其他进程的内存。
    • 磁盘 IO 限制:限制容器写磁盘的速度,防止它把硬盘 “写爆” 影响其他程序。

Cgroups 通过以下组件实现资源控制:

  1. 核心系统调用

    • clone():创建进程时加入特定 Cgroup(通过 CLONE_NEWCGROUP 标志,需内核支持)。
    • cgroup_set()/cgroup_get():直接操作 Cgroup 文件系统接口。
    • cgroup_procs 文件:将进程 PID 写入对应 Cgroup 目录,实现进程分组。
  2. 文件系统接口
    Cgroups 通过虚拟文件系统 cgroupfs 或 systemd-cgroup 暴露控制接口,典型路径如:

    /sys/fs/cgroup/cpu/my_container/cpu.cfs_quota_us  # CPU 配额配置
    /sys/fs/cgroup/memory/my_container/memory.limit_in_bytes  # 内存限制
Cgroups 的核心实现逻辑

以 CPU 资源限制内存资源限制为例,解析具体工作机制:

1. CPU 子系统(cpu,cpuacct)的实现
  • CFS(完全公平调度)配额机制

    1. 周期与配额配置

      • cfs_period_us:定义资源分配周期(默认 100ms)。
      • cfs_quota_us:定义进程组在周期内最多可使用的 CPU 时间(-1 表示无限制)。
        例如:cfs_period_us=100000(100ms)+ cfs_quota_us=50000(50ms)表示限制为 50% CPU 使用率。
    2. 调度执行逻辑

      • 内核通过 cpu_cgroup_schedule() 钩子介入进程调度,检查当前进程所属 Cgroup 的配额。
      • 当进程使用的 CPU 时间超过 cfs_quota_us 时,将其加入等待队列,直到下一个周期。
  • CPU 权重(shares)机制

    • 通过 cpu.shares 配置相对权重(默认 1024),用于多核或多进程组竞争资源时的比例分配。
    • 例如:组 A(shares=2048)和组 B(shares=1024)竞争 1 个 CPU 核心时,组 A 获得 2/3 资源。
2. 内存子系统(memory)的实现
  • 内存限制与 OOM(Out of Memory)机制

    1. 限制配置

      • 通过 memory.limit_in_bytes 设定进程组可使用的最大内存(包括物理内存和 Swap)。
      • memory.swappiness 控制内存换页策略(0 表示优先淘汰页面,100 表示优先使用 Swap)。
    2. 内存监控与回收

      • 内核通过 memory_cgroup_commit_charge() 钩子监控内存分配,超过限制时触发:
        • 优先回收进程组内的页面缓存(page cache)。
        • 若仍不足,触发 OOM Killer,杀死组内占用内存最多的进程。
  • 内存统计与压力反馈

    • memory.usage_in_bytes 实时统计当前内存使用量。
    • 内存压力通过 memory.pressure 文件暴露,供上层调度系统(如 OOM Killer)决策。

三、UnionFS:给容器 “搭积木” 的文件系统魔术师

UnionFS 是一种「联合文件系统」,核心能力是将多个不同位置的目录「合并」成一个虚拟文件系统,让用户看起来像访问单个目录一样。它的特点是 分层存储、写时复制(Copy-on-Write),这也是容器镜像实现轻量化的关键。

核心作用:将多个文件系统层 “叠加” 在一起,形成一个统一的目录结构,用于保存操作系统和应用文件,同时支持 “只读 + 可写” 的分层机制。

工作原理类比
像搭积木一样,最底层是操作系统的基础镜像(只读,如 Ubuntu 系统),中间层是应用依赖(如 Java 环境,只读),最上层是容器运行时的临时修改(可写)。就像在一本固定的教科书(底层镜像)上贴便签(应用层),再用铅笔写笔记(运行时修改),最后看到的是 “书 + 便签 + 笔记” 的整体内容,但修改不会影响原书。

  • 具体工作流程
    1. 镜像分层:比如 Docker 镜像由多个只读层组成(如 Ubuntu 层、Nginx 层),每层只保存和上一层的差异。
    2. 容器运行时:启动容器时,会在镜像顶层添加一个可写层(容器层),所有对文件的修改(如新建文件、修改配置)都保存在这个层里。
    3. 挂载到容器:通过 Mount Namespace 将这个 “叠加后的文件系统” 挂载到容器的根目录,容器进程看到的就是完整的文件系统,但实际修改不会影响底层镜像。
UnionFS 的核心工作逻辑:分层与挂载

以 Docker 中常用的 UnionFS 类型(如 Overlay2)为例,其工作原理可以拆解为以下步骤:

1. 镜像的分层结构
  • 基础层(只读层):容器镜像的每一层(如操作系统、应用依赖)都是一个只读的目录,类似「模板」。例如,一个 Ubuntu 镜像可能包含 base 层(系统基础文件)、python 层(Python 环境)、app 层(应用代码)。
  • 容器层(可写层):当容器运行时,UnionFS 会在所有只读层之上创建一个 可写层(Writable Layer),用于存储容器运行时修改的文件(如新增的文件、修改的配置)。
2. 合并挂载:虚拟文件系统的形成
  • UnionFS 的挂载过程
    1. 所有只读层按顺序堆叠(如 base → python → app),每一层只能读取上一层的文件,但不能修改。
    2. 最上层是可写层,容器进程对文件的所有修改(增、删、改)都只会作用于这一层。
    3. UnionFS 通过内核机制将这些分层「合并」成一个虚拟目录,容器进程看到的就是这个合并后的文件系统。

3. 写时复制(Copy-on-Write,CoW)
  • 修改文件的逻辑:当容器需要修改只读层中的某个文件时(例如修改 /etc/hosts),UnionFS 不会直接修改原文件,而是执行以下操作:
    1. 将只读层中的目标文件「复制」到可写层。
    2. 在可写层中修改这个复制后的文件,原只读层的文件保持不变。
    • 好处:多个容器共享同一个镜像时,只读层的文件只需存储一份,节省磁盘空间;修改时才复制,避免重复占用资源。
4. 删除与重命名的实现
  • 删除文件:如果删除的是只读层中的文件,UnionFS 不会真正删除原文件,而是在可写层中创建一个「白名单文件」(.wh 文件),标记该文件为「隐藏」,这样在虚拟文件系统中就看不到这个文件了。
  • 重命名文件:类似修改操作,会先将文件复制到可写层,再执行重命名操作。
UnionFS 在容器中的实际应用

以 Docker 启动容器为例:

  1. 镜像加载:Docker 拉取镜像时,将各分层解压到宿主机的 /var/lib/docker/overlay2 目录下的不同分层目录(如 lowerdir 存储只读层,upperdir 存储可写层)。
  2. 容器启动:通过 Mount Namespace 将 UnionFS 合并后的虚拟目录挂载为容器的根目录(/),并通过 chroot 切换容器进程的根目录,使其只能访问该虚拟文件系统。
  3. 运行时修改:容器运行中产生的所有文件修改都保存在可写层,容器删除后,可写层也会被清理,不影响原镜像。

常见 UnionFS 类型对比

类型系统支持特点
Overlay2Linux 4.0+Docker 默认 UnionFS,性能较好,支持分层数量多,适合生产环境。
AUFS旧版 Docker早期 Docker 使用,分层性能略低,目前逐渐被 Overlay2 取代。
Btrfs部分 Linux 系统支持快照、写时复制,功能全面,但对系统兼容性要求较高。
ZFS特定系统强于数据完整性和压缩,常用于存储密集型场景,但资源消耗较大。

 UnionFS 就像一个「文件拼接大师」,把多个分层目录合并成一个虚拟空间,让容器既能共享基础镜像的文件(只读层),又能独立保存自己的修改(可写层)。通过写时复制和分层机制,它实现了容器镜像的轻量化和隔离性,是容器技术中「一次构建,多次运行」的核心基础。

四、三者如何配合打造容器?
  1. UnionFS 先搭好 “舞台”:把操作系统和应用文件按分层叠好,形成容器的文件系统。
  2. Namespace 划分 “独立房间”:让容器进程以为自己在独立的系统里运行,看不到宿主机和其他容器的资源。
  3. Cgroups 定 “资源规矩”:限制容器进程能用多少 CPU、内存,防止它 “抢资源”。

举个生活例子

  • UnionFS 像搭建公寓楼(底层是毛坯房,中间层是装修,顶层是住户家具)。
  • Namespace 像给每个住户分配独立的房间,关上门后看不到其他住户。
  • Cgroups 像物业规定每户每月最多用 100 度电,不能超量。

通过这三个技术的配合,容器就能在隔离的环境中高效运行,同时不浪费宿主机资源,这也是 Docker 等容器技术的核心底层原理。

扩展参考文章: 

Docker 上 Kubernetes 的入门到进阶指南:构建和管理你的容器化应用_kubernetes docker 先装谁?-CSDN博客

深入理解K8s与Docker的关系:容器化技术的双雄-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旗浩QH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值