乐趣区

关于docker:搞懂容器技术的基石-namespace-上

大家好,我是张晋涛。

目前咱们所提到的容器技术、虚拟化技术(不管何种抽象层次下的虚拟化技术)都能做到资源层面上的隔离和限度。

对于容器技术而言,它实现资源层面上的限度和隔离,依赖于 Linux 内核所提供的 cgroup 和 namespace 技术。

咱们先对这两项技术的作用做个概括:

  • cgroup 的次要作用:治理资源的调配、限度;
  • namespace 的次要作用:封装形象,限度,隔离,使命名空间内的过程看起来领有他们本人的全局资源;

在上一篇文章中,咱们重点聊了 cgroup。本篇,咱们重点来聊 namespace。

Namespace 是什么?

咱们援用 wiki 上对 namespace 的定义:

Namespaces are a feature of the Linux kernel that partitions kernel resources such that one set of processes sees one set of resources while another set of processes sees a different set of resources. The feature works by having the same namespace for a set of resources and processes, but those namespaces refer to distinct resources.

namespace 是 Linux 内核的一项个性,它能够对内核资源进行分区,使得一组过程能够看到一组资源;而另一组过程能够看到另一组不同的资源。该性能的原理是为一组资源和过程应用雷同的 namespace,然而这些 namespace 实际上援用的是不同的资源。

这样的说法未免太绕了些,简略来说 namespace 是由 Linux 内核提供的,用于过程间资源隔离的一种技术。将全局的系统资源包装在一个形象里,让过程(看起来)领有独立的全局资源实例。同时 Linux 也默认提供了多种 namespace,用于对多种不同资源进行隔离。

在之前,咱们独自应用 namespace 的场景比拟无限,但 namespace 却是容器化技术的基石。

咱们先来看看它的倒退历程。

Namespace 的倒退历程

图 1,namespace 的历史过程

最晚期 – Plan 9

namespace 的晚期提出及应用要追溯到 Plan 9 from Bell Labs,贝尔实验室的 Plan 9。这是一个分布式操作系统,由贝尔实验室的计算科学研究核心在八几年至 02 年开发的(02 年公布了稳固的第四版,间隔 92 年公布的第一个公开版本已 10 年打磨),当初依然被操作系统的研究者和爱好者开发应用。在 Plan 9 的设计与实现中,咱们着重提以下 3 点内容:

  • 文件系统:所有系统资源都列在文件系统中,以 Node 标识。所有的接口也作为文件系统的一部分出现。
  • Namespace:能更好的利用及展现文件系统的层次结构,它实现了所谓的“拆散”和“独立”。
  • 规范通信协议:9P 协定(Styx/9P2000)。

图 2,Plan 9 from Bell Labs 图标

开始退出 Linux Kernel

Namespace 开始进入 Linux Kernel 的版本是在 2.4.X,最后始于 2.4.19 版本。然而,自 2.4.2 版本才开始实现每个过程的 namespace。

图 3,Linux Kernel Note

图 4,Linux Kernel 对应的各操作系统版本

Linux 3.8 根本实现

Linux 3.8 中终于齐全实现了 User Namespace 的相干性能集成到内核。这样 Docker 及其他容器技术所用到的 namespace 相干的能力就根本都实现了。

图 5,Linux Kernel 从 2001 到 2013 逐渐演变,实现了 namespace 的实现

Namespace 类型

namespace 名称 应用的标识 – Flag 管制内容
Cgroup CLONE_NEWCGROUP Cgroup root directory cgroup 根目录
IPC CLONE_NEWIPC System V IPC, POSIX message queues 信号量,音讯队列
Network CLONE_NEWNET Network devices, stacks, ports, etc. 网络设备,协定栈,端口等等
Mount CLONE_NEWNS Mount points 挂载点
PID CLONE_NEWPID Process IDs 过程号
Time CLONE_NEWTIME 时钟
User CLONE_NEWUSER 用户和组 ID
UTS CLONE_NEWUTS 零碎主机名和 NIS(Network Information Service) 主机名(有时称为域名)

Cgroup namespaces

Cgroup namespace 是过程的 cgroups 的虚拟化视图,通过 /proc/[pid]/cgroup/proc/[pid]/mountinfo 展现。

应用 cgroup namespace 须要内核开启 CONFIG_CGROUPS 选项。可通过以下形式验证:

(MoeLove) ➜ grep CONFIG_CGROUPS /boot/config-$(uname -r)
CONFIG_CGROUPS=y

cgroup namespace 提供的了一系列的隔离反对:

  • 避免信息透露(容器不应该看到容器外的任何信息)。
  • 简化了容器迁徙。
  • 限度容器过程资源,因为它会把 cgroup 文件系统进行挂载,使得容器过程无奈获取下层的拜访权限。

每个 cgroup namespace 都有本人的一组 cgroup 根目录。这些 cgroup 的根目录是在 /proc/[pid]/cgroup 文件中对应记录的绝对地位的基点。当一个过程用 CLONE_NEWCGROUP(clone(2) 或者 unshare(2))创立一个新的 cgroup namespace 时,它以后的 cgroups 的目录就变成了新 namespace 的 cgroup 根目录。

(MoeLove) ➜ cat /proc/self/cgroup 
0::/user.slice/user-1000.slice/session-2.scope

当一个指标过程从 /proc/[pid]/cgroup 中读取 cgroup 关系时,每个记录的路径名会在第三字段中展现,会关联到正在读取的过程的相干 cgroup 分层构造的根目录。如果指标过程的 cgroup 目录位于正在读取的过程的 cgroup namespace 根目录之外时,那么,门路名称将会对每个 cgroup 档次中的下层节点显示 ../

咱们来看看上面的示例(这里以 cgroup v1 为例,如果你想看 v2 版本的示例,请在留言中通知我):

  1. 在初始的 cgroup namespace 中,咱们应用 root(或者有 root 权限的用户),在 freezer 层下创立一个子 cgroup 名为 moelove-sub,同时,将过程放入该 cgroup 进行限度。
(MoeLove) ➜ mkdir -p /sys/fs/cgroup/freezer/moelove-sub
(MoeLove) ➜ sleep 6666666 & 
[1] 1489125                  
(MoeLove) ➜ echo 1489125 > /sys/fs/cgroup/freezer/moelove-sub/cgroup.procs
  1. 咱们在 freezer 层下创立另外一个子 cgroup,名为 moelove-sub2,并且再放入执行过程号。能够看到以后的过程曾经纳入到 moelove-sub2的 cgroup 下治理了。
(MoeLove) ➜ mkdir -p /sys/fs/cgroup/freezer/moelove-sub2
(MoeLove) ➜ echo $$
1488899
(MoeLove) ➜ echo 1488899 > /sys/fs/cgroup/freezer/moelove-sub2/cgroup.procs 
(MoeLove) ➜ cat /proc/self/cgroup |grep freezer
7:freezer:/moelove-sub2
  1. 咱们应用 unshare(1) 创立一个过程,这里应用了 -C参数示意是新的 cgroup namespace,应用了 -m参数示意是新的 mount namespace。
(MoeLove) ➜ unshare -Cm bash
root@moelove:~#
  1. 从用 unshare(1) 启动的新 shell 中,咱们能够在 /proc/[pid]/cgroup 文件中看到,新 shell 和以上示例中的过程:
root@moelove:~# cat /proc/self/cgroup | grep freezer
7:freezer:/
root@moelove:~# cat /proc/1/cgroup | grep freezer
7:freezer:/..

# 第一个示例过程
root@moelove:~# cat /proc/1489125/cgroup | grep freezer
7:freezer:/../moelove-sub
  1. 从下面的示例中,咱们能够看到新 shell 的 freezer cgroup 关系中,当新的 cgroup namespace 创立时,freezer cgroup 的根目录与它的关系也就建设了。
root@moelove:~# cat /proc/self/mountinfo | grep freezer
1238 1230 0:37 /.. /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer
  1. 第四个字段 (/..) 显示了在 cgroup 文件系统中的挂载目录。从 cgroup namespaces 的定义中,咱们能够晓得,过程以后的 freezer cgroup 目录变成了它的根目录,所以这个字段显示 /..。咱们能够从新挂载来解决它。
root@moelove:~# mount --make-rslave /
root@moelove:~# umount /sys/fs/cgroup/freezer
root@moelove:~# mount -t cgroup -o freezer freezer /sys/fs/cgroup/freezer
root@moelove:~# cat /proc/self/mountinfo | grep freezer
1238 1230 0:37 / /sys/fs/cgroup/freezer rw,relatime - cgroup freezer rw,freezer
root@moelove:~# mount |grep freezer
freezer on /sys/fs/cgroup/freezer type cgroup (rw,relatime,freezer)

IPC namespaces

IPC namespaces 隔离了 IPC 资源,如 System V IPC objects、POSIX message queues。每个 IPC namespace 都有着本人的一组 System V IPC 标识符,以及 POSIX 音讯队列零碎。在一个 IPC namespace 中创立的对象,对所有该 namespace 下的成员均可见(对其余 namespace 下的成员均不可见)。

应用 IPC namespace 须要内核反对 CONFIG_IPC_NS 选项。如下:

(MoeLove) ➜ grep CONFIG_IPC_NS /boot/config-$(uname -r)
CONFIG_IPC_NS=y

能够在 IPC namespace 中设置以下 /proc 接口:

  • /proc/sys/fs/mqueue – POSIX 音讯队列接口
  • /proc/sys/kernel – System V IPC 接口(msgmax, msgmnb, msgmni, sem, shmall, shmmax, shmmni, shm_rmid_forced)
  • /proc/sysvipc – System V IPC 接口

当 IPC namespace 被销毁时(空间里的最初一个过程都被进行删除时),在 IPC namespace 中创立的 object 也会被销毁。

Network namepaces

Network namespaces 隔离了与网络相干的系统资源(这里列举一些):

  • network devices – 网络设备
  • IPv4 and IPv6 protocol stacks – IPv4、IPv6 的协定栈
  • IP routing tables – IP 路由表
  • firewall rules – 防火墙规定
  • /proc/net(即 /proc/PID/net)
  • /sys/class/net
  • /proc/sys/net 目录下的文件
  • 端口、socket
  • UNIX domain abstract socket namespace

应用 Network namespaces 须要内核反对 CONFIG_NET_NS 选项。如下:

(MoeLove) ➜ grep CONFIG_NET_NS /boot/config-$(uname -r)
CONFIG_NET_NS=y

一个物理网络设备只能存在于一个 Network namespace 中。当一个 Network namespace 被开释时(空间里的最初一个过程都被进行删除时),物理网络设备将被挪动到初始的 Network namespace 而不是下层的 Network namespace。

一个虚构的网络设备(veth(4)),在 Network namespace 间通过一个相似管道的形式进行连贯。这使得它能存在于多个 Network namespace,然而,当 Network namespace 被捣毁时,该空间下蕴含的 veth(4) 设施可能被毁坏。

Mount namespaces

Mount namespaces 最早呈现在 Linux 2.4.19 版本。Mount namespaces 隔离了各空间中挂载的过程实例。每个 mount namespace 的实例下的过程会看到不同的目录层次结构。

每个过程在 mount namespace 中的形容能够在上面的文件视图中看到:

  • /proc/[pid]/mounts
  • /proc/[pid]/mountinfo
  • /proc/[pid]/mountstats

一个新的 Mount namespace 的创立标识是 CLONE_NEWNS,应用了 clone(2) 或者 unshare(2)。

  • 如果 Mount namespace 用 clone(2) 创立,子 namespace 的挂载列表是从父过程的 mount namespace 拷贝的。
  • 如果 Mount namespace 用 unshare(2) 创立,新 namespace 的挂载列表是从调用者之前的 moun namespace 拷贝的。

如果 mount namespace 产生了批改,会引起什么样的连锁反应?上面,咱们就在 共享子树 中谈谈。

每个 mount 都被能够有如下标记:

  • MS_SHARED – 与组内每个成员分享 events。也就是说雷同的 mount 或者 unmount 将主动产生在组内其余的 mounts 中。反之,mount 或者 unmount 事件 也会影响这次的 event 动作。
  • MS_PRIVATE – 这个 mount 是公有的。mount 或者 unmount events 都不会影响这次的 event 动作。
  • MS_SLAVE – mount 或者 unmount events 会从 master 节点传入影响该节点。然而这个节点下的 mount 或者 unmount events 不会影响组内的其余节点。
  • MS_UNBINDABLE – 这也是个公有的 mount。任何尝试绑定的 mount 在这个设置下都将失败。

在文件 /proc/[pid]/mountinfo 中能够看到 propagation 类型的字段。每个对等组都会由内核生成惟一的 ID,同一对等组的 mount 都是这个 ID(即,下文中的 X)。

(MoeLove) ➜ cat /proc/self/mountinfo  |grep root  
65 1 0:33 /root / rw,relatime shared:1 - btrfs /dev/nvme0n1p6 rw,seclabel,compress=zstd:1,ssd,space_cache,subvolid=256,subvol=/root
1210 65 0:33 /root/var/lib/docker/btrfs /var/lib/docker/btrfs rw,relatime shared:1 - btrfs /dev/nvme0n1p6 rw,seclabel,compress=zstd:1,ssd,space_cache,subvolid=256,subvol=/root
  • shared:X – 在组 X 中共享。
  • master:X – 对于组 X 而言是 slave,即,从属于 ID 为 X 的主。
  • propagate_from:X – 接管从组 X 收回的共享 mount。这个标签总是个 master:X 一起呈现。
  • unbindable – 示意不能被绑定,即,不与其余关联隶属。

新 mount namespace 的流传类型取决于它的父节点。如果父节点的流传类型是 MS_SHARED,那么新 mount namespace 的流传类型是 MS_SHARED,不然会默认为 MS_PRIVATE。

对于 mount namespaces 咱们还须要留神以下几点:

(1)每个 mount namespace 都有一个 owner user namespace。如果新的 mount namespace 和拷贝的 mount namespace 分属于不同的 user namespace,那么,新的 mount namespace 优先级低。

(2)当创立的 mount namespace 优先级低时,那么,slave 的 mount events 会优先于 shared 的 mount events。

(3)高优先级和低优先级的 mount namespace 有关联被锁定在一起时,他们都不能被独自卸载。

(4)mount(2) 标识和 atime 标识会被锁定,即,不能被流传影响而批改。

小结

以上就是对于 Linux 内核中 namespace 的一些介绍了,篇幅起因,残余局部以及 namespace 在容器中的利用咱们放在下一篇中介绍,敬请期待!


欢送订阅我的文章公众号【MoeLove】

退出移动版