所属技术领域:
k8s
| 名词定义 |
k8s对Pods之间如何进行组网通信提出了要求,k8s对集群的网络有以下要求:
• 所有的Pods之间可以在不使用NAT网络地址转换的情况下相互通信
• 所有的Nodes之间可以在不使用NAT网络地址转换的情况下相互通信
• 每个Pod自己看到的自己的ip和其他Pod看到的一致
k8s网络模型设计基础原则:每个Pod都拥有一个独立的 IP地址,而且 假定所有 Pod 都在一个可以直接连通的、扁平的网络空间中 。 所以不管它们是否运行在同 一 个 Node (宿主机)中,都要求它们可以直接通过对方的 IP 进行访问。设计这个原则的原因 是,用户不需要额外考虑如何建立 Pod 之间的连接,也不需要考虑将容器端口映射到主机端口等问题。
由于 Kubemetes Kubernetes的网络模型假设 Pod 之间访问时使用的是对方 Pod 的实际地址,所以一个
Pod 内部的应用程序看到的自己的 IP 地址和端口与集群内其他 Pod 看到的一样。它们都是 Pod 实际分配的IP地址 (从dockerO上分配的)。将IP地址和端口在Pod内部和外部都保持一致, 我们可以不使用 NAT 来进行转换,地址空间也自然是平的。
鉴于上面这些要求,我们需要解决四个不同的网络问题::
• Docker容器和Docker容器之间的网络
• Pod与Pod之间的网络
• Pod与Service之间的网络
• Internet与Service之间的网络
| 发展历程 |
| 技术特点 |
容器和容器之间的网络
在k8s中每个Pod中管理着一组Docker容器,这些Docker容器共享同一个网络命名空间。
Pod中的每个Docker容器拥有与Pod相同的IP和port地址空间,并且由于他们在同一个网络命名空间,他们之间可以通过localhost相互访问。
什么机制让同一个Pod内的多个docker容器相互通信那?其实是使用Docker的一种网络模型:–net=container
container模式指定新创建的Docker容器和已经存在的一个容器共享一个网络命名空间,而不是和宿主机共享。新创建的Docker容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等
每个Pod容器有有一个pause容器其有独立的网络命名空间,在Pod内启动Docker容器时候使用 –net=container就可以让当前Docker容器加入到Pod容器拥有的网络命名空间(pause容器)
Pod与Pod之间的网络
k8s中,每个Pod拥有一个ip地址,不同的Pod之间可以直接使用该改ip与彼此进行通讯
在同一个Node上,从Pod的视角看,它存在于自己的网络命名空间中,并且需要与该Node上的其他网络命名空间上的Pod进行通信。
那么是如何做到的?这多亏了使用linux虚拟以太网设备或者说是由两个虚拟接口组成的veth对使不同的网络命名空间链接起来,这些虚拟接口分布在多个网络命名空间上(这里是指多个Pod上)。
为了让多个Pod的网络命名空间链接起来,我们可以让veth对的一端链接到root网络命名空间(宿主机的),另一端链接到Pod的网络命名空间。
每对Veth就像一根接插电缆,连接两侧并允许流量在它们之间流动;这种veth对可以推广到同一个Node上任意多的Pod上,如上图这里展示使用veth对链接每个Pod到虚拟机的root网络命名空间。
下面我们看如何使用网桥设备来让通过veth对链接到root命名空间的多个Pod进行通信。
linux以太网桥(Linux Ethernet bridge)是一个虚拟的2层网络设备,目的是把多个以太网段链接起来,网桥维护了一个转发表,通过检查转发表通过它传输的数据包的目的地并决定是否将数据包传递到连接到网桥的其他网段,网桥代码通过查看网络中每个以太网设备特有的MAC地址来决定是传输数据还是丢弃数据。
网桥实现了ARP协议用来根据给定的ip地址找到对应机器的数据链路层的mac地址,一开始转发表为空,当一个数据帧被网桥接受后,网桥会广播该帧到所有的链接设备(除了发送方设备),并且把响应这个广播的设备记录到转发表;随后发往相同ip地址的流量会直接从转发表查找正确的mac地址,然后转发包到对应的设备。
如上图显示了两个Pod通过veth对链接到root网络命名空间,并且通过网桥进行通信
3.1 同一个Node中的Pod之间的一次通信
鉴于每个Pod有自己独立的网络命名空间,我们使用虚拟以太网设备把多个Pod的命名空间链接到了root命名空间,并且使用网桥让多个Pod之间进行通信,下面我们看如何在两个pod之间进行通信:
通过网桥这里把veth0和veth1组成为一个以太网,他们直接是可以直接通信的,另外这里通过veth对让pod1的eth0和veth0、pod2的eth0和veth1关联起来,从而让pod1和pod2相互通信。
Pod 1通过自己默认的以太网设备eth0发送一个数据包,eth0把数据传递给veth0,数据包到达网桥后,网桥通过转发表把数据传递给veth1,然后虚拟设备veth1直接把包传递给Pod2网络命名空间中的虚拟设备eth0.
3.2 不同Node中的Pod之间通信
k8s网络模型需要每个pod必须通过ip地址可以进行访问,每个pod的ip地址总是对网络中的其他pod可见,并且每个pod看待自己的ip与别的pod看待的是一样的(虽然他没规定如何实现),下面我们看不同Node间Pod如何交互
k8s中每个集群中的每个Node都会被分配了一个CIDR块(无类别域间路由选择,把网络前缀都相同的连续地址组成的地址组称为CIDR地址块)用来给该Node上的Pod分配IP地址。(保证pod的ip不会冲突)
另外还需要把pod的ip与所在的nodeip关联起来()
如上图Node1(vm1)上的Pod1与Node2(vm2)上Pod4之间进行交互。
首先pod1通过自己的以太网设备eth0把数据包发送到关联到root命名空间的veth0上,然后数据包被Node1上的网桥设备cbr0接受到,网桥查找转发表发现找不到pod4的Mac地址,则会把包转发到默认路由(root命名空间的eth0设备),然后数据包经过eth0就离开了Node1,被发送到网络。
数据包到达Node2后,首先会被root命名空间的eth0设备,然后通过网桥cbr0把数据路由到虚拟设备veth1,最终数据表会被流转到与veth1配对的另外一端(pod4的eth0)
每个Node都知道如何把数据包转发到其内部运行的Pod,当一个数据包到达Node后,其内部数据流就和Node内Pod之间的流转类似了。
对于如何来配置网络,k8s在网络这块自身并没有实现网络规划的具体逻辑,而是制定了一套CNI(Container Network Interface)接口规范,开放给社区来实现。
例如AWS,亚马逊为k8s维护了一个容器网络插件,使用CNI插件来让亚马逊VPC(虚拟私有云)环境中的Node与Node直接进行交互.
CoreOS的Flannel是k8s中实现CNI规范较为出名的一种实现。
| 相关词 |
Flannel网络模型
简单说flanneld是配置不同的node节点为vtep,然后利用vxlan报文进行跨集群通信的。CNI插件,就是配置pod的网卡关联到vxlan隧道上(学名 tunnel)。每个pod都有独立的ip地址,就像我们有独立的电话号码一样,只要在这张网里,那你就可以拨号(ping一下试试)和另外的pod通信。
Pause容器
如果不细心,可能不会发现每个pod都会创建一个pause容器,pause容器和网络有什么关系呢。
这个容器负责
在pod中担任Linux命名空间共享的基础;
启用pid命名空间,开启init进程。
我们都知道同一个pod里面的容器共享命名空间,Linux 内核中就提供了这六种 namespace 隔离的系统调用,如下表所示。
pause容器就接管了pod里面其他容器的网络,同一个Pod里的容器之间仅需通过localhost就能互相通信。。
VXLan
VXLAN 协议格式 VXLAN 全称是 Virtual eXtensible Local Area Network,虚拟可扩展的局域网。它是一种 overlay 技术,通过三层网络来搭建虚拟二层网络,其帧格式为:
从这个报文中可以看到 3 部分:
最外层的 UDP 协议报文用来在底层网络上传输,也就是 vtep 之间互相通信的基础;
中间是 VXLAN 头部,vtep 接受到报文之后,去除前面的 UDP 协议部分。根据这部分来处理 VXLAN 的逻辑,主要是根据 VNI 发送到最终的虚拟机;
最里面是原始的报文,也就是虚拟机看到的报文内容。
VTEP(VXLAN Tunnel Endpoints):VXLAN 网络的边缘设备,用来进行 VXLAN 报文的处理(封包和解包)。vtep 可以是网络设备(比如交换机),也可以是一台机器(比如虚拟化集群中的宿主机);
VNI(VXLAN Network Identifier):VNI 是每个 VXLAN 的标识,是个 24 位整数,一共有 2^24 = 16,777,216(一千多万),一般每个 VNI 对应一个租户,也就是说使用 VXLAN 搭建的公有云,理论上可以支撑千万级别的租户;
Tunnel:隧道是一个逻辑上的概念,在 VXLAN 模型中并没有具体的物理实体相对应。隧道可以看做是一种虚拟通道,VXLAN 通信双方(图中的虚拟机)认为自己是在直接通信,并不知道底层网络的存在。从整体来说,每个 VXLAN 网络像是为通信的虚拟机搭建了一个单独的通信通道,也就是隧道。
Flannel
Flannel 是 CoreOS 团队针对 Kubernetes 设计的一个网络规划实现。简单来说,它的功能有以下几点:
使集群中的不同 Node 主机创建的 Docker 容器都具有全集群唯一的虚拟 IP 地址;
建立一个覆盖网络(overlay network),这个覆盖网络会将数据包原封不动的传递到目标容器中。覆盖网络是建立在另一个网络之上并由其基础设施支持的虚拟网络。覆盖网络通过将一个分组封装在另一个分组内来将网络服务与底层基础设施分离。在将封装的数据包转发到端点后,将其解封装;
创建一个新的虚拟网卡 flannel0 接收 docker 网桥的数据,通过维护路由表,对接收到的数据进行封包和转发(VXLAN);
路由信息一般存放到 etcd 中:多个 Node 上的 Flanneld 依赖一个 etcd cluster 来做集中配置服务,etcd 保证了所有 Node 上 Flannel 所看到的配置是一致的。同时每个 Node 上的 Flannel 都可以监听 etcd 上的数据变化,实时感知集群中 Node 的变化;
Flannel 首先会在 Node 上创建一个名为 flannel0 的网桥(VXLAN 类型的设备),并且在每个 Node 上运行一个名为 Flanneld 的代理。每个 Node 上的 Flannel 代理会从 etcd 上为当前 Node 申请一个 CIDR 地址块用来给该 Node 上的 Pod 分配地址;
Flannel 致力于给 Kubernetes 集群中的 Node 提供一个三层网络,它并不控制 Node 中的容器是如何进行组网的,仅仅关心流量如何在 Node 之间流转。
如上图, IP 为 10.1.15.2 的 Pod1 与另外一个 Node 上 IP 为 10.1.20.3 的 Pod2 进行通信;
首先 Pod1 通过 veth 对把数据包发送到 docker0 虚拟网桥,网桥通过查找转发表发现 10.1.20.3 不在自己管理的网段,就会把数据包转发给默认路由(这里为 flannel0 网桥);
flannel0 网桥是一个 VXLAN 设备,flannel0 收到数据包后,由于自己不是目的 IP 地址 10.1.20.3,也要尝试将数据包重新发送出去。数据包沿着网络协议栈向下流动,在二层时需要封二层以太包,填写目的 MAC 地址,这时一般应该发出 arp:”who is 10.1.20.3″。但 VXLAN 设备的特殊性就在于它并没有真正在二层发出这个 arp 包,而是由 linux kernel 引发一个”L3 MISS”事件并将 arp 请求发到用户空间的 Flannel 程序中;
Flannel 程序收到”L3 MISS”内核事件以及 arp 请求 (who is 10.1.20.3) 后,并不会向外网发送 arp request,而是尝试从 etcd 查找该地址匹配的子网的 vtep 信息,也就是会找到 Node2 上的 flannel0 的 MAC 地址信息。Flannel 将查询到的信息放入 Node1 host 的 arp cache 表中,flannel0 完成这项工作后,Linux kernel 就可以在 arp table 中找到 10.1.20.3 对应的 MAC 地址并封装二层以太包了: <缺图>
由于是 Vlanx 设备,flannel0 还会对上面的包进行二次封装,封装新的以太网 MAC 帧:
Node 上 2 的 eth0 接收到上述 VXLAN 包,kernel 将识别出这是一个 VXLAN 包,于是拆包后将 packet 转给 Node 上 2 的 flannel0。flannel0 再将这个数据包转到 docker0,继而由 docker0 传输到 Pod2 的某个容器里。
如上图,总的来说就是建立 VXLAN 隧道,通过 UDP 把 IP 封装一层直接送到对应的节点,实现了一个大的 VLAN。
适用场景:
- 同一Pod内的网络通信。在同一个Pod内的容器共享同一个网络命名空间,共享同一个Linux协议栈。所以对于网络的各类操作,就和它们在同一台机器上一样,它们可以用localhost地址直接访问彼此的端口。其实这和传统的一组普通程序运行的环境是完全一样的,传统的程序不需要针对网络做特别的修改就可以移植了。这样做的结果是简单、安全和高效,也能减少将已经存在的程序从物理机或者虚拟机移植到容器下运行的难度。
- Pod1到Pod2的网络,分两种情况。Pod1与Pod2不在同一台主机与Pod1与Pod2在同一台主机。
• 先说Pod1与Pod2不在同一台主机。Pod的地址是与docker0在同一个网段的,但docker0网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行。将Pod的IP和所在Node的IP关联起来,通过这个关联让Pod可以互相访问。
• Pod1与Pod2在同一台主机。Pod1和Pod2在同一台主机的话,由Docker0网桥直接转发请求到Pod2,不需要经过Flannel。
- Pod到Service的网络。创建一个Service时,相应会创建一个指向这个Service的域名,域名规则为{服务名}.{namespace}.svc.{集群名称}。之前Service IP的转发由iptables和kube-proxy负责,目前基于性能考虑,全部为iptables维护和转发。iptables则由kubelet维护。Service仅支持UDP和TCP协议,所以像ping的ICMP协议是用不了的,所以无法ping通Service IP。
- Pod到外网。Pod向外网发送请求,查找路由表, 转发数据包到宿主机的网卡,宿主网卡完成路由选择后,iptables执行Masquerade,把源IP更改为宿主网卡的IP,然后向外网服务器发送请求。
- 集群外部访问Pod或Service
由于Pod和Service是Kubernetes集群范围内的虚拟概念,所以集群外的客户端系统无法通过Pod的IP地址或者Service的虚拟IP地址和虚拟端口号访问到它们。为了让外部客户端可以访问这些服务,可以将Pod或Service的端口号映射到宿主机,以使得客户端应用能够通过物理机访问容器应用。
总结:Flannel实现了对Kubernetes网络的支持,但是它引入了多个网络组件,在网络通信时需要转到flannel0网络接口,再转到用户态的flanneld程序,到对端后还需要走这个过程的反过程,所以也会引入一些网络的时延损耗。另外Flannel默认的底层通信协议是UDP。UDP本身是非可靠协议,虽然两端的TCP实现了可靠传输,但在大流量、高并发应用场景下还需要反复调试,确保不会出现传输质量的问题。特别是对网络依赖重的应用,需要评估对业务的影响。
资料来源:
- 名词定义:https://www.jianshu.com/p/3f2401d14c78
- 技术特点:https://www.jianshu.com/p/3f2401d14c78
- 相关词:https://zhuanlan.zhihu.com/p/79270447
延伸阅读:
k8s网络模型