作者:李勇
一.案例背景
在容器云项目刚刚开始的时候,发现了这样一个问题:当一个应用迁移到 k8s 集群上之后,集群外部的子系统通过 Dubbo 方式调用它,直接报错,无法连接。检查 SOA 的 Zookeeper(在集群之外)发现,应用注册到 Zookeeper 里的 IP 是容器的 IP!容器的 IP 对 k8s 集群外不可见,必然连不通。
二.案例分析
为什么容器注册到 SOA 中的会是容器的 IP?这在当时我们有些不解,要知道容器发出的数据包出集群时是要做 SNAT 转换成宿主机的 IP 的,所以按说在 SOA 注册中心看到的应该是宿主机的 IP 才对。经了解才知道,SOA 服务注册采取的是“客户端发现”机制,它并不是站在 SOA 注册中心一端看向它发注册请求的机器来自于哪个 IP,而是发注册请求的机器收集自己的本地网卡 IP,然后把这个 IP 报送给注册中心,现在应用是跑在容器里的,它收集到的自然是容器内的本地网卡 IP,也就是容器的 IP。
众所周知,针对集群外主机想访问集群内的应用,k8s 只提供一种形式:暴露一个 nodeport 类型的服务(使用宿主机 IP + 端口),映射转发到容器中(通过一系列 iptables 规则实现)。可现在集群外主机偏偏要用容器 IP 去找应用,无疑给我们出了一个大难题。
k8s 的各种网络组件,Flannel、Calico、Weave等,解决了跨主机间容器直接使用自身 IP 进行通信的问题,遗憾的是它们的作用范围仅限于集群内部宿主机。仔细研究他们的工作原理,不难发现有几个共同点:
1、容器网络模式使用网桥(Bridge)模式。
2、负责容器启动时的 IP 地址分配,每台宿主机上运行的容器都属于同一个网段,不同宿主机上的容器网段必不相同,了解整个集群内的容器网段与宿主机对应关系。
和任何一个容器 IP 通信时,根据其网段查找,数据包转发到对应宿主机(区别仅在于有的通过打 VXLAN 隧道方式转发,如 Flannel,有的使用三层路由方式转发,如 Calico),然后通过宿主机内的网桥到达容器内部。
这些组件的工作原理给了我们很大启发,不管主机位于何处,要想直接和容器 IP 通信,就得了解集群内容器网段与宿主机对应关系,沿用此思路我们想到一个办法:向集群外发布到达各宿主机容器网段的路由。
三.具体实施
我们使用 Flannel 作为 k8s 网络组件,Flannel 的特点是每个宿主机上的容器网段是固定的,部署完不会再变,那我们只需要在宿主机的网关设备上添加静态路由就好。
假设集群有两个节点:
节点 1
主机IP:10.132.2.22
netmask:255.255.255.0
Gateway:10.132.2.254
容器网段:10.131.10.0/24
节点 2
主机IP:10.132.2.23
netmask:255.255.255.0
Gateway:10.132.2.254
容器网段:10.131.73.0/24
那么我们就在网关设备 10.132.2.254 上添加下列路由:
ip route 10.131.101.0/24 10.132.2.22
ip route 10.131.73.0/24 10.132.2.23
如果你的网络中运行着路由协议(OSPF、EIGRP、IS-IS…),把上述静态路由重发布(Route Redistribution)到路由协议中即可,没有,就只能一跳一跳添加静态路由了。
逻辑拓扑大致如下:
细心的读者可能会问,前面说到容器数据包出集群的时候要做 SNAT 的,那外部访问容器 IP,如果回包出集群时 SNAT 成宿主机 IP,那 TCP 三次握手不就无法建立了么,怎么能通呢?经抓包测试,k8s 只有在容器主动发起连接集群外的时候才做 SNAT,回包不做转换。
这样我们就将容器网段“透”到了集群之外,实现容器网段在机房内全网可达。
温馨提示:集群的容器网段范围在集群搭建前就要统一规划好,不能和机房任何现有网段冲突。
四.总结
此法简单有效地实现了集群内外 Dubbo 方式相互调用,虽然它还不够优雅,每增加一个计算节点就要增加一条静态路由,如果集群节点数量比较多,维护就比较麻烦,但它实实在在的解决了我们的问题,对业务系统迁移容器云工作具有重大意义。
未来随着集群规模加大,我们将考虑采用 Calico 网络组件,Calico 是在每个宿主机上运行一个虚拟路由器(VR),它们之间跑 BGP 协议,发现并公告自己之上的容器网段,只要宿主机的网关设备上支持 BGP,也参与其中,就可学到容器网段路由,发布到全网,无需人工干预。此外,我们还会考虑引入 SDN 技术与容器网络进行对接的可行性,敬请期待!