Namespace是对全局系统资源的一种封装隔离,使得处于不同Namespace的进程拥有独立的全局系统资源,改变一个Namespace中的系统资源只会影响当前Namespace里的进程,对其他Namespace中的进程没有影响。
Namespace 是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。在日常使用 Linux 时如果我们在服务器上启动了多个服务,这些服务其实会相互影响的,每一个服务都能看其他服务的进程,也可以访问宿主机器上的任意文件,这是很多时候我们都不愿意看到的,我们更希望运行在同一台机器上的不同服务能做到完全隔离,就像运行在多台不同的机器上一样。
当 Docker 创建一个容器时,它会创建新的以上六种 namespace 的实例,然后把容器中的所有进程放到这些 namespace 之中,使得Docker 容器中的进程只能看到隔离的系统资源。
Namespace6种资源隔离
PID namespace
PID 用来隔离进程的ID空间,使得不同pid namespace里的进程id可以重复且相互之间不影响。
我们能看到同一个进程,在容器内外的 PID 是不同的:
- 在容器内 PID 是 1,PPID 是 0。
- 在容器外 PID 是 50349, PPID 是 1059即 containerd-shim 进程.
#容器启动一个镜像
`[root@node1 ~]# docker run -it -d registry.cn-beijing.aliyuncs.com/alexzhang/centos-full`
`f90a6a1cab36effc6a2f5058673320cf8f80b8c65ea84deef3ad4981f3d7baee`
#通过镜像id查找进程
`[root@node1 ~]# ps -ef | grep f90a6a1cab36eff`
`root 50349 1059 0 12:57 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/f90a6a1cab36effc6a2f5058673320cf8f80b8c65ea84deef3ad4981f3d7baee -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc -systemd-cgroup`
`root 50438 47301 0 12:58 pts/0 00:00:00 grep --color=auto f90a6a1cab36effc6a2f5058673320cf8f80b8c65ea84deef3ad4981f3d7baee`
#进入容器
`[root@node1 ~]# docker exec -it f90a6a1cab36eff bash`
#查看容器内进程
`[root@f90a6a1cab36 /]# ps -ef`
`UID PID PPID C STIME TTY TIME CMD`
`root 1 0 0 04:57 pts/0 00:00:00 /bin/bash`
`root 14 0 0 04:58 pts/1 00:00:00 bash`
`root 27 14 0 04:58 pts/1 00:00:00 ps -ef
Namespace允许在新的Namespace创建一棵新的进程树,它可以有自己的PID为1的进程。在PID namespace的隔离下,子进程名字空间无法知道父进程名字空间的进程,如在Docker容器中无法看到宿主机的进程,而父进程名字空间可以看到子进程名字空间的所有进程。如图所示:
Network namespace
network用来隔离网络设备,ip地址,端口。每个namespace将会有自己独立的网格线,路由表,防火墙规则,socket等。
Docker容器中另一个重要特性是网络独立,使用到Linux 的 NET Namespace以及veth。veth主要的目的是为了跨NET namespace之间提供一种类似于Linux进程间通信的技术,所以veth总是成对出现,如下面的veth0和veth1。它们位于不同的NET namespace中,在veth设备任意一端接收到的数据,都会从另一端发送出去。veth实现了不同namespace的网络数据传输。
在Docker中,宿主机的veth端会桥接到网桥中,接收到容器中的veth端发过来的数据后会经由网桥docker0再转发到宿主机网卡eth0,最终通过eth0发送数据。当然在发送数据前,需要经过iptables MASQUERADE
规则将源地址改成宿主机ip,这样才能接收到响应数据包。而宿主机网卡接收到的数据会通过iptables DNAT
根据端口号修改目的地址和端口为容器的ip和端口,然后根据路由规则发送到网桥docker0中,并最终由网桥docker0发送到对应的容器中。
Docker里面网络模式分为bridge,host,overlay等几种模式,默认是采用bridge模式网络如图所示。如果使用host模式,则不隔离直接使用宿主机网络。overlay网络则是更加高级的模式,可以实现跨主机的容器通信。
User namespace
user namespace用于隔离用户和组信息,在不同的namespace中用户可以有相同的 UID 和 GID,它们之间互相不影响。父子namespace之间可以进行用户映射,如父namespace(宿主机)的普通用户映射到子namespace(容器)的root用户,以减少子namespace的root用户操作父namespace的风险。
Mount namespace
将一个文件系统的顶层目录挂到另一个文件系统的子目录上,使它们成为一个整体,称为挂载。把该子目录称为挂载点。 Mount namespace用来隔离文件系统的挂载点, 使得不同的mount namespace拥有自己独立的挂载点信息,不同的namespace之间不会相互影响,这对于构建用户或者容器自己的文件系统目录非常有用。
UTS namespace
UTS,UNIX Time-sharing System namespace提供了主机名和域名的隔离。能够使得子进程有独立的主机名和域名(hostname),这一特性在Docker容器技术中被用到,使得docker容器在网络上被视作一个独立的节点,而不仅仅是宿主机上的一个进程。
#查看主机hostname
[root@node1 ~]# hostname
node1
#进入容器
[root@node1 ~]# docker exec -it f90a6a1cab36effc6a2f5058673320cf8f80b8c65ea84deef3ad4981f3d7baee bash
#查看容器主机hostname
[root@f90a6a1cab36 /]# hostname
f90a6a1cab36
[root@f90a6a1cab36 /]#
IPC namespace
IPC全称 Inter-Process Communication,是Unix/Linux下进程间通信的一种方式,IPC有共享内存、信号量、消息队列等方法。所以,为了隔离,我们也需要把IPC给隔离开来,这样,只有在同一个Namespace下的进程才能相互通信。如果你熟悉IPC的原理的话,你会知道,IPC需要有一个全局的ID,即然是全局的,那么就意味着我们的Namespace需要对这个ID隔离,不能让别的Namespace的进程看到。