news banner

很多人会认为同一台主机下,通常会有很多容器。且这些容器彼此都靠网络沟通,各种服务都需要放在容器里,例如:数据库放在容器下且暴露在外底纹下,不就很不安全吗?

以下就介绍几个Linux下容器的隔离技术,来说明数据库放在容器下是安全的。

从前面几篇的部落格文章中(容器技术革命:Docker带动IT转型与软件开发革命的Kubernetes),我们可以知道利用容器化技术可以在同一台主机上很快速的仿真成多台主机跑许多不同应用互相隔离的假象,那它里面是透过什么方式办到的?

 

命名空间(Namespace)

在Linux世界中,docker使用六种不同的Namespace,来实现资源隔离的容器。下面简单的介绍一下,这六种Namespace:

1. UTS namespace(Unix Time-sharing):每台不同的主机都会有不同的主机名,然而UTS namespace提供了主机名与域名的隔离,这样每个docker容器就可以拥有独立的主机名和域名了,在网络上可以被视作一个独立的节点,而不会与宿主机的主机名互相干扰。

2. IPC namespace(Inter-Process Communication):是Unix/Linux下行程之间通信的方式,例如IPC有共享内存、讯号量、消息队列等方法。既然是仿真多台主机的方式,那你的宿主机与容器里面的行程总不可能共享同一份内存空间吧?所以才需要透过此方式来隔离。

3. PID namespace(Process ID):不管在Windows或是Linux上,在同一个主机上,你上面跑的Process的ID是不可能一样的,例如:主机A上面跑了两个行程tomcat(PID 10000)、mongodb(PID 10001),这边的tomcat PID与mongodb PID在同一台主机下永远是不可能一样的,否则当你要以PID号码发讯号杀掉一个行程时,操作系统怎么会知道要杀掉tomcat还是mongodb?但是如果在另外一台B主机下,PID就可能会重复。那我们有没有办法既在同一台主机下,可以仿真成多台主机下,彼此可以拥有相同的PID又不互相干扰?没错,就是使用划分不同的PID namespace,每个PID namespace,里面的process可以和另外一个PID namespace,拥有重复的PID名称,彼此不互相干扰,就像在多台主机上。

4. mount namespace:顾名思义,就是用来隔离文件系统的挂载点,使得不同的mount namespace拥有自己的独立挂载点讯息,宿主机与容器内互相使用不同的mount namespace而不会互相干扰。

5. network namespace:这个namespace算得上是容器技术的精华,主要提供了关于网络资源的隔离,包含网络设备、IPv4、IPv6协议、IP路由表、防火墙等资源的隔离,否则如果你在容器内执行一个apache的行程,宿主机上也执行apahce,你很可能会得到80端口已经被占用的错误。就是靠这个network namespace才有办法使得我们在容器的世界里,宿主机与各个不同容器内,都使用不同的network namespace,彼此才可以使用相同的行程相同的端口不互相被干扰。

6. user namespace:主要隔离了user权限相关的Linux资源,包括user IDs and group IDs,keys,和capabilities。这是目前namespace中最复杂的一个,因此在这边并不打算提太多,有兴趣可以参考下列连结:

参考数据1 / 参考数据2 / 参考数据3

因为user和权限息息相关,而权限又事关容器的安全,所以稍微有不慎就会出现安全问题。这边只打算举例一个比较直觉的例子,假设你在容器内所使用的root(超级使用者,Linux世界中拥有至高的权力),与外面宿主机上的root是同样的?试想,这是一件非常可怕的一件事,如果今天有心人拿到你任何一个容器root权限,那他不就可以对你的宿主机做任何事情了,很可怕吧。但这个安全问题在设计Linux与容器的专家也会想到这件事,因此他透过这个user namespace的机制,让容器内的root,映设回去宿主机的某一个普通的账号,而这个账号刚好是容器内那个伪root权限刚好可以做的事情,一来你在容器内,只会无感的觉得你仍是root身份,二来就算这个容器被骇了,它在宿主机上也仅仅只是一个普通user的身份,大大提高了安全性。

最后再提一下,除了用上面那六种namespace来达到隔离的效果之外,容器的技术也用了chroot来更改容器根目录的位置,举例来说,我在宿主机外有个目录是/a/b/c,在c目录中其实就是放了容器执行时所需要的配置文件以及相关的binary档,在执行chroot,把容器内的根目录的位置改成宿主机的/a/b/c。也因此透过那六个namespace,再加上chroot更改容器根目录的位置,当你登入容器时,就会像是真的在一台实体主机上,就是这样由来的。

前面讲了六个namespace,我用一张图来做个小结,如图:

Linux namespace

这张图是在同一台主机上,底层是硬设备,再来是那六个namespace,你应该可以发现一件事,原来那六个namespace是直接Linux Kernel可以支持,因此在Linux几乎是随手就可以用容器,非常的快速与方便,那右边的Control group是什么?他也是在Linux kernel里,用来控制容器内的资源限制,举例来说,有人在名叫movie容器执行了一个看影片的行程,为了看这个影片,把我的宿主机上所有的CPU、Memory资源都吃光光了?那其他人不就都拿不到资源都不用写code做事了,如果你是老板允许这种事发生吗?Control group就是用来控制容器使用宿主机上的硬件资源。再来最上面的就是我们关注的容器,由图可知,用Namespace的技术,来达到隔离的效果,就像是有三台主机CentOS、Ubuntu Precise、Ubuntu Trusty,底层分别跑apache、MySQL,tomcat、Rails,MongoDB、Nginx。

上面不管是资源限制、安全性考虑等等,容器技术都帮你考虑到了,这也是为什么现在容器化技术这么火红的原因了。

 

Docker容器网络(单机)

如下图:

Docker 3

同一台主机下,有三个不同的network namespace(Host、Container1、Container2),也因此彼此像是都在不同的实体的主机下,互相隔离,绿色的部分是由docker虚拟出来的网络接口,在Container1与Container2中的eth0像是这两台主机中自己的网卡接口,而docker0这个接口就像是我们实体的网络设备Switch(交换器),vethxxxxxx、vethyyyyyy就像是这台Switch上的某两个port网络孔,所以vethxxxxxx与Container1的eth0是一对的,而vethyyyyyy与Container2的eth0是一对的,你可以把它们整体想成我有两台主机分别为Container1与Container2,实体线路接在docker0这台Switch上,加上这两台主机又是在同一个网络(172.17.0.0/16)上,所以他们彼此可以互相沟通。

那个紫色的”实体”网卡接口eth0是做什么的?这也很容易理解,如果你的ContainerX需要出去因特网的话,自然就是透过这张卡出去的。

这里稍微再补充一下,这张图的容器网络网段是172.17.0.0/16,但是紫色那张eth0的实体网络接口的网段是192.168.1.0/24,显然是不同的网段,实际出去时会把封包表头来源地址172.17.0.5改成192.168.1.123再从紫色的eth0出去,这用了NAT的技术,更多细节请参考 wiki

 

Kubernetes网络(多机)

以下的范例架构图Kubernetes + calico (Kubernetes所使用的第三方网络插件) + ipip协议为例,如下图:

Kubernetes 5

左边的Pod web-service-0001尝试写数据进去Pod database-0001,那它们之间是怎么从容器再透过host1传送到host2中的容器的?如果忘记Pod是什么的话,可以参考之间部落格文章 ”带动IT转型与软件开发革命的Kubernetes”。

因为容器技术的关系,Pod web-service-0001像是独立的一台主机,所以它有自己独立的路由表,当web-service-0001要传送资料给database-0001的时候,会根据自己的路由表把封包传送到外面的host1的caliXXXXXX接口,这个接口是没有IP地址的,这段路由的过程使用了proxy arp技术,更多可以参考 wiki

然而封包送到caliXXXXXX之后,host1这台实体主机也有自己的路由表,会再根据自己的路由表把封包往下送到tunl0最后再由紫色的eth0实体接口出去,这边再提一下,因为host1的tunl0是172.17.0.0/16的网段,而紫色的eth0是192.168.1.0/24的网段,两个很明显的是不同的网段,在上个例子Docker单机网络这边是使用NAT的技术,而calico是使用IP in IP的技术出去,等host2收到来自host1的封包之后,也是根据host2路由表往database-0001里面送,最终database-0001就收到由web-service-0001送来的资料了!

 

用个比较生活化的例子来大概描述这个过程,假设小博哥(web-service-0001)想从台湾写一封情书给美国的小美(database-0001),小博哥把情书的内容写完之后,放入信封,附上小美他家的地址,并还要注明小美的大名,最后放入邮筒(web-service-0001 eth0),等邮差车来运载。当邮差来收完这封信的时候,会先载到邮局(caliXXXXXX),然后再把这封信运到地区的大型邮件集散地(host1 tunl0),并根据信封上面的地址进行包裹的分类,原来是要送往国外的,再把信件往海关(host1 eth0)送,最后以空运出去(途中LAN那条线),然而对方国家也是会有海关(host2 eth0),再送往大型邮件集散地(host2 tunl0),对方的大型邮件集散地将货柜打开之后,依据各包裹的地址进行分类送往邮局(host2 caliYYYYYY),由邮差将分类过后的信件运载到正确地址的邮筒(database-0001 eth0),最后小美从邮筒取出收到情书。

根据上述的技术可以看出,只要善用容器网络隔离技术,数据库放在容器中是非常安全的。容器中的服务是躲在宿主机里面的,没有直接暴露在宿主机上,毕竟它与宿主机的网段根本是不同的网段!

注:参考更多calico信息 以及 IP in IP是一种网络协议,由calico所采用,想要了解请参考:https://en.wikipedia.org/wiki/IP_in_IP

 

Kubernetes的网络有非常多作法,然而上面举的例子只是其中一个例子(calico)的一个网络协议(IP in IP),当然还有其他不只Calico的第三方插件,举例还有Flannel、Linen、Open vSwitch、Weave等其他第三方插件这么多种,Calico只是冰山一角而已,有兴趣的人可以上网找数据。

 

作者:德鸿科技 研发部 Andrew