https://docs.docker.com/engine/security/userns-remap/#prerequisites

注:以下验证环境为centos7.5 docker 18.09.0

User namespaces

  • 使用user namespaces可以防止容器权限过大造成的风险,user namespaces主要涉及用户和组。在unix系统中,用户和组可以决定文件的访问权限以及进程的拥有者(用于访问消息队列,全局变量以及锁等)。docker的user namespaces映射主要涉及2个文件:/etc/subgid和/etc/subuid。创建一个user namespace可以使用unshare或fork,当未指定CLONE_NEWUSER时会创建一个与父user namespace相同的namespace;使用setns可以加入一个namespace,但需要有CAP_SYS_ADMIN的capabilities。
  • 决定一个进程在一个user namespace中是否拥有某个capabilities的规则如下
    1. user namespace中的进程可以通过执行set_user_id(SUID)程序或设置了capabilities的文件来获得capabilities,也可以通过setns加入namespace来获得capabilities;
    2. 如果一个进程在一个user namespace中拥有某个capabilities,那么它在该user namespace的所有子namespace下都有该capabilities;
    3. 当创建一个user namespace时,内核会记录创建该namespace的进程对应的effective user id;如果父user namespace中的某个进程的effective user id与创建user namespace的进程一致,则程序拥有新创建的user namespace的所有capabilities。
  • user namespace里面的进程只能操作属于该user namespace的资源,但也有一些功能与namespace type无关(即全局资源),只有root user namespace可以设置,如修改系统时间,载入模块,创建设备等
CAP_SYS_TIME CAP_SYS_MODULE CAP_MKNOD
  • /proc/[pid]/setgroups文件用于限制使用setgroups函数设置group的权限,默认是“allow”,此值只能被设置一次,后续设置将会返回错误。子user namespace会继承父user namespace的设置。/proc/[pid]/setgroups文件的设置主要是为了解决可能的权限问题,如具有"rwx---rwx"权限的进程可以通过调用setgroups来获得group权限,会有权限泄露的风险。/proc/[pid]/setgroups文件的引入也是伴随user namespace的出现而出现的,因为user namespace使得unprivileged进程也有能力设置group权限。
  • 首先创建一个名为dockertest的用户和组
groupadd -g  dockertest
useradd -u -g dockertest dockertest
  • 查看该用户组的信息
# id dockertest
uid=(dockertest) gid=(dockertest) groups=(dockertest)
  • 修改/etc/subgid和/etc/subuid中的内容,由于user和group的名字定义一致,故这两个文件中的内容相同,如下,表示将host上名为dockertest的user和group映射到容器中,uid和gid的映射范围为[231072, (231072+65536)],其中231072对应容器里面的UID或GID 0。映射后的user和group继承了名为dockertest的user和group的权限。
dockertest::
  • 在/etc/docker/daemon.json中修改dockerd的启动参数,内容如下,将user映射到dockertest(默认是root)
{
"userns-remap":"dockertest"
}
  • 使用systemctl命令重新启动dockerd,查看docker info,发现新的docker根路径变为了/var/lib/docker/231072.231072,新目录名称为user namespaces的uid.gid。由于docker根目录发生了变化,此时使用docker images会发现原来的容器镜像都看不到了
# docker info|grep Root
Docker Root Dir: /var/lib/docker/231072.231072
  • 并启动一个容器
docker run -itd --rm --name=centos centos:latest /bin/sh

  查看该sh对应在host上的进程,可以看到其用户为231072,即在/etc/subuid中设置的值

[root@localhost ]# ps -ef|grep sh
...
Dec06 pts/ :: /bin/sh
...

在容器里面创建一个名为test的用户,并在新创建的用户下执行如下命令,可以看到test的uid为1000,按照上面的映射原理,其对应host的uid为231072+1000=232072

[test@0f3005ba4d63 etc]$ id
uid=(test) gid=(test) groups=(test)
[test@0f3005ba4d63 etc]$ ping 127.0.0.01

  在host上查看ping的进程,可以看到其uid为232072,跟预期一致。

# ps -ef|grep ping          : pts/    :: ping 127.0.0.1

在/proc/10466(对应容器中的ping进程)下面查看uid_map和gid_map文件,可以看到该进程的user namespaces的映射关系:base+docker_offset

[root@localhost ]# cat uid_map 

[root@localhost ]# cat gid_map
                    
  • 在一个user namespace中执行一个Set-user-ID 或set-group-ID程序时,如果文件的uid或gid映射到了该user namespace,则程序的effective user id会变为映射过来的值,否则文件的SUID或SGID会被忽略。

    • 在root namespace上创建一个可执行程序,用来获取当前进程的uid和capabilities(test1的源码代码参见linux和docker的capabilities介绍),修改其用户和组为映射到容器中的值(231072+1000)
# chown 232072:232072 test1
# chmod 4777 test1
# ll
-rwsrwxrwx. 1 232072 232072 8632 Dec 20 22:55 test1
    • 在容器中挂载该文件所在的目录并以root权限执行该文件,可以看到程序的effective user id为映射到user namespace的值为1000
# ./test1
real_user_id=, effictive_user_id=, saved_user_id=
Cap data ES=0x0, PS=0xa80425fb, IS=0xa80425fb

在root user namespace中修改test1文件的用户和组为200:200,再在容器中以root权限运行,可以看出SUID的标志位被忽略了,但该标志位仍在。

# ./test1
real_user_id=, effictive_user_id=, saved_user_id=
Cap data ES=0xa80425fb, PS=0xa80425fb, IS=0xa80425fb
# ls -al
-rwsrwxrwx. Dec : test1
  • 当新创建一个user namespace时,进程的uid_map和gid_map是空的,这两个文件只能设置一次,多次设置会返回错误。因此root user namespace下的进程的这两个文件都不能设置,因为它们已经被设置过一次。
# cat /proc//uid_map
             

下面演示设置uid_map和gid_map,首先使用root账户创建一个user namespace,查看其bash进程pid和uid,可以看到uid都是65535

# unshare -fUu /bin/bash
# echo $$
9988 # id
uid=65534(nfsnobody) gid=65534(nfsnobody) groups=65534(nfsnobody) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

  在root user namespace下修改9988进程的uid_map

# echo "0 0 10000" > uid_map

在新的user namespace查看新的id,其uid已经变为0,修改gid的方式也一样。需要特别注意的是设置到uid_map(gid_map)中的第二个参数必须是一个存在的user id,且必须是创建该user namespace的用户的id(此处均为0),否则即使设置成功了也不会生效。比如使用root user namespace下有一个newusr的用户,id为3000,使用echo "0 3000 10000" > uid_map并不会使得新user namespace中的uid变为0。参见Defining user and group ID mappings: writing to uid_map and gid_map

# id
uid=0(root) gid=65534(nfsnobody) groups=65534(nfsnobody) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
The writing process must have the same effective user ID as the process that created the user namespace.
  • 使用docker的user namespaces功能之后有如下3个限制(参见User namespace known limitations)
    1. 无法与host共享PID或net(使用--pid=host或--network=host)
    2. 与user映射不兼容的外部设备将无法使用(如host上的volume的user和容器映射的user不一致,此时需要修改volume的user才能被容器使用)
    3. docker run的时候没有指定--userns=host,但使用了--privileged

PID namespaces

    Docker PID namespaces:

PID namespaces使得每个 PID namespace 中的进程可以有其独立的 PID; 每个容器可以有其 PID 为 1 的root 进程;因为 namespace 中的进程 ID 和 host 无关,也使得容器可以在不同的 host 之间迁移。这也使得容器中的每个进程有一对映射的PID:容器中的 PID 和 host 上的 PID。

  • 在容器中启动一个名为testcontainer.sh的shell脚本
#!/bin/sh
while true
dosleep
done
  • 在容器中查看对应的进程如下,对应进程号为26
sh-4.2# ps -ef
UID PID PPID C STIME TTY TIME CMD
root : pts/ :: /bin/sh
root : pts/ :: /bin/sh
root : pts/ :: /bin/sh ./testcontainer.sh
root : pts/ :: /bin/sh
  • 在host上查看该容器中对应的进程,其在host上的进程号为101442
[root@localhost ns]# ps -ef|grep testcontainer
root : pts/ :: grep --color=auto testcontainer
root : ? :: /bin/sh ./testcontainer.sh
  • 查看/proc/101442/ns下面的namespace,为4026532575
# ll
total
lrwxrwxrwx. root root Dec : ipc -> ipc:[]
lrwxrwxrwx. root root Dec : mnt -> mnt:[]
lrwxrwxrwx. root root Dec : net -> net:[]
lrwxrwxrwx. root root Dec : pid -> pid:[]
lrwxrwxrwx. root root Dec : user -> user:[]
lrwxrwxrwx. root root Dec : uts -> uts:[]
  • 按照上述方式查看host上pid为1的根进程的pid namespace,为4026531836,可以看到testcontainer.sh运行在一个独立的ipc下面。pid namespace 通过将 host 上 PID 映射为容器内的PID,使得容器内的进程看起来有个独立的 PID 空间,也可以在容器里面使用ps命令查看(该命令依赖于/proc虚拟文件的挂载)。注:可以看到容器中的user namespace和host是一致的,因为其没有执行userns-remap操作。
# ll
total
lrwxrwxrwx. root root Dec : ipc -> ipc:[]
lrwxrwxrwx. root root Dec : mnt -> mnt:[]
lrwxrwxrwx. root root Dec : net -> net:[]
lrwxrwxrwx. root root Dec : pid -> pid:[]
lrwxrwxrwx. root root Dec : user -> user:[]
lrwxrwxrwx. root root Dec : uts -> uts:[]

     自定义pid namespaces

  • 首先使用unshare创建一个pid namespace,其中--mount-proc用于挂载/proc虚拟文件,这样使用ps才能看到其namespace内部的进程信息。也可以单独使用mount -t proc proc /proc来挂载/proc。unshare默认执行/bin/sh创建一个shell交互界面
unshare --fork --pid --mount-proc
  • 这样就启动了一个独立的pid namespace
sh-4.2# ping 127.0.0.1
  • 查看相应的进程(已过滤无关内容),可以看到ping进程是unshare的子进程,当unshare退出之后,namespace也会随之被销毁
[root@localhost home]# ps -ef|grep ping
root : pts/ :: ping 127.0.0.1
[root@localhost home]# ps -ef|grep
root : pts/ :: /bin/sh
root : pts/ :: ping 127.0.0.1
[root@localhost home]# ps -ef|grep
root : pts/ :: unshare -pf --mount-proc /bin/sh
root : pts/ :: /bin/sh
  • 对比unshare和ping进程对应的ns,可以发现仅有pid的namespace不一样
# ll (查看ping进行对应的ns)
total
lrwxrwxrwx. root root Dec : ipc -> ipc:[]
lrwxrwxrwx. root root Dec : mnt -> mnt:[]
lrwxrwxrwx. root root Dec : net -> net:[]
lrwxrwxrwx. root root Dec : pid -> pid:[]
lrwxrwxrwx. root root Dec : user -> user:[]
lrwxrwxrwx. root root Dec : uts -> uts:[]
# ll ../..//ns/ (查看unshare进程对应的ns)
total
lrwxrwxrwx. root root Dec : ipc -> ipc:[]
lrwxrwxrwx. root root Dec : mnt -> mnt:[]
lrwxrwxrwx. root root Dec : net -> net:[]
lrwxrwxrwx. root root Dec : pid -> pid:[]
lrwxrwxrwx. root root Dec : user -> user:[]
lrwxrwxrwx. root root Dec : uts -> uts:[]
  • 除了使用unshare打开的shell交互界面进入ns,也可以使用nsenter进入ping进程的pid namespace查看相应的进程,不过此时需要mount /proc目录,否则使用ps看到的是host的信息
# nsenter --pid=pid
# mount -t proc proc /proc
# ps -ef
UID PID PPID C STIME TTY TIME CMD
root : pts/ :: /bin/sh
root : pts/ :: ping 127.0.0.1
root : pts/ :: -bash
root : pts/ :: ps -ef

Mount namespaces

docker mount namespaces(后续会增加一个讲解docker 存储的篇章)

自定义mount namespaces

当一个进程调用clone或unshare创建一个mount namespace时,子mount namespace的mount point list会拷贝父namespace下的mount point list,可以查看/proc/$pid/mountinfo文件,可以看到子namespace和父namespace中的内容是一样的,但后续各自namespace对mount的操作将互不影响(取决于shared subtrees特性)

为了解决在多个mount namespace的场景下,对同一个mount point在不同mount namespace中的维护(mount umount)问题,Linux 2.6.15版本中推出了Share Subtrees特性,Share Subtrees有4种propagation类型:

    1. MS_SHARED:mount和unmount事件会在同一个peer group中所有成员的mount point之间传播。即对peer group中的一个成员的mount point操作会影响到其他成员中对应的mount point。mount point有2种方式可以加入同一个peer group:第一种是使用clone或unshare方式创建一个新的mount namespace,这样多个mount namespace下的peer group信息是一样的;另一种是使用bind mount方式,但源mount point必须是同一个peer group的成员
    2. MS_PRIVATE:该类型不会加入peer group,mount和unmount事件也不会传播
    3. MS_SLAVE:该类型的mount point会接收来自master shared peer group的事件,但不会传播事件到master peer group,即mount和unmount事件的传播是单向的,从master到slave。但一个mount point可以同时是一个peer group的MS_SLAVE,同时又是另外一个peer group的MS_SHARED。docker的cgroup namespaces就是使用了MS_SLAVE的propagation
    4. MS_UNBINDABLE:类似MS_PRIVATE,此外,还禁止bind mount。
  • 测试MS_SHARED和MS_PRIVATE。创建4个虚拟磁盘,格式化为ext2;创建3个文件夹,mntS用于测试MS_SHARED,mntP用于测试MS_PRIVATE,mntS-B用于测试bind mount下的shared peer group
[root@host /]# dd if=/dev/zero of=./vdisk1 bs=1M count=
[root@host /]# dd if=/dev/zero of=./vdisk2 bs=1M count=
[root@host /]# dd if=/dev/zero of=./vdisk3 bs=1M count=
[root@host /]# dd if=/dev/zero of=./vdisk4 bs=1M count=
[root@host /]# mkfs vdisk1
[root@host /]# mkfs vdisk2
[root@host /]# mkfs vdisk3
[root@host /]# mkfs vdisk4
[root@host /]# mkdir mntS
[root@host /]# mkdir mntP
[root@host /]# mkdir /mntS-B

将vdisk1 mount到/mntS,vdisk2 mount到/mntP,/mntS-B bind mount到/mntS

[root@host /]# mount --make-shared vdisk1 /mntS
[root@host /]# mount --make-private vdisk2 /mntP
[root@host ~]# mount --bind /mntS /mntS-B/

在当前mount namespace下面查看mount结果,可以看到/mntS和/mntS-B的类型为shared,peer group ID为162;而mntP没有任何类型标识,即为private。mountinfo文件中第一列为mount point ID,第二列为父mount point ID,可以看到它们的父mount point ID都是40

[root@host /]# cat /proc/self/mountinfo |grep mnt
: / /mntS rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntP rw,relatime - ext2 /dev/loop1 rw,seclabel
: / /mntS-B rw,relatime shared: - ext2 /dev/loop0 rw,seclabel

查看40对应的信息,可以看到其mount point为系统根目录"/",根目录对应的类型为shared,peer group ID为1。默认情况下子mount point会继承父mount point的propagation类型,所以上述mount --make-shared vdisk1 /mntS也可以写为mount vdisk1 /mntS

[root@host /]# cat /proc/self/mountinfo | awk '$1 == 40' | sed 's/ - .*//'
: / / rw,relatime shared:

创建一个mount namespace,并将其hostname改为container。unshare默认会将新创建的mount namespace的propagation类型转变为private,使用--propagation unchanged可以让其继承父mount namespace的propagation类型

[root@host /]# unshare -fmu --propagation unchanged
[root@host /]# hostname container
[root@host /]# exec bash
[root@container /]#

查看新创建的namespace中的/mntS和/mntP的信息,可以看大盘/mntS和/mntS-B的shared peer group ID为162,与host上/mntS的ID一致,可以看到两个/mntS为同一个peer group;而/mntP均为private类型

[root@container ~]#  cat /proc/self/mountinfo |grep mnt
: / /mntS rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntP rw,relatime - ext2 /dev/loop1 rw,seclabel
: / /mntS-B rw,relatime shared: - ext2 /dev/loop0 rw,seclabel

在container mount namespace中的/mntS和/mntP下创建子目录,并将vdisk3和vdisk4 mount到这两个目录

[root@container /]# mkdir -p /mntS/mntS-sub
[root@container /]# mkdir -p /mntP/mntP-sub
[root@container /]# mount vdisk3 /mntS/mntS-sub/
[root@container /]# mount vdisk4 /mntP/mntP-sub/

在container下查看moutinfo,可以发现/mntS和/mntP的子目录下的mout point继承了父mount point的propagation类型

[root@container /]# cat /proc/self/mountinfo |grep mnt
: / /mntS rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntP rw,relatime - ext2 /dev/loop1 rw,seclabel
: / /mntS-B rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntS/mntS-sub rw,relatime shared: - ext2 /dev/loop2 rw,seclabel
: / /mntS-B/mntS-sub rw,relatime shared: - ext2 /dev/loop2 rw,seclabel
: / /mntP/mntP-sub rw,relatime - ext2 /dev/loop3 rw,seclabel

在host上查看mountinfo,发现其与container中不同在于没有mntP-sub目录的信息,这也表面了private类型下的mount umount事件不会传递,而shared则会在同peer group内传递这些事件

[root@host ~]# cat /proc/self/mountinfo |grep mnt
: / /mntS rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntP rw,relatime - ext2 /dev/loop1 rw,seclabel
: / /mntS-B rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntS/mntS-sub rw,relatime shared: - ext2 /dev/loop2 rw,seclabel
: / /mntS-B/mntS-sub rw,relatime shared: - ext2 /dev/loop2 rw,seclabel
  • 测试MS_SLAVE。清除上述环境,首先创建2个shared类型的mount point
[root@host /]# mkdir mntS1
[root@host /]# mkdir mntS2
[root@host /]# mount --make-shared vdisk1 /mntS1/
[root@host /]# mount --make-shared vdisk2 /mntS2/

查看host上mntS*目录的mountinfo,可以看到其类型为shared,分属于2个不同的peer group

[root@host /]# cat /proc/self/mountinfo |grep mnt
: / /mntS1 rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntS2 rw,relatime shared: - ext2 /dev/loop1 rw,seclabel

创建一个新的mount namespace,集成其父mount namespace的propagation属性

[root@host /]# unshare -fum --propagation unchanged
[root@host /]# hostname container
[root@host /]# exec bash

当然,其与host上的mount point属于同一个peer group

[root@container /]# mount --make-slave /mntS2
[root@container /]# cat /proc/self/mountinfo |grep mnt
: / /mntS1 rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntS2 rw,relatime shared: - ext2 /dev/loop1 rw,seclabel

将container的mntS2的propagation类型转变为slave,查看mountinfo信息,可以看到/mntS2变为slave,其master为ID为170的peer group,就是host上的/mntS2

[root@container /]# cat /proc/self/mountinfo |grep mnt
: / /mntS1 rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntS2 rw,relatime master: - ext2 /dev/loop1 rw,seclabel

在/mntS1和/mntS2下创建新的mount point,可以看到在/mntS1下的mount point类型为shared,而在/mntS2下面的mount point类型为private

[root@container /]# mkdir -p /mntS1/mntS1-sub
[root@container /]# mkdir -p /mntS2/mntS2-sub
[root@container /]# mount vdisk3 /mntS1/mntS1-sub/
[root@container /]# mount vdisk4 /mntS2/mntS2-sub/
[root@container /]# cat /proc/self/mountinfo |grep mnt
: / /mntS1 rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntS2 rw,relatime master: - ext2 /dev/loop1 rw,seclabel
: / /mntS1/mntS1-sub rw,relatime shared: - ext2 /dev/loop2 rw,seclabel
: / /mntS2/mntS2-sub rw,relatime - ext2 /dev/loop3 rw,seclabel

查看host的mountinfo,可以看到/mntS1新建的子目录,而无法看到slave类型的/mntS2新建的子目录。即slave的mount umount事件是无法传递到master的

[root@host mntS2]# cat /proc/self/mountinfo |grep mnt
: / /mntS1 rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntS2 rw,relatime shared: - ext2 /dev/loop1 rw,seclabel
: / /mntS1/mntS1-sub rw,relatime shared: - ext2 /dev/loop2 rw,seclabel

在host的/mnt2目录下创建一个新的mount point,可以看到/mntS2/mntS2-sub2创建成功,并创建一个新的子peer group

[root@host /]# mkdir -p /mntS2/mntS2-sub2
[root@host /]# mount vdisk5 /mntS2/mntS2-sub2/
[root@host /]# cat /proc/self/mountinfo |grep mnt
: / /mntS1 rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntS2 rw,relatime shared: - ext2 /dev/loop1 rw,seclabel
: / /mntS1/mntS1-sub rw,relatime shared: - ext2 /dev/loop2 rw,seclabel
: / /mntS2/mntS2-sub2 rw,relatime shared: - ext2 /dev/loop4 rw,seclabel

在container中查看moutinfo,可以看到在host上新建的mount point,且为同一个peer group,ID为256。符合slave的定义,即slave的mount umount事件无法传递给master,但是反过来是可以的

[root@container /]# cat /proc/self/mountinfo |grep mnt
: / /mntS1 rw,relatime shared: - ext2 /dev/loop0 rw,seclabel
: / /mntS2 rw,relatime master: - ext2 /dev/loop1 rw,seclabel
: / /mntS1/mntS1-sub rw,relatime shared: - ext2 /dev/loop2 rw,seclabel
: / /mntS2/mntS2-sub rw,relatime - ext2 /dev/loop3 rw,seclabel
: / /mntS2/mntS2-sub2 rw,relatime master: - ext2 /dev/loop4 rw,seclabel
  • propagation类型使用mount --make-*时的转换规则如下
             make-shared   make-slave      make-priv  make-unbind
shared shared slave/priv [] priv unbind
slave slave+shared slave [] priv unbind
slave+shared slave+shared slave priv unbind
private shared priv [] priv unbind
unbindable shared unbind [] priv unbind

使用mount --bind时的转换规则如下

                            source(A)
shared private slave unbind
───────────────────────────────────────────────────────────────
dest(B) shared | shared shared slave+shared invalid
nonshared | shared private slave invalid

使用mount move时的转换规则如下:

                            source(A)
shared private slave unbind
──────────────────────────────────────────────────────────────────
dest(B) shared | shared shared slave+shared invalid
nonshared | shared private slave unbindable

  mount 命名空间通过隔离/proc/[pid]/mounts, /proc/[pid]/mountinfo, 和/proc/[pid]/mountstats文件,使得通过mount命令仅能查看到容器的挂载信息

UTS namespaces

用于在独立的namespace下设置系统hostname和NIS domain name。使用方式比较简单,其中hostname的使用参见上述mount namespace,使用unshare创建uts namespace时指定选项-u即可

Network namespaces

用于创建一个独立的网络namespace,其中包括独立的网卡,ip,掩码,路由,iptables等,主要通过ip link,ip netns,ip addr,iptables,ip route,bridge等命令实现,可以参见docker网络之bridge

由于网络命名空间的存在,容器中的网络信息和容器外面的网络信息并不相同,如使用ip,nstat,ss等命令查看网络信息时,实际读取的是/proc/net中的信息,从Linux 2.6.25开始,/proc/net会符号链接到/proc/self/net(如下图)。如果两个进程属于不同的命名空间,则它们的/proc/$pid/net的信息相同;如果两个进程属于相同的命名空间,则它们的/proc/$pid/net的信息相同。

容器的网络信息可以通过其对应的进程下面的/proc/$pid/net查看

除/proc/$pid/net目录外,网络命名空间还隔离了/sys/class/net,/proc/sys/net,socket等。

IPC namespaces

IPC用于进程间通信,常用到的方法有:信号量,共享内存,消息队列,系统文件和socket,前面四个主要用于本机上ipc通信,最后一个套接字可以支持不同主机间的ipc通信。IPC namespace用于隔离信号量,共享内存和消息队列(文件系统可以由mount namespace隔离,socket可以由network namespace隔离)

如下文件在不同IPC namespace中是不同的(其中消息队列有POSIX和system V两个标准)

*  POSIX消息队列接口 /proc/sys/fs/mqueue.
* System V IPC /proc/sys/kernel, namely: msgmax,msgmnb, msgmni, sem, shmall, shmmax, shmmni, and shm_rmid_forced.
* System V IPC /proc/sysvipc.

使用ipcs -l可知当前系统使用的消息队列是system V的

[root@host kernel]# ipcs -l

------ Messages Limits --------
max queues system wide =
max size of message (bytes) =
default max size of queue (bytes) = ------ Shared Memory Limits --------
max number of segments =
max seg size (kbytes) =
max total shared memory (kbytes) =
min seg size (bytes) = ------ Semaphore Limits --------
max number of arrays =
max semaphores per array =
max semaphores system wide =
max ops per semop call =
semaphore max value =
[root@host kernel]# cat /proc/sys/kernel/msg*
  • 在host上新创建一个消息队列和共享内存,查看结果如下(已删除不相关信息)
[root@host ~]# ipcmk -Q
Message queue id:
[root@host ~]# ipcmk -M
Shared memory id:
[root@host ~]# ipcs ------ Message Queues --------
key msqid owner perms used-bytes messages
0xc8232df1 root ------ Shared Memory Segments --------
key shmid owner perms bytes nattch status0xc932e3e5 root ------ Semaphore Arrays --------
key semid owner perms nsems
  • 新创建一个IPC namespace,查看ipc信息如下,可以发现在子IPC namespace下无法看到host的IPC信息。反过来在子IPC namespace下创建IPC,在host上也是看不到的
[root@host ~]# unshare -fui
[root@host ~]# hostname container
[root@host ~]# exec bash
[root@container ~]# ipcs ------ Message Queues --------
key msqid owner perms used-bytes messages ------ Shared Memory Segments --------
key shmid owner perms bytes nattch status ------ Semaphore Arrays --------
key semid owner perms nsems

IPC命名空间根据接口不同隔离了如下文件:

  1. 如果是POSIX,隔离/proc/sys/fs/mqueue
  2. 如果是System V IPC,隔离/proc/sysvipc;/proc/sys/kernel中的msgmax,msgmnb, msgmni, sem, shmall, shmmax, shmmni, 和shm_rmid_forced.

Cgroup namespaces

docker在1.8版本之后将分配给容器的cgroup挂载到了容器中。下例使用--memory选项限制容器使用的内存大小为200M(container ID=962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e )

# docker run --rm --memory=200M -it busybox:latest /bin/sh

在容器里面查看cgroup的mount的信息如下,容器962f63bbfbde的cgroup挂载点在host的/sys/fs/cgroup下面,采用的挂载propagation类型为MS_SLAVE

 # cat /proc/self/mountinfo |grep cgroup
: / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,seclabel,mode=
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/systemd ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd
: / /sys/fs/cgroup/rdma ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,rdma
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/net_cls,net_prio ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,net_cls,net_prio
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/perf_event ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,perf_event
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/pids ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,pids
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/hugetlb ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,hugetlb
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/cpuset ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,cpuset
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/cpu,cpuacct ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,cpu,cpuacct
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/blkio ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,blkio
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/devices ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,devices
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/freezer ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,freezer
: /docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e /sys/fs/cgroup/memory ro,nosuid,nodev,noexec,relatime master: - cgroup cgroup rw,seclabel,memory

在host上查看cgroup的mount,与容器中一一对应,如容器中的memory 的master:20对应host上的memory shared:20

  : / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd
: / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,rdma
: / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,net_cls,net_prio
: / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,perf_event
: / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,pids
: / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,hugetlb
: / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,cpuset
: / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,cpu,cpuacct
: / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,blkio
: / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,devices
: / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,freezer
: / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared: - cgroup cgroup rw,seclabel,memory

在host上查看memory限制如下,为200M

# pwd
/sys/fs/cgroup/memory/docker/962f63bbfbde77dff734d0c832b5538c5ac4df599825b78e22183cc75e87195e
# cat memory.limit_in_bytes

在container上查看memory限制如下,也为200M

# pwd
/sys/fs/cgroup/memory
# cat memory.limit_in_bytes

由于容器的/proc /sys /dev路径没有做绝对的隔离,会产生一些使用上的问题,如应用程序读取/proc/meminfo时显示的是host的内存信息,而不是cgroup限制的容器的内存信息。参见Docker容器实现原理及容器隔离性踩坑介绍

容器所在的cgroup可以通过容器的进程查看/proc/$pid/cgroup,进而直到其对应的cgroup所在目录

TIPS:

  • 在/proc/$pid/ns下有进程对应的namespace,可以使用nsenter命令查看,例如查看一个容器名为0f3005ba4d63(docker ps获得)中的进程对应的namespace,可以看到该进程对应的hostname与容器中的hostname是一致的(--preserve-credentials表示不修改namespace的gid和uid,参见nsenter
# nsenter --uts=uts --preserve-credentials hostname 0f3005ba4d63
  • 子pid namespace中的进程无法操作父pid namespace中的进程,但反过来是可以的。所有的线程必须在同一个namespace中
  • /proc/sys/user中定义了可创建的namespace的最大值
  • 可以使用如下方式查看系统是否开启namespace功能
$ uname -a
Linux . 4.19.-.el7.elrepo.x86_64 # SMP Mon Dec :: EST x86_64 x86_64 x86_64 GNU/Linux
[nfsnobody@ charlie]$ cat /boot/config-4.19.-.el7.elrepo.x86_64|grep CONFIG_USER_NS
CONFIG_USER_NS=y
  • kubernetes的pod中的container共用同一个network namespace,它使用docker的--net=container:id方式来加入已有的network namespace,可以通过查看容器进程的network namespace id来查看其关系,如/proc/$pid/ns

参考:

https://success.docker.com/article/introduction-to-user-namespaces-in-docker-engine

https://docs.docker.com/engine/security/userns-remap/#disable-namespace-remapping-for-a-container

https://success.docker.com/article/user-namespace-runtime-error

http://man7.org/linux/man-pages/man7/namespaces.7.html

http://man7.org/linux/man-pages/man7/pid_namespaces.7.html

http://man7.org/linux/man-pages/man1/unshare.1.html

http://man7.org/linux/man-pages/man7/mount_namespaces.7.html

http://man7.org/linux/man-pages/man7/user_namespaces.7.html

http://www.10tiao.com/html/606/201810/2664605819/1.html

https://segmentfault.com/a/1190000006899213

https://segmentfault.com/a/1190000006912742

https://segmentfault.com/a/1190000006913195

docker namespaces的更多相关文章

  1. docker – 你应该知道的10件事

      容器并不是一个全新的技术,但这并不妨碍Docker如风暴一样席卷整个世界. 如果你在IT圈里,你一定听说过Docker.就算与其他热门技术,如:Puppet/Chef,Hadoop或者MongoD ...

  2. 理解Docker(3):Docker 使用 Linux namespace 隔离容器的运行环境

    本系列文章将介绍Docker的有关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...

  3. 【新技术】Docker 学习笔记

    原文地址 一.Docker 简介 Docker 两个主要部件: Docker: 开源的容器虚拟化平台 Docker Hub: 用于分享.管理 Docker 容器的 Docker SaaS 平台 --  ...

  4. Docker的4种网络模式

    我们在使用docker run创建Docker容器时,可以用--net选项指定容器的网络模式,Docker有以下4种网络模式: · host模式,使用--net=host指定. · container ...

  5. docker好文收藏

    深入浅出Docker(一):Docker核心技术预览 2. 核心技术预览 Docker核心是一个操作系统级虚拟化方法, 理解起来可能并不像VM那样直观.我们从虚拟化方法的四个方面:隔离性.可配额/可度 ...

  6. docker 学习

    vim /usr/lib/systemd/system/docker.service ExecStart=/usr/bin/docker daemon --bip=172.18.42.1/16 --r ...

  7. Docker相关文档

    网上找到的一个入门级Docker学习笔记,写的不错,值得一看. 转自:http://www.open-open.com/lib/view/open1423703640748.html#articleH ...

  8. 关于docker容器是怎样建立新的namespace的。

    最近博客收到了一封交流的私信,感谢您的关注:现在就我理解的docker建立容器时namespace的建立问题做一个 个人的回答: 一,从原理角度来讲: docker创建container,说白了就是l ...

  9. docker 源码分析 六(基于1.8.2版本),Docker run启动过程

    上一篇大致了解了docker 容器的创建过程,其实主要还是从文件系统的视角分析了创建一个容器时需要得建立 RootFS,建立volumes等步骤:本章来分析一下建立好一个容器后,将这个容器运行起来的过 ...

随机推荐

  1. 运行Xcode时,提示:An error was encountered while running (Domain = FBSOpenApplicationErrorDomain, Code = 4)

    运行Xcode模拟器时,提示: An error was encountered while running (Domain = FBSOpenApplicationErrorDomain, Code ...

  2. javaweb获取项目路径的方法

    在jsp和class文件中调用的相对路径不同. 在jsp里,根目录是WebRoot 在class文件中,根目录是WebRoot/WEB-INF/classes 当然你也可以用System.getPro ...

  3. Trie树的数组实现原理

    Trie(Retrieval Tree)又称前缀树,可以用来保存多个字符串,并且非常便于查找.在trie中查找一个字符串的时间只取决于组成该串的字符数,与树的节点数无关.因此,它的查找速度通常比二叉搜 ...

  4. Codeforces Round #265 (Div. 2) E. Substitutes in Number

    http://codeforces.com/contest/465/problem/E 给定一个字符串,以及n个变换操作,将一个数字变成一个字符串,可能为空串,然后最后将字符串当成一个数,取模1e9+ ...

  5. python模块补充

    一.模块补充 configparser 1.基本的读取配置文件 -read(filename) 直接读取ini文件内容 -sections() 得到所有的section,并以列表的形式返回 -opti ...

  6. js-判断当前日期的天数

    <!DOCTYPE html><html> <head> <meta charset="utf-8" /> <title> ...

  7. excel查找某一列的值在、不在另一列中

    统计中遇到找出一列的值不在另一列的需求: 找出A列中不在B列的值 方法如下: 使用countif函数 比如找出A列中不在B列的值: 在C1中输入 COUNTIF(B:B,A1) 下拉单元格,在首行添加 ...

  8. StringBuffer 去掉最后一个字符

    StringBuffer stringBuffer=new StringBuffer (); stringBuffer.append("aaa,"); stringBuffer.d ...

  9. RxSwift学习笔记3:生命周期/订阅

    有了 Observable,我们还要使用 subscribe() 方法来订阅它,接收它发出的 Event. let observal = Observable.of("a",&qu ...

  10. XE7 & FMX 那些年我们一起上过的控件:ListView 之 (3) 加载数据时如何显示自定义样式

    本文介绍一下ListView下如何加载数据.及使用进度条反馈当前进度给用户. 注意: 原创作品,请尊重作者劳动成果,转载请注明出处!!!原文永久固定地址:http://www.cnblogs.com/ ...