大家好,我是张锦涛。至此我们所提到的容器技术和虚拟化技术(无论任何抽象层次的虚拟化技术)都可以实现资源层面的隔离和限制。对于容器技术,它实现了资源级别的限制和隔离,依赖于Linux内核提供的cgroup和namespace技术。我们先总结一下这两种技术的功能:cgroup的主要功能:管理资源的分配和限制;命名空间的主要作用:封装抽象、限制、隔离,让命名空间中的进程好像拥有自己的全局资源;在上一篇文章中,我们重点介绍了cgroups。在本文中,我们关注名称空间。什么是命名空间?我们引用wiki上命名空间的定义:命名空间是Linux内核的一个特性,它对内核资源进行分区,使得一组进程看到一组资源,而另一组进程看到另一组资源。该功能通过为一组资源和进程使用相同的命名空间来工作,但这些命名空间引用不同的资源。命名空间是Linux内核的一个特性,它对内核资源进行分区,使得一组进程可以看到一组资源;另一组资源一个进程可以看到一组不同的资源。该特性的原理是对一组资源和进程使用同一个命名空间,但这些命名空间实际上指的是不同的资源。这样的说法未免太绕口了。简单的说,命名空间是Linux内核提供的,是一种进程间资源隔离的技术。将全局系统资源包装在一个抽象中,使进程(看起来)有一个独立的全局资源实例。同时,Linux还默认提供了多种命名空间来隔离各种不同的资源。以往我们单独使用命名空间的场景比较有限,但命名空间是容器化技术的基石。让我们来看看它的发展历程。Namespace的发展历程图1、命名空间最早的历史进程——Plan9命名空间的早期提出和使用可以追溯到贝尔实验室的Plan9,贝尔实验室的Plan9。这是贝尔实验室计算科学研究中心于1980年至2002年开发的分布式操作系统(2002年发布稳定版第四版,在1992年发布第一个公开版打磨后的10年),目前仍在开发和使用中由操作系统研究人员和爱好者。在Plan9的设计和实现中,我们重点关注以下三点:文件系统:所有系统资源都列在文件系统中,由Node.js标识。所有接口也作为文件系统的一部分公开。命名空间:可以更好地应用和展示文件系统的层次结构,实现了所谓的“分离”和“独立”。标准通讯协议:9P协议(Styx/9P2000)。图2,来自贝尔实验室的Plan9图标开始加入LinuxKernelNamespace开始进入LinuxKernel的版本是2.4.X,最初是从2.4.19版本开始的。但是,从2.4.2版开始才实现了每个进程的命名空间。图3、LinuxKernelNote图4、各操作系统版本对应的LinuxKernelLinux3.8基本实现在Linux3.8中,UserNamespace的相关功能终于完全实现并集成到内核中。这样就基本实现了Docker等容器技术所使用的命名空间相关的能力。图5,LinuxKernel从2001年到2013年逐步演进,完成了namespace的实现。Namespacetypenamespacenameusedbyflag-Flag控制内容CgroupCLONE_NEWCGROUPCgroup根目录cgroup根目录IPCCLONE_NEWIPCSystemVIPC,POSIXmessagequeuessemaphore,messagequeueNetworkCLONE_NEWNETNetworkdevices,stack,ports等网络设备,协议栈,ports等MountCLONE_NEWNS挂载点挂载点PIDCLONE_NEWPIDProcessIDs进程IDTimeCLONE_NEWTIME时钟UserCLONE_NEWUSER用户和组IDUTSCLONE_NEWUTS系统主机名和NIS(网络信息服务)主机名(有时称为域名)Cgroup命名空间Cgroup命名空间是进程的cgroups的虚拟化视图,显示在/proc/[pid]/cgroup和/proc/[pid]/mountinfo。使用cgroup命名空间需要内核启用CONFIG_CGROUPS选项。可以通过:(萌爱)?grepCONFIG_CGROUPS/boot/config-$(uname-r)CONFIG_CGROUPS=ycgroup命名空间提供了一系列的隔离支持:防止信息泄露(容器不应该看到容器外的任何信息)。简化容器迁移。限制容器进程资源,因为它会挂载cgroup文件系统,使容器进程无法获取到上层的访问权限。每个cgroup命名空间都有自己的一组cgroup根。这些cgroups的root就是/proc/[pid]/cgroup文件中相应记录相对位置的基点。当进程使用CLONE_NEWCGROUP(clone(2)或unshare(2))创建新的cgroup命名空间时,其当前cgroups目录将成为新命名空间的cgroup根目录。(MoeLove)?cat/proc/self/cgroup0::/user.slice/user-1000.slice/session-2.scope当目标进程从/proc/[pid]/cgroup读取cgroup关系时,第三个字段中显示的每个记录都与正在读取的进程的关联cgroup层次结构的根相关。如果目标进程的cgroup目录位于正在读取的进程的cgroup命名空间的根目录之外,则路径名将显示../对于每个cgroup层次结构中的上层节点。我们看下面的例子(这里以cgroupv1为例,如果想看v2版本的例子,请留言告诉我):在初始的cgroup命名空间中,我们使用root(或者aroot权限的用户),在freezer层下创建一个名为moelove-sub的子cgroup,将进程放入这个cgroup进行限制。(MoeLove)?mkdir-p/sys/fs/cgroup/freezer/moelove-sub(MoeLove)?sleep6666666&[1]1489125(MoeLove)?echo1489125>/sys/fs/cgroup/freezer/moelove-sub/cgroup.procs我们在freezer层下再创建一个子cgroup,命名为moelove-sub2,放入执行进程号。可以看到当前进程已经被纳入到moelove-sub2的cgroup中进行管理。(MoeLove)?mkdir-p/sys/fs/cgroup/freezer/moelove-sub2(Moelove)?echo$$1488899(MoeLove)?echo1488899>/sys/fs/cgroup/freezer/moelove-sub2/cgroup.procs(MoeLove)?cat/proc/self/cgroup|grepfreezer7:freezer:/moelove-sub2我们使用unshare(1)创建一个进程,这里我们使用-C参数表示是一个新的cgroup命名空间,并使用-m参数表明它是新的挂载命名空间。(MoeLove)?unshare-Cmbashroot@moelove:~#从unshare(1)开始的新shell,我们可以在/proc/[pid]/cgroup文件中看到,新shell和上面例子中的进程:root@moelove:~#cat/proc/self/cgroup|grepfreezer7:freezer:/root@moelove:~#cat/proc/1/cgroup|目录grepfreezer7:freezer:/..#第一个示例进程root@moelove:~#cat/proc/1489125/cgroup|grepfreezer7:freezer:/../moelove-sub从上面的例子我们可以看出,在新shell的freezercgroup关系中,当新建一个cgroup命名空间时,freezercgroup的根目录与它也成立。root@moelove:~#cat/proc/self/mountinfo|grepfreezer123812300:37/../sys/fs/cgroup/freezerrw,nosuid,nodev,noexec,relatime-cgroupcgrouprw,freezer的第四个字段(/..)显示了cgroup文件系统中挂载的目录。从cgroupnamespaces的定义可以知道,进程当前的freezercgroup目录成为了它的根目录,所以该字段显示/..。我们可以重新安装来处理它。root@moelove:~#mount--make-rslave/root@moelove:~#umount/sys/fs/cgroup/freezerroot@moelove:~#mount-tcgroup-ofreezerfreezer/sys/fs/cgroup/freezerroot@moelove:~#cat/proc/self/mountinfo|grepfreezer123812300:37//sys/fs/cgroup/freezerrw,relatime-cgroupfreezerrw,freezerroot@moelove:~#mount|grepfreezerfreezeron/sys/fs/cgroup/freezertypecgroup(rw,relatime,freezer)IPC命名空间IPC命名空间隔离IPC资源,例如SystemVIPC对象、POSIX消息队列。每个IPC名称空间都有自己的一组SystemVIPC标识符和POSIX消息队列系统。在IPC命名空间中创建的对象对该命名空间的所有成员可见(对其他命名空间的成员不可见)。使用IPC命名空间需要内核支持CONFIG_IPC_NS选项。如下:(MoeLove)?grepCONFIG_IPC_NS/boot/config-$(uname-r)CONFIG_IPC_NS=yIPC命名空间可以设置如下/proc接口:/proc/sys/fs/mqueue-POSIX消息队列接口/proc/sys/kernel-SystemVIPC接口(msgmax,msgmnb,msgmni,sem,shmall,shmmax,shmmni,shm_rmid_forced)/proc/sysvipc-SystemVIPC接口当IPC命名空间被销毁时(空间中的最后一个进程stoppedanddeleted),在IPC命名空间中创建的对象也将被销毁。网络命名空间网络命名空间隔离网络相关的系统资源(这里列举一些):网络设备-网络设备IPv4和IPv6协议栈-IPv4、IPv6协议栈IP路由表-IP路由表防火墙规则-防火墙规则/proc/net(即/proc/PID/net)/sys/class/net/proc/sys/net目录文件端口,socketUNIX域抽象套接字命名空间使用网络命名空间需要内核支持CONFIG_NET_NS选项。如下:(MoeLove)?grepCONFIG_NET_NS/boot/config-$(uname-r)CONFIG_NET_NS=y一个物理网络设备只能存在于一个Networknamespace中。当一个Network命名空间被释放时(当空间中的最后一个进程被停止并删除时),物理网络设备将被移动到初始的Network命名空间而不是上层的Network命名空间。虚拟网络设备(veth(4))以类似管道的方式连接在网络命名空间之间。这允许它存在于多个网络名称空间中,但是,当网络名称空间被销毁时,包含在该空间下的veth(4)设备可能会被销毁。MountnamespacesMountnamespaces最早出现在Linux2.4.19版本。Mountnamespaces隔离了每个空间中挂载的进程实例。每个挂载命名空间实例下的进程将看到不同的目录层次结构。挂载命名空间中各个进程的描述可以在下面的文件视图中看到:/proc/[pid]/mounts/proc/[pid]/mountinfo/proc/[pid]/mountstats新建挂载命名空间的Flag是CLONE_NEWNS,使用clone(2)或unshare(2)。如果挂载命名空间是使用clone(2)创建的,则子命名空间的挂载列表将从父命名空间的挂载命名空间复制。如果Mount名称空间是使用unshare(2)创建的,则新名称空间的安装列表将从调用者之前的安装名称空间中复制。如果修改了挂载命名空间,会引起什么样的连锁反应?接下来,让我们谈谈共享子树。每个坐骑都可以用以下标记:MS_SHARED-与组中的每个人共享事件。也就是说,相同的挂载或卸载将自动发生在组中的其他挂载上。反之,mount或unmount事件也会影响这个事件动作。MS_PRIVATE-挂载是私有的。安装或卸载事件不会影响此事件操作。MS_SLAVE-来自主节点的挂载或卸载事件影响该节点。但该节点下的mount或unmount事件不会影响组内的其他节点。MS_UNBINDABLE-这也是一个私有挂载。任何尝试绑定的挂载都将因该设置而失败。类型传播的字段可以在文件/proc/[pid]/mountinfo中看到。每个peergroup都会有一个内核生成的唯一ID,同一个peergroup的mount都会有这个ID(也就是下面的X)。(MoeLove)?cat/proc/self/mountinfo|greproot6510:33/root/rw,relatimeshared:1-btrfs/dev/nvme0n1p6rw,seclabel,compress=zstd:1,ssd,space_cache,subvolid=256,subvol=/root1210650:33/root/var/lib/docker/btrfs/var/lib/docker/btrfsrw,relatime共享:1-btrfs/dev/nvme0n1p6rw,seclabel,compress=zstd:1,ssd,space_cache,subvolid=256,subvol=/rootshared:X-在组X中共享。master:X-组X的从属,即从属于IDX的主。propagate_from:X-从组X接收共享安装。此标签总是和master:X一起出现。unbindable——表示不能绑定,即不依赖于其他关联。新挂载命名空间的传播类型取决于其父节点。如果父节点的传播类型为MS_SHARED,则新挂载命名空间的传播类型为MS_SHARED,否则默认为MS_PRIVATE。关于挂载命名空间,我们还需要注意以下几点:(1)每个挂载命名空间都有一个所有者用户命名空间。如果新挂载命名空间和复制的挂载命名空间属于不同的用户命名空间,则新挂载命名空间的优先级较低。(2)当创建的mountnamespace的优先级较低时,slave的mount事件将优先于shared的mount事件。(3)当高优先级和低优先级mountnamespace被锁在一起时,无法单独卸载。(4)mount(2)标志和atime标志将被锁定,即它们不能被传播效果修改。小结以上是Linux内核中关于命名空间的一些介绍。由于篇幅原因,我们将在下一篇文章中介绍其余部分以及命名空间在容器中的应用,敬请期待!欢迎订阅我的文章公众号【MoeLove】
