天天看点

Docker容器实战(六) - Docker是如何实现隔离的?(上)1 Namespace

1 Namespace

1.1 容器为何需要进程隔离

  • 被其他容器修改文件,导致安全问题
  • 资源的并发写入导致不一致性
  • 资源的抢占,导致其他容器被影响
docker run -it --name demo_docker busybox /bin/sh
/ # ps -ef      
Docker容器实战(六) - Docker是如何实现隔离的?(上)1 Namespace

在宿主机查看进程ID

ps -ef|grep busybox      

真实的 docker 容器 pid

Docker容器实战(六) - Docker是如何实现隔离的?(上)1 Namespace

这就是进程资源隔离表象:

  • 对于宿主机

    docker run

    启动的只是一个进程,它的pid是44451
  • 而容器程序本身被隔离了,容器的内部都只能看到自己内部的进程
  • 这其实是基于Linux的Namespace技术(即使是 Windows 版本的 Docker 也是依托于 Windows 实现的类似Namespace的技术)

1.2 Linux Namespace

Linux 命名空间对全局操作系统资源进行了抽象,对于命名空间内的进程来说,他们拥有独立的资源实例,在命名空间内部的进程可以实现资源可见。

对于命名空间外部的进程,则不可见,实现了资源的隔离。这种技术广泛的应用于容器技术里。

Namespace实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容。对于宿主机来说,这些被“隔离”了的进程跟其他进程并没有区别。

1.3 Docker Engine 使用了如下 Linux 的隔离技术

  • The pid namespace:管理 PID namespace (PID: Process ID)
  • The net namespace: 管理网络namespace(NET: Networking)
  • The ipc namespace: 管理进程间通信命名空间(IPC: InterProcess Communication)
  • The mnt namespace:管理文件系统挂载点命名空间(MNT: Mount).
  • The uts namespace: Unix 时间系统隔离(UTS: Unix Timesharing

    System).

1.4 进程资源隔离原理

Linux Namespaces是Linux创建新进程时的一个可选参数,在Linux系统中创建进程的系统调用是clone()方法。

int clone(int (*fn) (void *),void *child stack,
int flags, void *arg, . . .
/* pid_ t *ptid, void *newtls, pid_ t *ctid */ ) ;
      

通过调用该方法,这个进程会获得一个独立的进程空间,它的pid是1 ,并且看不到宿主机上的其他进程,也就是在容器内执行PS命令的结果。

不应该把Docker Engine或者任何容器管理工具放在跟Hypervisor相同的位置,因为它们并不像Hypervisor那样对应用进程的隔离环境负责,也不会创建任何实体的“容器”,真正对隔离环境负责的是宿主机os:

Docker容器实战(六) - Docker是如何实现隔离的?(上)1 Namespace
Docker容器实战(六) - Docker是如何实现隔离的?(上)1 Namespace

该对比图应该把Docker画在跟应用同级别并且靠边的位置。

用户运行在容器里的应用进程,跟宿主机上的其他进程一样,都由宿主机操作系统统一管理,只不过这些被隔离的进程拥有额外设置过的Namespace参数,Docker在这里更多的是辅助和管理工作。

这也解释了

为何Docker项目比虚拟机更好?

使用虚拟化技术作为应用沙盒,就必须由Hypervisor负责创建虚拟机,这个虚拟机是真实存在的,它里面必须运行一个完整的Guest OS才能执行用户的应用进程。这就不可避免地带来额外的资源消耗和占用。

据实验,一个运行着CentOS的KVM虚拟机启动后,在不做优化的情况下,虚拟机自己就需要占用100~200 MB内存。此外,用户应用运行在虚拟机里面,它对宿主机操作系统的调用就不可避免地要经过虚拟化软件的拦截和处理,这本身又是一层性能损耗,尤其对计算资源、网络和磁盘I/O的损耗非常大。

而容器化后的用户应用,依然还是宿主机上的一个普通进程,这就意味着这些因为虚拟化而带来的性能损耗都不存在。

使用Namespace作为隔离手段的容器无需单独的Guest OS,使得容器额外的资源占用可忽略不计。

“敏捷”和“高性能”是容器相较于虚拟机最大的优势。

有利必有弊,基于 Namespace 的隔离机制相比虚拟化技术也有很多不足。

1.5 Namespace的缺点

隔离不彻底

多容器间使用的还是同一宿主机os内核

尽管可在容器里通过 Mount Namespace 单独挂载其他不同版本的os文件,比如 CentOS 或者 Ubuntu,但这并不能改变共享宿主机内核的事实!

所以不可能在Windows宿主机运行Linux容器或在低版本Linux宿主机运行高版本Linux容器。

而拥有硬件虚拟化技术和独立Guest OS的虚拟机,比如Microsoft的云计算平台Azure,就是运行于Windows服务器集群,但可在其上面创建各种Linux虚拟机。

Linux内核很多资源无法被Namespace

最典型的比如时间。

若你的容器中的程序使用settimeofday(2)系统调用修改时间,整个宿主机的时间都会被随之修改,这并不符合用户预期。

而相比于在虚拟机里可自己随便折腾,在容器里部署应用时,“什么能做,什么不能做”,用户都必须考虑。

尤其是共享宿主机内核:

容器给应用暴露出来的攻击面是相当大的

应用“越狱”难度也比虚拟机低得多。

尽管可使用Seccomp等技术,过滤和甄别容器内部发起的所有系统调用来进行安全加固,但这就多了一层对系统调用的过滤,一定会拖累容器性能。默认情况下,也不知道到底该开启哪些系统调用,禁止哪些系统调用。

所以,在生产环境中,无人敢把运行在物理机上的Linux容器直接暴露至公网。

基于虚拟化或者独立内核技术的容器实现,则可以比较好地在隔离与性能之间做出平衡。

继续阅读