在Cloud Foundry v2版本中,该平台使用warden技术来实现用户应用实例运行的资源控制与隔离。
简要的介绍下warden,就是dea_ng如果需要运行用户应用实例(本文暂不考虑warden container提供staging打包环境),则发送相应请求给warden server,由warden server来创建warden container,并在warden container内部运行应用实例,而warden container的具体实现中使用cgroups等内核虚拟化技术,保证了container内进程组的资源控制与隔离。warden的架构与实现可以参考我的博文:Cloud Foundry中warden的架构与实现。
warden container的功能可以认为与传统的虚拟机类似,只是传统的虚拟机通过Hypervisor等技术来实现资源的控制与隔离,而warden container通过虚拟化内核来达到该目的。而在网络方面,warden container技术创建出一块虚拟网卡,专门供warden container内部使用,另外为warden container内部的虚拟网卡在warden server所在的宿主机上也配对了一块虚拟网卡,充当container的外部网关。仅仅创建出两块虚拟网卡,原则上可以保证物理上的“连通”,但是却很难做到网络间通信的“联通”,故在物理资源以及虚拟物理资源完备的情况,warden server在宿主机上通过iptables设计并添加了多条规则,保证整个网络的通信可以满足warden container的要求,同时又不影响宿主机的通信。
以下是warden server所在宿主机的物理环境、iptables所在的网络层、操作系统、用户应用层之间的关系图:
function setup_filter() { teardown_filter # Create or flush forward chain iptables -N ${filter_forward_chain} 2> /dev/null || iptables -F ${filter_forward_chain} iptables -A ${filter_forward_chain} -j DROP # Create or flush default chain iptables -N ${filter_default_chain} 2> /dev/null || iptables -F ${filter_default_chain} # Always allow established connections to warden containers iptables -A ${filter_default_chain} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT for n in ${ALLOW_NETWORKS}; do if [ "$n" == "" ] then break fi iptables -A ${filter_default_chain} --destination "$n" --jump RETURN done for n in ${DENY_NETWORKS}; do if [ "$n" == "" ] then break fi iptables -A ${filter_default_chain} --destination "$n" --jump DROP done iptables -A ${filter_default_chain} --jump REJECT # Accept packets related to previously established connections iptables -I INPUT -m state --state ESTABLISHED,RELATED --jump ACCEPT -m comment --comment 'related-traffic' # Reject traffic from containers to host if [ "$ALLOW_HOST_ACCESS" != "true" ]; then iptables -A INPUT -s ${POOL_NETWORK} --jump REJECT -m comment --comment 'block-traffic-to-host' fi # Forward outbound traffic via ${filter_forward_chain} iptables -A FORWARD -i w-+ --jump ${filter_forward_chain} # Forward inbound traffic immediately default_interface=$(ip route show | grep default | cut -d' ' -f5 | head -1) iptables -I ${filter_forward_chain} -i $default_interface --jump ACCEPT }
# Always allow established connections to warden containers iptables -A ${filter_default_chain} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT由注释可以发现,该规则添加在filter_default_chain链中,当匹配条件为:该数据包表示为是在已建立的连接上传输时,对该数据包采取ACCEPT操作。该规则从功能的角度很好理解,也就是:假如warden container内部发起一个请求,向外建立一个连接,则在该连接上返回数据包时,始终接受这样的数据包。然而,我始终对该规则的底层实现,抱有很大的疑惑。待下文分析了更多的warden 网络背景知识之后,再进行进一步的阐述。
# Accept packets related to previously established connections iptables -I INPUT -m state --state ESTABLISHED,RELATED --jump ACCEPT -m comment --comment 'related-traffic' # Reject traffic from containers to host if [ "$ALLOW_HOST_ACCESS" != "true" ]; then iptables -A INPUT -s ${POOL_NETWORK} --jump REJECT -m comment --comment 'block-traffic-to-host' fi
# Forward outbound traffic via ${filter_forward_chain} iptables -A FORWARD -i w-+ --jump ${filter_forward_chain} # Forward inbound traffic immediately default_interface=$(ip route show | grep default | cut -d' ' -f5 | head -1) iptables -I ${filter_forward_chain} -i $default_interface --jump ACCEPT其中第一条规则表示:对于从网络设备接口名字为‘w-+’(正则表达式,也就是为warden container而虚拟出的虚拟网卡的设备名字)发来,同时又需要执行转发工作的数据包,将该数据包跳转至filter_forward_chain链。这样保证了,所有从warden container发出,又不是发往宿主机的网卡的数据包,进入FORWARD链后直接进入filter_forward_chain链。
function setup_nat() { teardown_nat # Create prerouting chain iptables -t nat -N ${nat_prerouting_chain} 2> /dev/null || true # Bind chain to PREROUTING (iptables -t nat -S PREROUTING | grep -q "\-j ${nat_prerouting_chain}\b") || iptables -t nat -A PREROUTING \ --jump ${nat_prerouting_chain} # Bind chain to OUTPUT (for traffic originating from same host) (iptables -t nat -S OUTPUT | grep -q "\-j ${nat_prerouting_chain}\b") || iptables -t nat -A OUTPUT \ --out-interface "lo" \ --jump ${nat_prerouting_chain} # Create postrouting chain iptables -t nat -N ${nat_postrouting_chain} 2> /dev/null || true # Bind chain to POSTROUTING (iptables -t nat -S POSTROUTING | grep -q "\-j ${nat_postrouting_chain}\b") || iptables -t nat -A POSTROUTING \ --jump ${nat_postrouting_chain} # Enable NAT for traffic coming from containers (iptables -t nat -S ${nat_postrouting_chain} | grep -q "\-j SNAT\b") || iptables -t nat -A ${nat_postrouting_chain} \ --source ${POOL_NETWORK} \ --jump SNAT \ --to $(external_ip) }
该部分的内容比较容易理解:
1.首先创建nat_prerouting_chain 链,随后将所有的PREROUTING链中的数据包都跳转至nat_prerouting_chain;
2.随后将链绑定在nat_prerouting_chain上,保证通过lo网络设备的数据包,都跳转到该链;
3.创建nat_postrouting_chain链,保证将默认的POSTROUTING链中的所有数据包都跳转至创建的该链;
4.为从warden container中发出的数据包都采取SNAT处理,使得数据包的源IP都替换为warden server宿主机的IP。
其实在完成以上这两步之后,还执行了以下代码:
# Enable forwarding echo 1 > /proc/sys/net/ipv4/ip_forward
前篇分析的是,warden server启动的时候,对warden server所在的宿主机进行的iptbales设置,在整个过程中,不涉及任何的container。而这一章节,将介绍分析warden server在启动某个具体warden container的时候,再对warden server所在的宿主机设置iptables规则。该文件地址如下:warden/warden/root/linux/skeleton/net.sh。
首先分析setup_filter方法,源码如下:
function setup_filter() { teardown_filter # Create instance chain iptables -N ${filter_instance_chain} iptables -A ${filter_instance_chain} \ --goto ${filter_default_chain} # Bind instance chain to forward chain iptables -I ${filter_forward_chain} 2 \ --in-interface ${network_host_iface} \ --goto ${filter_instance_chain} # Create instance log chain iptables -N ${filter_instance_log_chain} iptables -A ${filter_instance_log_chain} \ -p tcp -m conntrack --ctstate NEW,UNTRACKED,INVALID -j LOG --log-prefix "${filter_instance_chain} " iptables -A ${filter_instance_log_chain} \ --jump RETURN }
1. 该方法创建了关于instance的链,随后,将该warden container instance链内的所有数据包都跳至filter_default_chain,也就是在前一章节中提到的filter_default_chain;
2.绑定该warden container instance的FORWARD链至filter_foward链,也就是说,所有进入filter_foward_chain的数据包,根据数据包从哪个网络接口设备来,进入相应的filter_instance_chain链。也就是说,从network_host_iface网络设备“接收”到的数据包(network_host_iface即为network_container_iface="w-${id}-1"),都转至filter_instance_chain。
3.创建log chain。
在创建warden container的时候,也会设置nat,源码如下:
function setup_nat() { teardown_nat # Create instance chain iptables -t nat -N ${nat_instance_chain} # Bind instance chain to prerouting chain iptables -t nat -A ${nat_prerouting_chain} \ --jump ${nat_instance_chain} }
使得Cloud Foundry内warden container的用户应用实例可以为外部提供web服务,那么warden container和warden server所在宿主机进行一个端口映射操作,也就是warden server提供的net_in接口,真正的执行部分,即为创建容器的该net.sh文件中的in参数执行。
以下是net_in具体操作的源码实现:
"in") if [ -z "${HOST_PORT:-}" ]; then echo "Please specify HOST_PORT..." 1>&2 exit 1 fi if [ -z "${CONTAINER_PORT:-}" ]; then echo "Please specify CONTAINER_PORT..." 1>&2 exit 1 fi iptables -t nat -A ${nat_instance_chain} \ --protocol tcp \ --destination "${external_ip}" \ --destination-port "${HOST_PORT}" \ --jump DNAT \ --to-destination "${network_container_ip}:${CONTAINER_PORT}" ;;
以上便是对Cloud Foundry v2中warden的网络设计之iptables规则制定,做了简要的分析。然而,在这之中,有很多的设计仅仅是留了接口,并未真正实现。
另,感谢浙江大学VLIS实验室的实习生高相林的共同研究。
关于作者:
孙宏亮,DAOCLOUD软件工程师。两年来在云计算方面主要研究PaaS领域的相关知识与技术。坚信轻量级虚拟化容器的技术,会给PaaS领域带来深度影响,甚至决定未来PaaS技术的走向。
转载请注明出处。
本文更多出于我本人的理解,肯定在一些地方存在不足和错误。希望本文能够对接触warden网络配置的人有些帮助,如果你对这方面感兴趣,并有更好的想法和建议,也请联系我。