理解docker [二] - namespace

本文详细介绍了Linux的Namespace机制,作为Docker容器隔离的基础。Namespace提供了包括mount、IPC、UTS、PID、network和user等多种维度的隔离,使得每个容器拥有独立的视图。文章解释了各类型Namespace的工作原理,如mount Namespace限制了文件系统的挂载操作,IPC Namespace确保进程间通信的隔离,网络Namespace允许独立的网络设备和配置。此外,还提到了如何通过clone()和unshare()系统调用来创建和脱离Namespace。
摘要由CSDN通过智能技术生成

说明:本文所使用的图片主要来自于Gao Feng@fujitsu的Lightweight virtual system mechanism。

虚幻世界- Namespace

"namespace"通常被翻译为「命名空间」,听起来好像比较抽象,其实重点是在这个"space"。它和描述进程的虚拟地址空间的address space一样,都是提供一种独占的视角(假象)。只是address space针对的是进程的地址,而namespace针对的是docker container,且维度更多,但它们的目的都是一样的:隔离,以尽可能地减少互相的干扰和影响。

【多维时空】

目前,Linux可以为docker提供的namespace种类包括文件系统mount、进程间通信、网络等,它们其实大都在内核2.6.24前就已经完成了。所以啊,docker所依赖的Linux的这些机制并不新鲜,但它通过一种新的需求,让这些沉淀多年的模块又重新焕发了生机。

文件系统 - Mount

那么多namespace,从哪个介绍起呢。按照先来后到的顺序,先来说说在内核2.4.19时代就出现的mount namespace吧(也许叫做"mount space"更好理解)。其实现原理大致是在前面介绍过的bind mount的基础上,又增加了mount propagation的特性。

在新的mount space中,进程对文件系统的mount/unmount操作都被“隐藏”了起来,对其他space不可见,也不会影响到其他的space。之后介绍了创建namespace的方法后,将通过一个小实验来印证这一点。

进程间通信 - IPC

接下来出场的是在2.6.19版本诞生的IPC namespace,用上这个space之后,进程间的通信(Inter Process Communication)就被限定在了同一个space内部,即一个container中的某个进程只能和同一container中的其他进程通信,container外部的进程对它来说好像不存在一样,因为它根本看不到。

主机域名 - UTS

由于多个container是共享OS内核的,因而像UTS里的os type和os release等信息是不可能更改的,但是每个container可以有自己独立的host name和domain name,以便于标识和区分(比如可以通过主机名来访问网络中的机器),这就是UTS namespace的作用。

进程编号 - PID

当你启动了多个container,然后在每个container内部用"ps"命令看一下,你会发现它们都有一个PID为1的进程。要知道,在一个Linux系统中,应该只有一个1号进程(以前是SysVinit,现在是systemd)。

想想Linux上物理地址也是唯一的,但不同的进程不是可以有相同的虚拟地址嘛,只要通过page table映射一下就可以了。同样地,这些container内部PID为1的进程,也会映射到host上其对应的真实的PID号上。从Linux host的视角,这都是些普通进程,但在container内部,它们却有了特殊的地位和意义。

那如何知道这种映射的对应关系呢?一个办法是在"/proc/<pid>/status"中找到"NSpid"后面有两列的item:

网络设备 - network

然后来到2.6.24时代,出现了针对网络的namespace。在每个network space内部,可以有独立的网络设备(虚拟的或者真实的)、IP地址、路由表和防火墙规则等。其中的应用所bind的端口也是per-namespace的,比如http默认使用的是80端口,使用network space后,同一host上的各个container内部就都可以运行各自的web server。

用户控制 - User

在host上全局的user id(uid)和group id(gid),到了user namespace内部就分别被映射成了各自的kuid和kgid。这样做有什么用呢?比如在container内部是root用户(kuid为0),可以「为所欲为」,但是在container外部,就是一个普通的unprivileged user(uid非0)。

看完以上这六种给container带来虚幻错觉的花招,不难发现,其实它们要达到的效果都是差不多的,就是在不同的space里面,可以有相同的name,不会重名,只能看见和使用与该namespace相关的资源。这种isolation,是一种软件层面的隔离,而不是物理上「泾渭分明」的隔离。

随着cgroup namespace(4.6版本引入)和time namespace的新鲜出炉,截止内核5.6版本,Linux支持的namespace已经多达8种。也许以后namespace的种类还会继续增加,以提供更多的隔离性选择(比如正在proposal阶段的syslog namespace)。在使用container的时候,也并不是所有namepsace都必须要有,可以根据需要选择其中的几个。

【自立门户】

同挂在sysfs中,依靠mkdir/rmdir/mv等基础的文件操作命令来新建/销毁/移动的cgroup不同,创建一个namespace需要使用clone()系统调用,namespace的种类由标志位"CLONE_NEWxxx"来决定。

clone()不是用于创建新的进程/线程的函数吗?没错,与fork()相比,clone()可以更细粒度地控制与父进程共享哪些资源,因此也就可以用于选择是否与父进程共用namespace。

就像fork()可以搭配"unshare"来实现和clone同样的效果,对于一个既有的进程(创建阶段没有脱离父进程的namespace),也还是有第二次“分家的”机会,就是通过"unshare"来创建新的namespace(借助这个小实验,可以更好地体会"unshare"和mount namespace的用法及其意义)。

【改弦更张】

那如果一个进程并不想开宗立派,只是想加入一个既有的namespace呢?在Linux中,进程的相关信息一般是记录在procfs里,namspace也不例外,在"/proc/<pid>/ns"中,通过symbol link的形式,给出了该进程所属的各个namespace的编号。

namespace也可被抽象为一个广义上的文件,因此这个编号其实就是文件的inode号。如果两个进程的某一ns指向的inode号相同,说明它们在这个ns对应的属性上,是属于同一namespace的。

当进程通过open()操作打开ns对应的文件,就获得了一个"fd",接下来再使用setns(),就可以加入这个namespace:

intsetns(intfd,intnstype);

参考:

LWN - Namespaces in operation

Wiki - Linux namespaces

LWN - Namespace file descriptors

Mount Namespace 在 Kernel 里是怎么实现的

原文链接:https://zhuanlan.zhihu.com/p/149886216

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值