17.2 命名空间
命名空间(namespace)是Linux内核的一个强大特性,为容器虚拟化的实现带来极大便利。
利用这一特性,每个容器都可以拥有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统环境中一样。命名空间机制保证了容器之间彼此互不影响。
在操作系统中,包括内核、文件系统、网络、PID、UID、IPC、内存、硬盘、CPU等资源,所有的资源都是应用进程直接共享的。要想实现虚拟化,除了要实现对内存、CPU、网络IO、硬盘IO、存储空间等的限制外,还要实现文件系统、网络、PID、UID、IPC等的相互隔离。前者相对容易实现一些,后者则需要宿主主机系统的深入支持。
随着Linux系统对于命名空间功能的逐步完善,现在已经可以实现这些需求,让进程在彼此隔离的命名空间中运行。虽然这些进程仍在共用同一个内核和某些运行时环境(runtime,例如一些系统命令和系统库),但是彼此是不可见的,并且认为自己是独占系统的。
1.进程命名空间
Linux通过命名空间管理进程号,对于同一进程(即同一个task_struct),在不同的命名空间中,看到的进程号不相同,每个进程命名空间有一套自己的进程号管理方法。进程命名空间是一个父子关系的结构,子空间中的进程对于父空间是可见的。新fork出的进程在父命名空间和子命名空间将分别有一个进程号来对应。
例如,查看Docker主进程的pid进程号是5989,如下所示:
- $ ps -ef |grep docker
- root 5989 5988 0 14:38 pts/6 00:00:00 docker -d
新建一个Ubuntu的“hello world”容器:
- $ docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world;
- sleep 1; done"
- ec559327572b5bf99d0f80b98ed3a3b62023844c7fdbea3f8caed4ffa5c62e86
- ...
查看新建容器进程的父进程,正是Docker主进程5989:
- $ ps -ef |grep while
- root 6126 5989 0 14:41 ? 00:00:00 /bin/sh -c while true; do echo
- hello world; sleep 1; done
2.网络命名空间
如果有了pid命名空间,那么每个命名空间中的进程就可以相互隔离,但是网络端口还是共享本地系统的端口。
通过网络命名空间,可以实现网络隔离。网络命名空间为进程提供了一个完全独立的网络协议栈的视图,包括网络设备接口、IPv4和IPv6协议栈、IP路由表、防火墙规则、sockets等,这样每个容器的网络就能隔离开来。Docker采用虚拟网络设备(Virtual Network Device)的方式,将不同命名空间的网络设备连接到一起。默认情况下,容器中的虚拟网卡将同本地主机上的docker0网桥连接在一起,如图17-2所示。

图17-2 网络命名空间
使用brctl工具可以看到桥接到宿主主机docker0网桥上的虚拟网口:
- $ brctl show
- bridge name bridge id STP enabled interfaces
- docker0 8000.56847afe9799 no veth4148
- vethd166
- vethd533
3.IPC命名空间
容器中进程交互还是采用了Linux常见的进程间交互方法(Interprocess Communication,IPC),包括信号量、消息队列和共享内存等。PID Namespace和IPC Namespace可以组合起来一起使用,同一个IPC命名空间内的进程可以彼此可见,允许进行交互;不同空间的进程则无法交互。
4.挂载命名空间
类似于chroot,将一个进程放到一个特定的目录执行。挂载命名空间允许不同命名空间的进程看到的文件结构不同,这样每个命名空间中的进程所看到的文件目录彼此被隔离。
5.UTS命名空间
UTS(UNIX Time-sharing System)命名空间允许每个容器拥有独立的主机名和域名,从而可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。
默认情况下,Docker容器的主机名就是返回的容器ID:
- $ docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- ec559327572b ubuntu:14.04 /bin/sh -c 'while tr 18 minutes ago
- Up 18 minutes furious_goodall
- $ docker inspect -f {{".Config.Hostname"}} ec5
- ec559327572b
6.用户命名空间
每个容器可以有不同的用户和组id,也就是说可以在容器内使用特定的内部用户执行程序,而非本地系统上存在的用户。
每个容器内部都可以有root帐号,但跟宿主主机不在一个命名空间。
通过使用隔离的用户命名空间可以提高安全性,避免容器内进程获取到额外的权限。
