17.2 命名空间

命名空间(namespace)是Linux内核的一个强大特性,为容器虚拟化的实现带来极大便利。

利用这一特性,每个容器都可以拥有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统环境中一样。命名空间机制保证了容器之间彼此互不影响。

在操作系统中,包括内核、文件系统、网络、PID、UID、IPC、内存、硬盘、CPU等资源,所有的资源都是应用进程直接共享的。要想实现虚拟化,除了要实现对内存、CPU、网络IO、硬盘IO、存储空间等的限制外,还要实现文件系统、网络、PID、UID、IPC等的相互隔离。前者相对容易实现一些,后者则需要宿主主机系统的深入支持。

随着Linux系统对于命名空间功能的逐步完善,现在已经可以实现这些需求,让进程在彼此隔离的命名空间中运行。虽然这些进程仍在共用同一个内核和某些运行时环境(runtime,例如一些系统命令和系统库),但是彼此是不可见的,并且认为自己是独占系统的。

1.进程命名空间

Linux通过命名空间管理进程号,对于同一进程(即同一个task_struct),在不同的命名空间中,看到的进程号不相同,每个进程命名空间有一套自己的进程号管理方法。进程命名空间是一个父子关系的结构,子空间中的进程对于父空间是可见的。新fork出的进程在父命名空间和子命名空间将分别有一个进程号来对应。

例如,查看Docker主进程的pid进程号是5989,如下所示:


  1. $ ps -ef |grep docker
  2. root 5989 5988 0 14:38 pts/6 00:00:00 docker -d

新建一个Ubuntu的“hello world”容器:


  1. $ docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world;
  2. sleep 1; done"
  3. ec559327572b5bf99d0f80b98ed3a3b62023844c7fdbea3f8caed4ffa5c62e86
  4. ...

查看新建容器进程的父进程,正是Docker主进程5989:


  1. $ ps -ef |grep while
  2. root 6126 5989 0 14:41 ? 00:00:00 /bin/sh -c while true; do echo
  3. hello world; sleep 1; done

2.网络命名空间

如果有了pid命名空间,那么每个命名空间中的进程就可以相互隔离,但是网络端口还是共享本地系统的端口。

通过网络命名空间,可以实现网络隔离。网络命名空间为进程提供了一个完全独立的网络协议栈的视图,包括网络设备接口、IPv4和IPv6协议栈、IP路由表、防火墙规则、sockets等,这样每个容器的网络就能隔离开来。Docker采用虚拟网络设备(Virtual Network Device)的方式,将不同命名空间的网络设备连接到一起。默认情况下,容器中的虚拟网卡将同本地主机上的docker0网桥连接在一起,如图17-2所示。

17.2 命名空间 - 图1

图17-2 网络命名空间

使用brctl工具可以看到桥接到宿主主机docker0网桥上的虚拟网口:


  1. $ brctl show
  2. bridge name bridge id STP enabled interfaces
  3. docker0 8000.56847afe9799 no veth4148
  4. vethd166
  5. vethd533

3.IPC命名空间

容器中进程交互还是采用了Linux常见的进程间交互方法(Interprocess Communication,IPC),包括信号量、消息队列和共享内存等。PID Namespace和IPC Namespace可以组合起来一起使用,同一个IPC命名空间内的进程可以彼此可见,允许进行交互;不同空间的进程则无法交互。

4.挂载命名空间

类似于chroot,将一个进程放到一个特定的目录执行。挂载命名空间允许不同命名空间的进程看到的文件结构不同,这样每个命名空间中的进程所看到的文件目录彼此被隔离。

5.UTS命名空间

UTS(UNIX Time-sharing System)命名空间允许每个容器拥有独立的主机名和域名,从而可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。

默认情况下,Docker容器的主机名就是返回的容器ID:


  1. $ docker ps
  2. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  3. ec559327572b ubuntu:14.04 /bin/sh -c 'while tr 18 minutes ago
  4. Up 18 minutes furious_goodall
  5. $ docker inspect -f {{".Config.Hostname"}} ec5
  6. ec559327572b

6.用户命名空间

每个容器可以有不同的用户和组id,也就是说可以在容器内使用特定的内部用户执行程序,而非本地系统上存在的用户。

每个容器内部都可以有root帐号,但跟宿主主机不在一个命名空间。

通过使用隔离的用户命名空间可以提高安全性,避免容器内进程获取到额外的权限。