1. 概述

kubernetes的网络所涉及的内容很多,拥有许多成熟的技术,要理解清楚网络模型的概念,就要搞懂以下四个点:

  • 容器到容器网络
  • Pod 到 Pod 网络
  • Pod 到服务网络
  • Internet 到服务网络

这里先对一些基本的名词概念做一些通俗易懂的解释与说明。

**网络名称空间:**Linux网络命名空间可以看成网络协议栈的副本。每个 namespace 里面将原本是全局资源进行了隔离,彼此互相不可见。单纯从网络的视角来看,一个 namespace 提供了一份独立的网络协议栈(网络设备接口、 IPv4、IPv6、IP 路由、防火墙规则、 sockets 等) 。一个设备( Linux Device )只能位于一个namespace 中,不同 namespace 中的设备可以利用 veth pair 进行桥接。
**Pod:**pod相当于是K8s云平台中的虚拟机,是K8s中最小的基本调度单位。所谓Pod网络,就是能够保证K8s集群中的所有Pods包括同一节点上的,也包括不同节点上的Pods能够互相进行通信。pod内部的容器不能在同一个端口开启服务,否则会有端口冲突问题。
**eth0:**是节点主机上的网卡,docker0是一个虚拟网桥,可以简单理解为一个虚拟交换机,它是支持该节点上的Pod之间进行IP寻址和互通的设备。
**veth:**是linux的一种虚拟网络设备(虚拟网卡接口),通常用来连接不同网络命名空间,为实现在不同网络命名空间的互联通信。可以将 veth 设备视为设备之间的虚拟跳线,一端连着NS1的内核协议栈,另一端连着NS2的内核协议栈,一端发送的数据会立刻被另一端接收。一般都是成对出现。
**iptables:**Linux内置的防火墙有iptables和netfilter两个组件组成,可以对IP数据包做一些过滤、更改、转发的操作,防火墙在做这些操作时会遵循一系列防火墙规则,这些规则会存储在专用的table中,iptables存在于用户空间,是用户操作防火墙数据包规则的一个工具,或者也可以理解成一个客户端。
**kube-DNS:**是Kubernetes中的一个内置插件,用于将系统名称与 IP 地址相关联,它将域名转换为用于定位计算机服务的数字 IP 地址,目前作为一个独立的开源项目维护。通过将 Service 注册到 DNS 中,Kuberentes 可以为我们提供一种简单的服务注册发现与负载均衡方式。至此,别的服务就可以通过名称来访问相关的服务。
**ingress:**只是一个统称,由Ingress和Ingress Controller两部分组成。Ingress用作将原来需要手动配置的规则抽象成一个 Ingress 对象,使用 YAML 格式的文件来创建和管理。Ingress Controller用作通过与 Kubernetes API 交互,动态的去感知集群中 Ingress 规则变化。暂时看不懂可以简单理解为Service的统一网关入口。
**kube-proxy:**是Kubernetes的核心组件,部署在每个Node节点上,它是实现Kubernetes Service通信与负载均衡机制的重要组件;
kube-proxy负责为Pod创建代理服务,从apiserver获取所有server信息,并根据server信息创建代理服务,实现server到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络。
简单来说:

  • kube-proxy其实就是管理service的访问入口,包括集群内Pod到Service的访问和集群外访问service。
  • kube-proxy管理sevice的Endpoints,该service对外暴露一个Virtual IP,也称为Cluster IP, 集群内通过访问这个Cluster IP:Port就能访问到集群内对应的serivce下的Pod。
  • service是通过Selector选择的一组Pods的服务抽象,其实就是一个微服务,提供了服务的LB和反向代理的能力,而kube-proxy的主要作用就是负责service的实现。
  • service另外一个重要作用是,一个服务后端的Pods可能会随着生存灭亡而发生IP的改变,service的出现,给服务提供了一个固定的IP,而无视后端Endpoint的变化。

**node ip:**Node节点的IP地址,即物理网卡的IP地址。每个Service都会在Node节点上开通一个端口,外部可以通过NodeIP : NodePort即可访问Service里的Pod,和我们访问服务器部署的项目一样,IP : 端口/项目名。
**pod ip:**Pod的IP地址,即容器的IP地址。他是Docker Engine根据docker网桥的IP地址段进行分配的,通常是一个虚拟的二层网络。

  • 同Service下的pod可以直接根据Pod IP相互通信。
  • 不同Service下的pod在集群间pod通信要借助于 cluster ip。
  • pod和集群外通信,要借助于node ip。

**cluster ip:**Service的IP地址,此为虚拟IP地址。外部网络无法ping通,只能在kubernetes集群内部访问使用。

  • Cluster IP 仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配P地址。
  • Cluster IP 无法被 ping通,他没有一个“实体网络对象”来响应。
  • Cluster IP 只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备通信的基础,并且他们属于Kubernetes集群这样一个封闭的空间。
  • 在不同Service下的pod节点在集群间相互访问可以通过Cluster IP。

2. 集群内部通信

K8s 在设计网络的时候,采用的准则就一点:“灵活”!那怎么才能灵活呢?答案就是“我不管,你自己做”~
没错,k8s 自己没有实现太多跟网络相关的操作,而是制定了一个规范。
该规范贼简单,一共就三点:

  • “首先!有一个配置文件!配置文件里写上要使用的网络插件名儿,然后以及一些该插件需要的信息”。
  • “其次!让 CRI 调用这个插件,并把容器的运行时信息,包括容器的命名空间,容器 ID 等信息传给插件”。
  • “最后!你这插件自己爱干啥干啥去吧,都嚯嚯完了玩够了给我吐一个结果,结果里头能让我知道你给 pod 的 ip 是啥就行了”

没错一共就这三点,这就是大名鼎鼎的 CNI 规范!
虽然简单,但足够灵活!因为我啥也不干!不过恰恰就是因为 k8s 自己“啥也不干”,所以大家可以自由发挥,自由实现不同的 CNI 插件,反正 pod 的相关你都给我了,我要的配置也都通过配置文件设置好了,那我作为插件的实现方,当然就可以 free style 了,反正最终目的就是能让 pod 有一个健康的网络就好了嘛~
摘自知乎大佬:原文地址 https://zhuanlan.zhihu.com/p/450140876

集群内部的通信主要包括:容器到容器之间的网络连通、Pod 到 Pod之间的网络连通、Pod 到服务之间的网络连通。下面会结合图片进行阐述。

2.1 容器和容器之间网络通信

容器之间可通过IP、Docker DNS Server或 joined三种方式进行通信。
**IP:**IP通信很简单,只要容器使用相同网络,那么就可以使用IP进行访问。

**Docker DNS Server:**使用IP通信存在一个最大的问题就是容器的IP地址一般是随机的不固定的,所以导致这种方式不够灵活,对于这个问题,可以通过Docker自带的DNS服务器解决。
使用Docker DNS有个限制:只能在用户自定义网络中使用。也就是说默认的Bridge网络是无法使用的。

**Joined容器:**joined容器是另一种实现容器间通信的方式,这种方式非常特别,它可以使多个容器共享同一个网络栈,共享网卡和配置信息。
就 Docker 结构而言,Pod 被建模为一组共享网络命名空间的 Docker 容器。Pod 中的容器都具有相同的 IP 地址和端口空间,这些 IP 地址和端口空间是通过分配给 Pod 的网络命名空间分配的,并且可以通过 localhost 找到彼此,因为它们位于同一个命名空间中。

在Kubernetes中,还可以使用共享的Kubernetes卷,作为在Pod中的容器之间共享数据的简单有效的方法。在大多数情况下,Pod中所有容器共享使用主机上的目录就足够了。
Kubernetes Volumes使数据能够在容器重启后幸免于难,但是这些卷具有与Pod相同的生存期。这意味着卷(及其存储的数据)与Pod存在的时间完全一样。如果出于任何原因删除了该Pod,即使创建了相同的替换,共享卷也将被破坏并从头开始创建。

2.2 Pod 到 Pod之间的网络连通

在 Kubernetes 中,每个 Pod 都有一个真实的 IP 地址,并且每个 Pod 都使用该 IP 地址与其他 Pod 通信。
同一节点:
在一个 pod 中可以有多个容器,在拉起 pod 之前,会先给 pod 创建一个 pause 容器,这个 pause 容器是一个特别小又特别稳定的进程,主要用来挂载网络命名空间和存储资源。同时,它可以使 pod 内的所有容器都共享同一个网卡和数据卷。下图中,虚拟机的网卡 eth0 为了和 pod 通信,创建了一个虚拟的网桥 docker0,docker0 是支持该节点上的 pod 之间进行 ip 寻址和互通的设备。同时由于虚拟机和 pod 不在同一个网络命名空间内,所以可以使用 Linux 虚拟以太网设备或由两个虚拟接口组成的 veth 对连接命名空间,eth0 通过 veth0 连接到 root namespace,通过 veth0 和 veth1 的连接,所以 pod1 就可以和 pod2 进行连通了。
同一节点pod网络.jpg

下图可以看出 pod1 与 pod2 之间详细的连通过程,pod1 发出请求后,通过 eth0 发送到 veth0 中,veth0 收到后在将请求转发给网桥 cbr0,一旦数据包到达网桥,网桥使用 ARP 协议解析出其正确的目标网段 veth1,之后网桥会发送给 veth1,veth1 发送给 pod2 的 eth0,这样两个 pod 就可以连通了。
同节点pod连通.gif

不同节点:
K8S网络模型要求Pod IP在整个网络中都可访问,这种需求是由第三方网络组件实现。
下图就是不同节点之间的 pod 通信,VM1 中的 Pod1 如何和 VM2 的 Pod4 进行通信的,我们来看看具体流程:
①首先pod1通过自己的以太网设备 eth0 把数据包发送到关联到 root 命名空间的 veth0 上。
②然后数据包被 VM1上的网桥设备 cbr0 接收到,网桥查找转发表发现找不到 pod4 的Mac地址,则会把包转发到默认路由(root命名空间的eth0设备)。
③然后数据包经过 eth0 就离开了 VM1,被发送到集群中。
④数据包到达 VM2 后,首先会被 root 命名空间的 eth0 设备接收到
⑤然后通过网桥把数据路由到虚拟设备 veth1,最终数据表会被流转到与 veth1 配对的另外一端(pod4的eth0)
每个节点都知道如何把数据包转发到其内部运行的 Pod,当一个数据包到达节点后,其内部数据流就和节点内Pod之间的流转类似了。
补充说明:对于如何来配置网络,k8s在网络这块自身并没有实现网络规划的具体逻辑,而是制定了一套CNI(Container Network Interface)接口规范,开放给社区来实现。Flannel就是k8s中比较出名的一个。Flannel不是太了解,感兴趣的可以自行查找学习。
不同节点pod连通.gif

2.3 Pod 到服务之间的网络连通

Pod 间可以直接通过 IP 地址通信,但前提是 Pod 知道对方的 IP。在 Kabenete 集群中,Pod 可能会频繁地销毁和创建,新Pod会分配到新的IP地址,也就是说 Pod 的 IP 不是固定的。例如:有一组 Pod 对外提供 HTTP 服务,它们的 IP 很可能发生变化,那么客户端如何找到并访问这个服务呢,Service由此而生。
Service的理论解释是:将一组pods公开为网络服务的抽象方法,提供了访问 Pod 的抽象层。
Service从逻辑上代表了一组Pod,具体是哪些由 iptables 来挑选。目前基于性能考虑,全部为 iptables 维护和转发。Service可以通过pod标签选中一组 pod 暴露成公共的 ip 地址或服务。Service有自己的 IP,这个 IP 是不变的。客户端只需要访问Service IP,无论后端的 Pod 如何变化,Service 都作为稳定的前端对外提供服务,对使用 Service 的用户来说是无感知的。同时 Serice 还提供了高可用和负载均衡功能(只提供4层负载均衡能力,而没有7层功能),Service 负责将请求转发给正确的Pod。

在这里在详细的解释下 iptables,它是一个用户空间程序,提供了一个基于表的系统,用于定义使用 netfilter 框架操作和转换数据包的规则。在 Kubernetes 中,iptables 规则由 kube-proxy 控制器配置,该控制器监视 Kubernetes API 服务器的更改。当更改更新了 Service 的虚拟 IP 地址 或 Pod 的 IP时,iptables 规则会更新以正确地将指向 Service 的流量转发到正确的Pod。iptables 的规则监视着发往 Service 的虚拟 IP 的流量。在匹配时,从可用 Pod 集中选择一个随机 Pod IP 地址,并且 iptables 规则将数据包的目标 IP 地址从 Service 的虚拟 IP 更改为选定的 Pod。当 Pod 启动或关闭时,iptables 规则集会更新以反映集群不断变化的状态。换句话说,iptables 已经在机器上进行了负载平衡,以将定向到服务 IP 的流量转移到实际 pod 的 IP。
在返回路径上,IP 地址来自目标 Pod。在这种情况下,iptables 再次重写 IP 标头以将 Pod IP 替换为 Service 的 IP,以便 Pod 认为它一直只与 Service 的 IP 通信。

下图展示了从 pod 到 service 的连通详细过程,可以看到,请求从 pod 发出后,经过 veth0 到达网桥 cbr0 后,网桥上运行的 ARP 协议不知道 Service,因此它通过默认路由 eth0 (3) 将数据包传输出去(这里5出去后代表的是集群内部的另一个服务)。在 eth0 接收之前,数据包会通过 iptables (4)过滤。iptables 收到数据包后,使用 kube-proxy 安装在 Node 上的规则表响应 Service 或 Pod,将数据包的目的地从 Service IP 重写为另个一个服务上的 Pod IP(4)。数据包现在注定要到达 Pod 4,而不是服务的虚拟 IP。iptables 利用 Linux 内核的 conntrack 实用程序来记住所做的 Pod 选择,以便将来的流量路由到同一个 Pod(除非发生任何扩展事件)。本质上,iptables 直接在 Node 上做了集群内负载均衡。然后流量使用我们已经检查过的 Pod 到 Pod 路由流向 Pod (5)。

一个 Kubernetes Service 管理了一组 Pod 的状态,可以追踪一组 Pod 的 IP 地址的动态变化过程。一个 Service 拥有一个 IP 地址,并且充当了一组 Pod 的 IP 地址的“虚拟 IP 地址”。任何发送到 Service 的 IP 地址的数据包将被负载均衡到该 Service 对应的 Pod 上。在此情况下,Service 关联的 Pod 可以随时间动态变化,客户端只需要知道 Service 的 IP 地址即可(该地址不会发生变化)。在 Kubernetes 中,kube-proxy 控制器监听 apiserver 中的变化,并配置 iptables 规则。当 Service 或 Pod 发生变化时(例如 Service 被分配了 IP 地址,或者新的 Pod 被关联到 Service),kube-proxy 控制器将更新 iptables 规则,以便将发送到 Service 的数据包正确地路由到其后端 Pod 上。iptables 规则将监听所有发向 Service 的虚拟 IP 的数据包,并将这些数据包转发到该Service 对应的一个随机的可用 Pod 的 IP 地址,同时 iptables 规则将修改数据包的目标 IP 地址(从 Service 的 IP 地址修改为选中的 Pod 的 IP 地址)。当 Pod 被创建或者被终止时,iptables 的规则也被对应的修改。换句话说,iptables 承担了从 Service IP 地址到实际 Pod IP 地址的负载均衡的工作。在返回数据包的路径上,数据包从目标 Pod 发出,此时,iptables 规则又将数据包的 IP 头从 Pod 的 IP 地址替换为 Service 的 IP 地址。从请求的发起方来看,就好像始终只是在和 Service 的 IP 地址通信一样。

在了解了pod到服务之间网络连通后,我们再理解几个概念。
Service在K8s中有以下四种类型

  • Clusterlp:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟IP。
  • NodePort:在 ClusterlP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 Nodeport 来访问该服务。
  • LoadBalancer:在 NodePort 的基础上,借助 Cloud provider 创建一个外部负载均衡器,并将请求转发到 NodePort。
  • ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建。目前只有kubernetes 1.7 或更高版本的kube-dns才支持。

从Pod创建之初到人为删除这段时间,为其创建的Service访问方式都是稳定的,即它的访问IP不变,再配合集群内置的DNS服务,客户端可以利用不变的Service名称或Service IP访问到目标Pods,而且Service还实现了简单的负载均衡功能。
**VIP和Service代理 **
在Kubernetes集群中,每个Node运行一个kube-proxy进程。kube-proxy负责为Service实现了一种 VIP (虚拟IP)的形式,而不是ExternalName的形式。在Kubernetes v1.0版本,代理完全在 userspace。在 Kubernetes v1.1版本,新增了 iptables 代理,但并不是默认的运行模式。从Kubernetes v1.2起,默认就是 iptables代理。
从k8s的1.8版本开始,kube-proxy引入了 IPVS 模式,IPVS 模式与 iptables 同样基于Netfilter,但是 ipvs 采用的 hash表,iptables 采用一条条的规则列表,从上到下匹配,iptables 又是为了防火墙设计的,集群数量越多 iptables 规则就越多, 所以效率就越是低下。因此当 service 数量达到一定规模时,hash查表的速度优势就会显现出来,从而提高service的服务性能。
ipvs和iptables都是基于netfilter的,两者差别如下:

  • ipvs 为大型集群提供了更好的可扩展性和性能
  • ipvs 支持比 iptables 更复杂的负载均衡算法(最小负载、最少连接、加权等等)
  • ipvs 支持服务器健康检查和连接重试等功能



在Kubernetes v1.0版本,Service是"4层" (TCP/UDP over IP)概念。在Kubernetes v1.1版本,新增了 Ingress API (beta版) ,用来表示"7层" (HTTP)服务。

3. 集群外部通信

kubernetes集群的外部通信有三种方式。

  • NodePort(通过指定端口暴露服务,范围在30000-32767之间)
  • 通过Load Balancer提供外部访问入口
  • 通过 Ingress 暴露

3.1 NodePort 连接 Internet

NodePort通过端口号直接把Service暴露到了集群节点上,通过访问节点IP和端口号,就可以访问到Service。
下图中,数据包从 Pod 的命名空间 (1)发出,并经过连接到达了根命名空间 (2) 的 veth 对。一旦进入根命名空间,数据包就会从网桥 cbr0 移动到默认设备,因为数据包上的 IP 与连接到网桥的任何网段都不匹配。在到达根命名空间的以太网设备之前,iptables 会破坏数据包 (3)。数据包的源 IP 地址是 Pod,如果将源保留为 Pod,Internet 网关将拒绝它,因为网关 NAT 只了解连接到 VM 的 IP 地址。解决方案是让 iptables 执行源 NAT——更改数据包源——使数据包看起来是来自 VM 而不是 Pod。有了正确的源 IP,数据包就可以离开 VM (4) 并到达 Internet 网关 (5)。Internet 网关将执行另一个 NAT,将源 IP 从 VM 内部 IP 重写为外部 IP。最后,数据包将到达公共 Internet (6)。在返回的路上,数据包遵循相同的路径,并且任何源 IP 修改都被撤消,以便系统的每一层都接收到它理解的 IP 地址。
nodeport连接internet.gif
NodePort.png

这种通过 NodePort 提供外部访问入口的方式也有缺点:

  • 请求的目标 Node 可能会 Down 掉或其他别的原因导致网络不能访问。
  • 服务一旦多起来,NodePort 在每个节点上开启的端口会及其庞大,且难以维护。
  • 对外暴露的端口有限制,默认端口范围是30000—32767,这就限制了可以对外暴露服务的个数。

使用这些不易记录的端口访问服务也是让人头疼的问题,基于这几个缺点,所以生产环境不建议这么使用。

3.2 通过 Load Balancer 连接 Internet

通过 Load Balancer 连接 Internet 就是在原有的基础上添加一个负载均衡器 (1)。因为负载均衡器不支持容器,所以流量到达负载均衡器后,它就会分布在组成集群的所有虚拟机中 (2)。传入的流量会由每个 VM 上的 iptables 规则引导到正确的 Pod (3) 。Pod 到客户端的响应将返回 Pod 的 IP。
下图显示了托管 Pod 的三个 VM 前面的负载均衡器 Load Balancer。流量请求从 Internet 传入到服务的负载均衡器(1)。一旦负载均衡器收到数据包 (2),就会随机选择一个 VM。但是这里它选择了一个没有运行 Pod 的 VM:VM 2 (3)。在 VM2中的 iptables 规则将使用 kube-proxy 安装到集群中的内部负载平衡规则将数据包定向到正确的 Pod。iptables 执行正确的 NAT 并将数据包转发到正确的 Pod (4)。
Loadbalancer和Service通信.gif
LoadBalancer.png

这种方式的缺点是:

  • 每一个用 LoadBalancer 暴露的服务都会有它自己的 IP 地址和端口,不能做到一个ip地址就可以访问所有服务。
  • 在公有云上,公网LB+IP是需要花钱买的,要暴露一个服务就需要购买一个独立的LB+IP,如果要暴露十个服务就需要购买十个LB+IP,显然,从成本考虑这是不划算也不可扩展的。

3.3 通过 Ingress 暴露 Internet

通过 Ingress 的连接 Internet 的方式与 LoadBalancer 的非常相似。区别在于 Ingress 知道 URL 的路径(允许并可以根据路径将流量路由到服务)。
看下图可知,流量请求从 Internet 发出后,云提供商将会给你创建一个新的 Ingress 负载均衡器 (1)。由于负载均衡器不支持容器,因此流量到达负载均衡器后 (2),它就会分布在组成集群的所有的虚拟机中。传入的流量会由 VM 上的 iptables (3) 规则引导到正确的 Pod (4) 。Pod 到客户端的响应将返回 Pod 的 IP,但客户端需要有负载均衡器的 IP 地址。iptables 和 conntrack 用于在返回路径上正确重写 IP。
Ingress.gif
Ingress.png
下图是根据自己的理解画的示意图。
image.png

4. 总结

通过这两天的学习,大概理解了 k8s 网络模型的整体结构,对容器和容器的通信、pod 到 pod的通信、pod 到服务
之间的通信过程有了基本的了解,知道了通信过程中需要哪些工具、组件,怎样工作完成的通信。
同时对集群外部通信的三种方式:NodePort、Load Balancer 和 Ingress原理也有了基本了解,知道请求数据进入
集群后的发送过程是怎样的,用到了哪些工具、组件,通信的过程是怎样的,每种方式的优缺点是什么。
当然,这些了解的内容仅停留在理论知识方面,真正的掌握运用还需要在实际的操作中去研究与理解,只有把理论
搞懂了,实操也能熟练使用,才算是完全掌握。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐