Namespace
concept
Linux Namespace
是Kernel的一个功能,它可以隔离一系列的系统资源。例如PID、UserID、Network等。Namespace
也可以在一些资源上,将进程隔离起来,这些资源包括进程树、网络接口、挂载点等。
例如一家公司向外界出售自己的计算资源。但是需要将不同的客户隔离起来。这个时候,Linux Namespace
就派上了用场,使用Namespace
就可以做到UID级别的隔离,也就是说,可以以UID为n的用户,虚拟化出来一个Namespace
,在这个Namespace
里面,用户是具有root权限的。但是在真实的物理机器上,他还是那个以UID为n的用户。
除了User Namespace
,PID也是可以被虚拟的。命名空间建立系统的不同视图。从用户的角度来看,每一个命名空间应该像一台单独的Linux计算机一样,有自己的init
进程(PID为1),其他进程的PID依次递增。A和B空间都有PID为1的init
进程,子命名空间的进程映射到父命名空间的进程上,父命名空间可以知道每一个子命名空间的运行状态,而子命名空间与子命名空间之间是隔离的。
如下图所示,进程3在父命名空间中的PID为 3,但是在子命名空间内,它的PID就是1。也就是说用户从子命名空间A内看进程3就像init
进程一样,以为这个进程是自己的初始化进程,但是从整个host来看,它其实只是3号进程虚拟化出来的一个空间而己。
当前Linux一共实现了6种不同类型的Namespace
Namespace
的API主要是用如下3个系统调用
clone()
创建新进程。根据系统调用参数来判断哪些类型的Namespace
被创建,而且它们的子进程也会被包含到这些Namespace
中unshare()
将进程移出某个Namespace
setns()
将进程加入到Namespace
中
UTS Namespace
UTS Namespace
主要用来隔离nodename
和domainname
两个系统标识。在UTS Namespace
里面,每个Namespace
允许有自己的hostname
输出当前的PID
1 | echo $$ |
查看进程的UTS
1 | readlink /proc/<PID>/ns/uts |
IPC Namespace
IPC Namespace
用来隔离System V IPC
和POSIX message queues
。每一个IPC Namespace
都有自己的System V IPC
和POSIX message queues
查看现有的ipc Message Queues
1 | ipcs -q |
PID Namespace
PID Namespace
是用来隔离进程ID的。同样一个进程在不同的PID Namespace
里可以拥有不同的PID。这样就可以理解,在docker container
里面,使用ps-ef
经常会发现,在容器 内,前台运行的那个进程PID是1,但是在容器外,使用ps -ef
会发现同样的进程却有不同的PID,这就是PID Namespace
做的事情
查看进程树
1 | pstree -pl |
Mount Namespace
Mount Namespace
用来隔离各个进程看到的挂载点视图。在不同Namespace
的进程中, 看 到的文件系统层次是不一样的。在Mount Namespace
中调用mount()
和umount()
仅仅只会影响当前Namespace
内的文件系统 ,而对全局的文件系统是没有影响的
User Namespace
User Namespace
主要是隔离用户的用户组ID。也就是说,一个进程的User ID
和Group ID
在User Namespace
内外可以是不同的。比较常用的是,在宿主机上以一个非root用户运行 创建一个User Namespace
, 然后在User Namespace
里面却映射成root用户。这意味着, 这个进程在User Namespace
里面有root权限,但是在User Namespace
外面却没有root的权限。从Linux Kernel 3.8
开始,非root进程也可以创建User Namespace
,并且此用户在Namespace
里面可以被映射成root,且在Namespace
内有root权限
Network Namespace
Network Namespace
是用来隔离网络设备、IP地址端口等网络栈的Namespace
。Network Namespace
可以让每个容器拥有自己独立的(虚拟的)网络设备,而且容器内的应用可以绑定到自己的端口,每个Namespace
内的端口都不会互相冲突。在宿主机上搭建网桥后,就能很方 便地实现容器之间的通信,而且不同容器上的应用可以使用相同的端口
Cgroups
concept
Namespace
技术能够帮助进程隔离出自己单独的空间,但是Docker是怎么限制每个空间的大小,保证它们不会互相争夺呢。这里就用到了Cgroups
技术。
Linux Cgroups
(Control Groups)提供了对一组进程及将来子进程的资源限制、控制和统计的能力,这些资源包括CPU、内存、存储、网络等。通过Cgroups
,可以方便地限制某个进 程的资源占用,并且可以实时地监控进程的监控和统计信息
Cgroups中的3个组件
cgroup
是对进程分组管理的一种机制,一个cgroup
包含一组进程,并可以在这个cgroup
上增加Linux subsystem
的各种参数配置,将一组进程和一组subsystem
的系统参数关联起来。subsystem
是一组资源控制的模块,一般包含如下几项blkio
设置对块设备(比如硬盘)输入输出的访问控制cpu
设置cgroup
中进程的CPU
被调度的策略cpuacct
可以统计cgroup
中进程的CPU
占用cpuset
在多核机器上设置cgroup
中进程可以使用的CPU
和内存(此处内存仅使用于NUMA 架构)devices
控制cgroup
中进程对设备的访问freezer
用于挂起(suspend)和恢复(resume)cgroup
中的进程memory
用于控制cgroup
中进程的内存占用net_els
用于将cgroup
中进程产生的网络包分类,以便Linux
的tc(traffic controller)
可以根据分类区分出来自某个cgroup
的包并做限流或监控net_prio
设置cgroup
中进程产生的网络流量的优先级ns
这个subsystem
比较特殊,它的作用是使cgroup
中的进程在新的Namespace
中fork
新进程CNEWNS
时,创建出一个新的cgroup
,这个cgroup
包含新的Namespace
中的进
每个subsystem
会关联到定义了相应限制的cgroup
上,并对这个cgroup
中的进程做相应的限制和控制。
hierarchy
的功能是把一组cgroup
串成一个树状的结构,一个这样的树便是一个hierarchy
,通过这种树状结构,Cgroups
可以做到继承。比如,系统对一组定时的任务进程通过cgroup1
限制了CPU的使用率,然后其中有一个定时dump
日志的进程还需要限制磁盘 IO,为了避免限制了磁盘IO之后影响到其他进程,就可以创建cgroup2
,使其继承于cgroup1
并限制磁盘的IO,这样cgroup2
便继承了cgroup1
中对CPU
使用率的限制,并且增加了磁盘IO的限制而不影响到cgroup1
中的其他进程
三个组件之间的关系
- 系统在创建了新的
hierarchy
之后,系统中所有的进程都会加入这个hierarchy
的cgroup
根节点,这个cgroup
根节点是hierarchy
默认创建的 - 一个
subsystem
只能附加到一个hierarchy
上面 - 一个
hierarchy
可以附加多个subsystem
- 一个进程可以作为多个
cgroup
的成员,但是这些cgroup
必须在不同的hierarchy
中 - 一个进程
fork
出子进程时,子进程是和父进程在同一个cgroup
中的,也可以根据需要将其移动到其他cgroup
中
Docker是如何使用Cgroups的
设置内存限制
1 | docker run -itd -m 128m ubuntu |
docker会为每个容器在系统的hierarchy
种创建cgroup
1 | cd /sys/fs/cgroup/memory/docker/<container_id> |
1 | cat memory.limit_in_bytes #查看cgroup的内存限制 |
可以看到,Docker通过为每个容器创建cgroup
,并通过cgroup
去配置资源限制和资源监控
Union File System
concept
Union File System
简称UnionFS
,是一种位Linux
、FreeBSD
和NetBSD
操作系统设计的,把其他文件系统联合到一个联合挂载点的文件系统服务。它使用branch
把不同文件系统的文件和目录”透明地”覆盖,形成一个单一一致的文件系统。这些branch
或者是read-only
的,或者是read-write
的,所以当对这个虚拟后的联合文件系统进行写操作的时候,系统是真正写到了一个新的文件中。看起来这个虚拟后的联合文件系统是可以对任何文件进行操作的,但是其实它并没有改变原来的文件,这是因为unionfs
用到了一个重要的资源管理技术,叫写时复制。
写时复制(copy-on-write,简称CoW),也叫隐式共享,是一种对可修改资源实现高效复制的资源管理技术。它的思想是,如果一个资源是重复的,但没有任何修改,这时并不需要立即创建一个新的资源,这个资源可以被新旧实例共享。创建新资源发生在第一次写操作,也就是对资源进行修改的时候。通过这种资源共享的方式,可以显著地减少未修改资源复制带来的消耗,但是也会在进行资源修改时增加小部分的开销
AUFS
AUFS
,英文全称是Advanced Multi-Layered Unification Filesystem
,曾经也叫Acronym Multi-Layered Unification Filesystem
、Another Multi-Layered Unification Filesystem
。 AUFS
完全重写了早期的UnionFS 1.x
,其主要目的是为了可靠性和性能,并且引入了一些新的功能,比如可写分支的负载均衡。AUFS
的一些实现已经被纳入UnionFS 2.x
版本
Docker是如何使用AUFS的
AUFS
是Docker选用的第一种存储驱动。AUFS
具有快速启动容器、高效利用存储和内存的优点。直到现在,AUFS
仍然是Docker支持的一种存储驱动类型。
Image layer和AUFS
每一个Docker image
都是由一系列read-only layer
组成的,image layer
的内容都存储在Docker hosts filesystem
的/var/lib/docker/aufs/diff
目录下。而/var/lib/docker/aufs/layers
目录,则存储着image layer
如何堆栈这些layer
的metadata
Container layer和AUFS
Docker使用AUFS
的CoW
技术来实现image layer
共享和减少磁盘空间占用。CoW
意味着一旦某个文件只有很小的部分有改动, AUFS
也需要复制整个文件。这种设计会对容器性能产生一定的影响,尤其是在待复制的文件很大,或者位于很多image layer
下方,又或者AUFS
需要深度搜索目录结构树的时候。不过也不用过度担心,对于一个容器而言,每个image layer
最多只需要复制一次。后续的改动都会在第一次拷贝的container layer
上进行。
启动一个container
的时候,Docker会为其创建一个read-only
的init layer
,用来存储与这个容器内环境相关的内容,Docker还会为其创建一个read-write
的layer
来执行所有写操作。
container layer
的mount
目录也是/var/lib/docker/aufs/mnt
。container
的metadata
和配置文件都存放在/var/lib/docker/containers/<container-id>
目录中。container
的read-write layer
存储在/var/lib/docker/aufs/diff/
目录下。即使容器停止,这个可读写层仍然存在,因而重启容器不会丢失数据,只有当一个容器被删除的时候,这个可读写层才会一起删除。
最后,讲一下AUFS
如何为container
删除一个文件。如果要删除file1时,AUFS
会在container
的read-write
层生成一个.wh.file1
的文件来隐藏所有read-only
层的file1文件。
Linux proc介绍
Linux下的/proc
文件系统是由内核提供的,它其实不是一个真正的文件系统,只包含了系统运行时的信息(比如系统内存、mount设备信息、一些硬件配置等),只存在于内存中,而不占用外存空间。它以文件系统的形式,为访问内核数据的操作提供接口。
当遍历/proc
目录时,会发现很多数字,这些都是为每个进程创建的空间。
一些具体的文件信息如下所示
1 | /proc/N PID为N的进程信息 |