深入解析:Docker 容器如何实现文件系统与资源的多维隔离?

目录

    • 一、RootFs
      • [1. Docker 镜像与文件系统层](#1. Docker 镜像与文件系统层)
      • [2. RootFs 与容器隔离的意义](#2. RootFs 与容器隔离的意义)
    • [二、Linux Namespace](#二、Linux Namespace)
      • [1. 进程命名空间](#1. 进程命名空间)
        • [1.1 `lsns` 命令说明](#1.1 lsns 命令说明)
        • [1.2 查看"祖先进程"命名空间](#1.2 查看“祖先进程”命名空间)
        • [1.3 查看当前用户进程命名空间](#1.3 查看当前用户进程命名空间)
      • [2. 容器进程命名空间](#2. 容器进程命名空间)
        • [2.1 查看容器进程命名空间列表](#2.1 查看容器进程命名空间列表)
        • [2.2 容器进程命名空间的具体体现](#2.2 容器进程命名空间的具体体现)
    • 三、cgroups
      • [1. cpu 子系统](#1. cpu 子系统)
        • [1.1 CFS (Completely Fair Scheduler)](#1.1 CFS (Completely Fair Scheduler))
        • [1.2 RT (Real-time Scheduler)](#1.2 RT (Real-time Scheduler))
        • [1.3 示例](#1.3 示例)
      • [2. cpuset 子系统](#2. cpuset 子系统)
      • [3. cpuacct 子系统](#3. cpuacct 子系统)
      • [4. memory 子系统](#4. memory 子系统)
      • [5. blkio 子系统](#5. blkio 子系统)
      • [6. devices 子系统](#6. devices 子系统)
      • [7. freezer 子系统](#7. freezer 子系统)
      • [8. net_cls 子系统](#8. net_cls 子系统)
      • [9. net_prio 子系统](#9. net_prio 子系统)
      • [10. perf_event](#10. perf_event)
      • [11. hugetlb](#11. hugetlb)
    • 总结

一、RootFs

在传统的 Linux 系统中,rootfs (根文件系统)就是系统的"根"目录,一般位于 /,其下包含 /bin/dev/etc 等常见目录结构。

在容器中,则通过镜像(Image) +**容器层(Container Layer)**的组合来提供容器自身的根文件系统,这个容器的根文件系统就是rootfs,与宿主机的 rootfs 相对隔离。

1. Docker 镜像与文件系统层

  • **镜像(Image)**由多层只读层(read-only layers)构成,利用了 Union FS 或 OverlayFS 等联合文件系统技术。
  • **容器层(Container Layer)**是该镜像之上的可写层(read-write layer)。容器运行时,对文件的修改都会写入到这个容器层中,不会影响只读层的内容。

2. RootFs 与容器隔离的意义

  • 当启动一个容器时,容器看到的"根文件系统"并非与宿主机相同,而是来自镜像 + 容器层组合形成的 rootfs。
  • 对于容器内部的进程来说,/ 就是容器自身的根目录,与宿主机的 / 相互独立(尽管底层还是同一个内核)。
  • 这种文件系统级别的隔离为容器提供了和宿主机隔离的外观,不同容器之间也不会直接污染彼此的文件系统。

二、Linux Namespace

Linux Namespace 是容器隔离的核心机制之一。Namespace 的主要作用是将系统资源进行"分名字空间"隔离,如进程(PID)、网络(NET)、文件系统挂载(MOUNT)、用户(USER)、IPC 等,从而让容器"以为"它拥有自己独立的环境。

1. 进程命名空间

1.1 lsns 命令说明
  • lsns 是 Linux 提供的一个查看系统中命名空间信息的命令。可以用 lsns 查看当前系统里所有命名空间,如 lsns -t pid 可以查看 PID 命名空间,lsns -t net 查看网络命名空间等。
  • 常见列示信息包含:
    • NS TYPE : 命名空间类型(如 pidnetmntutsipcuser 等)。
    • NSID : 命名空间 ID,一般可以在 /proc/pid/ns 路径中看到相应符号链接。
    • PID: 对应的进程号。
    • Command: 该命名空间对应进程的启动命令。
1.2 查看"祖先进程"命名空间

在 Linux 中,每个进程都有对应的命名空间引用(指针)。如果想要查看某个进程(比如 PID=1 或者宿主机上的某个 PID)的命名空间,可以通过:

列出系统所有命名空间

复制代码
sudo lsns --output0all
复制代码
ls -l /proc/<PID>/ns

这会展示对应的符号链接,比如:

复制代码
lrwxrwxrwx 1 root root 0 Jan  1 00:00 /proc/1/ns/pid -> pid:[4026531836]

这样就能看到 pid:[4026531836] 对应的命名空间 ID。通常 PID=1(系统的 init 或 systemd)会在宿主机的最初始 namespace 中。

1.3 查看当前用户进程命名空间

直接对你当前 shell 的 PID 进行查看:

复制代码
echo $$
# 假设输出为 12345
ls -l /proc/12345/ns

就能看到你当前 shell 进程所使用的 namespace。

2. 容器进程命名空间

容器之所以能够让其内部进程彼此隔离,主要原因之一是 Docker(或其他容器运行时)在启动容器进程时,会为该进程创建或加入单独的命名空间(PID/NET/IPC/UTS 等)。

2.1 查看容器进程命名空间列表

假设我们有一个正在运行的容器,可以先找到容器对应的"容器进程":

复制代码
docker ps
docker inspect <container_id> | grep "Pid"

然后拿到对应的 PID,比如是 23456。接着:

复制代码
ls -l /proc/23456/ns

就能看到容器进程使用的所有 namespace 绑定信息。

2.2 容器进程命名空间的具体体现
  • PID Namespace
    容器内部查看到的进程号(PID)从 1 开始,而在宿主机上,这个容器的进程是一个完全不同的 PID 值。容器内部的"PID=1"通常是容器内的 init 进程。
  • Network Namespace
    容器有自己单独的网卡配置(如 eth0),与宿主机是隔离的。通过容器的 network namespace,可以将容器网络与宿主机网络解耦(或进行端口映射)。
  • Mount Namespace
    容器有自己挂载的文件系统视图,比如 / 是容器自己的 rootfs。与宿主机的挂载点不同。
  • IPC Namespace
    容器之间的共享内存、消息队列等 IPC 机制互不影响。
  • UTS Namespace
    容器可以有自己独立的 hostname。容器内 hostname 与宿主机可以不同。

借助这些命名空间,容器可以呈现一个与宿主机几乎隔离的操作系统视图。


三、cgroups

cgroups (control groups) 是 Linux 提供的另一项关键特性,用于对系统资源进行"配额、限制、监控、隔离"。Docker 容器通过将容器进程加入到相应的 cgroup,来限制其对 CPU、内存、IO 等资源的使用或进行统计。

cgroups 是一个可插拔的框架,常见的子系统包括:

  • cpu
  • cpuset
  • cpuacct
  • memory
  • blkio
  • devices
  • freezer
  • net_cls
  • net_prio
  • perf_event
  • hugetlb
    等。

下面分别简要介绍这些子系统在容器隔离中的作用或使用示例。

1. cpu 子系统

cpu 子系统主要用于限制或分配 CPU 时间片给某个 cgroup 内进程。让我们来看看常见的调度器和示例。

1.1 CFS (Completely Fair Scheduler)
  • Linux 默认的 CPU 调度器。可通过 cpu.sharescpu.cfs_period_uscpu.cfs_quota_us 等文件对 CPU 使用进行相对或绝对限额设置。
  • 例如要限制某个 cgroup 的进程只能使用"相当于一个 CPU 核心"的计算量,可以在 cpu.cfs_period_us = 100000(默认100ms)和 cpu.cfs_quota_us = 100000 之间做设置,这样就大致等价于 1 core。
1.2 RT (Real-time Scheduler)
  • RT 调度针对实时任务,可以用来做实时优先级的资源控制。不过容器中常见应用较少直接动用 RT 调度。
1.3 示例

在手动配置 cgroup 时,可能会:

  1. 创建目录:mkdir /sys/fs/cgroup/cpu/test_cgroup

  2. 写入一些限制:

    复制代码
    echo 200000 > /sys/fs/cgroup/cpu/test_cgroup/cpu.cfs_quota_us
    echo 100000 > /sys/fs/cgroup/cpu/test_cgroup/cpu.cfs_period_us

    表示此 cgroup 一次调度周期内(100ms),只能用 200ms CPU 时间,相当于可以使用 2 核的 CPU 时间。

  3. 把某个进程写入 tasks:

    复制代码
    echo <pid> > /sys/fs/cgroup/cpu/test_cgroup/tasks

    该进程的 CPU 使用就受限于此组的规则。

Docker 在启动容器时会自动做这些事情,如 --cpus--cpuset-cpus 等参数。

2. cpuset 子系统

  • cpuset 子系统允许指定某些 CPU 核、某些内存节点给特定的 cgroup。
  • 比如可以指定容器只能在 CPU 0 和 1 上运行,或者只能从 NUMA 节点0分配内存。
  • Docker 对应的参数是 --cpuset-cpus="0-1" 之类。

3. cpuacct 子系统

  • cpuacct 用于统计某个 cgroup 内进程的 CPU 使用情况(用户态、内核态占用总时长),只做统计不做限制。
  • Docker 可以通过这个子系统查看容器的 CPU 使用状态。

4. memory 子系统

memory 子系统用于限制和统计进程的内存使用,包括物理内存和 swap。

  • 常见的控制文件:
    • memory.limit_in_bytes:该 cgroup 最大物理内存限制。
    • memory.memsw.limit_in_bytes:物理内存 + swap 限制(如果启用 swap 记账)。
  • 通过设置这些值,可防止某些进程用光系统所有内存。
示例
  1. 创建 cgroup:mkdir /sys/fs/cgroup/memory/test_mem

  2. 设置限制:

    复制代码
    echo 524288000 > /sys/fs/cgroup/memory/test_mem/memory.limit_in_bytes  # 500MB
  3. 将进程加入:

    复制代码
    echo <pid> > /sys/fs/cgroup/memory/test_mem/tasks

    这样该进程占用内存在超过 500MB 时可能会触发 OOM(Out Of Memory)动作。

5. blkio 子系统

  • blkio (Block IO) 用于限制进程的块设备 IO 速率,比如磁盘读写速度。
  • 可以设置读取速率、写入速率的限制。对需要在容器层面做 IO QoS 的场景很有帮助。

6. devices 子系统

  • devices 子系统可以控制某个 cgroup 中的进程可以访问哪些设备、只能读或写、或完全禁止访问等。
  • 容器通常为了安全,会只允许访问少数必要设备(比如 /dev/null/dev/random 等)。

7. freezer 子系统

  • freezer 子系统提供了把 cgroup 内进程"冻结/解冻"的功能。
  • 可以把某个 cgroup 的状态设置为 FROZEN,则该组内所有进程都挂起,等到切回 THAWED 才继续运行。

8. net_cls 子系统

  • net_cls 可以为网络数据包打上一个分类标识(classid),配合 tc(traffic control)做网络流量整形或带宽控制。

9. net_prio 子系统

  • net_prio 子系统可以为 cgroup 中的进程设置网络优先级(priority),从而在同一台宿主机上的容器间做网络流量优先级区分。

10. perf_event

  • perf_event 子系统方便对一组进程进行性能计数器(performance counter)的监控,比如 CPU cycle、cache miss 等。

11. hugetlb

  • hugetlb 用于管理大页内存(Huge Pages)。可以限制某个 cgroup 使用多少大页内存。

总结

通过 RootFsLinux Namespacecgroups 的巧妙组合,Docker 容器能够在同一个 Linux 内核上运行,却拥有与宿主机和其他容器相对独立的文件系统、进程空间、网络环境、IPC、以及严格的资源配额/限制。这为容器提供了接近虚拟机的隔离性,同时也保留了"共享同一个内核"的优势(启动速度快、资源开销小等)。

  1. RootFs:让容器拥有独立的文件系统视图,与宿主机的根目录区分开来。
  2. Linux Namespace
    • PID Namespace 让容器内部进程有各自的 PID 视图。
    • Network Namespace 让容器拥有独立的虚拟网卡、网络栈。
    • Mount Namespace 让容器控制自己的挂载点。
    • UTS、IPC、User Namespace 等也实现其他层面的隔离。
  3. cgroups
    • 对 CPU、内存、IO 等进行资源限制和监控。
    • 通过 Docker 参数可很方便地指定容器的资源上限、优先级。

这些机制共同构成了 Docker 容器环境下的核心隔离限制手段,使得容器能够安全、稳定地在生产环境中运行各种应用。


参考:
0voice · GitHub

相关推荐
一水鉴天5 小时前
整体设计 逻辑系统程序 之18 Source 容器(Docker)承载 C/P/D 三式的完整设计与双闭环验证 之2
docker·架构·认知科学·公共逻辑
飞快的蜗牛7 小时前
利用linux系统自带的cron 定时备份数据库,不需要写代码了
java·docker
火星MARK7 小时前
k8s面试题
容器·面试·kubernetes
香吧香8 小时前
Docker Registry 使用总结
docker
赵渝强老师8 小时前
【赵渝强老师】Docker容器的资源管理机制
linux·docker·容器·kubernetes
haicome10 小时前
deepseek部署
docker·ragflow·deepseek 部署
乄bluefox10 小时前
保姆级docker部署nacos集群
java·docker·容器
每天进步一点_JL10 小时前
Docker 是什么?
后端·docker·容器
一叶飘零_sweeeet11 小时前
从 0 到 1 掌控云原生部署:Java 项目的 Docker 容器化与 K8s 集群实战指南
docker·云原生·kubernetes·项目部署
森林猿11 小时前
docker-compose-kafka 4.1.0
docker·容器·kafka