在Kubernetes环境中,容器间如何进行网络通信

提到容器就不得不提起容器的核心底层技术Namespace-隔离机制。

列出docker中7种命名空间

Mount Namespace(装载命名空间)   文件系统隔离

UTS Namespace                  主机名隔离

IPC Namespace                  进程间通信隔离

PID Namespace                  进程号隔离

Network Namespace              网络隔离

User Namespace                 用户认证隔离

Control group(Cgroup) Namespace   Cgroup认证(since Linux 4.6)

 

通过命令lsns可以查看到宿主机上所有的Namespace(注意需要使用root用户执行,否则可能会出现有些Namespace看不到的情况):

 

解释一下lsns输出的含义:

NS         Namespace identifierinode number

TYPE      kind of namespace

NPROCS number of processes in the namespaces

PID        lowers PID in the namespace

USER      username of the PID

COMMAND  command line of the PID

NS命名空间标识符(索引节点号)

命名空间类型

NPROCS命名空间中的进程数

PID降低名称空间中的PID

PID的用户用户名

PID的命令行

 

了解namespace之后可以知道不同的两个Network Namespace在网络上是相互隔离的,即不能够直接进行通信,就好比两个相互隔离的局域网之间不能直接通信,那么通过Network Namespace隔离后的容器是如何实现与外部进行通信的呢?

Pod网络基本原理

容器通过使用linux内核提供的Cgroups和Namespaces技术实现了相互之间的网络、存储等资源的隔离与限制。对于网络,kubernetes项目中的Pod则通过让一组用户容器和pause容器/infra容器共享一个network namespace的方式,使一个Pod内的容器都使用了一个网络栈。而一个 Network Namespace 的网络栈包括:网卡(Network Interface)、回环设备(Loopback Device)、路由表(Routing Table)和 iptables 规则。所以,不难想象,Pod的网络和一台虚拟机的网络栈配置起来实际上是类似的,比如同样需要虚拟网卡,IP、MAC地址等,并且每个Pod都有自己唯一的网络栈。当所有的Pod都有了自己的网络栈后,如果想要连接两个Pod进行通信,则类似于其他任何网络架构,需要配置交换机、路由器等,并为其规划IP,路由等信息。如果是对于物理机,我们可以使用网线、交换机、路由器这些设备进行连接,但在Pod中显然需要其他方式。

Kubernetes的Pod 网络通信的实现依赖于第三方插件Flannel ,由 CoreOS主推的目前比较主流的容器网络解决方案。讨论这些则主要是在关注容器跨主机通信的问题。而我们首先需要明白的问题是Pod的内部的网卡如何创建,又如何将网络数据包在宿主机和容器之间传递?

flannel做了什么:

1、flannel协助kubernetes,给每一个Node上的Docker容器分配互相不冲突的IP。
2、flannel能够在这些IP地址之间建立一个全覆盖网络(Overlay Network),通过这个覆盖网络将数据包原封不动的传递到容器内部(使用默认的VXLAN模式)。

3、flanneld 创建了一个 flannel.1接口,它是专门用来封装隧道协议的,默认分给集群的 Pod 网段为 10.244.0.0/16。

4、flannel 给 k8s-master 节点配置的 Pod 网络为 10.244.0.0 段,给 k8s-node01 节点配置的 Pod 网络为 10.244.1.0 段,如果有更多的节点,以此类推。

5、flannel利用Kubernetes API或者etcd用于存储整个集群的网络配置,根据配置记录集群使用的网段。etcd保证了所有node上flanned所看到的配置是一致的。同时每个node上的flanned监听etcd上的数据变化,实时感知集群中node的变化。

#Flannel默认使用8285端口作为UDP封装报文的端口,VxLan使用8472端口。

续解:

# overlay network (全覆盖网络)简单说来覆盖网络就是应用层网络,面向应用层。

#2012年10月,Linux内核增加了vxlan支持,内核的版本要求3.7+,所以一开始我们就把内核升级到4.4的版本

#vxlan(虚拟可扩展局域网):是一种基于IP网络(L3)的基础上虚拟(L2)网络连接的解决方案,为多用租户平台提供了虚拟网络强大的扩展能力和隔离性。

#七层网络模式:应用、表示、会话、传输、网络、数据链路、物理。

flannel架构图

 

 

 

 

各个组件的解释:

 

各个组件的解释:

Cni0:网桥设备,每创建一个pod都会创建一对 veth pair。其中一端是pod中的eth0,另一端是Cni0网桥中的端口(网卡)。Pod中从网卡eth0发出的流量都会发送到Cni0网桥设备的端口(网卡)上。

# Cni0 设备获得的ip地址是该节点分配到的网段的第一个地址。

Flannel.1: overlay网络的设备,用来进行 vxlan 报文的处理(封包和解包)。不同node之间的pod数据流量都从overlay设备以隧道的形式发送到对端。 

Flanneld:flannel在每个主机中运行flanneld作为agent(代理端),它会为所在主机从集群的网络地址空间中,获取一个小的网段subnet,本主机内所有容器的IP地址都将从中分配。同时Flanneld监听K8s集群数据库,为flannel.1设备提供封装数据时必要的mac,ip等网络数据信息。

veth的特点:

  • vEth作为一种虚拟以太网络设备,可以连接两个不同的Network Namespace。
  • vEth总是成对创建,所以一般叫veth pair。(因为没有只有一头的网线)。
  • vEth当中一端收到数据包后另一端也会立马收到。
  • 可以通过ethtool找到vEth的对端接口。

cni和veth的联系:

#如果集群把一个 Pod 调度到节点上,kubelet 会通过 flannel cni 为这个 Pod 本身创建网络命名空间和 veth 设备(veth成对出现),然后,把其中一个 veth 设备加入到 cni0 虚拟网桥里,并为 Pod 内的 veth 设备配置 IP 地址。这样 Pod 就和网络通信的干道连接在了一起。

 

#需要强调的是,前面部署的 flanneld 和这一节的 flannel cni 完全是两个组件。flanneld 是一个 daemonset(守护进程) 下发到每个节点的 pod,它的作用是搭建网络(干道),而 flannel cni 是节点创建的时候,通过kubernetes-cni 这个cni 插件,其被 kubelet 调用,用来为具体的 pod 创建网络(分枝)。

通信分析

使用brctl查看master节点上的该网桥:

brctl show cni0

#如下图可以看到刚好有一对veth容器的网络接口挂在了cni0网桥上。

#brctl网桥管理工具,安装用:yum install bridge-utils -y

 

测试正常访问:

1、本地ping podIP测试

2、进入pod ping podIP测试

# kubectl exec -it  nginx-deployment-76bb469c44-xz9jm --/bin/sh

 

master节点抓物理网卡的数据包

tcpdump -i ens33 -nn

#-nn:指定将每个监听到的数据包中的域名转换成IP、端口从应用名称转换成端口号后显示

#-i    指定监听的网络接口;

#安装tcpdump:yum -y install tcpdump

总结

以上完成 Pod 网络环境搭建。基于以上的网络环境,Pod 可以完成四种通信:本地通信;同节点 Pod 通信;跨节点 Pod 通信;以及 Pod 和 Pod 网络之外的实体通信(Pod与Service之间的通信)。

同一pod中的容器通信

在同一个 Pod 内部的不同容器(Pod 内的容器是不会垮宿主机的)共享同一个网络命名空间,共享一个 Linux 协议栈。所以直接用 lo(回环)口地址就可以访问彼此的端口。

#k8s 在启动容器的时候会先启动一个pause容器,这个容器就是实现这个功能的。

 

#协议栈,英语名称为Protocol stack,又称协议堆叠:是指网络中各层协议的总和,其形象的反映了一个网络中文件传输的过程,由上层协议到底层协议,再由底层协议到上层协议。

Pause容器

全称infrastucture container(又叫infra)基础容器。

Pause的作用

在node节点上都会起很多pause容器,和pod是一一对应的。

每个Pod里运行着一个特殊的被称之为Pause的容器,其他容器则为业务容器,这些业务容器共享Pause容器的网络栈和Volume挂载卷,因此他们之间通信和数据交换更为高效,在设计时我们可以充分利用这一特性将一组密切相关的服务进程放入同一个Pod中。同一个Pod里的容器之间仅需通过localhost就能互相通信。

pause容器主要为每个业务容器提供以下功能:

PID命名空间:Pod中的不同应用程序可以看到其他应用程序的进程ID。

Network命名空间:Pod中的多个容器能够访问同一个IP和端口范围。

IPC命名空间:Pod中的多个容器能够使用SystemV IPC或POSIX消息队列进行通信。

UTS命名空间:Pod中的多个容器共享一个主机名;Volumes(共享存储卷):

Pod中的各个容器可以访问在Pod级别定义的Volumes。

 

#IPC(interprocess communication ):进程间通信

#UTS(“UNIX Time-sharing System”):Unix分时系统

同节点不同pod之间的容器通信

同一节点上的Pod 1 和 Pod 2 都是通过 Veth 连接在同一个 docker0 网桥上的,在Docker启动的时候默认会创建一个名为docker0的网桥,且默认配置下采用的网络段为172.17.0.0/16,每一个在该节点上创建的容器都会被分配一个在该网段下的IP。容器通过连接到docker0上进行相互通信。

#这里其实可以抛弃k8s的概念,实际上相当于同一台服务器上的容器之间的通信。

 

#还可通过在docker0上通过tcpdump抓包:

tcpdump -n -i docker0

可以发现pod1和pod2正是通过docker0网桥进行网络包转发的

跨主机pod通讯:

  1. A节点pod中产生数据,根据pod的路由信息,将数据发送到Cni0
  2. Cni0 根据节点的路由表,将数据发送到隧道设备flannel.1
  3. Flannel.1查看数据包的目的ip,从flanneld获得对端隧道设备的必要信息,封装数据包。
  4. Flannel.1将数据包发送到对端设备(B节点)。B节点的网卡接收到数据包,发现数据包为overlay数据包,解开外层封装,并发送内层封装到flannel.1设备。
  5. Flannel.1设备查看数据包,根据路由表匹配,将数据发送给Cni0设备。
  6. Cni0匹配路由表,发送数据给网桥上对应的pod端口。

 

具体流程可通过route和tcpdump抓包来进行详细了解:

A节点podA-—>veth--àcni0--àflannel.1(封装)--àens33---àB节点-—>ens33--àflannel.1(拆封)--àcni0--àveth--àpodB

 

#vxlan的内层数据包完成封装的格式如下:

 

pod 访问service服务

这里涉及到k8s里面一个重要的概念service。它是一个服务的抽象,通过label(k8s会根据service和pod直接的关系创建endpoint,可以通过kubectl get ep查看)关联到后端的pod容器。

Service分配的ip叫cluster ip是一个虚拟ip(相对固定,除非删除service),这个ip只能在k8s集群内部使用,如果service需要对外提供,只能使用Nodeport方式映射到主机上,使用主机的ip和端口对外提供服务。(另外还可以使用LoadBalance方式,但这种方式是在gce这样的云环境里面使用的 )。

节点上面有个kube-proxy进程,这个进程从master apiserver获取信息,感知service和endpoint的创建,然后做两个事:

为每个service 在集群中每个节点上面创建一个随机端口,任何该端口上面的连接会代理到相应的pod

集群中每个节点安装iptables规则,用于clusterip + port路由到上一步定义的随机端口上面,所以集群中每个node上面都有service的转发规则:

 

最后一种情况,Pod 与非 Pod 网络的实体通信,通过各节点上 iptables 规则做 SNAT,而此规则就是 flanneld 依据命令行 --ip-masq 选项做的配置。

最新版中已经加入了LVS机制进行转发,效率更高

IPVS是 LVS 项目的一部分,是一款运行在 Linux Kernel 当中的 4 层负载均衡器,性能异常优秀。使用调优后的内核,可以轻松处理每秒 10 万次以上的转发请求。目前在中大型互联网项目中,IPVS 被广泛的用于承接网站入口处的流量。

Logo

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

更多推荐