Mnt Namespace shared tree隔离机制

2023年 12月 20日 23.2k 0

mnt namespace的作用

mnt namespace 是用系统用来隔离mount信息,在不同的mnt namespace中,系统拥有自己的独立的VFS(Virtual File System,虚拟文件系统)目录树以及挂载点信息

在Docker的容器技术中,每个容器都会运行在自己的mnt namespace中,这样就可以防止容器间的文件系统的相互干扰,通过改变容器的mount namespace,可以方便地挂载或者卸载文件系统,来满足容器的运行的需求。

mnt namespace实验演示

在之前我使用过clone系统调用来创建子进程来模拟容器运行,这里我继续放上这个代码,并运行一下这个程序

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
    "/bin/bash",
    NULL
    };

int container_main(void* arg)
{
    printf("Container [%5d] - inside the container!\n", getpid());
    sethostname("container",10);
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}
int main()
{
    printf("Parent - start a container!\n");
    int container_pid = clone(container_main, container_stack+STACK_SIZE,
                              CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

这个程序通过clone系统调用创建了一下子进程,其中CLONE_NEWPID , CLONE_NEWUTS , 这两个参数是修改了子进程的进程号视图以及主机名视图,进入子进程就会发现该子进程的PID为1并且可以随意修改主机名而不影响外面宿主机,CLONE_NEWNS则是这次的重点创建一个mnt namespace,该子进程的拥有一个新的挂载点的视图。

运行程序,在子进程中执行top命令:

[root@container ~]# top
top - 22:35:52 up 35 min,  2 users,  load average: 0.00, 0.00, 0.00
Tasks: 169 total,   1 running, 168 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni, 99.8 id,  0.0 wa,  0.2 hi,  0.0 si,  0.0 st
MiB Mem :   1785.3 total,   1288.5 free,    230.6 used,    266.2 buff/cache
MiB Swap:   2076.0 total,   2076.0 free,      0.0 used.   1400.5 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                 
    967 root      20   0  705000  31456  15500 S   0.3   1.7   0:04.00 tuned                                                                   
      1 root      20   0  240724  13592   8888 S   0.0   0.7   0:02.05 systemd                                                                 
      2 root      20   0       0      0      0 S   0.0   0.0   0:00.00 kthreadd                                                                
      3 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 rcu_gp  
      ....

看到这个结果你会发现一个问题,为什么top这个命令读取到了宿主机的所有进程的信息? 这是top,ps这些命令本质是去读取/proc这个文件系统的信息,proc这这个文件系统用于访问进程信息和内核状态。每个运行中的进程都在/proc下有一个以其进程ID命名的目录,其中包含该进程的信息(proc作为特殊文件系统,linux系统会在启动时进行直接挂载)

在子进程访问/proc目录会更直观

[root@container ~]# ls /proc
1     1621  1800  203  215  226  26   39   50   54   642  773  872  921  982        driver       key-users    mpt           swaps
10    1668  19    204  216  227  28   4    51   55   643  774  873  924  acpi       execdomains  kmsg         mtrr          sys
11    1672  195   205  217  228  29   40   52   56   644  775  874  93   buddyinfo  fb           kpagecgroup  net           sysrq-trigger
12    1673  196   206  218  229  3    41   521  57   645  776  875  938  bus        filesystems  kpagecount   pagetypeinfo  sysvipc
13    17    197   207  219  23   30   43   522  58   647  8    889  939  cgroups    fs           kpageflags   partitions    thread-self
14    1705  198   208  22   230  31   44   523  59   648  828  9    940  cmdline    interrupts   loadavg      sched_debug   timer_list
15    1706  199   209  220  231  32   449  526  6    708  836  911  947  consoles   iomem        locks        schedstat     tty
16    1727  2     210  221  232  34   45   527  605  734  866  912  957  cpuinfo    ioports      mdstat       scsi          uptime
1605  1771  20    211  222  233  347  46   528  613  769  868  915  967  crypto     irq          meminfo      self          version
1611  1772  200   212  223  234  350  47   529  639  770  869  916  969  devices    kallsyms     misc         slabinfo      vmallocinfo
1614  1799  201   213  224  24   36   48   533  640  771  870  918  978  diskstats  kcore        modules      softirqs      vmstat
1620  18    202   214  225  25   372  49   534  641  772  871  919  979  dma        keys         mounts       stat          zoneinfo

这个/proc就是宿主机本身的/proc,所以top才会显示宿主机的所有进程运行,在容器运行中,这种情况怎么能够接受。

上面我们提过子进程在创建的时候已经有了自己的mnt namespace, 那么这个时候我们可以重新挂载一下的/proc试试

我们可以将代码修改一下 增加一个挂载/proc操作

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
    "/bin/bash",
    NULL
    };

int container_main(void* arg)
{
    printf("Container [%5d] - inside the container!\n", getpid());
    sethostname("container",10);
    /*将/proc文件系统在子进程中挂载*/
    system("mount -t proc proc /proc");
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}
int main()
{
    printf("Parent - start a container!\n");
    int container_pid = clone(container_main, container_stack+STACK_SIZE,
                              CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);

    printf("Parent - container stopped!\n");
    return 0;
}

也可以直接 在子进程的shell中执行 mount -t proc proc /proc 命令 而不需要修改代码重新运行。我们再次使用top命令查看进程运行情况

[root@container ~]# top
top - 22:52:50 up 52 min,  2 users,  load average: 0.04, 0.03, 0.00
Tasks:   2 total,   1 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.1 us,  0.0 sy,  0.0 ni, 99.8 id,  0.0 wa,  0.1 hi,  0.0 si,  0.0 st
MiB Mem :   1785.3 total,   1262.3 free,    231.5 used,    291.6 buff/cache
MiB Swap:   2076.0 total,   2076.0 free,      0.0 used.   1399.2 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                 
      1 root      20   0  237380   5864   3880 S   0.0   0.3   0:00.02 bash                                                                    
     42 root      20   0  275104   4352   3704 R   0.0   0.2   0:00.00 top   

//查看/proc
[root@container ~]# ls /proc/
1          cmdline    dma          interrupts  keys         loadavg  mounts        sched_debug  stat           timer_list   zoneinfo
45         consoles   driver       iomem       key-users    locks    mpt           schedstat    swaps          tty
acpi       cpuinfo    execdomains  ioports     kmsg         mdstat   mtrr          scsi         sys            uptime
buddyinfo  crypto     fb           irq         kpagecgroup  meminfo  net           self         sysrq-trigger  version
bus        devices    filesystems  kallsyms    kpagecount   misc     pagetypeinfo  slabinfo     sysvipc        vmallocinfo
cgroups    diskstats  fs           kcore       kpageflags   modules  partitions    softirqs     thread-self    vmstat

发现现在整个top命令能看到进程视图只有两个进程了,再次查看/proc目录 发现确确实实只剩两个进程,发现mnt namespace确实隔离了/proc 这个文件系统,使得子进程能够有自己的独立的文件系统视图。

一般到这里对mnt namespace的作用解释的比较清晰了, 但是由于我们使用了clone系统调用来演示mnt namespace的隔离作用,所以补充说一下关于挂载点的shared subtrees的部分内容,

Shared subtrees

当我们按照上面实验,子进程在mnt namespace 通过挂载命令挂载proc文件系统,这时候我们从另外一个shell窗口来看当前宿主机的/proc目录,并且执行top命令

[root@bogon ~]# ls /proc
ls: cannot read symbolic link '/proc/self': No such file or directory
ls: cannot read symbolic link '/proc/thread-self': No such file or directory
1          consoles   driver       iomem     key-users    locks    mpt           schedstat  swaps          tty
acpi       cpuinfo    execdomains  ioports   kmsg         mdstat   mtrr          scsi       sys            uptime
buddyinfo  crypto     fb           irq       kpagecgroup  meminfo  net           self       sysrq-trigger  version
bus        devices    filesystems  kallsyms  kpagecount   misc     pagetypeinfo  slabinfo   sysvipc        vmallocinfo
cgroups    diskstats  fs           kcore     kpageflags   modules  partitions    softirqs   thread-self    vmstat
cmdline    dma        interrupts   keys      loadavg      mounts   sched_debug   stat       timer_list     zoneinfo

[root@bogon ~]# top
Error, do this: mount -t proc proc /proc

很明显发现到了一个问题,子进程的挂载的proc文件系统影响到了宿主机的proc文件系统,导致宿主机中proc文件系统被修改了。

proc作为伪文件系统在/etc/fstab记录中,当linux系统启动时会扫描/etc/fstab这个文件,将相应的文件系统进行挂载进入初始的root namespace中,而刚刚子进程的挂载却对root namespace的挂载造成了影响,

这个是由于我们在创建一个新的mnt namespace时 会将当前的mnt namespace的挂载点信息在内核中拷贝一份到新的mnt namespace中,其中有个重要的传播属性 就是shared subtrees。

这个属性决定了某个挂载点之下新增或者移除的子挂载点是否会同步影响到它的副本,

假设现在在root namespace下创建一个ns1的mnt namespace, 并且在ns1基础上创建一个ns2的mnt namespace

private 表示新创建的mnt namespace中的挂载点的shared subtrees属性都设置为private,即ns1和ns2的挂载点互不影响
shared 表示新创建的mnt namespace中的挂载点的shared subtrees属性都设置为shared,即ns1或ns2中新增或移除子挂载点都会同步到另一方
slave 表示新创建的mnt namespace中的挂载点的shared subtrees属性都设置为slave,即ns1中新增或移除子挂载点会影响ns2,但ns2不会影响ns1
unchanged 表示拷贝挂载点信息时也拷贝挂载点的shared subtrees属性,也就是说挂载点A原来是shared,在mnt namespace中也将是shared

默认来说直接使用mount 挂载时不指定传播属性时,如果当前的挂载点为shared,那么复制过去的挂载点的传播属性也为shared,否则为private

可以从实验来看,使用findmnt查看当前宿主机的挂载点的传播属性,以及查看将子进程的mount系统调用删除之后运行的挂载点

//宿主机的挂载点信息
[root@bogon ~]# findmnt -o TARGET,PROPAGATION | grep proc
├─/proc                               shared
│ └─/proc/sys/fs/binfmt_misc          shared
//子进程的挂载点信息
[root@container ~]# findmnt -o TARGET,PROPAGATION |grep proc
├─/proc                               shared
│ └─/proc/sys/fs/binfmt_misc          shared

发现proc文件系统在root namespace下的传播属性时shared,那么我们通过系统调用创建子进程的mnt namespace的传播属性时一致的,所以当我们在子进程中重新挂载proc文件系统,当然会对宿主机造成影响

在子进程中执行以下命令

[root@container ~]# mount --make-rprivate /proc
[root@container ~]# findmnt -o TARGET,PROPAGATION |grep proc
├─/proc                               private
│ └─/proc/sys/fs/binfmt_misc          private

发现子进程中的挂载点传播属性已经变成了private,这样再执行mount -t proc proc /proc将proc文件系统挂载进入子系统之后,在分别在宿主机和子进程中执行top

//子进程
[root@container ~]# top
top - 04:22:21 up 36 min,  2 users,  load average: 0.00, 0.00, 0.00
Tasks:   2 total,   1 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni, 99.9 id,  0.0 wa,  0.1 hi,  0.0 si,  0.0 st
MiB Mem :   1785.3 total,   1222.4 free,    243.2 used,    319.7 buff/cache
MiB Swap:   2076.0 total,   2076.0 free,      0.0 used.   1383.4 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                 
      1 root      20   0  237512   6000   3896 S   0.0   0.3   0:00.06 bash                                                                    
     63 root      20   0  275104   4424   3772 R   0.0   0.2   0:00.00 top   
//宿主机
[root@bogon ~]# top
top - 04:23:08 up 37 min,  2 users,  load average: 0.00, 0.00, 0.00
Tasks: 170 total,   1 running, 169 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.2 hi,  0.0 si,  0.0 st
MiB Mem :   1785.3 total,   1222.2 free,    243.4 used,    319.7 buff/cache
MiB Swap:   2076.0 total,   2076.0 free,      0.0 used.   1383.2 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                 
     57 root      20   0       0      0      0 I   0.3   0.0   0:00.16 kworker/1:1-events_freezable                                            
    974 root      20   0  705000  29828  15684 S   0.3   1.6   0:04.22 tuned                                                                   
   1706 root      20   0       0      0      0 I   0.3   0.0   0:01.42 kworker/0:1-ev

发现两者互不干扰,即子进程中mnt namespace创建的挂载点并没有对宿主机的挂载点造成任何影响,即完成了真正意义上的隔离。

如果直接unshare来创建mnt namespace,那么可以直接指定mnt namespace所有挂载点的传播属性

相关文章

KubeSphere 部署向量数据库 Milvus 实战指南
探索 Kubernetes 持久化存储之 Longhorn 初窥门径
征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
那些年在 Terraform 上吃到的糖和踩过的坑
无需 Kubernetes 测试 Kubernetes 网络实现
Kubernetes v1.31 中的移除和主要变更

发布评论