http://blog.youkuaiyun.com/coolboylai2/article/details/25410401
2.7.1 综述
网络隔离主要是利用namespace技术中的networknamespace。当创建一个instance时,首先容器会在DEA这台主机上创建一对虚拟网卡。将其中一个网卡留在宿主机的default network namespace,另一张网卡留在container networknamespace。然后对两张网卡的IP,掩码进行配置。
外界需要访问容器的时候,首先通过访问宿主机上的某一个端口,然后通过DNAT进行转换,把宿主机的某一个端口映射到容器里面的某一端口。容器里面需要访问外界的时候,通过容器内的虚拟网卡(这对虚拟网卡很有意思,任何一端接受到的消息都会发送给另外一个端),然后访问到容器外界对应的虚拟网卡。IPtables会把所有的容器的源地址转换为宿主机的IP(SNAT),然后根据Kernel Route Table表,根据不同的目的地址发往到对应的网卡。
在容器内部的KernelRoute Table会添加一个缺省的网关,这个网关就是容器外对应的张虚拟网卡。容器内对外界的所有访问都会通过这对虚拟网卡。
2.7.2 网络拓扑结构
输入流:
访问主机上的某一个端口,iptables对数据包的目的地址进行DNAT转换。然后根据KernelRoute Table将数据包分发给相应的虚拟网卡,容器外虚拟网卡接收到这些信息之后发完容器内对应的虚拟网卡,再把数据交由Application。
输出流:
容器内的数据包会通过首先发送到容器内对应的虚拟网卡,然后发送给容器外对应的虚拟网卡,经过Iptables的源地址转换,把数据包的源地址换成物理网卡IP地址,然后再发送出去。
2.7.3 Network Namespace
Network Namespace网络资源隔离的一种技术,是为了给每一个容器创建一个独立的网络空间。在clone或者fork一个子进程的时候,指定相应的flag便可创建一个独立的namespace。使用network namespace的意义在于,在每一个容器内部看来,它们都认为自己有一张独立的网卡,内核路由表以及iptables规则等。并且一个容器无法感知到其它容器的存在。
2.7.4 容器的创建
容器创建网络空间隔离,主要是用内核提供的Virtual Ethenet技术,内核提供了iplink add指令,为我们创建一对虚拟网卡(interface-0,interface-1)。这对虚拟网卡的任何一端收到消息将会发送给了另外一端。刚开始这些虚拟网卡都是存在于宿主机的default网络空间中,在启动whd子进程的时候通过ip link set指令将interface-1移入wshd这个子进程的的网络空间中,而interface-0则继续留在宿主机的default网络空间中。
create一个容器,warden会为我们创建一对虚拟网卡,如下:
w-17k4bbqqh7o-0 Link encap:以太网 硬件地址5a:87:b0:2f:aa:e5
inet 地址:10.254.0.1 广播:10.254.0.3 掩码:255.255.255.252
容器内的一个网卡
w-17k4bbqqh7o-1 Link encap:Ethernet HWaddr 1e:43:92:cd:bd:55
inet addr:10.254.0.2 Bcast:10.254.0.3 Mask:255.255.255.252
每一个容器会占用4个IP地址,相当于一个很小的子网,Ip地址使用范围参见4.5.1.
在DEA启动容器的时候会执行两次net_in 操作,但是端口没有指定,从默认的61001端口开始,容器的端口也是一样。端口使用范围4.5.2
DEA在启动一个实例的时候会进行两次NETIN操作,把主机上的两个端口映射到容器里面相应的端口。端口映射即在宿主机的nat表中添加两条转换规则把宿主机的。
iptables -t nat-A ${nat_instance_chain}
--protocoltcp
--destination"${external_ip}"
--destination-port"${HOST_PORT}"
--jump DNAT
--to-destination "${network_container_ip}:${CONTAINER_PORT}
执行两次上面的命令。查看iptablesnat表结果如下:
Chainwarden-instance-17mgcetupda (1 references)
target prot opt source destination
DNAT tcp -- anywhere cfv2b-a2b77e16-916d-4233-80a7-7102a0dc88b7.novalocal tcp dpt:61001 to:10.254.0.9:61001
DNAT tcp -- anywhere cfv2b-a2b77e16-916d-4233-80a7-7102a0dc88b7.novalocal tcp dpt:61002 to:10.254.0.10:61002
Route表的修改: Route表根据数据包的目的地址确定相应的发送给相应的网卡,路由分为几种,一种是网络路由,对应的目的地址是一端网络。一种是主机路由,对应的目的地址是一个主机地址,还有一个就是缺省路由,所有无法匹配的的地址都将会通过默认的接口进行发送。
容器内部的路由器表主要有两条,一条是网络路由,一条是缺省路由,如下:
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 10.254.0.9 0.0.0.0 UG 0 0 0 w-17m6fqlrpd9-1
10.254.0.8 * 255.255.255.252 U 0 0 0 w-17m6fqlrpd9-1
上面的路由表表示,目的网络是10.254.0.8的数据包交由w-17m6fqlrp-1这张网卡发送,这个数据包再发送到宿主机上面。缺省的发送到容器外对应的虚拟网卡10.254.0.9处理。
2.7.5 外界访问容器内部
在Cloud Foundry中,DEA负责运行应用程序,而真正负责运行的是warden创建的container。当你的一个instance在container中启动之后,外部如何访问呢?
我们知道route转发请求到某一个应用上面是通过DEAIP:PORT来进行转发的。由此可知外界是通过IP+PORT的方式来访问到应用。那么这是怎么实现的呢?主要是通过源地址转换,这个在容器创建的时候已经为我们创建了规则。以Container1为例,见下图:
DNAT tcp -- anywhere cfv2b-a2b77e16-916d-4233-80a7-7102a0dc88b7.novalocal tcp dpt:61001 to:10.254.0.10:61001
DNAT tcp -- anywhere cfv2b-a2b77e16-916d-4233-80a7-7102a0dc88b7.novalocal tcp dpt:61002 to:10.254.0.10:61002
主机上的61001端口映射到容器内的61001端口,通过这个端口我们就可以访问到应用程序。以下是端口访问示意图:
以Container1为例,在访问主机的61001端口之后,首先通过DNAT转换之后,即10.10.18.38:61001转换为10.254.0.10:61001。经过KernelRoute Table交由w-17m6fqlrpd9-0网卡。容器外部分路由表如下所示:
Kernel IP routing table
Destination Gateway Genmask Flags MetricRef Use Iface
0.0.0.0 10.10.18.254 0.0.0.0 UG 0 0 0 eth0
10.10.18.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0
10.254.0.8 0.0.0.0 255.255.255.252 U 0 0 0 w-17m6fqlrpd7-0
从第三条路由可以看到,所有发往10.254.0.8这个子网的所有数据包都是通过w-17m6fqlrpd9-0网卡发送。因为10.254.0.10/30属于子网10.254.0.8,所以发往w-17m6fqlrpd7-0网卡。虚拟网卡任何一端收到消息都会发送给另外一个端口。然后数据包发往w-17m6fqlrpd7-1网卡,然后发送到10.254.0.10:61001端口,最后被App接收。
2.7.6容器向外发送数据
容器里面的应用需要访问外界,首先会通过容器内的虚拟网卡发往在主机网络空间容器外的虚拟网卡。然后通过SNAT((SNAT:源地址转换,将数据包的源地址进行替换,把容器里面发出来的数据转发到主机的这张网卡上)转换,把数据包的源地址转换成物理网卡的IP地址,然后发送出去。
当Application要访问外部的时候,数据包首先发送到Virtual Interface-1,再发送到容器外对应的虚拟网卡Virtual Interface-0,然后通过源地址转换。
Chainwarden-postrouting (1 references)
target prot opt source destination
SNAT all -- 10.254.0.0/22 anywhere to:10.10.18.38
把所有10.254.0.0/22(容器的虚拟网卡对应IP地址段)转换到10.10.18.38(物理网卡IP)。最后通过物理网卡发送出去。
2.7.7 端口使用
DEA在start一个instance的时候会进行两次Netin操作,也就是进行两次端口映射,把宿主机上的两个端口映射到容器内部,DEA使用的是默认端口(端口使用).一个端口用于应用容器的访问,一个端口用于容器console链接。
一个端口用于应用程序访问,比如访问hello.cf.local,访问到的就是相应的应用程序端口。
在2.7.4中提到在instance启动时候会把主机上的两个端口映射给对应的容器。
DNAT tcp -- anywhere cfv2b-a2b77e16-916d-4233-80a7-7102a0dc88b7.novalocal tcp dpt:61001 to:10.254.0.46:61001
DNAT tcp -- anywhere cfv2b-a2b77e16-916d-4233-80a7-7102a0dc88b7.novalocal tcp dpt:61002 to:10.254.0.46:61002
假设hello这个应用程序就是存放在部署在10.254.0.46这个容器里面,访问hello,cf.local就会访问到DeaHostIp:61001,然后访问到10.254.0.46:61001,最后被应用程序接收。
另外一个端口用于console链接,console链接是用来对应用程序在运行过程中出现的一些问题或者删除一些数据,都需要通过console端口来进行使用。cf console这个命令的资料较少。主要是CloudFoundry官博上的两篇文章来理解cf console的使用。
在hello这个程序中就是61001这个端口,通过cfconsole hello(cf console appname)就可以访问到应用的console,不过这需要应用程序的支持,也就是说应用程序必须监听这个端口,并且提供相应的操作。
2.7.8 Virtual Ethernet
Virtual Ehernet技术是由内核提供用于网络空间的隔离,对应的指令是iplink add name $network_host_iface type veth peer name $network_container_iface
添加两个网卡,一个是network_host_iface,一个$network_container_iface。
Ip link add用的就是veth技术,veth就是专门为linux container所创建的,他会创建一个成对的设备,一个在container之内,一个在container之外。两个设备除了名字不一样,其他都是一样的,一个设备接收到数据包会发送给另外一个设备。所以两者是相通的,veth全称就是 Virtual Ethernet。