姚益祁

Linux的隔离技术

本文会基于上述问题完成讨论。

1 namespace

1.1 定义

例如某个进程集合只能看到某个资源集合;另一个进程集合只能看到一个不同的资源集合。

1.2 用途

这么做能保证进程集之间不会互相干扰,保证一定程度的安全性。使得创建一个namespace像创建一个虚拟机一样对资源进行隔离。

1.3 种类

1.4 使用实例

Linux提供了unshare命令用于创建和持久化namespace

这是man对它的解释:

NAME
          unshare - run program in new name namespaces

1.4.1 PID的隔离以及用户的隔离

以下内容是以一个没有提权能力的普通用户执行unshare的过程

下面这个命令创建一个PID namespace

unshare --pid --fork fish

报错了!

仔细想想:unshare默认以root用户的形式启动,如果没有root权限自然是无法执行的,1.3节介绍的时候讲过User namespace可以让普通用户在该namespace获得root权限,那么我们尝试增加User namespace

unshare --pid --user --fork fish

我们这里创建了User namespace,可是我怎么进入变成nobody/依然报错?

正确的指令如下:

unshare --pid --user --map-root-user --fork fish

注:为什么这里三条指令都用了--fork?不fork会发生什么?(可以自己尝试一下)

1.3节提到过一个进程在两个PID namespace同时存在的情况下会拥有两个PID,可以这样验证:

namespace的外部和内部均执行ps命令查看fish对应的进程号:

ps -ef | grep fish

糟糕!怎么在内部无法执行?麻烦真是一堆!

仔细分析:因为当前的隔离环境没有mount namespace,所以/proc文件夹还是外面的内容,导致实际环境与/proc文件夹的内容不一致;而ps -ef指令需要/proc文件夹的内容来分析哪些属于当前用户

好说,那就写一段C代码调用API来看PPID(因为在fish终端执行的程序其父进程就是fish):

#include <stdio.h>
#include <unistd.h>
int main() {
    printf("PPID = %d\n", getppid());
    return 0;
}

可以看到,在外部拥有一个PID号的fish,在内部有独立的PID号:PID=1

注:为了防止环境不完整导致的命令执行失败以及权限等各种问题,统一使用一个较为完整的隔离环境是更好的选择 例如,下面的指令包括PID、User、mount、UTS、cgroups,并且设置好了User映射和/proc文件夹的挂载

unshare --user --pid --map-root-user --mount-proc --uts --mount --cgroup  --fork fish

由于这里的/proc是直接通过挂载完成的,故umount可以让它原形毕露

1.4.2 namespace的Unix哲学:万物皆文件

在namespace的外部可以通过lsns命令查看所有与当前用户相关的namespace的信息

        NS TYPE   NPROCS    PID USER COMMAND
4026532522 user        2 123916 yyq  unshare --user --pid --map-root-user --mount-proc --uts --mount --f
4026532524 mnt         2 123916 yyq  unshare --user --pid --map-root-user --mount-proc --uts --mount --f
4026532525 uts         2 123916 yyq  unshare --user --pid --map-root-user --mount-proc --uts --mount --f
4026532526 pid         1 123917 yyq  └─fish

NPROCS表示在namespace中的进程数,其中PID namespace只有1,但如果运行其他程序,就会增加

信息还不够多,可以用lsns --output-all来试试

4026532527 user   /proc/176430/ns/user        2 176430 176229 unshare 1001 yyq                                       4026531837 4026531837
4026532531 mnt    /proc/176430/ns/mnt         2 176430 176229 unshare 1001 yyq                                                0 4026532527
4026532532 uts    /proc/176430/ns/uts         2 176430 176229 unshare 1001 yyq                                                0 4026532527
4026532533 pid    /proc/176431/ns/pid         1 176431 176430 fish    1001 yyq                                       4026531836 4026532527
4026532534 cgroup /proc/176430/ns/cgroup      2 176430 176229 unshare 1001 yyq                                                0 4026532527

没想到namespace也是由文件系统来管理的!

实际上这种组织方式是十分合理的:PID namespace对应的第一个进程既是外部的进程也是内部的进程,可以很好地作为namespace管理信息的入口;其他namespace则由创建它们的unshare来管理,也是自然而然的。

2 cgroups

2.1 定义

cgroups提供了对一系列进程的资源(CPU、内存、硬盘I/O、网络等)的限制、管理、隔离

2.2 特征

2.3 使用实例

2.3.1 打印cgroups树

对于使用systemd的Linux系统,cgroups由systemd接管,可以使用下面的命令:

# systemctl status
systemd-cgls

注:cgroups本身是由多个资源Hierachy(如CPU、内存等)及单个Hierachy中的依赖关系组织的多个树形结构,但是systemd是以service、slice等脚本文件将重要程序的启动顺序和执行依赖等信息管理,并将这些脚本文件本身生成一个依赖关系树,进而将cgroup变为这些脚本文件的内容、组织为cgroups树

2.3.2 cgroup资源占用显示

systemd-cgtop

2.3.3 自定义cgroups

在systemd管理下,可以使用/etc/systemd/system/文件夹设置自己的cgroups

例如创建文件/etc/systemd/system/my.slice

[Slice]
CPUQuota=30%

设置完成后不要忘记daemon-reload:

systemctl daemon-reload

之后root使用systemd-run即可按照cgroups分配的情况运行一个程序

systemd-run --slice=my.slice ./a.out # 任意一个程序

注:这里可以用htop看到程序占用CPU情况,也可以用systemd-cgtop看到my.slice是直接在根下面的cgroup

3 二者的区别

同样是隔离,namespacecgroups有什么区别?

答:

  1. 前者是抽象性隔离(将各个进程集互相隔离),后者是具象性隔离(将资源可见地分配给各个进程/进程集)
  2. 前者以多进程(集)间安全性隔离为主(阻止各命名空间之间互相交互),后者以单进程(集)资源分配隔离为主(给一个进程多少CPU就只能用那么多)
  3. 前者提供了各个用户级别的隔离,后者只提供了系统级别的隔离
  4. 前者提供了更加简单易用的命令行工具和API(clone、unshare、setns),后者只能通过文件系统或者需要依赖于系统资源管理工具本身加以管理(如systemd)

4 k8s namespace

k8s中也有一个概念叫作namespace,它和Linux中的namespace一样么?

Tips: 如果一样还单独列出来做什么呢:)

4.1 定义

它与Linux中的namespace类似,提供了对k8s资源的隔离,将同一集群中资源划分为相互隔离的组。同一namespace资源名称唯一。

注:k8s里的资源指的是k8s对象,几乎可以说所有的在配置清单上的都是资源对象。

4.2 初始名字空间

4.3 使用实例

获得名字空间信息:

kubectl get namespace

请求时设置名字空间:

kubectl run nginx --image=nginx --namespace=\<namespace\>
kubectl get pods --namespace=\<namespace\>

4.4 然而

虽然刚刚讲到了绝大多数配置清单上的都是资源,但不是所有资源都在名字空间中

可以用下面的方法检查哪些资源在名字空间中,而哪些不在:

kubectl api-resources --namespaced=true
kubectl api-resources --namespaced=false

主要参考资料

What Are Namespaces and cgroups, and How Do They Work?

unshare manual

user_namespaces manual

cgroups ArchWiki

namespace API

k8s namespace