本文由 CNCF + Alibaba 云原生技术公开课 整理而来

Kubernetes 网络模型演化

  • Docker 网络:

20. Kubernetes网络模型_Kubernetes

容器网络发端于 Docker 的网络。Docker 使用了一个比较简单的网络模型,即内部的网桥加内部的保留 IP。这样设计的好处在于容器的网络和外部世界是解耦的,无需占用宿主机的 IP 或者宿主机的资源,完全是虚拟的。

它的设计初衷是:当需要访问外部世界时,会采用 SNAT 这种方法来借用 Node 的 IP 去访问外面的服务。比如容器需要对外提供服务的时候,所用的是 DNAT 技术,也就是在 Node 上开一个端口,然后通过 iptable 或者别的某些机制,把流量导入到容器的进程上以达到目的。

该模型的问题在于,外部网络无法区分哪些是容器的网络与流量、哪些是宿主机的网络与流量。比如,如果要做一个高可用的时候,172.16.1.1 和 172.16.1.2 是拥有同样功能的两个容器,此时我们需要将两者绑成一个 Group 对外提供服务,而此时从外部看来两者没有相同之处,它们的 IP 都是借用宿主机的端口,因此很难将两者归拢到一起。

  • Kubernetes 网络:

20. Kubernetes网络模型_Kubernetes_02

在此基础上,Kubernetes 提出了这样一种机制:即每一个 Pod,也就是一个功能聚集小团伙应有自己的“身份证”,或者说 ID。在 TCP 协议栈上,这个 ID 就是 IP。

这个 IP 是真正属于该 Pod 的,外部世界不管通过什么方法一定要给它。对这个 Pod IP 的访问就是真正对它的服务的访问,中间拒绝任何的变造。比如以 10.1.1.1 的 IP 去访问 10.1.2.1 的 Pod,结果到了 10.1.2.1 上发现,它实际上借用的是宿主机的 IP,而不是源 IP,这样是不被允许的。Pod 内部会要求共享这个 IP,从而解决了一些功能内聚的容器如何变成一个部署的原子的问题。

剩下的问题就是部署手段。Kubernetes 对怎么实现这个模型其实是没有什么限制的,用 underlay 网络来控制外部路由器进行导流是可以的;如果希望解耦,用 overlay 网络在底层网络之上再加一层叠加网,这样也是可以的。总之,只要达到模型所要求的目的即可。


Pod 如何上网

容器网络的网络包究竟是怎么传送的?

20. Kubernetes网络模型_云原生_03

可以从两个维度来看:

协议层次

网络拓扑

  • 协议层次:

它和 TCP 协议栈的概念是相同的,需要从两层、三层、四层一层层地摞上去,发包的时候从右往左,即先有应用数据,然后发到了 TCP 或者 UDP 的四层协议,继续向下传送,加上 IP 头,再加上 MAC 头就可以送出去了。收包的时候则按照相反的顺序,首先剥离 MAC 的头,再剥离 IP 的头,最后通过协议号在端口找到需要接收的进程。

  • 网络拓扑:

一个容器的包所要解决的问题分为两步:第一步,如何从容器的空间 (c1) 跳到宿主机的空间 (infra);第二步,如何从宿主机空间到达远端。

个人的理解是,容器网络的方案可以通过接入、流控、通道这三个层面来考虑。

接入,就是说容器和宿主机之间是使用哪一种机制做连接,比如 Veth + bridge、Veth + pair 这样的经典方式,
      也有利用高版本内核的新机制等其他方式(如 mac/IPvlan 等),来把包送入到宿主机空间;
 
流控,就是说方案要不要支持 Network Policy,如果支持的话又要用何种方式去实现。这里需要注意的是,实现方式一定需要在数据路径必经的一个关节点上。
      如果数据路径不通过该 Hook 点,那就不会起作用;
 

通道,即两个主机之间通过什么方式完成包的传输。有很多种方式,比如以路由的方式,具体又可分为 BGP 路由或者直接路由,还有各种各样的隧道技术等等。
      最终实现的目的就是一个容器内的包通过容器,经过接入层传到宿主机,再穿越宿主机的流控模块(如果有)到达通道送到对端。


Service 如何工作

Service 其实是一种负载均衡 (Load Balance) 的机制。

可以认为它是一种用户侧(Client Side) 的负载均衡,也就是说 VIP 到 RIP 的转换在用户侧就已经完成了,并不需要集中式地到达某一个 NGINX 或者是一个 ELB 这样的组件来进行决策。

20. Kubernetes网络模型_云原生_04

Service 的实现是这样的:首先是由一群 Pod 组成一组功能后端,再在前端上定义一个虚 IP 作为访问入口。一般来说,由于 IP 不太好记,因此还会附赠一个 DNS 的域名,Client 先访问域名得到虚 IP 之后再转成实 IP。Kube-Proxy 则是整个机制的实现核心,它隐藏了大量的复杂性。它的工作机制是通过 ApiServer 监控 Pod/Service 的变化(比如是不是新增了 ServicePod)并将其反馈到本地的规则或者是用户态进程。


Service 类型

Service 类型可以分为以下 4 类。

  • ClusterIP

Kubernetes 集群内部的一个虚拟 IP,这个 IP 会绑定到一堆服务的 Group Pod 上面,这也是默认的服务方式。它的缺点是这种方式只能在 Node 内部也就是集群内部使用。

  • NodePort

供集群外部调用。类似于容器的端口映射,将 Service 承载在 Node 的静态端口上,端口号和 Service 一一对应,那么集群外的用户就可以通过 的方式调用到 Service

  • LoadBalancer

给云厂商的扩展接口。像阿里云、亚马逊这样的云厂商都是有成熟的 LB 机制的,这些机制可能是由一个很大的集群实现的,为了不浪费这种能力,云厂商可通过这个接口进行扩展。它首先会自动创建 NodePortClusterIP 这两种机制,云厂商可以选择直接将 LB 挂到这两种机制上,或者两种都不用,直接把 Pod 的 RIP 挂到云厂商的 ELB 的后端也是可以的。

  • ExternalName

摈弃内部机制,依赖外部设施,比如某个用户特别强,他觉得内部提供的都没什么用,就是要自己实现,此时一个 Service 会和一个域名一一对应起来,整个负载均衡的工作都是外部实现的。

下图是一个实例。它灵活地应用了 ClusterIPNodePort 等多种服务方式,又结合了云厂商的 ELB,变成了一个很灵活、极度伸缩、生产上真正可用的一套系统。

20. Kubernetes网络模型_云原生_05

首先用 ClusterIP 来做功能 Pod 的服务入口。可以看到,如果有三种 Pod 的话,就有三个 Service ClusterIP 作为它们的服务入口。这些方式都是 Client 端的,如何在 Server 端做一些控制呢?

首先会起一些 IngressPodIngress 是 Kubernetes 新增的一个资源对象,本质上还是一堆同质的 Pod),然后将这些 Pod 组织起来,暴露到一个 NodePort 的 IP,Kubernetes 的工作到此就结束了。

任何一个用户访问 23456 端口的 Pod 就会访问到 Ingress 的服务,它的后面有一个 Controller,会把 Service IPIngress 的后端进行管理,最后会调到 ClusterIP,再调到功能 Pod。前面提到去对接云厂商的 ELB,可以让 ELB 去监听所有集群节点上的 23456 端口,只要在 23456 端口上有服务的,就认为有一个 Ingress 的实例在跑。

整个的流量经过外部域名的一个解析跟分流到达了云厂商的 ELB,ELB 经过负载均衡并通过 NodePort 的方式到达 IngressIngress 再通过 ClusterIP 调用到后台真正的 Pod。这种系统看起来比较丰富,健壮性也比较好。任何一个环节都不存在单点的问题,任何一个环节也都有管理与反馈。