Linux Namespace 简单理解

本文将介绍 Linux Namespace

一提到 容器 技术,随之想到的便是 namespace 是容器化的基石,本篇文章将对 Namespace 进行简单的了解。

本文所依赖的环境为:

root@debian:~# hostnamectl
 Static hostname: windows11pro
       Icon name: windowsBooks
         Chassis: laptop 💻
Operating System: Debian GNU/Linux 12 (bookworm)
          Kernel: Linux 6.1.0-26-amd64
    Architecture: x86-64
 Hardware Vendor: ASUSTeK COMPUTER INC.
  Hardware Model: X441UVK
Firmware Version: X441UVK.314
root@debian:~#

Namepace 也称之为命名空间,最早出现在内核 2.4.19 上,后期逐步迭代的,它的作用是将 Linux 的资源隔离开来,可以这样理解, Nmaespace 就是为了虚拟化而存在的。

Namespace 类型众多,这里列举一个表格说明。

内核版本 Namespace 系统调用参数 含义
2.4.19 Mount Namespace CLONE_NEWNS 文件系统的隔离
2.6.19 UTS Namespace CLONE_NEWUTS nodename和damainname的隔离
2.6.19 IPC Namespace CLONE_NEWIPC 进程间通信资源隔离
2.6.24 PID Namespace CLONE_NEWPID 进程ID的隔离
2.6.29 Network Namespace CLONE_NEWNET 网络的隔离
3.8 User Namespace CLIONE_NEWUSER 用户和组的隔离
5.6 Time Namespace CLONE_NEWTIME 系统时钟的隔离

前置准备

陆游曾说,”纸上得来终觉浅,绝知此事要躬行。“,实践才是检验真理的唯一标准,所以,在正式了解 Namespace 之前,得先了解下关于 Namespace 相关的工具: unshare,如果要解释该工具,用 unshare - run program in new namespaces 表达再合适不过了,它可以在新的命名空间中运行要指定的程序。

unshare工具的使用

创建 Namespace 主要的参数有以下几种:

  • -m--mount:用以创建 Mount namespace
  • -u--uts:用以创建 Uts Namespace
  • -i--ipc:用以创建 IPC Namespace
  • -p--pid:用以创建 Pid Namespace
  • -n--net:用以创建 Network Namespace
  • -U--user:用以创建 User Namespace
  • -T--time:用以创建 Time Namespace

举一个最简单的例子,在 Network Namespace 启动一个 bash 进程,可以使用如下命令:

在此之前,我们需要先获取一下当前机器的 IP 信息,已做参考:

root@debian:~# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:7c:92:af brd ff:ff:ff:ff:ff:ff
root@debian:~#

Network Namespace 中执行命令

root@debian:~# unshare -n /usr/bin/bash
root@debian-linux:~#
root@debian-linux:~# ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
root@debian-linux:~#

使用 unshare -n /usr/bin/bash 会创建一个 Network Namespace,并且在该 Namespace 中执行 bash 操作,使用 exit 即可退出该 Namespace

这是 unshare 的最简单的用法。

修改提示符

Linux 的提示符格式都被记录到了 PS1 的内置变量中,可以使用 echo 查看其内容。

root@debian:~# echo $PS1
${debian_chroot:+($debian_chroot)}\u@\h:\w\$
root@debian:~#

为了方便使用 unshare 启动新进程更加直观,所以需要修改一下命令提示符,首先在 /root/.bashrc 文件末尾添加如下代码:

# 打开调试
set -x

# 定义默认的PS1值
export PS1="${debian_chroot:+($debian_chroot)}[\u@\h]:[\w][tty:\l]\\$ "

# 设置别名
alias unshare="UNSHARE=1 unshare "

# 如果变量中的UNSHARE值为1,则修改PS1值
if [ ! -e ${UNSHARE} ] && [ ${UNSHARE} -eq 1 ];then
        export PS1="(unshare) ${debian_chroot:+($debian_chroot)}[\u@\h]:[\w][tty:\l]\\$ "
fi

# 关闭调试
set +x

增加上述代码后,重新加载一下 /root/.bashrc 就可以使用 unshare 启动 bash 进程了。

首先先重新加载一下 /root/.bashrc 文件

root@debian:~# source /root/.bashrc
++ export 'PS1=[\u@\h]:[\w][tty:\l]\$ '
++ PS1='[\u@\h]:[\w][tty:\l]\$ '
++ alias 'unshare=UNSHARE=1 unshare '
++ '[' '!' -e ']'
++ set +x
[root@debian]:[~][tty:1]#

使用 unshare 启动 bash 进程


[root@debian]:[~][tty:1]# unshare -n /usr/bin/bash
+ export 'PS1=[\u@\h]:[\w][tty:\l]\$ '
+ PS1='[\u@\h]:[\w][tty:\l]\$ '
+ alias 'unshare=UNSHARE=1 unshare '
+ '[' '!' -e 1 ']'
+ '[' 1 -eq 1 ']'
+ export 'PS1=(unshare) [\u@\h]:[\w][tty:\l]\$ '
+ PS1='(unshare) [\u@\h]:[\w][tty:\l]\$ '
+ set +x
(unshare) [root@debian]:[~][tty:1]#

通过上面设置,就可以更加直观的看出当前 shell 是否在 Namespace 中,后续为了信息更加简洁,所以就暂时将调试给关闭了。

命名空间文件描述符

Linux 中,查看2个进程是否属于同一个命名空间,可以查看 /proc/进程ID/ns/* 下面的文件描述符,如果文件描述符一致,则证明是处于同一命名空间的。

例如,如下2个进程的 namespace 文件描述符如下:

[root@debian]:[~][tty:0]# readlink /proc/983/ns/*
cgroup:[4026531835]
ipc:[4026531839]
mnt:[4026531841]
net:[4026531840]
pid:[4026531836]
pid:[4026531836]
time:[4026531834]
time:[4026531834]
user:[4026531837]
uts:[4026531838]
[root@debian]:[~][tty:0]# readlink /proc/998/ns/*
cgroup:[4026531835]
ipc:[4026531839]
mnt:[4026531841]
net:[4026531840]
pid:[4026531836]
pid:[4026531836]
time:[4026531834]
time:[4026531834]
user:[4026531837]
uts:[4026531838]
[root@debian]:[~][tty:0]#

可以看到,其每项 namespace 的文件描述符都是一致的,所以证明该2个进程属于同一命名空间。

如果是使用 unshare 创建的命名空间,其 namespace 文件描述符应该不一致。

# unshare -m /usr/bin/bash
(unshare) [root@debian]:[~][tty:0]# echo $$
1000
(unshare) [root@debian]:[~][tty:0]#
(unshare) [root@debian]:[~][tty:0]# readlink /proc/1000/ns/*
cgroup:[4026531835]
ipc:[4026531839]
mnt:[4026532316]
net:[4026531840]
pid:[4026531836]
pid:[4026531836]
time:[4026531834]
time:[4026531834]
user:[4026531837]
uts:[4026531838]
(unshare) [root@debian]:[~][tty:0]#

在该 mount namespace 中,其 mount namespace 文件描述符为 4026532316

(unshare) [root@debian]:[~][tty:0]# exit
exit
[root@debian]:[~][tty:0]# echo $$
983
[root@debian]:[~][tty:0]# readlink /proc/983/ns/*
cgroup:[4026531835]
ipc:[4026531839]
mnt:[4026531841]
net:[4026531840]
pid:[4026531836]
pid:[4026531836]
time:[4026531834]
time:[4026531834]
user:[4026531837]
uts:[4026531838]
[root@debian]:[~][tty:0]#

在该宿主机中,其 mount namespace 文件描述符为 4026531841。在宿主机的 namespace ,也称之为默认 namespace

可以看到,2个文件描述符不一致,说明2个进程是在不同的 mount namespace 中。

各个Nmaespace介绍

UTS Namespace

UTS Namespace 是用来隔离主机名和域名的,可以使不同的 Namespace 拥有不同的主机名。

使用 unshare 进入 bash 后尝试修改主机名,不会影响到宿主机的,例如如下:

[root@debian]:[~][tty:1]# unshare -u /usr/bin/bash
(unshare) [root@debian]:[~][tty:1]#
(unshare) [root@debian]:[~][tty:1]# echo $$
13858
(unshare) [root@debian]:[~][tty:1]#
(unshare) [root@debian]:[~][tty:1]# hostname new-debian-uts
(unshare) [root@debian]:[~][tty:1]#
(unshare) [root@debian]:[~][tty:1]# hostname
new-debian-uts
(unshare) [root@debian]:[~][tty:1]#

上述命令使用 unshare 创建了一个 uts namespace ,并且将 bash 放入其中执行,首先打印了其进程的 PID13858 、而后进行了主机名设置。

同时在不关闭该 shell 的前提下,打开另一个终端,获取主机名信息。

[root@debian]:[~][tty:5]# hostname
debian
[root@debian]:[~][tty:5]#
[root@debian]:[~][tty:5]# echo $$
13863
[root@debian]:[~][tty:5]#
[root@debian]:[~][tty:5]#

可以看到,在 uts namespace 命名空间中修改主机名,并不影响宿主机环境,同时,还可以查询一下2个进程的 ns 值:

# readlink /proc/{13858,13863}/ns/uts
uts:[4026532256]
uts:[4026531838]
[root@debian]:[~][tty:5]#

可以看到,上述2个进程的 uts namespace 文件描述符并不一致,所以是2个不同的命名空间,所以,修改主机名才不会互相影响。

IPC Namespace

ipc namespace 是隔离进程间的通信资源,但不是所有的进程间通信都会被隔离,它主要隔离 消息队列、信号量、共享内存等。

关于 IPC 通信,是否可以被 ipc namespace 隔离,可以查看如下表格:

IPC机制 需求 IPC Namespace隔离
管道、命名管道 进程间简单数据交换 不会隔离
消息队列 进程间异步信息交换 隔离
信号量 进程间同步和互斥 隔离
共享内存 进程间大块数据共享 隔离
套接字 进程间网络通信 不会隔离

这里举个简单的例子。

在宿主机上查询共享内存段信息。

[root@debian]:[~][tty:0]# ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 3          lightdm    600        524288     2          dest
0x00000000 4          lightdm    600        33554432   2          dest

[root@debian]:[~][tty:0]#

在宿主机创建一个新的共享内存段。

[root@debian]:[~][tty:0]# ipcmk -M 128
Shared memory id: 6
[root@debian]:[~][tty:0]#
[root@debian]:[~][tty:0]# ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 3          lightdm    600        524288     2          dest
0x00000000 4          lightdm    600        33554432   2          dest
0x8b111882 6          root       644        128        0

[root@debian]:[~][tty:0]#

而后在 ipc namespace 中来查询共享内存段。

# unshare -i -u /usr/bin/bash
(unshare) [root@debian]:[~][tty:0]#
(unshare) [root@debian]:[~][tty:0]# ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status

(unshare) [root@debian]:[~][tty:0]#

可以看到,在 ipc namespace 中共享内存段都没了,同样的,在该 namespace 中创建共享内存段,其宿主机也不可以间。

(unshare) [root@debian]:[~][tty:0]# ipcmk -M 128
Shared memory id: 0
(unshare) [root@debian]:[~][tty:0]#
(unshare) [root@debian]:[~][tty:0]# ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x4ec24363 0          root       644        128        0

(unshare) [root@debian]:[~][tty:0]#

在上述 namespace 不退出的情况下,在宿主机中查询共享内存段。

[root@debian]:[~][tty:1]# ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 3          lightdm    600        524288     2          dest
0x00000000 4          lightdm    600        33554432   2          dest
0x8b111882 6          root       644        128        0

[root@debian]:[~][tty:1]#

如上例子说明,在 ipc namespace 中, IPC 资源已被隔离。

Pid Namespace

Pid Namespace 是用来隔离进程 ID 的,在 Pid Namespace 中的进程都有独立的 PID

使用 unshare 创建 pid namespace 并且获取当前 PID

[root@debian]:[~][tty:2]# unshare -u -i -p --fork /usr/bin/bash
(unshare) [root@debian]:[~][tty:2]#
(unshare) [root@debian]:[~][tty:2]# echo $$
1
(unshare) [root@debian]:[~][tty:2]#
(unshare) [root@debian]:[~][tty:2]#

创建 pid namespace 的时候,一定要加 --forkfork 一个新的进程,因为只有新的子进程才会分配 pid,在使用 pid namespace 后,新的命名空间中的 pid 都会从1开始计数。

实际上查询进程树信息,可以发现 linux 是做了一个转换,将宿主机的 pid 会映射进 pid namespace 中的进程 ID

[root@debian]:[~][tty:1]# ps axjf | head -n 1
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
      1     756     756     756 ?             -1 SLsl     0   0:00 /usr/sbin/lightdm
    756     777     777     777 tty7         777 Ssl+     0   0:02  \_ /usr/lib/xorg/Xorg :0 -seat seat0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch
    756    1104     756     756 ?             -1 Sl       0   0:00  \_ lightdm --session-child 15 26
   1104    1133    1133    1133 ?             -1 Ssl      0   0:00      \_ /usr/bin/lxsession -s LXDE -e LXDE
   1133    1228    1228    1228 ?             -1 Ss       0   0:00          \_ /usr/bin/ssh-agent x-session-manager
   1133    1259    1133    1133 ?             -1 S        0   0:00          \_ openbox --config-file /root/.config/openbox/lxde-rc.xml
   1133    1264    1133    1133 ?             -1 Sl       0   0:00          \_ lxpolkit
   1133    1266    1133    1133 ?             -1 Sl       0   0:00          \_ lxpanel --profile LXDE
   1133    1269    1133    1133 ?             -1 Sl       0   0:00          \_ pcmanfm --desktop --profile LXDE
   1269    1431    1133    1133 ?             -1 Sl       0   0:00          |   \_ lxterminal
   1431    1434    1434    1434 pts/2       1469 Ss       0   0:00          |       \_ bash
   1434    1468    1468    1434 pts/2       1469 S        0   0:00          |           \_ unshare -u -i -p --fork /usr/bin/bash
   1468    1469    1469    1434 pts/2       1469 S+       0   0:00          |               \_ /usr/bin/bash

其实该 bash 的进程 id1469 ,但是使用了 pid namespace ,所以在该 namesapce 中会被映射为了 1 ,此时若在宿主机上 kill -9 1469 ,则会影响到命名空间的进程。

[root@debian]:[~][tty:1]# kill -9 1469
[root@debian]:[~][tty:1]#

同样在命名空间中的进程会被 kill 掉。

(unshare) [root@debian]:[~][tty:2]# unshare: sigprocmask unblock failed: Invalid argument
[root@debian]:[~][tty:2]#

Mount Namespace

Mount Namespace 是最早出现的 Namespace ,可以看到其系统调用参数为 CLONE_NEWNS ,和后续出现的 Namespace 系统调用参数都不同,这是因为最开始的时候,大佬们认为,不会在出现其他 Namespace 了,所以就以此为命名,后面的事情,大家就都知道了,为了兼容,所以该 Mount Namespace 还是以 CLONE_NEWNS 来命名的。

Mount Namespace 的作用是实现文件系统的隔离和管理。

使用 unshare 创建 mount namespace 并且重新挂载 proc 分区。

[root@debian]:[~][tty:2]# unshare -u -i -p -m --fork /usr/bin/bash
(unshare) [root@debian]:[~][tty:2]#
(unshare) [root@debian]:[~][tty:2]# echo $$
1
(unshare) [root@debian]:[~][tty:2]#
(unshare) [root@debian]:[~][tty:2]# mount -t proc proc /proc
(unshare) [root@debian]:[~][tty:2]#
(unshare) [root@debian]:[~][tty:2]# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.1   7196  3996 pts/2    S    03:40   0:00 /usr/bin/bash
root           3  0.0  0.2  11084  4456 pts/2    R+   03:40   0:00 ps aux
(unshare) [root@debian]:[~][tty:2]#

上述使用 unshare 创建了 mount namespace ,并且将 bash 放入其执行,在该 mount namespace 中,使用 mount 重新挂载了 proc ,接着查询所有进程,由于 pid namesapce 隔离,所以只能查询到当前命名空间中的进程了。

默认的 mount namespace 会继承宿主机的命名空间,若不进行重新挂载 proc ,查看到的信息就是宿主机的信息。

同样的,在该 namesapce 中设置的挂载,在宿主机是隔离开来的,例如:

(unshare) [root@debian]:[~][tty:2]# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        19G  3.9G   14G  23% /
udev            953M     0  953M   0% /dev
tmpfs           984M  4.0K  984M   1% /dev/shm
tmpfs           197M 1004K  196M   1% /run
tmpfs           5.0M  8.0K  5.0M   1% /run/lock
tmpfs           197M   44K  197M   1% /run/user/1000
tmpfs           197M   44K  197M   1% /run/user/0
/dev/sda5       6.4G  348M  5.8G   6% /var
/dev/sda8        71G  1.2G   67G   2% /home
/dev/sda7       1.2G   68K  1.1G   1% /tmp
(unshare) [root@debian]:[~][tty:2]# mount /dev/sda1 /data
(unshare) [root@debian]:[~][tty:2]# mount | grep /data
/dev/sda1 on /data type ext4 (rw,relatime,errors=remount-ro)
(unshare) [root@debian]:[~][tty:2]#

在不退出当前 mount namesapce 环境下,查询宿主机关于 /data 的挂载。

[root@debian]:[~][tty:1]# mount /dev/sda1 /data
[root@debian]:[~][tty:1]#

如此, mount namespace 隔离了文件系统挂载。

Network Namespace

network namespace 隔离的是网络环境,网络通信是较为复杂的操作,这里先仅仅证明 network namesapce 隔离性。

使用 unshare 创建 network namespace ,并且使 bash 在其运行。

(unshare) [root@debian]:[~][tty:2]# unshare  -u -i -p -m -n --fork /usr/bin/bash
(unshare) [root@debian]:[~][tty:2]#
(unshare) [root@debian]:[~][tty:2]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
(unshare) [root@debian]:[~][tty:2]#

创建了 network namespace 之后,查询其 ip 信息,只有本地回环地址,状态还是关闭的,说明 network namespace 隔离了其网络环境。

User Namespace

user namespace 隔离的是用户和用户组,换句话说,不同的 user namespace 中的 user idgroup id 可以是不同的。最为常用的是在宿主机以一个非 root 用户运行创建了一个 user namespace ,然后在该 user namespace 中却被映射成了 root 用户。

使用 unshare 创建 user namespace,并且使 bash 在其运行。

[root@debian]:[~][tty:2]# unshare -u -i -p -m -n -U --fork /usr/bin/bash
(unshare) [nobody@debian]:[~][tty:2]$
(unshare) [nobody@debian]:[~][tty:2]$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
(unshare) [nobody@debian]:[~][tty:2]$

可以看到,增加了 user namespace 后,其用户被映射为了 nobody 用户。

如果想要修改其容器中的 userid 值,可以使用 newuidmap 进行 uid 映射。

首先,需要找到其 user namespace 下运行进程的 pid

[root@debian]:[~][tty:0]# ps axjf
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
      1     756     756     756 ?             -1 SLsl     0   0:00 /usr/sbin/lightdm
    756    2547    2547    2547 tty7        2547 Ssl+     0   0:01  \_ /usr/lib/xorg/Xorg :0 -seat seat0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch
    756    2633     756     756 ?             -1 Sl       0   0:00  \_ lightdm --session-child 15 26
   2633    2637    2637    2637 ?             -1 Ssl      0   0:00      \_ /usr/bin/lxsession -s LXDE -e LXDE
   2637    2729    2729    2729 ?             -1 Ss       0   0:00          \_ /usr/bin/ssh-agent x-session-manager
   2637    2749    2637    2637 ?             -1 S        0   0:00          \_ openbox --config-file /root/.config/openbox/lxde-rc.xml
   2637    2750    2637    2637 ?             -1 Sl       0   0:00          \_ lxpolkit
   2637    2752    2637    2637 ?             -1 Sl       0   0:00          \_ lxpanel --profile LXDE
   2637    2754    2637    2637 ?             -1 Sl       0   0:00          \_ pcmanfm --desktop --profile LXDE
   2754    2886    2637    2637 ?             -1 Sl       0   0:00          |   \_ lxterminal
   2886    2889    2889    2889 pts/2       2916 Ss       0   0:00          |       \_ bash
   2889    2915    2915    2889 pts/2       2916 S        0   0:00          |           \_ unshare -u -i -p -m -n -U --fork /usr/bin/bash
   2915    2916    2916    2889 pts/2       2916 S+       0   0:00          |               \_ /usr/bin/bash

可以看到,其真实的 pid2916,则在宿主机上需要执行如下映射。

[root@debian]:[~][tty:1]# newuidmap 2916 0 0 1
[root@debian]:[~][tty:1]#

此时,再回到 user namespace 中,查看用户信息。

(unshare) [nobody@debian]:[~][tty:2]$ id
uid=0(root) gid=65534(nogroup) groups=65534(nogroup)
(unshare) [nobody@debian]:[~][tty:2]$
(unshare) [nobody@debian]:[~][tty:2]$ mount -t proc proc /proc
(unshare) [nobody@debian]:[~][tty:2]$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.1   7196  3876 pts/2    S    04:42   0:00 /usr/bin/bash
root           8  0.0  0.2  11084  4328 pts/2    R+   04:49   0:00 ps aux
(unshare) [nobody@debian]:[~][tty:2]$

uid 则变为了 root ,也可以进行磁盘挂载。

当然,在普通用户下,也是同理。

使用普通用户使用 unshare 创建 user namespace 并且启动 bash 命令。

[wangli@debian]:[~][tty:2]$ unshare -u -i -p -m -n -U --fork /usr/bin/bash
(unshare) [nobody@debian]:[~][tty:2]$
(unshare) [nobody@debian]:[~][tty:2]$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
(unshare) [nobody@debian]:[~][tty:2]$
(unshare) [nobody@debian]:[~][tty:2]$

查询其真实的 pid

[root@debian]:[~][tty:0]# ps axjf
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
      1     756     756     756 ?             -1 SLsl     0   0:00 /usr/sbin/lightdm
    756    2992    2992    2992 tty7        2992 Ssl+     0   0:01  \_ /usr/lib/xorg/Xorg :0 -seat seat0 -auth /var/run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch
    756    3077     756     756 ?             -1 Sl       0   0:00  \_ lightdm --session-child 14 26
   3077    3083    3083    3083 ?             -1 Ssl   1000   0:00      \_ /usr/bin/lxsession -s LXDE -e LXDE
   3083    3169    3169    3169 ?             -1 Ss    1000   0:00          \_ /usr/bin/ssh-agent x-session-manager
   3083    3185    3083    3083 ?             -1 S     1000   0:00          \_ openbox --config-file /home/wangli/.config/openbox/lxde-rc.xml
   3083    3186    3083    3083 ?             -1 Sl    1000   0:00          \_ lxpolkit
   3083    3189    3083    3083 ?             -1 Sl    1000   0:00          \_ lxpanel --profile LXDE
   3083    3192    3083    3083 ?             -1 Sl    1000   0:00          \_ pcmanfm --desktop --profile LXDE
   3192    3387    3083    3083 ?             -1 Sl    1000   0:00          |   \_ lxterminal
   3387    3390    3390    3390 pts/2       3418 Ss    1000   0:00          |       \_ bash
   3390    3417    3417    3390 pts/2       3418 S     1000   0:00          |           \_ unshare -u -i -p -m -n -U --fork /usr/bin/bash
   3417    3418    3418    3390 pts/2       3418 S+    1000   0:00          |               \_ /usr/bin/bash

查询到其 PID 为3418,在另外的终端,执行 newuidmap 操作

[wangli@debian]:[/root][tty:1]$ id
uid=1000(wangli) gid=1000(wangli) groups=1000(wangli),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev),111(lpadmin),114(scanner)
[wangli@debian]:[/root][tty:1]$
[wangli@debian]:[/root][tty:1]$ newuidmap 3418 0 1000 1
[wangli@debian]:[/root][tty:1]$

请注意,这里 loweruid 设置为了1000,这是因为 wangli 这个用户的 uid 为1000。

此时再到 user namespace 中查看 id 信息。

(unshare) [nobody@debian]:[~][tty:2]$ id
uid=0(root) gid=65534(nogroup) groups=65534(nogroup)
(unshare) [nobody@debian]:[~][tty:2]$
(unshare) [nobody@debian]:[~][tty:2]$ mount -t proc proc /proc
(unshare) [nobody@debian]:[~][tty:2]$
(unshare) [nobody@debian]:[~][tty:2]$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.2   8008  4720 pts/2    S    04:55   0:00 /usr/bin/bash
root           6  0.0  0.2  11084  4440 pts/2    R+   05:02   0:00 ps aux
(unshare) [nobody@debian]:[~][tty:2]$

可以看到,其 uid 已经被映射为了 root ,此时,执行 mount 进行挂载也可以实现。

Time Namespace

Time Nmaespace 允许进程在独立的时间上下文中运行。使用了 time namespace 的进程可以拥有自己独立的时间视图并且和其他 time namespace 和宿主机隔离开来。

下面举一个最简单的例子:

先查询系统启动的秒数

[wangli@debian]:[~][tty:2]$  cat /proc/uptime
11397.34 10829.19
[wangli@debian]:[~][tty:2]$

使用 unshare 启动 time namespace 命名空间,且运行 bash 进程。

[wangli@debian]:[~][tty:2]$ unshare -u -i -p -m -n -U -T --boottime -11397 --fork /usr/bin/bash
(unshare) [nobody@debian]:[~][tty:2]$ uptime
 05:36:09 up 0 min,  3 users,  load average: 0.00, 0.01, 0.00
(unshare) [nobody@debian]:[~][tty:2]$
(unshare) [nobody@debian]:[~][tty:2]$

在命名空间中,可以看到,其 uptime 的启动时间已经为0了。

在不退出 time namespace 的情况下,开一个新的 shell 来查看 uptime 信息

[wangli@debian]:[~][tty:3]$ uptime
 05:39:01 up  3:12,  3 users,  load average: 0.00, 0.01, 0.00
[wangli@debian]:[~][tty:3]$

由此证明, time namespace 对于系统时间的隔离性。

总结

首先,在介绍 Linux Nmaespace 之前先进行了前置准备,包括 unshare 工具的使用、修改提示符、命名空间文件描述符的基本说明等。

而后就介绍了 Linux Namespace 基本信息,归纳如下:

Linux NmaespaceLinux 内核一项强大的功能,是容器化的基石,至目前为止,一共有7种类别的 Namespace,按照发布时间顺序,分别是:

  • Mount Namespace :隔离文件系统。
  • UTS Namespace: 隔离主机名和域名。
  • IPC Namespace:隔离进程间通信。
  • PID Namespace:隔离进程ID。
  • Network Namespace:隔离网络。
  • User Namespace:隔离用户和组。
  • Time Namespace:隔离系统时钟。

如上众多的 Namespace 使得在同一台机器上运行多个“独立”的进程环境,称为可能,所以 Linux Namespace 才是容器化的基础。

原文阅读