K8S网络总结

前言

K8S的网络模型假定了所有pod都在一个可以直接连通的扁平的网络空间中(所有的pod都可以通过对方的ip直接"到达")。
在这里插入图片描述
在这里插入图片描述

本文主要研究

  • Docker容器和Docker容器之间的网络
  • CNI网络插件
  • Pod与Pod之间的网络
  • Pod与Service之间的网络
  • Internet与Service之间的网络

一、容器和容器之间的网络

1.Pod中的容器网络

在K8S的pod容器中,多容器之间共享网络堆栈,所以容器直接可以使用localhost访问其他容器。由于网络共享,因此每个容器不能有一样的端口号。如下图所示,图中有一个pause容器,k8s在启动容器的时候会先启动一个pause容器,这个容器的作用是
1.可以根据pause根容器为依据,评估整个Pod健康状态。
2.实现Pod内部中容器间网络共享

在这里插入图片描述

在这里插入图片描述

2.pause容器

参考文章
https://zhuanlan.zhihu.com/p/81666226

docker ps -a 查看
每个pod中都会有一个pause容器

在这里插入图片描述
1:为什么要pause容器?

  • Docker部署多个容器时候,比如有个java程序,然后还需要一些监控模块。需要通过namespace,cgruop机制来共享linux资源。
  • 创建一个父容器就可以配置docker来管理容器组之间的共享问题。这个父容器需要能够准确的知道如何去创建共享运行环境的容器,还能管理这些容器的生命周期。为了实现这个父容器的构想,kubernetes中,用pause容器来作为一个pod中所有容器的父容器。
  • pause容器有两个核心的功能,第一,它提供整个pod的Linux命名空间的基础。第二,启用PID命名空间,它在每个pod中都作为PID为1进程,并回收僵尸进程。

a.共享命名空间

在Linux中,当我们运行一个新的进程时,这个进程会继承父进程的命名空间。而运行一个进程在一个新的命名空间,是通过“unsharing”父进程的命名空间从而创建一个新的命名空间。这里举个例子,使用unshare工具来运行一个具有新PID,UTS,IPC以及mount命名空间的shell。

sudo unshare --pid --uts --ipc --mount -f chroot rootfs /bin/sh

随后通过如下命令加入创建好的pause命名空间

docker run xxx --net=container:pause --ipc=container:pause --pid=container:pause nginx

在这里插入图片描述

b.僵尸进程问题

子进程的结束和父进程的运行是一个异步过程
僵尸进程是指它的父进程已经退出(父进程没有等待(调用wait/waitpid)它),而该进程dead之后没有进程接受,就成为僵尸进程,也就是(zombie)进程。

每个进程在系统进程表里有存在一条记录。它记录了关于进程状态和退出码的相关信息。当子进程已经结束运行时,它在进程表中的记录仍然存在,只有当父进程通过使用wait系统调用取回了它的退出码。这个过程就叫做回收僵尸进程。
在这里插入图片描述
在容器中,每个PID命名空间必须有一个进程作为init进程。Docker中每个容器通常有自己的PID命名空间,入口点进程是init进程。但是,在kubernetes pod中,我们可以使容器在另一个容器的命名空间中运行。在这种情况下,一个容器必须承担init进程的角色,而其他容器则作为init进程的子元素添加到命名空间中。
在这里插入图片描述

Pause容器内其实就运行了一个非常简单的进程

static void sigdown(int signo) {
  psignal(signo, "Shutting down, got signal");
  exit(0);
}

static void sigreap(int signo) {
  while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main() {
  if (getpid() != 1)
    /* Not an error because pause sees use outside of infra containers. */
    fprintf(stderr, "Warning: pause should be the first process\n");

  if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
    return 1;
  if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
    return 2;
  if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,
                                             .sa_flags = SA_NOCLDSTOP},
                NULL) < 0)
    return 3;

  for (;;)
    pause();
  fprintf(stderr, "Error: infinite loop terminated\n");
  return 42;
}

在这里插入图片描述
其中第24行里一个无限循环for(; 😉,
这个无限循环里执行的是一个系统调用pause,
因此pause容器大部分时间都在沉睡,等待有信号将其唤醒。
接收什么信号呢?
一旦收到SIGCHLD信号,pause进程就执行注册的sigreap函数。
在这里插入图片描述
SIGCHLD,在一个进程正常终止或者停止时,将SIGCHLD信号发送给其父进程,按系统默认将忽略此信号,如果父进程希望被告知其子系统的这种状态,则应捕捉此信号。调用另一个系统调用waitpid来获得子进程终止的原因。
在这里插入图片描述
进而回收僵尸进程

3.CNI(pause容器网路配置)

这里只分析pause ip分配情况

  • pause的ip又是从哪里分配到的?如果还是用一个以docker0为网关的内网ip就会出现问题了。
  • docker默认的网络是为同一台宿主机的docker容器通信设计的,K8s的Pod需要跨主机与其他Pod通信
  • 所以需要设计一套让不同Node的Pod实现透明通信(without NAT)的机制。
  • docker0的默认ip是172.17.0.1,docker启动的容器也默认被分配在172.17.0.1/16的网段里。跨主机的Pod
  • 通信要保证Pod的ip不能相同,所以还需要设计一套为Pod统一分配IP的机制。
  • 以上两点,就是K8s在Pod network这一层需要解决的问题,可以利用插件解决如:flannel,calico
    在这里插入图片描述

a.Container Runtime Interface (CRI容器运行时接口)

CRI 是以容器为中心的API,设计 CRI 的初衷是不希望向容器(比如 docker)暴露 pod 信息或 pod 的api,Pod始终是Kubernetes编排概念,与容器无关,因此这就是为什么必须使该API以容器为中心。
CRI 工作在 kubelet 与 container runtime之间,目前常见的 runtime 有:

  • docker: 目前 docker 已经将一部分功能移至 containerd 中,CRI 可以直接与
  • containerd 交互。 因此,Docker本身并不需要支持CRI(containerd已经支持)。
  • containerd:containerd 可以通过 shim 对接不同 low-level runtime,这部分后文会详细介绍
  • cri-o:一种轻量级的 runtime,支持runc和Clear Containers作为low-level runtimes。

b.CRI 是如何工作的?

CRI 大体包含三部分接口:Sandbox 、 Container 和 Image,其中提供了一些操作容器的通用接口,包括 Create Delete List 等。

Sandbox 为 Container 提供一定的运行环境,这其中包括 pod 的网络等。 Container 包括容器生命周期的具体操作,Image 则提供对镜像的操作。

kubelet会通过 grpc 调用 CRI 接口,首先去创建一个环境,也就是所谓的 PodSandbox。当 PodSandbox 可用后,继续调用 Image 或 Container 接口去拉取镜像和创建容器。其中,shim 会将这些请求翻译为具体的 runtime API,并执行不同 low-level runtime 的具体操作。
在这里插入图片描述

c.PodSandbox(paused容器)

  • 上文所说的 Sandbox 到底是什么东西呢? 我们从虚拟机和容器化两方面来看,这两者都使用 cgroups
    做资源配额,而且概念上都抽离出一个隔离的运行时环境,只是区别在于资源隔离的实现。
  • 因此 Sandbox 是 K8s 为兼容不同运行时环境预留的空间,也就是说 K8s 允许 low-level runtime
    依据不同的是实现去创建不同的 PodSandbox,对于 kata 来说 PodSandbox 就是虚拟机,对于 docker 来说就是
    Linux namespace。
  • 当 Pod Sandbox 建立起来后,Kubelet 就可以在里面创建用户容器。当到删除 Pod 时,Kubelet 会先移除 Pod
    Sandbox 然后再停止里面的所有容器,对 container 来说,当 Sandbox 运行后,只需要将新的 container 的
    namespace 加入到已有的 sandbox 的 namespace中。

二、CNI容器网络接口插件

1.Calico

a.Calico是什么

Calico 是一种容器之间互通的网络方案。在虚拟化平台中,比如 OpenStack、Docker 等都需要实现 workloads 之间互连,但同时也需要对容器做隔离控制,就像在 Internet 中的服务仅开放80端口、公有云的多租户一样,提供隔离和管控机制。
而在多数的虚拟化平台实现中,通常都使用二层隔离技术来实现容器的网络,这些二层的技术有一些弊端,比如需要依赖 VLAN、bridge 和隧道等技术,其中 bridge 带来了复杂性,vlan 隔离和 tunnel 隧道则消耗更多的资源并对物理环境有要求,随着网络规模的增大,整体会变得越加复杂。我们尝试把 Host 当作 Internet 中的路由器,同样使用 BGP 同步路由,并使用 iptables 来做安全访问策略,最终设计出了 Calico 方案。

**设计思想:**Calico 不使用 tunnel 隧道或 NAT 来实现转发,而是巧妙的把所有二三层流量转换成三层流量,并通过 host 上路由配置完成跨 Host 转发。

b.Calico的设计优势

1.更优的资源利用

二层网络通讯需要依赖广播消息机制,广播消息的开销与 host 的数量呈指数级增长,Calico 使用的三层路由方法,则完全抑制了二层广播,减少了资源开销。

另外,二层网络使用 VLAN 隔离技术,天生有 4096 个规格限制,即便可以使用 vxlan 解决,但 vxlan 又带来了隧道开销的新问题。而 Calico 不使用 vlan 或 vxlan 技术,使资源利用率更高。

2.可扩展性

Calico 使用与 Internet 类似的方案,Internet 的网络比任何数据中心都大,Calico 同样天然具有可扩展性。

3.简单而更容易 debug

因为没有隧道,意味着 workloads 之间路径更短更简单,配置更少,在 host 上更容易进行 debug 调试。

4.更少的依赖

Calico 仅依赖三层路由可达。

5.可适配性

Calico 较少的依赖性使它能适配所有 VM、Container、白盒或者混合环境场景。

c.Calico的架构

在这里插入图片描述
Calico网络模型主要工作组件:

**1.Felix:**运行在每一台 Host 的 agent 进程,主要负责网络接口管理和监听、路由、ARP 管理、ACL 管理和同步、状态上报等。

**2.etcd:**分布式键值存储,主要负责网络元数据一致性,确保Calico网络状态的准确性,可以与kubernetes共用;

**3.BGP Client(BIRD):**Calico 为每一台 Host 部署一个 BGP Client,使用 BIRD 实现,BIRD 是一个单独的持续发展的项目,实现了众多动态路由协议比如 BGP、OSPF、RIP 等。在 Calico 的角色是监听 Host 上由 Felix 注入的路由信息,然后通过 BGP 协议广播告诉剩余 Host 节点,从而实现网络互通。

**4.BGP Route Reflector:**在大型网络规模中,如果仅仅使用 BGP client 形成 mesh 全网互联的方案就会导致规模限制,因为所有节点之间俩俩互联,需要 N^2 个连接,为了解决这个规模问题,可以采用 BGP 的 Router Reflector 的方法,使所有 BGP Client 仅与特定 RR 节点互联并做路由同步,从而大大减少连接数。

Felix

Felix会监听ECTD中心的存储,从它获取事件,比如说用户在这台机器上加了一个IP,或者是创建了一个容器等。用户创建pod后,Felix负责将其网卡、IP、MAC都设置好,然后在内核的路由表里面写一条,注明这个IP应该到这张网卡。同样如果用户制定了隔离策略,Felix同样会将该策略创建到ACL中,以实现隔离。

BIRD

BIRD是一个标准的路由程序,它会从内核里面获取哪一些IP的路由发生了变化,然后通过标准BGP的路由协议扩散到整个其他的宿主机上,让外界都知道这个IP在这里,你们路由的时候得到这里来。

在这里插入图片描述
typha iptables的同步

架构特点

由于Calico是一种纯三层的实现,因此可以避免与二层方案相关的数据包封装的操作,中间没有任何的NAT,没有任何的overlay,所以它的转发效率可能是所有方案中最高的,因为它的包直接走原生TCP/IP的协议栈,它的隔离也因为这个栈而变得好做。因为TCP/IP的协议栈提供了一整套的防火墙的规则,所以它可以通过IPTABLES的规则达到比较复杂的隔离逻辑。
在这里插入图片描述
上图为官方提供的完整架构图
1.calico-kube-controllers 负责watch K8S的apiServer,如果apiServer的一些资源有变化,则把一些网络信息存储到自己的的存储里。
2.左下角confd是用于将数据进行转换成 Typha的能读懂的数据

d.IPIP工作模式

宿主机三层互通场景

在容器云场景,宿主机之间跨网段,二层不可达的情况是广泛存在的(例如:openstack云平台下,同一租户的不同网络。或者物理VLAN隔离的网络),“原始IP包”不能通过“第一跳”就到达“目的宿主机”,“源宿主机”和“目的宿主机”之间隔着不同的VLAN网络,需要经过中间的n个路由器转发。(多个路由转发,源IP包会不见)

有人会问,“原始IP包”就这样通过中间的n个路由器转发就可以到达“目的容器”了吗?答案是“非也”。因为这些中间的路由器,它们不具备BGP协议的学习能力,学习不到calico网络里的路由规则,因此并不知道“目的容器”在哪个宿主机上。这种情况下,“源IP包”,经过N次缺省路由转发,当IP包的ttl等于0,这个“源IP包”就被丢弃了。

Calico具备先天的路由自学习能力优势,学习到了“目的容器”在哪个宿主机上后,在两台宿主机之间建立ip tunnel,通过这个tunnel来封装承载“原始IP包”

tunl0设备,是一个IP隧道(IP tunnel)设备。宿主机网络栈从Veth pair收到“原始IP包”,查询路由后,就被linux内核的IPIP驱动就接管,“原始IP包”直接被封装成新IP的payload,宿主机1和宿主机2三层是互通的,经过中间的n跳,最终跳到节点2。
就是容器的源ip和目的ip的数据会被宿主机包装一层,宿主机通过路由n跳到达节点2。
在这里插入图片描述
在这里插入图片描述

e.BGP模式

宿主机二层互通场景
calico插件与fannel的host-gw模式不同之处就是,**它不会在宿主机上创建类似docker0、cni0这样的网桥设备,**Veth Pair在宿主机的一端的接口直接暴露在宿主机上,并通过设置路由规则,将容器IP暴露到宿主机的通信路由上。
在这里插入图片描述

在这里插入图片描述
广播维护路由表。
node1中有node2的路由信息
node2中有node1的路由信息

总结看一下,容器的IP地址是个单点网络,其通过一条特殊的默认路由(gw:169.254.1.1)发送“原始IP包”到Veth pair在宿主机一端的设备;然后就由内核完成路由查找,IP包发送。这些过程无需用户软件参与,都由操作系统完成。

f.模式区别比较

IPIP网络:

流量:tunlo设备封装数据,形成隧道,承载流量。

适用网络类型:适用于互相访问的pod不在同一个网段中,跨网段访问的场景。外层封装的ip能够解决跨网段的路由问题。

效率:流量需要tunl0设备封装,效率略低

BGP网络:

流量:使用路由信息导向流量

适用网络类型:适用于互相访问的pod在同一个网段,适用于大型网络。

效率:原生hostGW,效率高

g.iptables networkPolicy

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
mark打标记,匹配则为1,不匹配则拒绝。
在这里插入图片描述
给他打标记为1 后return回去
查看calico node的pod,实际是运行着calico felix进程,felix进程实施iptables规则。
在这里插入图片描述
在这里插入图片描述

连接好的accept
未连接好的丢弃

Chain cali-fw-cali633b4786df1 (1 references)
target     prot opt source               destination         
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            /* cali:k8-j2VThmyc-5bIz */ ctstate RELATED,ESTABLISHED
DROP       all  --  0.0.0.0/0            0.0.0.0/0            /* cali:eTV9Uea7oVfOQS6l */ ctstate INVALID
MARK       all  --  0.0.0.0/0            0.0.0.0/0            /* cali:lO-uRs6-c_f3t30Y */ MARK and 0xfffeffff
DROP       udp  --  0.0.0.0/0            0.0.0.0/0            /* cali:9lGtP5BQhdI55uIf */ /* Drop VXLAN encapped packets originating in workloads */ multiport dports 4789
DROP       4    --  0.0.0.0/0            0.0.0.0/0            /* cali:vA_03jKfp7Ih0phH */ /* Drop IPinIP encapped packets originating in workloads */

2.Flannel

a.Flannel是什么

Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。

在默认的Docker配置中,每个节点上的Docker服务会分别负责所在节点容器的IP分配。这样导致的一个问题是,不同节点上容器可能获得相同的内外IP地址。并使这些容器之间能够之间通过IP地址相互找到,也就是相互ping通。

Flannel的设计目的就是为集群中的所有节点重新规划IP地址的使用规则,从而使得不同节点上的容器能够获得“同属一个内网”且”不重复的”IP地址,并让属于不同节点上的容器能够直接通过内网IP通信。

Flannel实质上是一种“覆盖网络(overlaynetwork)”,也就是将TCP数据包装在另一种网络包里面进行路由转发和通信,目前已经支持udp、vxlan、host-gw、aws-vpc、gce和alloc路由等数据转发方式,默认的节点间数据通信方式是UDP转发。

b.Flannel的特点

  1. 使集群中的不同Node主机创建的Docker容器都具有全集群唯一的虚拟IP地址。
  2. 建立一个覆盖网络(overlay network),通过这个覆盖网络,将数据包原封不动的传递到目标容器。覆盖网络是建立在另一个网络之上并由其基础设施支持的虚拟网络。覆盖网络通过将一个分组封装在另一个分组内来将网络服务与底层基础设施分离。在将封装的数据包转发到端点后,将其解封装。
  3. 创建一个新的虚拟网卡flannel0接收docker网桥的数据,通过维护路由表,对接收到的数据进行封包和转发(vxlan)。
  4. etcd保证了所有node上flanned所看到的配置是一致的。同时每个node上的flanned监听etcd上的数据变化,实时感知集群中node的变化。

c.Flannel对K8s网络要求提出的解决方案

1.flannel利用Kubernetes API或者etcd用于存储整个集群的网络配置,根据配置记录集群使用的网段。

2.flannel在每个主机中运行flanneld作为agent,它会为所在主机从集群的网络地址空间中,获取一个小的网段subnet,本主机内所有容器的IP地址都将从中分配。
在这里插入图片描述在这里插入图片描述
在flannel network中,每个pod都会被分配唯一的ip地址,且每个K8s node的subnet各不重叠,没有交集。

d.Flannel架构图

在这里插入图片描述
在这里插入图片描述
各个组件的解释:

Cni0:
网桥设备,每创建一个pod都会创建一对 veth pair。其中一端是pod中的eth0,另一端是Cni0网桥中的端口(网卡)。Pod中从网卡eth0发出的流量都会发送到Cni0网桥设备的端口(网卡)上。
在这里插入图片描述
Flannel.1:
overlay网络的设备,用来进行 vxlan 报文的处理(封包和解包)。不同node之间的pod数据流量都从overlay设备以隧道的形式发送到对端。
Cni0 设备获得的ip地址是该节点分配到的网段的第一个地址。在这里插入图片描述
Flanneld:
flannel在每个主机中运行flanneld作为agent,它会为所在主机从集群的网络地址空间中,获取一个小的网段subnet,本主机内所有容器的IP地址都将从中分配。同时Flanneld监听K8s集群数据库,为flannel.1设备提供封装数据时必要的mac,ip等网络数据信息。

e.hostgw模式

hostgw是最简单的backend,它的原理非常简单,直接添加路由,将目的主机当做网关,直接路由原始封包。

例如,我们从etcd中监听到一个EventAdded事件subnet为10.1.15.0/24被分配给主机Public IP 192.168.0.100,hostgw要做的工作就是在本主机上添加一条目的地址为10.1.15.0/24,网关地址为192.168.0.100,输出设备为上文中选择的集群间交互的网卡即可。

优点:简单,直接,效率高

缺点:要求所有的pod都在一个子网中,如果跨网段就无法通信。
在这里插入图片描述在这里插入图片描述

f.udp封装模式

如何应对?将Pod的网络包作为一个应用层的数据包,使用UDP封装之后在集群里传输。即overlay。
在这里插入图片描述

在这里插入图片描述
当容器10.1.15.2/24要和容器10.1.20.2/24通信时,

1.因为该封包的目的地不在本主机subnet内,因此封包会首先通过网桥转发到主机中。

2.在主机上经过路由匹配,进入网卡flannel.1。(需要注意的是flannel.1是一个tun设备,它是一种工作在三层的虚拟网络设备,而flanneld是一个proxy,它会监听flannel.1并转发流量。)

3.当封包进入flannel.1时,flanneld就可以从flanne.1中将封包读出,由于flanne.1是三层设备,所以读出的封包仅仅包含IP层的报头及其负载。

4.最后flanneld会将获取的封包作为负载数据,通过udp socket发往目的主机。

5.在目的主机的flanneld会监听Public IP所在的设备,从中读取udp封包的负载,并将其放入flannel.1设备内。

6.容器网络封包到达目的主机,之后就可以通过网桥转发到目的容器了。

优点:Pod能够跨网段访问

缺点:隔离性不够,udp不能隔离两个网段。

d.vxlan模式

vxlan和上文提到的udp backend的封包结构是非常类似的,不同之处是多了一个vxlan header,以及原始报文中多了个二层的报头。
在这里插入图片描述
当初始化集群里,vxlan网络的初始化工作:

主机B加入flannel网络时,它会将自己的三个信息写入etcd中,分别是:subnet 10.1.16.0/24、Public IP 192.168.0.101、vtep设备flannel.1的mac地址 MAC B。之后,主机A会得到EventAdded事件,并从中获取上文中B添加至etcd的各种信息。这个时候,它会在本机上添加三条信息:

  1. 路由信息:所有通往目的地址10.1.16.0/24的封包都通过vtep设备flannel.1设备发出,发往的网关地址为10.1.16.0,即主机B中的flannel.1设备。
  2. fdb信息:MAC地址为MAC B的封包,都将通过vxlan发往目的地址192.168.0.101,即主机B
  3. arp信息:网关地址10.1.16.0的地址为MAC B
    在这里插入图片描述

3.Calico和Flannel对比

容器虚拟化网络方案,总体分为2种截然不同的发展路线:
基于隧道
基于路由

1.基于隧道

1、隧道方案最具普适性,在任何网络环境下都可以正常工作,这与它的原理密不可分。
2、最常见的隧道方案是flannel vxlan模式,以及calico的ipip模式,其核心原理包含了2个部分。

分配网段
每台宿主机上都有网络插件的agent进程,它们连接到etcd集中式存储,从虚拟IP池中申请一个IP段占位己有,宿主机上每个容器则从IP段中分配得到1个虚拟IP。

封装/解封
1)、当不同宿主机上的容器互相访问时,数据包的源IP和目标IP都是容器IP。
2)、数据包经过宿主机的agent进程进行封装后,新数据包的源IP和目标IP则变成了两端宿主机的物理IP。
3)、数据包送到目标宿主机后,经过agent解封后得到原始数据包,并将数据包送入容器中处理,这就给两端容器营造了一种互通的感觉。
4)、因为物理IP属于3层网络,可以在互联网中经过中间路由设备互相送达,所以隧道方案对宿主机之间的网络环境没有特殊要求,因此隧道方案具备普适性。

优势/劣势
优势就是对物理网络环境没有特殊要求,只要宿主机IP层可以路由互通即可。

劣势就是性能差,这需要从以下方面看:
封包和解包耗费CPU性能;
额外的封装导致带宽浪费,大约有30%左右的带宽损耗;

flannel vxlan和calico ipip模式都是隧道方案,但是calico的封装协议IPIP的header更小,所以性能比flannel vxlan要好一点点。

2.基于路由

1、路由方案性能最好,原因是该方案不需要封包和解包,所以没有隧道方案的劣势,网络性能很好。
2、常见的路由方案包括了flannel的host-gw模式,以及calico的bgp模式。

下面以calico bpg模式为例,分析基于路由的方案原理,其包含了3个部分。
分配网段
每台宿主机也有agent,会从etcd中的虚拟IP池分配到一个IP子网段,宿主机上每个容器则从该IP段中分配得到1个虚拟IP。

本地路由
1)、假设我们在宿主机A上新建了一个容器,则该容器分配了一个虚拟IP,我们假设它是值是k。
2)、agent会在本机配置一条路由规则,即:如果数据包的目标地址等于k,那么把数据包送到容器的虚拟网卡上。
3)、另外一台宿主机B上的一个容器,其IP是m,向k容器发数据包,则数据包的目标地址是k,原地址是m。
4)、既然路由方案是不使用隧道封包为物理IP在网络中流通的,那么该数据包又该如何送达到虚拟IP k呢?

广播路由
1)、路由方案会采用如下的手段,搞定m到k的虚拟IP互通问题。
2)、即宿主机A会通过某种方式(比如BGP广播协议)把自己的虚拟IP网段广播给宿主机B。
3)、在宿主机B收到广播后,会给本机配置一条路由规则:如果数据包的目标地址属于宿主机A的虚拟IP网段,则把该数据包发给宿主机A的物理IP。
4)、这条路由规则相当于为宿主机A的虚拟IP网段配置了转发网关,而这个网关就是宿主机A的物理IP。这就要求,宿主机B和宿主机A在2层网络是互通的,也就是它们在一个交换机下面,可以基于MAC地址直接互通。
5)、一旦该数据包被送往宿主机A的物理IP,则宿主机A就可以应用刚才讲过的”本地路由“规则了,即:数据包的目标IP是k,直接送给对应容器的虚拟网卡。

整个过程中从m发往k的数据包采用的都是虚拟容器IP,没有经过任何封装和解封,而仅仅是通过宿主机B收到的广播路由+宿主机A的本地路由,就实现了在2层网络互通环境下的高效通讯。

优势/劣势
优势就是没有封包和解包过程,完全基于两端宿主机的路由表进行转发。

劣势包含2方面:
要求宿主机处于同一个2层网络下,也就是连在一台交换机上,这样才能基于MAC通讯,而不需要在IP上动封包/解包的手脚。
路由表膨胀导致性能降低,因为宿主机上每个容器需要在本机添加一条路由规则,而不同宿主机之间需要广播自己的网段路由规则。
优势/劣势
优势就是对物理网络环境没有特殊要求,只要宿主机IP层可以路由互通即可。

劣势就是性能差,这需要从以下方面看:
封包和解包耗费CPU性能;
额外的封装导致带宽浪费,大约有30%左右的带宽损耗;

flannel vxlan和calico ipip模式都是隧道方案,但是calico的封装协议IPIP的header更小,所以性能比flannel vxlan要好一点点。

三、Pod与Pod之间的网络

pod与pod之间的网络:首先pod自身拥有一个IP地址,不同pod之间直接使用IP地址进行通信即可

1.同一node节点上pod和pod通信

a.默认情况下通信

疑问:那么不同pod之间,也就是不同网络命名空间之间如何进行通信(现在还是,同一台node节点上)

解决:简单说veth对就是一个成对的端口,所有从这对端口一端进入的数据包,都将从另一端出来。
​ 为了让多个Pod的网络命名空间链接起来,我们可以让veth对的一端链接到root网络命名空间(宿主机的),另一端链接到Pod的网络命名空间。
在这里插入图片描述
在这里插入图片描述

b.使用网络插件calico

在这里插入图片描述
如下看到pod id

[root@k8s-master net.d]# kubectl get pod -owide -A -n kube-system
NAMESPACE       NAME                                       READY   STATUS             RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
argo            argo-server-6f547886b4-648zj               1/1     Running            18         29d   172.16.235.196   k8s-master   <none>           <none>
argo            argoworkflow-ci4p8rb-1871813636            0/2     Completed          0          25d   172.16.235.225   k8s-master   <none>           <none>
argo            argoworkflow-ci4p8rb-2990628649            0/2     Completed          0          25d   172.16.235.226   k8s-master   <none>           <none>
argo            minio-bbb49cb8c-79grd                      1/1     Running            124        29d   172.16.235.211   k8s-master   <none>           <none>
argo            postgres-56fd897cf4-mnxr4                  1/1     Running            6          29d   172.16.235.207   k8s-master   <none>           <none>
argo            workflow-controller-759854959c-qkt5n       0/1     CrashLoopBackOff   162        29d   172.16.235.205   k8s-master   <none>           <none>

查看路由表 可见calico在一个nodes里pod之间通信是通过路由转发完成的

[root@k8s-master net.d]# route -n 
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.43.2    0.0.0.0         UG    100    0        0 ens33
172.16.235.192  0.0.0.0         255.255.255.192 U     0      0        0 *
172.16.235.195  0.0.0.0         255.255.255.255 UH    0      0        0 calideaa39e2bc4
172.16.235.196  0.0.0.0         255.255.255.255 UH    0      0        0 cali8174bb2da07
172.16.235.197  0.0.0.0         255.255.255.255 UH    0      0        0 calie8717fd694d
172.16.235.198  0.0.0.0         255.255.255.255 UH    0      0        0 cali8981fd78aa6
172.16.235.204  0.0.0.0         255.255.255.255 UH    0      0        0 cali3fd54cdf54c
172.16.235.205  0.0.0.0         255.255.255.255 UH    0      0        0 cali69c6eba62ed
172.16.235.207  0.0.0.0         255.255.255.255 UH    0      0        0 cali114397079e7
172.16.235.209  0.0.0.0         255.255.255.255 UH    0      0        0 calic9dfed857f9
172.16.235.210  0.0.0.0         255.255.255.255 UH    0      0        0 cali3cac0fcd007
172.16.235.211  0.0.0.0         255.255.255.255 UH    0      0        0 cali5b1d5e528a1
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.43.0    0.0.0.0         255.255.255.0   U     100    0        0 ens33
192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0

在这里插入图片描述

2.不同node节点上pod和pod通信

a.使用网络插件calico(ipip)

在这里插入图片描述
在这里插入图片描述

  1. K8s在创建Pod的时候,会创建一个veth pair设备。设备的一端是pod2的网卡,另一端就是我们在node中看见的cali.4e了。
  2. pod的veth和cali.4e是一对 veth pair 他们的流量流向相同 当pod2 ping pod1 流向为10.100.9.206->10.100.15.150
  3. node2 route表,发现有一条。
 Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.100.15.128   172.31.112.2    255.255.255.192 UG    0      0        0 tunl0
  1. 所有发往10.100.15.128/255.255.255.192的ip报都需要通过tunl0,经过172.31.112.2作为gateway发送。
    因此,cali.c2的ip报会发往tunl0。
    经过tunl0的ip报会被再封上一层ip。通过node2 的route规则,会发往eth0,因此我们在eth0处的抓包结果为 172.31.127.252 > 172.31.112.2: IP 10.100.9.206 > 10.100.15.150
    5.etho0将ipip拆封后,将流量发给tunl0,tunl0再转发给cali.4e。
 Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.100.15.128   172.31.112.2    255.255.255.192 UG    0      0        0 tunl0
 Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.31.112.0    0.0.0.0         255.255.240.0   U     0      0        0 eth0

 

b.使用网络插件calico(BGP模式)

在安装calico网络时,默认安装是IPIP网络。calico.yaml文件中,将CALICO_IPV4POOL_IPIP的值修改成 “off”,就能够替换成BGP网络。
在这里插入图片描述
BGP网络相比较IPIP网络,最大的不同之处就是没有了隧道设备 tunl0。 前面介绍过IPIP网络pod之间的流量发送tunl0,然后tunl0发送对端设备。BGP网络中,pod之间的流量直接从网卡发送目的地,减少了tunl0这个环节。

在这里插入图片描述

c.使用网络插件Flannel(UDP模式)

在这里插入图片描述

  1. pod中产生数据,根据pod的路由信息,将数据发送到Cni0
    在这里插入图片描述
  2. Cni0 根据节点的路由表,将数据发送到隧道设备flannel.1

在这里插入图片描述

  1. Flannel.1查看数据包的目的ip,从flanneld获得对端隧道设备的必要信息,封装数据包。
    flannel.1为vxlan设备,当数据包来到flannel.1时,需要将数据包封装起来。此时的dst ip 为192.20.1.43,src ip为192.20.0.51。数据包继续封装需要知道192.20.1.43 ip地址对应的mac地址。此时,flannel.1不会发送arp请求去获得192.20.1.42的mac地址,而是由Linux kernel将一个“L3 Miss”事件请求发送到用户空间的flanned程序。Flanned程序收到内核的请求事件之后,从etcd查找能够匹配该地址的子网的flannel.1设备的mac地址,即发往的pod所在host中flannel.1设备的mac地址。Flannel在为Node节点分配ip网段时记录了所有的网段和mac等信息,所以能够知道。交互流程如下图所示:在这里插入图片描述
    在这里插入图片描述

  2. Flannel.1将数据包发送到对端设备。对端节点的网卡接收到数据包,发现数据包为overlay数据包,解开外层封装,并发送内层封装到flannel.1设备。

  3. Flannel.1设备查看数据包,根据路由表匹配,将数据发送给Cni0设备。

  4. Cni0匹配路由表,发送数据给网桥上对应的端口。

d.使用网络插件Flannel(hostgw模式)

hostgw是最简单的backend,它的原理非常简单,直接添加路由,将目的主机当做网关,直接路由原始封包。例如,我们从etcd中监听到一个EventAdded事件:subnet为10.1.15.0/24被分配给主机Public IP 192.168.0.100,hostgw要做的工作非常简单,在本主机上添加一条目的地址为10.1.15.0/24,网关地址为192.168.0.100,输出设备为上文中选择的集群间交互的网卡即可。对于EventRemoved事件,删除对应的路由即可。

e.使用网络插件Flannel(vxlan模式)

在这里插入图片描述
1.当nodeAPOD发送数据包到nodeBpod。首先会从通过网桥转发到宿主机,
2.通过路由表发现请求10.1.160.0会通过flannel.1发送到网关10.1.160.0。
3.查找arp表找到nodeB的mac地址。
4.进行数据包封装。到现在为止,vxlan负载部分的数据已经封装完成。由于flannel.1是vtep设备,会对通过它发出的数据进行vxlan封装(这一步是由内核完成的,相当于udp backend中的proxy)
5.通过查询fdb,我们就能知道目的主机的IP地址为192.168.0.101。

三、Pod与Service之间的网络

pod的ip地址是不持久的,当集群中pod的规模缩减或者pod故障或者node故障重启后,新的pod的ip就可能与之前的不一样的。所以k8s中衍生出来Service来解决这个问题。

Service管理了多个Pods,每个Service有一个虚拟的ip,要访问service管理的Pod上的服务只需要访问你这个虚拟ip就可以了,这个虚拟ip是固定的,当service下的pod规模改变、故障重启、node重启时候,对使用service的用户来说是无感知的,因为他们使用的service的ip没有变。

当数据包到达Service虚拟ip后,数据包会被通过k8s给该servcie自动创建的负载均衡器路由到背后的pod容器。

  • 在k8s里,iptables规则是由kube-proxy配置,kube-proxy监视APIserver的更改,因为集群中所有service(iptables)更改都会发送到APIserver上,所以每台kubelet-proxy监视APIserver,当对service或pod虚拟IP进行修改时,kube-proxy就会在本地更新,以便正确发送给后端pod
  • pod到service包的流转:
    在这里插入图片描述

1.kube-proxy

kube-proxy是Kubernetes的核心组件,部署在每个Node节点上,它是实现Kubernetes Service的通信与负载均衡机制的重要组件; kube-proxy负责为Pod创建代理服务,从apiserver获取所有server信息,并根据server信息创建代理服务,实现server到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络。

kubernetes里kube-proxy支持三种模式,在v1.8之前我们使用的是iptables 以及 userspace两种模式,在kubernetes 1.8之后引入了ipvs模式,并且在v1.11中正式使用,其中iptables和ipvs都是内核态也就是基于netfilter,只有userspace模式是用户态。下面详细介绍下各个模式:

a.userspace

在k8s v1.2后就已经被淘汰了,userspace的作用就是在proxy的用户空间监听一个端口,所有的svc都转到这个端口,然后proxy的内部应用层对其进行转发。proxy会为每个svc随机监听一个端口,并增加一个iptables规则,从客户端到 ClusterIP:Port 的报文都会被重定向到 Proxy Port,Kube-Proxy 收到报文后,通过 Round Robin (轮询) 或者 Session Affinity(会话亲和力,即同一 Client IP 都走同一链路给同一 Pod 服务)分发给对应的 Pod。所有流量都是在用户空间进行转发的,虽然比较稳定,但是效率不高。如下图为userspace的工作流程。
在这里插入图片描述

b.Iptables

iptables这种模式是从kubernetes1.2开始并在v1.12之前的默认模式。在这种模式下proxy监控kubernetes对svc和ep对象进行增删改查。并且这种模式使用iptables来做用户态的入口,而真正提供服务的是内核的Netilter,Netfilter采用模块化设计,具有良好的可扩充性。其重要工具模块IPTables从用户态的iptables连接到内核态的Netfilter的架构中,Netfilter与IP协议栈是无缝契合的,并允许使用者对数据报进行过滤、地址转换、处理等操作。这种情况下proxy只作为Controller。
Kube-Proxy 监听 Kubernetes Master 增加和删除 Service 以及 Endpoint 的消息。对于每一个 Service,Kube Proxy 创建相应的 IPtables 规则,并将发送到 Service Cluster IP 的流量转发到 Service 后端提供服务的 Pod 的相应端口上。并且流量的转发都是在内核态进行的,所以性能更高更加可靠。

在这种模式下缺点就是在大规模的集群中,iptables添加规则会有很大的延迟。因为使用iptables,每增加一个svc都会增加一条iptables的chain。并且iptables修改了规则后必须得全部刷新才可以生效。
在这里插入图片描述
在这里插入图片描述
如上图

  1. api服务器增删改查SVC或者ENDPOINT的消息的时候,kube-proxy都会创建相应的iptables规则。内核会检查数据包是否匹配这些iptables规则。
  2. 当某pod访问SVC上图所示,如果某个规则规定如果有任何数据包的目的地IP等于172.30.0.1(SVC)、端口为80,那么数据包的目的ip就会替换成随机被选中的pod的IP地址
    在这里插入图片描述

ServiceIP 无法ping在这里插入图片描述
拿argo SVC举例,argo-server对应CLUSTER-IP 10.101.149.237

[root@k8s-master ~]# kubectl get svc -n argo
NAME                          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
argo-server                   ClusterIP   10.101.149.237   <none>        2746/TCP   34d
minio                         ClusterIP   10.103.254.106   <none>        9000/TCP   34d
postgres                      ClusterIP   10.108.230.207   <none>        5432/TCP   34d
workflow-controller-metrics   ClusterIP   10.109.229.193   <none>        9090/TCP   34d


看一下iptables:

[root@k8s-master ~]# iptables  -S -t nat
[root@k8s-master ~]# iptables -S -t nat
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P POSTROUTING ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-N KUBE-MARK-DROP
-N KUBE-MARK-MASQ
-N KUBE-POSTROUTING
-N KUBE-KUBELET-CANARY
-N KUBE-PROXY-CANARY
-N KUBE-SERVICES
-N KUBE-NODEPORTS
-N KUBE-SVC-NPX46M4PTMTKRN6Y
-N KUBE-SEP-5HM4D4VNYL5LJ3YS
-N KUBE-SVC-TCOU7JCQXEZGVUNU
-N KUBE-SEP-MCBGDC3IDDOJPOZW
-N KUBE-SEP-CZTEP63QU7QLBG7I
-N KUBE-SVC-ERIFXISQEP7F7OF4
-N KUBE-SEP-OJEUXTKD2IPFL4TE
-N KUBE-SEP-VPFGRB7AWBSEZJS3
-N KUBE-SVC-JD5MR3NA4I4DYORP
-N KUBE-SEP-VJYMPICMIZECFH6N
-N KUBE-SEP-LSJOPL6U2F7J3EPE
-N cali-nat-outgoing
-N cali-PREROUTING
-N cali-POSTROUTING
-N cali-OUTPUT
-N cali-fip-dnat
-N cali-fip-snat
-N KUBE-SVC-ZOCK3AR54624ZYO5
-N KUBE-SEP-OIQGZUXSF6GARWC4
-N KUBE-SVC-X252YBINIXYQMUPV
-N KUBE-SEP-6PU7AATW2E6NOHDZ
-N KUBE-SVC-F7OUP6RCRFD6CPEL
-N KUBE-SEP-K6FFYB5NWZ7YZAWL
-N KUBE-SVC-V2J5CEF3QMJSDCUB
-N KUBE-SEP-JGB6QJP2WMHSLDC4
-N KUBE-SVC-3DOJ5NE7U7VPDI4F
-N KUBE-SEP-QGUWI4AWTUUKHZ5E
-N KUBE-SVC-4QHPRSEZSM2F6AWF
-N KUBE-SEP-VOPWWVXLDV4PPQKW
-A PREROUTING -m comment --comment "cali:6gwbT8clXdHdC1b1" -j cali-PREROUTING
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -m comment --comment "cali:O3lYWMrLQYEMJtB5" -j cali-POSTROUTING
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 192.168.122.0/24 -d 224.0.0.0/24 -j RETURN
-A POSTROUTING -s 192.168.122.0/24 -d 255.255.255.255/32 -j RETURN
-A POSTROUTING -s 192.168.122.0/24 ! -d 192.168.122.0/24 -p tcp -j MASQUERADE --to-ports 1024-65535
-A POSTROUTING -s 192.168.122.0/24 ! -d 192.168.122.0/24 -p udp -j MASQUERADE --to-ports 1024-65535
-A POSTROUTING -s 192.168.122.0/24 ! -d 192.168.122.0/24 -j MASQUERADE
-A POSTROUTING -s 172.17.0.3/32 -d 172.17.0.3/32 -p tcp -m tcp --dport 443 -j MASQUERADE
-A POSTROUTING -s 172.17.0.3/32 -d 172.17.0.3/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A OUTPUT -m comment --comment "cali:tVnHkvAo15HuiPy0" -j cali-OUTPUT
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 4430 -j DNAT --to-destination 172.17.0.3:443
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 800 -j DNAT --to-destination 172.17.0.3:80
-A KUBE-MARK-DROP -j MARK --set-xmark 0x8000/0x8000
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE --random-fully
-A KUBE-SERVICES -d 10.101.246.66/32 -p tcp -m comment --comment "middleware/redis:redis cluster IP" -m tcp --dport 6379 -j KUBE-SVC-3DOJ5NE7U7VPDI4F
-A KUBE-SERVICES -d 10.103.254.106/32 -p tcp -m comment --comment "argo/minio: cluster IP" -m tcp --dport 9000 -j KUBE-SVC-V2J5CEF3QMJSDCUB
-A KUBE-SERVICES -d 10.109.229.193/32 -p tcp -m comment --comment "argo/workflow-controller-metrics:metrics cluster IP" -m tcp --dport 9090 -j KUBE-SVC-4QHPRSEZSM2F6AWF
-A KUBE-SERVICES -d 10.96.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
-A KUBE-SERVICES -d 10.101.149.237/32 -p tcp -m comment --comment "argo/argo-server:web cluster IP" -m tcp --dport 2746 -j KUBE-SVC-X252YBINIXYQMUPV
-A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-SVC-ERIFXISQEP7F7OF4
-A KUBE-SERVICES -d 10.108.230.207/32 -p tcp -m comment --comment "argo/postgres: cluster IP" -m tcp --dport 5432 -j KUBE-SVC-ZOCK3AR54624ZYO5
-A KUBE-SERVICES -d 10.105.115.121/32 -p tcp -m comment --comment "middleware/postgresql:postgres cluster IP" -m tcp --dport 5432 -j KUBE-SVC-F7OUP6RCRFD6CPEL
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A KUBE-SVC-NPX46M4PTMTKRN6Y -m comment --comment "default/kubernetes:https" -j KUBE-SEP-5HM4D4VNYL5LJ3YS
-A KUBE-SEP-5HM4D4VNYL5LJ3YS -s 192.168.43.3/32 -m comment --comment "default/kubernetes:https" -j KUBE-MARK-MASQ
-A KUBE-SEP-5HM4D4VNYL5LJ3YS -p tcp -m comment --comment "default/kubernetes:https" -m tcp -j DNAT --to-destination 192.168.43.3:6443
-A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-MCBGDC3IDDOJPOZW
-A KUBE-SVC-TCOU7JCQXEZGVUNU -m comment --comment "kube-system/kube-dns:dns" -j KUBE-SEP-CZTEP63QU7QLBG7I
-A KUBE-SEP-MCBGDC3IDDOJPOZW -s 172.16.235.212/32 -m comment --comment "kube-system/kube-dns:dns" -j KUBE-MARK-MASQ
-A KUBE-SEP-MCBGDC3IDDOJPOZW -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 172.16.235.212:53
-A KUBE-SEP-CZTEP63QU7QLBG7I -s 172.16.235.220/32 -m comment --comment "kube-system/kube-dns:dns" -j KUBE-MARK-MASQ
-A KUBE-SEP-CZTEP63QU7QLBG7I -p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 172.16.235.220:53
-A KUBE-SVC-ERIFXISQEP7F7OF4 -m comment --comment "kube-system/kube-dns:dns-tcp" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-OJEUXTKD2IPFL4TE
-A KUBE-SVC-ERIFXISQEP7F7OF4 -m comment --comment "kube-system/kube-dns:dns-tcp" -j KUBE-SEP-VPFGRB7AWBSEZJS3
-A KUBE-SEP-OJEUXTKD2IPFL4TE -s 172.16.235.212/32 -m comment --comment "kube-system/kube-dns:dns-tcp" -j KUBE-MARK-MASQ
-A KUBE-SEP-OJEUXTKD2IPFL4TE -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp" -m tcp -j DNAT --to-destination 172.16.235.212:53
-A KUBE-SEP-VPFGRB7AWBSEZJS3 -s 172.16.235.220/32 -m comment --comment "kube-system/kube-dns:dns-tcp" -j KUBE-MARK-MASQ
-A KUBE-SEP-VPFGRB7AWBSEZJS3 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp" -m tcp -j DNAT --to-destination 172.16.235.220:53
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-VJYMPICMIZECFH6N
-A KUBE-SVC-JD5MR3NA4I4DYORP -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-SEP-LSJOPL6U2F7J3EPE
-A KUBE-SEP-VJYMPICMIZECFH6N -s 172.16.235.212/32 -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-VJYMPICMIZECFH6N -p tcp -m comment --comment "kube-system/kube-dns:metrics" -m tcp -j DNAT --to-destination 172.16.235.212:9153
-A KUBE-SEP-LSJOPL6U2F7J3EPE -s 172.16.235.220/32 -m comment --comment "kube-system/kube-dns:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-LSJOPL6U2F7J3EPE -p tcp -m comment --comment "kube-system/kube-dns:metrics" -m tcp -j DNAT --to-destination 172.16.235.220:9153
-A cali-nat-outgoing -m comment --comment "cali:flqWnvo8yq4ULQLa" -m set --match-set cali40masq-ipam-pools src -m set ! --match-set cali40all-ipam-pools dst -j MASQUERADE --random-fully
-A cali-PREROUTING -m comment --comment "cali:r6XmIziWUJsdOK6Z" -j cali-fip-dnat
-A cali-POSTROUTING -m comment --comment "cali:Z-c7XtVd2Bq7s_hA" -j cali-fip-snat
-A cali-POSTROUTING -m comment --comment "cali:nYKhEzDlr11Jccal" -j cali-nat-outgoing
-A cali-POSTROUTING -o tunl0 -m comment --comment "cali:SXWvdsbh4Mw7wOln" -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE --random-fully
-A cali-OUTPUT -m comment --comment "cali:GBTAv2p5CwevEyJm" -j cali-fip-dnat
-A KUBE-SVC-ZOCK3AR54624ZYO5 -m comment --comment "argo/postgres:" -j KUBE-SEP-OIQGZUXSF6GARWC4
-A KUBE-SEP-OIQGZUXSF6GARWC4 -s 172.16.235.215/32 -m comment --comment "argo/postgres:" -j KUBE-MARK-MASQ
-A KUBE-SEP-OIQGZUXSF6GARWC4 -p tcp -m comment --comment "argo/postgres:" -m tcp -j DNAT --to-destination 172.16.235.215:5432
-A KUBE-SVC-X252YBINIXYQMUPV -m comment --comment "argo/argo-server:web" -j KUBE-SEP-6PU7AATW2E6NOHDZ
-A KUBE-SEP-6PU7AATW2E6NOHDZ -s 172.16.235.240/32 -m comment --comment "argo/argo-server:web" -j KUBE-MARK-MASQ
-A KUBE-SEP-6PU7AATW2E6NOHDZ -p tcp -m comment --comment "argo/argo-server:web" -m tcp -j DNAT --to-destination 172.16.235.240:2746
-A KUBE-SVC-F7OUP6RCRFD6CPEL -m comment --comment "middleware/postgresql:postgres" -j KUBE-SEP-K6FFYB5NWZ7YZAWL
-A KUBE-SEP-K6FFYB5NWZ7YZAWL -s 172.16.235.227/32 -m comment --comment "middleware/postgresql:postgres" -j KUBE-MARK-MASQ
-A KUBE-SEP-K6FFYB5NWZ7YZAWL -p tcp -m comment --comment "middleware/postgresql:postgres" -m tcp -j DNAT --to-destination 172.16.235.227:5432
-A KUBE-SVC-V2J5CEF3QMJSDCUB -m comment --comment "argo/minio:" -j KUBE-SEP-JGB6QJP2WMHSLDC4
-A KUBE-SEP-JGB6QJP2WMHSLDC4 -s 172.16.235.245/32 -m comment --comment "argo/minio:" -j KUBE-MARK-MASQ
-A KUBE-SEP-JGB6QJP2WMHSLDC4 -p tcp -m comment --comment "argo/minio:" -m tcp -j DNAT --to-destination 172.16.235.245:9000
-A KUBE-SVC-3DOJ5NE7U7VPDI4F -m comment --comment "middleware/redis:redis" -j KUBE-SEP-QGUWI4AWTUUKHZ5E
-A KUBE-SEP-QGUWI4AWTUUKHZ5E -s 172.16.235.214/32 -m comment --comment "middleware/redis:redis" -j KUBE-MARK-MASQ
-A KUBE-SEP-QGUWI4AWTUUKHZ5E -p tcp -m comment --comment "middleware/redis:redis" -m tcp -j DNAT --to-destination 172.16.235.214:6379
-A KUBE-SVC-4QHPRSEZSM2F6AWF -m comment --comment "argo/workflow-controller-metrics:metrics" -j KUBE-SEP-VOPWWVXLDV4PPQKW
-A KUBE-SEP-VOPWWVXLDV4PPQKW -s 172.16.235.251/32 -m comment --comment "argo/workflow-controller-metrics:metrics" -j KUBE-MARK-MASQ
-A KUBE-SEP-VOPWWVXLDV4PPQKW -p tcp -m comment --comment "argo/workflow-controller-metrics:metrics" -m tcp -j DNAT --to-destination 172.16.235.251:9090

可以观察到
直接访问cluster IP(10.101.149.237)的2746端口会直接跳转到KUBE-SVC-X252YBINIXYQMUPV

-A KUBE-SERVICES -d 10.101.149.237/32 -p tcp -m comment --comment "argo/argo-server:web cluster IP" -m tcp --dport 2746 -j KUBE-SVC-X252YBINIXYQMUPV

如果有两个KUBE-SEP

KUBE-SVC-X252YBINIXYQMUPV的链如下
这里利用了iptables的–probability的特性,使连接有50%的概率进入到KUBE-SEP-ID6YWIT3F6WNZ47P链,50%的概率进入到KUBE-SEP-IN2YML2VIFH5RO2T链。 KUBE-SEP-ID6YWIT3F6WNZ47P的链的具体作用就是将请求通过DNAT发送到172.16.235.240的2746端口:

-A KUBE-SVC-X252YBINIXYQMUPV -m comment --comment "argo/argo-server:web" -j KUBE-SEP-6PU7AATW2E6NOHDZ
-A KUBE-SEP-6PU7AATW2E6NOHDZ -s 172.16.235.240/32 -m comment --comment "argo/argo-server:web" -j KUBE-MARK-MASQ
-A KUBE-SEP-6PU7AATW2E6NOHDZ -p tcp -m comment --comment "argo/argo-server:web" -m tcp -j DNAT --to-destination 172.16.235.240:2746

在这里插入图片描述
在这里插入图片描述

c.ipvs mode

kube-proxy ipvs 是基于 NAT 实现的,通过ipvs的NAT模式,对访问k8s service的请求进行虚IP到POD IP的转发。当创建一个 service 后,kubernetes 会在每个节点上创建一个网卡,同时帮你将 Service IP(VIP) 绑定上,此时相当于每个 Node 都是一个 ds,而其他任何 Node 上的 Pod,甚至是宿主机服务(比如 kube-apiserver 的 6443)都可能成为 rs;

与iptables、userspace 模式一样,kube-proxy 依然监听Service以及Endpoints对象的变化, 不过它并不创建反向代理, 也不创建大量的 iptables 规则, 而是通过netlink 创建ipvs规则,并使用k8s Service与Endpoints信息,对所在节点的ipvs规则进行定期同步;
netlink 与 iptables 底层都是基于 netfilter 钩子,但是 netlink 由于采用了 hash table 而且直接工作在内核态,在性能上比 iptables 更优。其工作流程大体如下:
在这里插入图片描述
由此分析:ipvs 是目前 kube-proxy 所支持的最新代理模式,相比使用 iptables,使用 ipvs 具有更高的性能。

Ⅰ、ipvs kube-proxy原理分析

具体来说,ipvs 使用ipset来存储需要DROP或masquared的流量的源或目标地址,以确保 iptables 规则的数量是恒定的,这样我们就不需要关心我们有多少服务了。
先前基于iptables规则表的DNAT->SNAT方式来处理外部客户端到k8s集群pod内的流量
和集群内部流量(cluster-ip到pod ip),无需在宿主机上管理cluster-ip都由iptables来进行
管理。

使用IPVS后是需要对vs(虚拟服务也就是vip)进行管理,由于IPVS的DNAT钩子挂在INPUT链上,因此必须要让内核识别 VIP(cluster-ip) 是本机的 IP。k8s 通过设置将service cluster ip 绑定到虚拟网卡kube-ipvs0,其中下面的10.96.x.x都是VIP,也就是cluster-ip。如下图:
在这里插入图片描述

Ⅱ、从cluster-ip到pod访问

这里访问cluster ip为10.96.0.10,k8s集群内部的dns服务
1)、入口流量匹配:
数据包是通过本地协议发出的,在宿主机本地通过访问cluster-ip到后端真是的pod那么就要伪装所有访问 Service Cluster IP 的外部流量,k8s只能在OUTPUT这个链上来做相应的规则:

$iptables -S -tnat | grep OUTPUT

在这里插入图片描述
匹配到倒数第二条就是将流量引入到KUBE-SERVICES规则中处理。如下图:
2)、入口流量引流到全局链KUBE-SERVICES中:
ipset list KUBE-CLUSTER-IP
iptables -S -tnat | grep KUBE-SERVICES
在这里插入图片描述
第一步中上面的数据包流入到KUBE-SERVICES该规则中目的就是让源地址不是10.244.0.0/16,目的地址match 到 KUBE-CLUSTER-IP 的数据包打上标签。

3)、入口流量标签化处理:
将上面KUBE-SERVICES链中的流量进行打标签处理:
$iptables -S -tnat | grep KUBE-MARK-MASQ
在这里插入图片描述
4)、入口流量SNAT处理:
那么数据包在出去的时候一定是要经过POSTROUTING链进行SNAT即将所有来源外部
流量转换成该cluster ip的源地址。
$iptables -S -tnat | grep POSTROUTING

在这里插入图片描述
然后通过内部的lvs进行流量转发到后端pod上。如下图:
$ipvsadm -Ln
在这里插入图片描述
在这里插入图片描述

Ⅲ、kube-proxy ipvs和iptables的异同
  • iptables与IPVS都是基于Netfilter实现的,但因为定位不同,二者有着本质的差别:iptables是为防火墙而设计的;IPVS则专门用于高性能负载均衡,并使用更高效的数据结构(Hash表),允许几乎无限的规模扩张。
  • 与iptables相比,IPVS拥有以下明显优势:
  • 为大型集群提供了更好的可扩展性和性能;
  • 支持比iptables更复杂的复制均衡算法(最小负载、最少连接、加权等);
  • 支持服务器健康检查和连接重试等功能;
  • 可以动态修改ipset的集合,即使iptables的规则正在使用这个集合。

四、Internet与Service之间的网络

将k8s集群服务暴露给互联网上用户使用,有两个问题;

  • 从k8s的service访问Internet
  • 从Internet访问k8s的service

1.从k8s的service访问Internet

根据参考文章,通过Internet网关,node可以将流量路由到Internet,但是pod具有自己的IP地址,Internet网关上的NAT转换并不适用。
参考方案:就是node主机通过iptables的nat来解决

node到internet包的流转
在这里插入图片描述

  1. 数据包源自pod1网络命名空间,通过veth对连接到root网络命名空间,紧接着,转发表里没有IP对应的mac,会发送到默认路由,到达root网络命名空间的eth0
  2. 那么在到达root网络明明空间之前,iptables会修改数据包,现在数据包源ip是pod1的,继续传输会被Internet网关拒绝掉,因为网关NAT仅转发node的ip,解决方案:使iptables执行源NAT更改数据包源ip,让数据包看起来是来自于node而不是pod
  3. iptables修改完源ip之后,数据包离开node,根据转发规则发给Internet网关,Internet网关执行另一个NAT,内网ip转为公网ip,在Internet上传输。
    数据包回应时,也是按照:Internet网关需要将公网IP转换为私有ip,到达目标node节点,再通过iptables修改目标ip并且最终传送到pod的eth0虚拟网桥。

2.Internet到k8s的流量

让Internet流量进入k8s集群,这特定于配置的网络,可以在网络堆栈的不同层来实现:

a.NodePort

NodePort在集群中的主机节点上为Service提供一个代理端口,以允许从主机网络上对Service进行访问。

查看创建的service,可以看到kubernetes创建了一个名为webapp-nodeport-svc的service,并为该service在主机节点上创建了30080这个Nodeport。

master $ kubectl get svc
NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes             ClusterIP   10.96.0.1       <none>        443/TCP        36m
webapp1-nodeport-svc   NodePort    10.103.188.73   <none>        80:30080/TCP   3m

webapp-nodeport-svc后端对应两个Pod,其Pod的IP分别为10.32.0.3和10.32.0.5。

master $ kubectl get pod -o wide
NAME                                           READY     STATUS    RESTARTS   AGE       IPNODE      NOMINATED NODE
webapp1-nodeport-deployment-785989576b-cjc5b   1/1       Running   0          2m        10.32.0.3
webapp1-nodeport-deployment-785989576b-tpfqr   1/1       Running   0          2m        10.32.0.5

通过netstat命令可以看到Kube-proxy在主机网络上创建了30080监听端口,用于接收从主机网络进入的外部流量。

master $ netstat -lnp | grep 30080
tcp6       0      0 :::30080                :::*                    LISTEN      7427/kube-proxy

下面是Kube-proxy创建的相关iptables规则以及对应的说明。可以看到Kube-proxy为Nodeport创建了相应的IPtable规则,将发向30080这个主机端口上的流量重定向到了后端的两个Pod IP上。
观看上文的kube-proxy的iptables

iptables-save > iptables-dump
# Generated by iptables-save v1.6.0 on Thu Mar 28 07:33:57 2019
*nat
# Nodeport规则链
:KUBE-NODEPORTS - [0:0]
# Service规则链
:KUBE-SERVICES - [0:0]
# Nodeport和Service共用的规则链
:KUBE-SVC-J2DWGRZTH4C2LPA4 - [0:0]
:KUBE-SEP-4CGFRVESQ3AECDE7 - [0:0]
:KUBE-SEP-YLXG4RMKAICGY2B3 - [0:0]

# 将host上30080端口的外部tcp流量转到KUBE-SVC-J2DWGRZTH4C2LPA4链
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/webapp1-nodeport-svc:" -m tcp --dport 30080 -j KUBE-SVC-J2DWGRZTH4C2LPA4

#将发送到Cluster IP 10.103.188.73的内部流量转到KUBE-SVC-J2DWGRZTH4C2LPA4链
KUBE-SERVICES -d 10.103.188.73/32 -p tcp -m comment --comment "default/webapp1-nodeport-svc: cluster IP" -m tcp --dport 80 -j KUBE-SVC-J2DWGRZTH4C2LPA4

#将发送到webapp1-nodeport-svc的流量转交到第一个Pod(10.32.0.3)相关的规则链上,比例为50%
-A KUBE-SVC-J2DWGRZTH4C2LPA4 -m comment --comment "default/webapp1-nodeport-svc:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-YLXG4RMKAICGY2B3
#将发送到webapp1-nodeport-svc的流量转交到第二个Pod(10.32.0.5)相关的规则链上
-A KUBE-SVC-J2DWGRZTH4C2LPA4 -m comment --comment "default/webapp1-nodeport-svc:" -j KUBE-SEP-4CGFRVESQ3AECDE7

#将请求重定向到Pod 10.32.0.3
-A KUBE-SEP-YLXG4RMKAICGY2B3 -p tcp -m comment --comment "default/webapp1-nodeport-svc:" -m tcp -j DNAT --to-destination 10.32.0.3:80
#将请求重定向到Pod 10.32.0.5
-A KUBE-SEP-4CGFRVESQ3AECDE7 -p tcp -m comment --comment "default/webapp1-nodeport-svc:" -m tcp -j DNAT --to-destination 10.32.0.5:80

在这里插入图片描述

b.LoadBalancer

在那些支持外部负载均衡器的云提供者上面,将type字段设置为"LoadBalancer"会为你的Service设置好一个负载均衡器。该负载均衡器的实际的创建是异步进行的,并且该设置好均衡器会在该Service的status.loadBalancer字段中显示出来。例如:在这里插入图片描述

c.Ingress控制器。

在这里插入图片描述

总结

提示:这里 对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

Logo

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

更多推荐