最近发现自己似乎从来没学明白过Kubernetes网络通信方案,特开一贴复习总结一下。
在k8s中,每个 Pod 都拥有一个独立的 IP 地址,而且假定所有 Pod 都在一个可以直接连通的、扁平的网络空间中。所以不管它们是否允许在同一个 Node(宿主机)中,都要求它可以直接通过对方的 IP 进行访问。用户不需要额外考虑如何建立 Pod 之间的连接,也不需要考虑将容器端口映射到主机端口等问题。同时,外界的
省流,K8s网络的目标是为了实现:
- pod之间的互联互通
- 对外提供服务
为了实现上述功能,需要为每一个pod都维护完整的网络协议栈,同时,维护pod之间的网络路由。
CNI容器网络接口
首先,当你起了一个裸的、没有网络的pod之后,这个pod是没有任何基于TCP/IP手段和外界进行通信的。你可能只能通过crictl的客户端看到自己起了这么一个pod。
为了建立pod和外界的(基于TCP/IP的)通信,并且将不同pod的网络隔离开,需要为每一个pod构建一个网络命名空间。网络命名空间(Network Namespace)是一种内核级别的隔离机制,用于将不同进程组(比如一个pod就是一个被隔离的进程组)的网络环境隔离开来。每个网络命名空间拥有独立的网络栈,实现网络资源的完全隔离。
具体一点,网络命名空间的核心功能及特性是:
- 网络隔离
不同网络命名空间的进程无法直接通信,需通过跨命名空间的网络设备(如 veth 对、网桥)或物理设备连接。例如,pod A 在命名空间ns1,pod B 在命名空间ns2,它们的(虚拟)物理层、数据链路层、网络层、传输层、应用层都实现了相互独立。 - 独立网络栈
每个命名空间可独立配置 IP 地址、子网、网关、DNS 服务器等。 - 轻量级隔离
相比虚拟机,网络命名空间的资源开销极小,适合容器场景。
我们首先实现同一台物理机内pod之间的相互通信。
为了实现不同网络命名空间之间的通信,需要把若干的pod从物理层到网络层连起来。
我们首先实现(虚拟)物理层和数据链路层的相互连接,这基于veth设备对。
你可以把它想象为一根带有两个水晶头的网线,两端(veth1和veth2)分别插在两个pod上。如下图所示:

在有多个pod的情况下,一般会新建一个公用的虚拟交换机(如图所示的bridge),然后所有的pod都接到这个交换机上,也能通过这个交换机实现pod之间的通信。如下图所示:

图里面这个bridge是一个虚拟网络设备,所以具有网络设备的特征,可以配置IP、MAC地址等;其次,bridge是一个虚拟交换机,和物理交换机有类似的功能。
对于普通的网络设备来说,只有两端,从一端进来的数据会从另一端出去,如物理网卡从外面网络中收到的数据会转发给内核协议栈,而从协议栈过来的数据会转发到外面的物理网络中。
而bridge不同,bridge有多个端口,数据可以从任何端口进来,进来之后从哪个口出去和物理交换机的原理差不多,要看数据包指向的终端MAC地址。
进一步地,我们给pod、网桥都配上IP地址,于是我们实现了同一台物理机内pod之间的相互通信。

进一步地,我们需要实现跨不同主机pod之间的通信。
一种简单的想法是,建一个跨多个主机的bridge不就得了?

这种在已有的网络上通过软件构建一个扩展版虚拟网络的方法,被称为:Overlay Network(覆盖网络)。从实现效果来看,就是把若干个节点上的小bridge整合成了一个大bridge。
Calico和Flannel是overlay network的两种实现方案。
Calico和Flannel
为了实现跨节点之间的数据通信,这就需要calico和flannel出场了。
跨节点数据包路由,主要就是两种情形:
- 节点和节点之间能够通过IP直连(三层可达)
- 节点和节点之间连接复杂(三层不可达)
解决方案也很简单,对于三层可达的网络,可以直接配置节点的路由表进行转发。对于三层不可达的网络,搭一个隧道实现网络穿透就可以了。
从这个视角看,Calico和Flannel两个插件虽然名字不太一样,但是实现的功能大同小异。
calico中,主要提供两种跨node包路由的模式:
- BGP 模式:在三层可达的时候,Calico 在每个节点上运行 BGP 客户端,将 Pod 的 IP 路由信息通过 BGP 协议通告给其他节点。pod间流量直接通过路由规则进行转发。
- IPIP 模式:当两个节点不在同一子网(三层不可达)时,Calico 将Pod间数据包的外边再包一层IP 头,通过隧道实现传输。

flannel中,也提供两种跨节点包路由的方式:
- VXLAN模式:VXLAN 是 Linux 内核原生支持的网络虚拟化技术,通过在 IP 包内封装二层以太网帧,实现跨节点的虚拟二层网络。原理和Calico的IPIP模式类似。
- Host-gateway模式:直接利用节点的物理网络进行路由,无需隧道封装,通过将 Pod 子网的路由直接指向目标节点的物理 IP,实现跨节点通信。
Kube-proxy
在前文中,我们已经通过calico / flannel等技术实现了同一个集群内pod的互联互通。但是仍然有一些比较关键的需求需要解决。
在实际使用集群的过程中,pod启动时的IP是随机分配的,这意味着访问集群内部pod必不能用硬编码方式实现。此外, Pod 实例会动态变化,需要设计动态服务发现和动态负载均衡方案。要在pod不断的变化的IP中追求不变,并且抓住这个不变实现集群内部pod的访问。
K8S的解决方案是service+DNS。Service里记录了pod的标签和端口映射规则(只是一个配置文件)。在用户向集群提交service的配置后,集群会创建和service同名的endpoint对象,用于根据service记录的标签去匹配对应的某些pod。用户在访问某些特定pod的时候,只需要访问service/endpoint的IP即可,由endpoint实现流量的路由和负载均衡。
由于endpoint的IP也在变,所以集群内引入了DNS机制,为每一个service都赋予了一个域名,通过service域名访问对应的pod。
为了实现上述功能,需要不断地监控pod的状态,进而实现路由转发。Kube-proxy提供了这样的解决方案。
kube - proxy 会持续监控 API Server,当 Service 或 Endpoint 资源发生变更时,kube - proxy 会通过list-watch手段获取这些信息。
kube - proxy 根据这些信息更新本地的iptables/ipvs规则,这些规则决定了如何将流量从 Service 的 IP 和端口转发到后端的 Pod。
references
https://segmentfault.com/a/1190000009491002
https://learn.lianglianglee.com
https://zhuanlan.zhihu.com/p/439920165
https://blog.csdn.net/weixin_42587823/article/details/144938902