namespace
参考
- coolshell.cn/articles/17…
- coolshell.cn/articles/17…
- www.infoq.com/cn/articles…
- www.ibm.com/developerwo…
- yeasy.gitbooks.io/docker_prac…
- coolshell.cn/articles/17…
- www.infoq.com/cn/articles…
- www.infoq.com/cn/articles…
- www.infoq.com/cn/articles…
- lecury.cn/linux_names…
- lwn.net/Articles/53…
- coolshell.cn/articles/17…
- www.cnblogs.com/caoxiaojian…
- blog.csdn.net/zhangyifei2…
简介
Linux Namespace是Linux提供的一种内核级别环境隔离的方法。
提供了对UTS、IPC、mount、PID、network、User等的隔离机制。
分类
分类 | 系统调用参数 | 相关内核版本 | 隔离内容 | |
---|---|---|---|---|
Mount namespaces | CLONE_NEWNS | Linux 2.4.19 | 挂载点(文件系统) | |
UTS namespaces | CLONE_NEWUTS | Linux 2.6.19 | 主机名与域名,影响uname(hostname, domainname) | |
IPC namespaces | CLONE_NEWIPC | Linux 2.6.19 | 信号量、消息队列和共享内存, inter-process communication,有全局id | |
PID namespaces | CLONE_NEWPID | Linux 2.6.24 | 进程编号 | |
Network namespaces | CLONE_NEWNET | 始于Linux 2.6.24 完成于 Linux 2.6.29 | 网络设备、网络栈、端口等等 | |
User namespaces | CLONE_NEWUSER | 始于 Linux 2.6.23 完成于 Linux 3.8) | 用户和用户组 |
三个系统调用
调用 | 作用 |
---|---|
clone() | 实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述参数达到隔离。 |
unshare() | 使某进程脱离某个namespace |
setns() | 把某进程加入到某个namespace |
详解
测试代码
1 | 复制代码#define _GNU_SOURCE |
UTS Namespace
1 | 复制代码加入 |
IPC Namespace
1 | 复制代码 |
PID Namespace
1 | 复制代码 int container_pid = clone(container_main, container_stack+STACK_SIZE, |
pid 1 是一个特殊的pid需要有进程监控和资源回收的能力, docker 1.13 引入了一个 –init 参数解决这个问题
–init false Run an init inside the container that forwards signals and reaps processes
参考 blog.phusion.nl/2015/01/20/…
1 | 复制代码➜ ke git:(alb) ✗ docker run alpine ps |
unshare()和setns()系统调用对PID Namespace的处理不太相同,当unshare PID namespace时,调用进程会为它的子进程分配一个新的PID Namespace,但是调用进程本身不会被移到新的Namespace中。而且调用进程第一个创建的子进程在新Namespace中的PID为1,并成为新Namespace中的init进程。为什么创建其他的Namespace时unshare()和setns()会直接进入新的Namespace,而唯独PID Namespace不是如此呢?因为调用getpid()函数得到的PID是根据调用者所在的PID Namespace而决定返回哪个PID,进入新的PID namespace会导致PID产生变化。而对用户态的程序和库函数来说,他们都认为进程的PID是一个常量,PID的变化会引起这些进程奔溃。换句话说,一旦程序进程创建以后,那么它的PID namespace的关系就确定下来了,进程不会变更他们对应的PID namespace。
Mount Namespace
1 | 复制代码#include <stdlib.h> |
关于mount命令
1 | 复制代码模仿Docker的Mount Namespace。 |
进程在创建mount namespace时,会把当前的文件结构复制给新的namespace。新namespace中的所有mount操作都只影响自身的文件系统,而对外界不会产生任何影响。这样做非常严格地实现了隔离,但是某些情况可能并不适用。比如父节点namespace中的进程挂载了一张CD-ROM,这时子节点namespace拷贝的目录结构就无法自动挂载上这张CD-ROM,因为这种操作会影响到父节点的文件系统。
2006 年引入的挂载传播(mount propagation)解决了这个问题,挂载传播定义了挂载对象(mount object)之间的关系,系统用这些关系决定任何挂载对象中的挂载事件如何传播到其他挂载对象(参考自:www.ibm.com/developerwo…
进程在创建Mount Namespace时,会把当前的文件结构复制给新的Namespace,新的Namespace中的所有mount操作仅影响自身的文件系统。但随着引入挂载传播的特性,Mount Namespace变得并不是完全意义上的资源隔离,这种传播特性使得多Mount Namespace之间的挂载事件可以相互影响。
挂载传播定义了挂载对象之间的关系,系统利用这些关系来决定挂载对象中的挂载事件对其他挂载对象的影响。其中挂载对象之间的关系描述如下:
- 共享关系(MS_SHARED):一个挂载对象的挂载事件会跨Namespace共享到其他挂载对象。
- 从属关系(MS_SLAVE): 传播的方向是单向的,即只能从Master传播到Slave方向。
- 私有关系(MS_PRIVATE): 不同Namespace的挂载事件是互不影响的(默认选项)。
- 不可绑定关系(MS_UNBINDABLE): 一个不可绑定的私有挂载,与私有挂载类似,但是不能执行挂载操作
一个挂载状态可能为如下的其中一种:
- 共享挂载(shared)
- 从属挂载(slave)
- 共享/从属挂载(shared and slave)
- 私有挂载(private)
- 不可绑定挂载(unbindable)
image
挂载的过程是通过mount系统调用完成的,它有两个参数:一个是已存在的普通文件名,一个是可以直接访问的特殊文件,一个是特殊文件的名字。这个特殊文件一般用来关联一些存储卷,这个存储卷可以包含自己的目录层级和文件系统结构。mount所达到的效果是:像访问一个普通的文件一样访问位于其他设备上文件系统的根目录,也就是将该设备上目录的根节点挂到了另外一个文件系统的页节点上,达到了给这个文件系统扩充容量的目的。
可以通过/proc文件系统查看一个进程的挂载信息,具体做法如下:
1 | 复制代码cat /proc/$pid/mountinfo |
绑定挂载
的引入使得mount的其中一个参数不一定要是一个特殊文件,也可以是该文件系统上的一个普通文件目录。Linux中绑定挂载的用法如下:
1 | 复制代码mount --bind /home/work /home/qiniu |
User Namespace
要把容器中的uid和真实系统的uid给映射在一起,需要修改 /proc//uid_map 和 /proc//gid_map 这两个文件。这两个文件的格式为:ID-inside-ns ID-outside-ns length
- 第一个字段ID-inside-ns表示在容器显示的UID或GID,
- 第二个字段ID-outside-ns表示容器外映射的真实的UID或GID。
- 第三个字段表示映射的范围,一般填1,表示一一对应。
User namespace主要隔离了安全相关的标识符(identifiers)和属性(attributes),包括用户ID、用户组ID、root目录、key(指密钥)以及特殊权限。说得通俗一点,一个普通用户的进程通过clone()创建的新进程在新user namespace中可以拥有不同的用户和用户组。这意味着一个进程在容器外属于一个没有特权的普通用户,但是他创建的容器进程却属于拥有所有权限的超级用户,这个技术为容器提供了极大的自由。
User Namespace除了隔离用户ID和用户组ID之外,还对每个Namespace进行了Capability的隔离和控制,可以通过添加和删除相应的Capability来控制新Namespace中进程所拥有的权限,比如为新的Namespace中增加CAP_CHOWN权限,那么在这个Namespace的进程拥有改变文件属主的权限。
- user namespace被创建后,第一个进程被赋予了该namespace中的全部权限,这样这个init进程就可以完成所有必要的初始化工作,而不会因权限不足而出现错误。
- 我们看到namespace内部看到的UID和GID已经与外部不同了,默认显示为65534,表示尚未与外部namespace用户映射。我们需要对user namespace内部的这个初始user和其外部namespace某个用户建立映射,这样可以保证当涉及到一些对外部namespace的操作时,系统可以检验其权限(比如发送一个信号或操作某个文件)。同样用户组也要建立映射。
- 还有一点虽然不能从输出中看出来,但是值得注意。用户在新namespace中有全部权限,但是他在创建他的父namespace中不含任何权限。就算调用和创建他的进程有全部权限也是如此。所以哪怕是root用户调用了clone()在user namespace中创建出的新用户在外部也没有任何权限。
- 最后,user namespace的创建其实是一个层层嵌套的树状结构。最上层的根节点就是root namespace,新创建的每个user namespace都有一个父节点user namespace以及零个或多个子节点user namespace,这一点与PID namespace非常相似。
1 | 复制代码#define _GNU_SOURCE |
Network Namespace
在Linux下,我们一般用ip命令创建Network Namespace
image
一般情况下,物理网络设备都分配在最初的root namespace(表示系统默认的namespace,在PID namespace中已经提及)中。但是如果你有多块物理网卡,也可以把其中一块或多块分配给新创建的network namespace。需要注意的是,当新创建的network namespace被释放时(所有内部的进程都终止并且namespace文件没有被挂载或打开),在这个namespace中的物理网卡会返回到root namespace而非创建该进程的父进程所在的network namespace。
在建立起veth pair之前,新旧namespace该如何通信呢?答案是pipe(管道)。我们以Docker Daemon在启动容器dockerinit的过程为例。Docker Daemon在宿主机上负责创建这个veth pair,通过netlink调用,把一端绑定到docker0网桥上,一端连进新建的network namespace进程中。建立的过程中,Docker Daemon和dockerinit就通过pipe进行通信,当Docker Daemon完成veth-pair的创建之前,dockerinit在管道的另一端循环等待,直到管道另一端传来Docker Daemon关于veth设备的信息,并关闭管道。dockerinit才结束等待的过程,并把它的“eth0”启动起来。整个效果类似下图所示。
1 | 复制代码// docker 网络本质做的事就是 1. 创建网桥 2. 创建veth 虚拟网卡,一头在docker ns1,一头插在网桥上 3. 设置ip,路由规则,nat,让docker 网络能经过bridge 出去 外部访问容器网络 也是在本地的 iptable 的 nat 表中添加相应的规则 https://yeasy.gitbooks.io/docker_practice/content/advanced_network/port_mapping.html |
CGroup
cgroups可以限制、记录、隔离进程组所使用的物理资源(包括:CPU、memory、IO等),为容器实现虚拟化提供了基本保证,是构建Docker等一系列虚拟化管理工具的基石。
主要提供了如下功能:
- Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
- Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
- Accounting: 一些审计或一些统计,主要目的是为了计费。
- Control: 挂起进程,恢复执行进程。
对开发者来说,cgroups有如下四个有趣的特点:
- cgroups的API以一个伪文件系统的方式实现,即用户可以通过文件操作实现cgroups的组织管理。
- cgroups的组织管理操作单元可以细粒度到线程级别,用户态代码也可以针对系统分配的资源创建和销毁cgroups,从而实现资源再分配和管理。
- 所有资源管理的功能都以“subsystem(子系统)”的方式实现,接口统一。
- 子进程创建之初与其父进程处于同一个cgroups的控制组。
本质上来说,cgroups是内核附加在程序上的一系列钩子(hooks),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。
术语
- task(任务):cgroups的术语中,task就表示系统的一个进程。
- cgroup(控制组):cgroups 中的资源控制都以cgroup为单位实现。cgroup表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个cgroup,也可以从某个cgroup迁移到另外一个cgroup。
- subsystem(子系统):cgroups中的subsystem就是一个资源调度控制器(Resource Controller)。比如CPU子系统可以控制CPU时间分配,内存子系统可以限制cgroup内存使用量。
- hierarchy(层级树):hierarchy由一系列cgroup以一个树状结构排列而成,每个hierarchy通过绑定对应的subsystem进行资源调度。hierarchy中的cgroup节点可以包含零或多个子节点,子节点继承父节点的属性。整个系统可以有多个hierarchy
1 | 复制代码hchen@ubuntu:~$ mount -t cgroup #或者使用lssubsys -m命令: # lscgroup 查询 |
cgroups的使用方法简介
查询cgroup及子系统挂载状态
- 查看所有的cgroup:lscgroup
- 查看所有支持的子系统:lssubsys -a
- 查看所有子系统挂载的位置: lssubsys –m
- 查看单个子系统(如memory)挂载位置:lssubsys –m memory
创建hierarchy层级并挂载子系统
1 | 复制代码// 虚拟机操作,会影响系统 |
CPU 限制
1 | 复制代码root@container:~# mkdir -p /sys/fs/cgroup/cpu/wanglei |
下面的代码是一个线程的示例
1 | 复制代码#define _GNU_SOURCE /* See feature_test_macros(7) */ |
内存使用限制
测试一个耗尽内存的程序,限制内存,可以看到程序会被kill
1 | 复制代码#include <stdio.h> |
1 | 复制代码root@container:~/testcgroup# mkdir /sys/fs/cgroup/memory/wanglei |
磁盘I/O限制
1 | 复制代码root@container:~/testcgroup# dd if=/dev/vda of=/dev/null |
CGroup的子系统
- blkio: 这个subsystem可以为块设备设定输入/输出限制,比如物理驱动设备(包括磁盘、固态硬盘、USB等)。
- cpu: 这个subsystem使用调度程序控制task对CPU的使用。
- cpuacct: 这个subsystem自动生成cgroup中task对CPU资源使用情况的报告。
- cpuset: 这个subsystem可以为cgroup中的task分配独立的CPU(此处针对多处理器系统)和内存。
- devices 这个subsystem可以开启或关闭cgroup中task对设备的访问。
- freezer 这个subsystem可以挂起或恢复cgroup中的task。
- memory 这个subsystem可以设定cgroup中task对内存使用量的限定,并且自动生成这些task对内存资源使用情况的报告。
- perfevent 这个subsystem使用后使得cgroup中的task可以进行统一的性能测试。{![perf: Linux CPU性能探测器,详见perf.wiki.kernel.org/index.php/M…
- *net_cls 这个subsystem Docker没有直接使用,它通过使用等级识别符(classid)标记网络数据包,从而允许 Linux 流量控制程序(TC:Traffic Controller)识别从具体cgroup中生成的数据包。
组织结构与基本规则
大家在namespace技术的讲解中已经了解到,传统的Unix进程管理,实际上是先启动init进程作为根节点,再由init节点创建子进程作为子节点,而每个子节点由可以创建新的子节点,如此往复,形成一个树状结构。而cgroups也是类似的树状结构,子节点都从父节点继承属性。
它们最大的不同在于,系统中cgroup构成的hierarchy可以允许存在多个。如果进程模型是由init作为根节点构成的一棵树的话,那么cgroups的模型则是由多个hierarchy构成的森林。这样做的目的也很好理解,如果只有一个hierarchy,那么所有的task都要受到绑定其上的subsystem的限制,会给那些不需要这些限制的task造成麻烦。
了解了cgroups的组织结构,我们再来了解cgroup、task、subsystem以及hierarchy四者间的相互关系及其基本规则{![参照自:access.redhat.com/documentati…
规则1: 同一个hierarchy可以附加一个或多个subsystem。如下图1,cpu和memory的subsystem附加到了一个hierarchy。
image
图1 同一个hierarchy可以附加一个或多个subsystem
规则2: 一个subsystem可以附加到多个hierarchy,当且仅当这些hierarchy只有这唯一一个subsystem。如下图2,小圈中的数字表示subsystem附加的时间顺序,CPU subsystem附加到hierarchy A的同时不能再附加到hierarchy B,因为hierarchy B已经附加了memory subsystem。如果hierarchy B与hierarchy A状态相同,没有附加过memory subsystem,那么CPU subsystem同时附加到两个hierarchy是可以的。
image
图2 一个已经附加在某个hierarchy上的subsystem不能附加到其他含有别的subsystem的hierarchy上
规则3: 系统每次新建一个hierarchy时,该系统上的所有task默认构成了这个新建的hierarchy的初始化cgroup,这个cgroup也称为root cgroup。对于你创建的每个hierarchy,task只能存在于其中一个cgroup中,即一个task不能存在于同一个hierarchy的不同cgroup中,但是一个task可以存在在不同hierarchy中的多个cgroup中。如果操作时把一个task添加到同一个hierarchy中的另一个cgroup中,则会从第一个cgroup中移除。在下图3中可以看到,httpd进程已经加入到hierarchy A中的/cg1而不能加入同一个hierarchy中的/cg2,但是可以加入hierarchy B中的/cg3。实际上不允许加入同一个hierarchy中的其他cgroup野生为了防止出现矛盾,如CPU subsystem为/cg1分配了30%,而为/cg2分配了50%,此时如果httpd在这两个cgroup中,就会出现矛盾。
image
图3 一个task不能属于同一个hierarchy的不同cgroup
规则4: 进程(task)在fork自身时创建的子任务(child task)默认与原task在同一个cgroup中,但是child task允许被移动到不同的cgroup中。即fork完成后,父子进程间是完全独立的。如下图4中,小圈中的数字表示task 出现的时间顺序,当httpd刚fork出另一个httpd时,在同一个hierarchy中的同一个cgroup中。但是随后如果PID为4840的httpd需要移动到其他cgroup也是可以的,因为父子任务间已经独立。总结起来就是:初始化时子任务与父任务在同一个cgroup,但是这种关系随后可以改变。
image
图4 刚fork出的子进程在初始状态与其父进程处于同一个cgroup
补充
==
systemd
kuberlet有个systemd文档这么说:
This document describes how the node should be configured, and a set of enhancements that should be made to the kubelet to better integrate with these distributions independent of container runtime.
The Kernel direction for cgroup management is to promote a single-writer model rather than allowing multiple processes to independently write to parts of the file-system.In distributions that run systemd as their init system, the cgroup tree is managed by systemd by default since it implicitly interacts with the cgroup tree when starting units. Manual changes made by other cgroup managers to the cgroup tree are not guaranteed to be preserved unless systemd is made aware. systemd can be told to ignore sections of the cgroup tree by configuring the unit to have the Delegate= option.
是说再linux上就推荐用systemd来管理cgroup?而且这样还能不依赖docker?
sysctl
除了cgroup做资源限制,对于系统级别的资源限制相关的还有一个sysctl命令
sysctl命令被用于在内核运行时动态地修改内核的运行参数,可用的内核参数在目录/proc/sys中。它包含一些TCP/ip堆栈和虚拟内存系统的高级选项, 这可以让有经验的管理员提高引人注目的系统性能。用sysctl可以读取设置超过五百个系统变量。
Parameters are available via /proc/sys/ virtual process file system. The parameters cover various subsystems such as:
- kernel (common prefix: kernel.)
- networking (common prefix: net.)
- virtual memory (common prefix: vm.)
- MDADM (common prefix: dev.)
docker privileged可以设置,但是有些参数是系统级别的,没有隔离,改了会影响别的容器。后来版本docker做了限制,只能改一些whitelisted sysctls。
Only namespaced kernel parameters can be modified
k8s里面的设置github.com/kubernetes/…
本文转载自: 掘金