mnt namespace的作用
mnt namespace 是用系统用来隔离mount信息,在不同的mnt namespace中,系统拥有自己的独立的VFS(Virtual File System,虚拟文件系统)目录树以及挂载点信息
在Docker的容器技术中,每个容器都会运行在自己的mnt namespace中,这样就可以防止容器间的文件系统的相互干扰,通过改变容器的mount namespace,可以方便地挂载或者卸载文件系统,来满足容器的运行的需求。
mnt namespace实验演示
在之前我使用过clone系统调用来创建子进程来模拟容器运行,这里我继续放上这个代码,并运行一下这个程序
C
#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命令:
Bash
[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目录会更直观
Bash
[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操作
C
#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命令查看进程运行情况
Bash
[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命令
Bash
[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系统调用删除之后运行的挂载点
Bash
//宿主机的挂载点信息
[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文件系统,当然会对宿主机造成影响
在子进程中执行以下命令
Bash
[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
Bash
//子进程
[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所有挂载点的传播属性