51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Linux的 Namespace 和 Cgroups,如何实现资源隔离?

Linux 内核提供了多种机制来实现系统资源的隔离和管理,这篇文章,我们来详细分析两种关键的技术:Namespace 和 Cgroups。

Namespace 详解 {#Namespace-详解}

Namespace(命名空间,也称名称空间)是 Linux 内核用于隔离系统资源,使得不同的进程组可以拥有各自独立的资源视图。Namespace 的核心思想是通过将系统资源划分为不同的命名空间,进而实现资源的隔离。

Namespace通常包含以下几种类型:

  1. PID Namespace
  2. Mount Namespace
  3. UTS Namespace
  4. IPC Namespace
  5. Network Namespace
  6. User Namespace

PID Namespace {#PID-Namespace}

定义 {#定义}

PID Namespace 用于隔离进程 ID 空间,每个 PID Namespace 都有自己独立的进程 ID 号,父 Namespace 可以查看和管理子 Namespace 中的进程,但反之则不行。这种机制使得在容器中运行的进程可以拥有从 1 开始的 PID。

如下指令,可以创建一个新的 PID 命名空间:

|-------------|----------------------------------------------------------------------------| | 1 2 | # 创建新的 PID Namespace 并运行一个 Bash 进程 unshare -p -f --mount-proc bash |

命令解释

  • unshare:用于创建并进入一个或多个新的命名空间。
  • -p:创建一个新的 PID 命名空间。在新的 PID 命名空间中,进程可以拥有独立的进程 ID 号。
  • -f:让 unshare 命令 fork 一个新的进程,并在这个新的进程中执行指定的命令。这样可以确保 unshare 本身不会受到新的命名空间的影响。
  • --mount-proc:在新的 PID 命名空间中挂载一个新的 /proc 文件系统。这个选项通常与 -p 选项一起使用,以确保新的 PID 命名空间有一个一致的 /proc 视图。
  • bash:在新的命名空间中启动一个新的 Bash shell。

示例 {#示例}

为了更好地说明 Mount Namespace,下面以一个示例进行展示:

1.在终端A中执行以下指令:

|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 | # 运行 unshare -p -f --mount-proc bash unshare -p -f --mount-proc bash # 在新的 Bash shell 中查看进程 ID,只能看到PID=1的 bash进程和 ps aux进程 ps aux |

2.在另一个终端B中执行以下指令:

|-------------|-----------------------------| | 1 2 | # 可以看到很多 PID ps aux |

两段指令的终端对比图如下:
img

通过上面的对比图可以看出,在终端 A中实现了 PID的隔离,ps aux看不到宿主机中的所有进程,这样可以实现进程级别的安全隔离,即使在新的命名空间中运行的进程出现问题,也不会影响到系统中的其他进程。

使用场景 {#使用场景}

  1. 容器技术:PID 命名空间和挂载命名空间在容器技术中非常重要。通过 PID 命名空间,容器可以拥有独立的进程 ID 号,这使得容器中的进程可以独立管理和调试。通过挂载新的 /proc 文件系统,容器可以拥有独立的进程视图,便于监控和管理。
  2. 安全隔离:通过创建新的 PID 命名空间和挂载新的 /proc 文件系统,可以实现进程级别的安全隔离。这样一来,即使在新的命名空间中运行的进程出现问题,也不会影响到系统中的其他进程。

Mount Namespace {#Mount-Namespace}

定义 {#定义-1}

Mount Namespace 用于隔离挂载点。不同的 Mount Namespace 可以拥有各自独立的文件系统视图。这样一来,在一个 Namespace 中对文件系统的修改不会影响到其他 Namespace。

如下指令:系统会创建一个新的挂载命名空间,使得在该命名空间中的挂载操作不会影响到其他的命名空间

|-------------|--------------------------------------------------------------| | 1 2 | # 创建新的 Mount Namespace 并运行一个 Bash 进程 unshare -m bash |

当你运行unshare -m bash时,会发生以下事情:

  1. 创建新的挂载命名空间:使用 -m 选项,系统创建一个新的挂载命名空间。这个新的命名空间是当前进程的子命名空间,拥有独立的挂载点视图。
  2. 启动新的 Bash Shell:在这个新的挂载命名空间中启动一个新的 Bash shell。这个 Bash shell 及其子进程将只在这个新的挂载命名空间中运行。
  3. 独立的挂载操作:在这个新的 Bash shell 中进行的挂载和卸载操作(如 mount 和 umount 命令)将不会影响到其他命名空间中的挂载点。

示例 {#示例-1}

为了更好地说明 Mount Namespace,下面以一个示例进行展示:

1.在终端A中执行以下指令(子 namespace):

|-------------------------|------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 | # 运行 unshare -m bash unshare -m bash # 在新的挂载命名空间中挂载一个临时文件系统 mount -t tmpfs none /mnt # 查看挂载点 mount | grep /mnt |

2.在另一个终端B中执行以下指令(父 namespace):

|-------------|------------------------------------------------| | 1 2 | # 在父命名空间中,这个挂载点是不可见的 mount | grep /mnt |

两段指令的终端对比图如下:
img

使用场景 {#使用场景-1}

  1. 容器技术:通过挂载命名空间,容器可以拥有独立的文件系统视图,使得容器中的文件系统操作不会影响到宿主系统和其他容器。
  2. 安全隔离:通过挂载命名空间,可以实现文件系统级别的安全隔离。例如,你可以在一个挂载命名空间中挂载一个只读的文件系统,从而防止进程对文件系统进行修改。

UTS Namespace {#UTS-Namespace}

定义 {#定义-2}

UTS (UNIX Time-Sharing) Namespace 用于隔离主机名和域名。不同的 UTS Namespace 可以拥有不同的主机名和域名,这对于容器化应用非常有用。

如下指令:系统会创建一个新的命名空间,用于隔离主机名和域名

|---------------|----------------------------------------------------------------------------------| | 1 2 3 | # 创建新的 UTS Namespace 并运行一个 Bash 进程 unshare -u bash hostname new_hostname |

指令解释

  • unshare:用于创建并进入一个或多个新的命名空间。
  • -u:创建一个新的 UTS 命名空间。在新的 UTS 命名空间中,主机名和域名是独立的。
  • bash:在新的命名空间中启动一个新的 Bash shell。
  • hostname:用于查看或设置系统的主机名。
  • new_hostname:要设置的新主机名。

示例 {#示例-2}

为了更好地说明 UTS Namespace,下面以一个示例进行展示:

1.在终端A 中执行以下指令(子 namespace):

|---------------------------------|--------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 | # 创建新的 UTS 命名空间并启动 Bash unshare -u bash # 查看当前主机名 hostname # 修改主机名 hostname new_hostname # 再次查看主机名,确认修改 hostname |

2.在终端B 中执行以下指令(父 namespace):

|-------------|-----------------------------------------| | 1 2 | # 在父命名空间中查看主机名,主机名没有变化 hostname |

两段指令的终端对比图如下:
img

使用场景 {#使用场景-2}

  1. 容器技术:UTS 命名空间在容器技术中非常重要。通过 UTS 命名空间,每个容器可以拥有独立的主机名和域名,这对于多租户环境和隔离应用非常有用。
  2. 多租户环境:在多租户环境中,不同的租户需要独立的主机名和域名。通过 UTS 命名空间,可以为每个租户创建独立的命名空间,从而实现资源的隔离和独立管理。

IPC Namespace {#IPC-Namespace}

定义 {#定义-3}

IPC (Inter-Process Communication) Namespace 用于隔离进程间通信资源,如消息队列、信号量和共享内存。不同的 IPC Namespace 之间的通信资源是隔离的。

如下指令:是一个用于创建新的 IPC(Inter-Process Communication)命名空间并在其中启动一个新的 Bash shell 的命令

|-------------------|----------------------------------------------------------------------------------| | 1 2 3 4 5 | # 创建新的 IPC Namespace 并运行一个 Bash 进程 unshare -i bash # 创建一个新的消息队列 ipcmk -Q |

示例 {#示例-3}

为了更好地说明 UTS Namespace,下面以一个示例进行展示:

1.在终端A 中执行以下指令(子 namespace):

|-------------------------|------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 | # 创建一个新的 IPC 命名空间并启动一个新的 Bash shell unshare -i bash # 创建一个新的消息队列 ipcmk -Q # 查看当前消息队列 ipcs -q |

2.在终端B 中执行以下指令(父 namespace):

|-------------|-------------------------------------------------------------| | 1 2 | # 在父命名空间中查看消息队列,没有新的消息队列,这验证了 IPC 命名空间的隔离效果 ipcs -q |

两段指令的终端对比图如下:
img

使用场景 {#使用场景-3}

  1. 容器技术:通过 IPC 命名空间,每个容器可以拥有独立的进程间通信资源,这对于隔离应用和提高系统安全性非常有用。
  2. 多租户环境:在多租户环境中,不同的租户需要独立的进程间通信资源。通过 IPC 命名空间,可以为每个租户创建独立的命名空间,从而实现资源的隔离和独立管理。

Network Namespace {#Network-Namespace}

定义 {#定义-4}

Network Namespace 用于隔离网络资源,如网络接口、路由表、防火墙规则等。每个 Network Namespace 可以拥有独立的网络设备和配置。

|---------------|--------------------------------------------------------------------------------------------------------| | 1 2 3 | # 创建新的 Network Namespace 并运行一个 Bash 进程 ip netns add mynamespace ip netns exec mynamespace bash |

示例 {#示例-4}

为了更好地说明 Network Namespace,下面以一个示例进行展示如何创建和配置 Network Namespace:

1.在终端A 中执行以下指令(子 namespace):

|---------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # 创建新的 Network Namespace ip netns add mynamespace # 创建一对 veth 设备 ip link add veth0 type veth peer name veth1 # 将 veth1 移动到新的命名空间中 ip link set veth1 netns mynamespace # 在默认命名空间中设置 veth0 ip addr add 192.168.1.1/24 dev veth0 ip link set veth0 up # 在新的命名空间中设置 veth1 ip netns exec mynamespace ip addr add 192.168.1.2/24 dev veth1 ip netns exec mynamespace ip link set veth1 up # 在新的命名空间中设置默认路由 ip netns exec mynamespace ip route add default via 192.168.1.1 |

2.在终端B 中执行以下指令(父 namespace):

|-------------|---------------------------------------------------------------------| | 1 2 | # 在父命名空间可以通过 ping 命令测试与 mynamespace 中的网络连接 ping 192.168.1.2 |

3.在终端A 中执行以下指令(子 namespace):

|-------------|--------------------------------------------------------------------------------------------------| | 1 2 | # 在 mynamespace 中,可以通过 ping 命令测试与默认命名空间中的网络连接 ip netns exec mynamespace ping 192.168.1.1 |

三段指令的终端对比图如下:

img

使用场景 {#使用场景-4}

  1. 容器技术:通过 Network Namespace,每个容器可以拥有独立的网络栈,从而实现网络资源的隔离和独立管理。
  2. 多租户环境:在多租户环境中,不同的租户需要独立的网络配置和安全策略。通过 Network Namespace,可以为每个租户创建独立的网络栈,从而实现网络资源的隔离和独立管理。

User Namespace {#User-Namespace}

定义 {#定义-5}

User Namespace 用于隔离用户和用户组 ID。它允许在不同的 Namespace 中使用相同的用户 ID,但这些 ID 在不同的 Namespace 中是独立的。这样一来,即使在容器中运行的进程以 root 身份运行,也不会拥有对宿主系统的 root 权限。

|-------------|-------------------------------------------------------------| | 1 2 | # 创建新的 User Namespace 并运行一个 Bash 进程 unshare -U bash |

示例 {#示例-5}

为了更好地说明 User Namespace,下面以一个示例进行展示如何创建和配置 Network Namespace:

1.在终端A 中执行以下指令(子 namespace):

|---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 | # 创建新的 User Namespace 并启动 Bash unshare -U bash # 在新的命名空间中查看当前用户ID id # 在外部命名空间中设置用户ID映射 # 假设当前用户ID为1000 # 需要在另一个终端中运行以下命令 newuidmap $(pgrep -n bash) 0 1000 1 |

2.在终端B 中执行以下指令(父 namespace):

|-------------|--------------------------------------------------| | 1 2 | # 在父空间查看文件的所有者,找不到文件 ls -l /tmp/testfile |

3.在终端A 中执行以下指令(子 namespace):

|-------------------|-----------------------------------------------------------------------------| | 1 2 3 4 5 | # 在新的命名空间中创建一个文件 touch /tmp/testfile # 查看文件的所有者 ls -l /tmp/testfile |

三段指令的终端对比图如下:

img
文件的所有者将是新的用户ID映射中的用户,而不是外部命名空间中的用户。

使用场景 {#使用场景-5}

  1. 容器技术:通过 User Namespace,每个容器可以拥有独立的用户和用户组ID映射,从而实现特权分离和安全隔离。这使得容器中的进程可以以root身份运行,同时在宿主系统中仍然是非特权用户。
  2. 多租户环境:在多租户环境中,不同的租户需要独立的用户和用户组ID映射。通过 User Namespace,可以为每个租户创建独立的用户和用户组ID映射,从而实现资源的隔离和独立管理。

无法 Namespace的资源 {#无法-Namespace的资源}

尽管 Linux的 Namespace机制提供了对多种系统资源的隔离,但并不是所有的系统资源都能被 Namespace隔离,以下是一些不能被 Namespace隔离的资源及其原因:

  • 内核模块: 内核模块(Kernel Modules)在整个系统中是全局共享的。加载或卸载一个内核模块会影响到所有Namespace中的进程。
  • 内核参数 : 通过sysctl命令设置的内核参数(如/proc/sys下的参数)是全局的,无法在不同的Namespace中进行独立设置。
  • 硬件资源:硬件资源是物理存在的,无法通过软件机制进行隔离。
  • CPU:尽管Cgroups可以对CPU资源进行分配和限制,但CPU本身是一个物理资源,无法在不同的Namespace中进行隔离。
  • 内存:Cgroups可以对内存资源进行分配和限制,但物理内存本身无法在不同的Namespace中进行隔离。
  • 磁盘:磁盘设备是物理存在的,无法在不同的Namespace中进行隔离。尽管可以通过Cgroups对磁盘I/O进行限制,但磁盘设备本身是共享的。
  • 时间:系统时间(如系统时钟和硬件时钟)在整个系统中是共享的,无法在不同的Namespace中进行独立设置。
  • 安全机制:一些系统级的安全机制无法在不同的Namespace中进行隔离。
  • SELinux:SELinux(Security-Enhanced Linux)是一种安全模块,它的策略在整个系统中是全局的,无法在不同的Namespace中进行独立设置。
  • AppArmor:类似于SELinux,AppArmor也是一种安全机制,它的配置和策略在整个系统中是全局的。
  • 系统日志 :系统日志(如/var/log下的日志文件)在整个系统中是共享的,无法在不同的Namespace中进行独立管理。
  • 特殊设备文件 :一些特殊的设备文件(如/dev下的某些设备文件)在不同的Namespace中是共享的,无法进行隔离。例如,/dev/null/dev/zero等设备文件在整个系统中是全局的。

Cgroups 详解 {#Cgroups-详解}

Cgroups 的基本概念 {#Cgroups-的基本概念}

Cgroups (Control Groups,控制组)是 Linux 内核的一个特性,用于限制、记录和隔离一组进程的资源使用(CPU、内存、磁盘 I/O、网络等)。

Cgroups 通过将进程分组,然后对这些组应用资源限制来工作,核心组件包括:

Cgroup {#Cgroup}

Cgroup是一个控制组,表示一组进程。Cgroup通过层级结构组织,类似于文件系统的目录结构。每个节点代表一个Cgroup,节点之间的层级关系表示Cgroup的继承关系。

Hierarchy {#Hierarchy}

Hierarchy是Cgroup的组织结构。一个Hierarchy可以包含多个Cgroup,并且每个Hierarchy可以挂载到一个目录中。在同一个Hierarchy中,Cgroup之间存在父子关系,子Cgroup继承父Cgroup的限制和策略。

Subsystem {#Subsystem}

Subsystem是具体的资源控制器,如CPU、内存、块I/O等。每个Subsystem负责对特定类型的资源进行管理和限制。例如,cpu Subsystem用于控制CPU资源,memory Subsystem用于控制内存资源。

Cgroups 的子系统 {#Cgroups-的子系统}

Cgroups 支持多种子系统,每种子系统负责不同的资源控制:

  • cpu: 控制 CPU 资源的分配。
  • cpuacct: 提供 CPU 资源使用的统计信息。
  • memory: 控制内存资源的分配和使用。
  • blkio: 控制块设备的 I/O 操作。
  • net_cls: 控制网络资源的分类。
  • devices: 控制设备的访问权限。
  • freezer: 暂停和恢复进程。

创建和管理 Cgroups {#创建和管理-Cgroups}

通过命令行工具 cgcreatecgsetcgexec 可以方便地创建和管理 Cgroups。

|---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 | # 创建一个新的 Cgroup cgcreate -g cpu,memory:/mygroup # 设置 CPU 使用限制 cgset -r cpu.shares=512 mygroup # 设置内存使用限制 cgset -r memory.limit_in_bytes=256M mygroup # 将一个进程加入到 Cgroup cgexec -g cpu,memory:mygroup /bin/bash |

Cgroups v1 & v2 {#Cgroups-v1-v2}

Cgroups有两个版本:v1和v2。Cgroups v1提供了多种独立的子系统,每个子系统可以有独立的层级结构。Cgroups v2则提供了统一的层级结构,所有子系统共享同一个层级。

Cgroups v1 {#Cgroups-v1}

在Cgroups v1中,每个子系统可以有独立的层级结构。例如,可以有一个层级用于CPU资源控制,另一个层级用于内存资源控制。

Cgroups v2 {#Cgroups-v2}

Cgroups v2 是 Cgroups 的第二个版本,提供了更为统一和简化的接口。Cgroups v2 的主要特点包括:

  • 统一的层级结构,所有子系统共享同一个层级。
  • 更为简化的配置接口,减少了配置的复杂性。
  • 提高了资源控制的精度和灵活性。

示例 {#示例-6}

为了更好地说明 Cgroups,这里以两个示例进行说明

示例1:限制CPU使用 {#示例1:限制CPU使用}

下面是一个示例,展示如何使用Cgroups限制CPU使用:

|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 | # 创建一个新的Cgroup cgcreate -g cpu:/cpulimited # 设置CPU使用限制 cgset -r cpu.shares=512 cpulimited # 将一个进程加入到Cgroup cgexec -g cpu:cpulimited /bin/bash |

在这个示例中,我们创建了一个名为cpulimited的 Cgroup,并设置了CPU使用限制。然后,我们将一个新的Bash进程加入到这个Cgroup中。

示例2:限制内存使用 {#示例2:限制内存使用}

下面是一个示例,展示如何使用 Cgroups 限制内存使用:

|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 | # 创建一个新的Cgroup cgcreate -g memory:/memlimited # 设置内存使用限制 cgset -r memory.limit_in_bytes=256M memlimited # 将一个进程加入到Cgroup cgexec -g memory:memlimited /bin/bash |

在这个示例中,我们创建了一个名为memlimited的 Cgroup,并设置了内存使用限制。然后,我们将一个新的Bash进程加入到这个Cgroup中。

使用场景 {#使用场景-6}

  1. 容器技术:通过Cgroups,可以为每个容器设置独立的资源限制,从而实现资源的隔离和独立管理。这使得容器中的进程可以在有限的资源范围内运行,而不会影响到宿主系统和其他容器。
  2. 多租户环境:在多租户环境中,不同的租户需要独立的资源限制和管理策略。通过Cgroups,可以为每个租户创建独立的Cgroup,从而实现资源的隔离和独立管理。

为什么学习? {#为什么学习?}

上面的内容我们详细地分析了 Namespace 和 Cgroups两个技术点,那么,为什么要学习它们,它们有什么实际性的应用?

Namespace 和 Cgroups 的结合使用是容器技术(比如 Docker)的基础,Namespace 提供了进程级别的隔离,而 Cgroups 则用于资源的分配和限制,通过这两种机制,可以创建高效且安全的容器化环境。

容器的创建 {#容器的创建}

以下是一个简单的示例,展示如何使用 Namespace 和 Cgroups 创建一个容器:

|------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # 创建新的 Namespace unshare -p -f -m -u -i -n --mount-proc bash # 设置主机名 hostname container # 挂载新的文件系统 mount -t tmpfs none /tmp # 创建新的 Cgroup cgcreate -g cpu,memory:/container # 设置 CPU 和内存限制 cgset -r cpu.shares=512 container cgset -r memory.limit_in_bytes=256M container # 将当前进程加入到 Cgroup cgclassify -g cpu,memory:container $$ |

在上述示例中:

  • 首先,创建了一个新的 Namespace
  • 然后,设置了主机名并挂载了新的文件系统
  • 接着,创建了一个新的 Cgroup,并设置了 CPU 和内存限制
  • 最后,将当前进程加入到 Cgroup,这样一个容器就创建完成

总结 {#总结}

本文,我们分析了 Linux 内核提供的两种关键机制:Namespace 和 Cgroups,它们用于实现系统资源的隔离和管理。

Namespace 提供了进程级别的隔离,使得不同的进程组可以拥有各自独立的资源视图,而 Cgroups 则用于资源的分配和限制,通过将进程分组,然后对这些组应用资源限制来工作。结合使用这两种机制,可以创建高效且安全的容器化环境。

因此,掌握 Namespace 和 Cgroups 两个技术点,可以帮助我们更好地理解容器(比如 Docker)的实现原理。

赞(3)
未经允许不得转载:工具盒子 » Linux的 Namespace 和 Cgroups,如何实现资源隔离?