前言

计算机间的信息和数据在网络中必须按照数据传输的顺序、数据的格式内容等方面的约定或规则进行传输,这种约定或规则称做协议。各种网络协议分布于不同的网络分层中,网络分层分为OSI七层模型和TCP/IP五层模型两种。TCP/IP五层模型分别是应用层、传输层、网络层、链路层和物理层,与OSI七层模型的区别是在TCP/IP五层模型中,OSI七层模型中的会话层、表示层、应用层统一被称为应用层。计算机网络数据是按照协议规范采用分层的结构由发送端自上而下流动到物理层,再从物理层在网络分层中自下而上的流动到接收端的应用层,完成数据通信。网络分层中,高层级的应用模块仅利用低层级应用模块提供的接口和功能,低层级应用模块也仅是使用高层级应用模块传来的参数响应相关的操作,层次间每个应用模块都是可被能提供相同功能的应用模块替代。

Kubernetes的网络通信也遵守TCP/IP五层模型的定义,通过不同的资源对象在相应的层级提供相应的模块功能。Kubernetes资源对象在相应的网络层级与传统网络设备模块的对照表如表12-2所示。

表 12-2 设备模块对照表

网络分层 设备模块 Kubernetes资源对象
应用层 F5、Haproxy、Nginx Ingress
传输层 四层交换、路由 Service
网络层 路由器、三层交换机 flannel、calico、Pod(容器间通信)
链路层 网桥、二层交换机、网卡 vnet、bridge
物理层 中继器、集线器、网线

原生Docker的网络通信

Kubernetes是基于容器的管理系统,其使用Docker容器版本的Pod是由多个Docker容器组成的,为方便理解Pod的网络通信方式,先了解下Docker自有的网络模式,Docker容器有如下4种常见的网络模式。

  • 主机模式(host),容器与宿主机共享网络命名空间(netns,network namespace),在容器中可以查看到宿主机的所有网卡,可以通过访问宿主机IP,访问到容器中运行应用的所有网络端口。主机模式下网络传输效率最高,但宿主机上已经存在的网络端口将无法被该模式下的容器使用。

  • 无网卡模式(none),容器中只有环回(lo,Lookback)接口,运行在容器内的应用仅能使用环回接口实现网络层的数据传输。

  • 桥接模式(bridge), 容器内会被创建veth(Virtual ETHernet)设备并接入宿主机的桥接网络,通过宿主机的桥接网络,可与宿主机及宿主机中接入同一桥设备的其它容器进行通信。

  • Macvlan网络模式(macvlan),当宿主机的网络存在多个不同的VLAN时,可以通过该模式为容器配置VLAN ID, 使该容器与宿主机网络中同一VLAN ID的设备实现网络通信。

Docker容器间可以通过IP网络、容器名解析、joined容器三种方式实现通信。IP网络是在网络联通的基础上通过IP地址实现互访通信。容器名解析是在网络联通的基础上,由Docker内嵌的DNS进行容器名解析实现的互访通信方式,同一主机桥接模式的容器间需要启动时使用“--link”参数启用这一功能。joined 容器方式可以使多个容器共享一个网络命名空间,多个容器间通过环回接口直接通信,这种方式容器间传输效率最高。

Kubernetes的网络通信

Kubernetes的Pod网络通信包括如下几种方式:

Pod内容器间的数据通信

Pod是由多个Docker容器以joined容器方式构成的,多个容器共享由名为pause的容器创建的网络命名空间(netns,network namespace),容器内的进程彼此间通过环回(lo,Lookback)接口实现数据通信。环回接口不依赖链路层和物理层协议,一旦传输层检测到目的端地址是环回接口地址时,数据报文离开网络层时会把它返回给自己。这种模式传输效率较高,非常适用于容器间进程的频繁通信。

同节点的Pod间数据通信

每个Pod拥有唯一的IP和彼此隔离的网络命名空间,在Linux系统中,Pod间跨网络命名空间的数据通信是通过Veth(Virtual ETHernet)设备实现的。Veth设备工作在链路层,总是成对出现也被称为Veth-pair设备。在网络插件是Flannel的虚拟网络结构中,Flannel被Kubernetes触发并收到相关Pod参数时,会为Pod创建Veth设备并分配IP,Veth设备一端是Pod的eth0接口,一端是Node节点中网络空间名为default的Veth虚拟接口。Flannel在初始安装时,创建了网桥设备cni0,网络空间default中被创建的Veth虚拟接口都被加入到网桥设备cni0中,相当于所有的Pod都被接入到这个虚拟交换机中,在同一虚拟交换机中的Pod实现了链路层的互联并进行网络通信。工作原理如图12-5所示。

Kubernetes探索实践之网络通信

图 12-5 同节点的Pod间数据通信

可用如下命令查看当前节点服务器的网络命名空间和网桥信息。

# 查看系统中的网络命名空间
ls /var/run/docker/netns

# 查看每个命名空间的网络接口信息
nsenter --net=/var/run/docker/netns/default ifconfig -a

# 查看网桥信息
brctl show

跨主机的Pod间数据通信

Flannel是由CoreOS使用Go语言开发的,Flannel实现了一种基于Vxlan(Virtual eXtensible Local Area Network)封装的覆盖网络(overlay network),其将TCP数据封装在另一种网络包中进行路由转发和通信。

Vxlan协议是一种隧道协议,基于UDP协议传输数据。Flannel的Vxlan虚拟网络是比较简单的,在每个Kubernetes节点上只有1个Vxlan网络和1个Vxlan接口(默认为flannel.1)。Vxlan接口叫做VTEP(Vxlan tunnel endpoint)设备,VTEP设备的MAC地址由Flannel被Kubernetes触发并同步缓存在本地的fdb(forwarding database)中。Kubernetes集群中整个flannel网络默认配置网段为10.244.0.0/16,每个节点都分配了唯一的24位的子网,Flannel在Kubernetes集群中类似一个传统网络中的三层交换设备,每个Node节点的桥设备通过VTEP设备接口互联,使运行在不同Node节点中不同子网IP的容器实现跨主机互通。

可用如下命令查看当前节点服务器的arp信息。

# 本地桥 arp表
bridge fdb

bridge fdb show dev flannel.1

Pod应用在Kubernetes集群内发布服务

Kubernetes 通过副本集控制器能够动态地创建和销毁 Pod,每个Pod可被动态的创建或销毁于集群中的任意一个节点,因此Pod IP也将随之变化。Flannel构建的虚拟网络使得集群中的每个Pod在网络上已经实现互联互通,由于Pod IP变化的不确定性,使得运行在Pod中的应用服务无法被其他应用固定访问。为使动态变化IP的Pod应用可以被其他应用访问,Kubernetes通过标签筛选的形式对具有相同指定标签的一组Pod定义为Service,每个Service的Pod成员信息通过端点控制器在etcd中保存及更新。Service为应用Pod提供了固定的虚拟IP和端口用以提供固定的访问,让集群内其他Pod应用可以访问这个服务。

Service 是 “4层”(TCP/UDP over IP)概念,其构建了一个有固定ClusterIP(集群虚拟IP,Virtual IP) 和 Port 的虚拟集群,每个节点上运行的kube-proxy 进程通过主节点的接口服务监控资源对象Service和 Endpoints内Pod列表的变化,kube-proxy 默认使用iptables 代理模式,其通过对每个Service配置对应的iptables 规则捕获到达该 Service 的 ClusterIP和 Port 的请求,当捕获到请求时,会将访问请求按比例随机分配给Service 中的一个Pod,如果被选择的Pod没有响应(依赖“readiness probes”的配置),则自动重试另一个Pod。Service访问逻辑如图12-6 所示。

Kubernetes探索实践之网络通信

图 12-6 Service访问逻辑

  • kube-proxy 根据集群中Service和Endpoint资源对象的状态初始化所在节点的iptables规则。
  • kube-proxy 通过接口服务监听集群中Service和Endpoint资源对象的变化并更新本地的iptables规则。
  • iptables规则监听所有请求,将对应ClusterIP和 Port 的请求使用随机负载均衡算法负载到后端Pod。

kube-proxy 在集群中的每个节点都会配置集群中所有Service 的iptables规则,iptables规则设置如下。

  • kube-proxy 首先是建立filter表的INPUT规则链和nat表的PREROUTING规则链,将访问节点的流量全部跳转到KUBE-SERVICES规则链进行处理。
  • kube-proxy 遍历集群中的Service资源实例,为每个Service资源实例创建两条 KUBE-SERVICES 规则。
  • KUBE-SERVICES 中一条规则是对访问Service的非集群Pod IP交由KUBE-MARK-MASQ规则标记为0x4000/0x4000,在执行到POSTROUTING规则链时由KUBE-POSTROUTING规则链对数据流量实现SNAT。
  • KUBE-SERVICES 中另一条规则将访问目标是Service的请求跳转到对应的KUBE-SVC规则链。
  • KUBE-SVC 规则链是由组成目标Service端点列表中每个Pod的处理规则组成,这些规则包括随机负载均衡策略及会话保持(sessionAffinity)的实现。
  • KUBE-SVC 每条规则链命名是将“服务名+协议名”按照SHA256 算法生成哈希值后通过base32对该哈希值再编码,取编码的前16位与KUBE-SVC为前缀组成的字符串。
  • KUBE-SEP 每个Pod有两条KUBE-SEP规则,一条是将请求数据DNAT到Pod IP,另一条用来将Pod返回数据交由KUBE-POSTROUTING规则链实现SNAT。
  • KUBE-SEP 每条规则链命名是将“服务名+协议名+端口”按照SHA256 算法生成哈希值后通过base32对该哈希值再编码,取编码的前16位与KUBE-SEP为前缀组成的字符串。

Service的负载均衡是由iptables的statistic模块实现的,statistic模块的random模式可以将被设定目标的请求数在参数probability设定的概率范围内分配,参数设定的值在0.0至1.0之间,当参数设定值为0.5时,表示该目标有50%概率分配到请求。kube-proxy 遍历Service中的Pod列表时,按照公式1.0/float64(n-i)为每个Pod计算概率值,n是Pod的总数量,i是当前计数。当有3个Pod时,计算值分别为33%、50%、100%,3个Pod的总流量负载分配分别为33%,35%,32%。

Service也支持会话保持功能,是应用iptables的recent模块实现的。recent允许动态的创建源地址列表,并对源地址列表中匹配的来源IP执行相应的Iptables动作。recent模块参数如表12-3所示。

表 12-3 模块参数

参数 参数说明
set 把匹配动作的源IP添加到地址列表
name 源地址列表名称
mask 源地址列表中IP的掩码
rsource 设置源地址列中保存数据包的源IP地址
rcheck 检查当前数据包源IP是否在源地址列表中
seconds 与rcheck配合使用,设置对指定时间内更新的IP地址与数据包源IP进行匹配检查,单位为秒
reap 与seconds配合使用,将清除源地址列表中指定时间内没被更新的IP地址

配置Service 会话保持,只需在Service 中做如下配置即可。

spec:
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800

kube-proxy 实现Service的方法有四种,分别是userspace、iptables、ipvs和winuserspace,iptables只是默认配置,因kube-proxy的其他将在其他章节进行深入探讨。

Pod应用在Kubernetes集群外发布服务

Service 实现了Pod访问的固定IP和端口,但ClusterIP并不是绑定在网络设备上的,其只是kube-proxy 进程设定的iptables本地监听转发规则,只能在K8集群内的节点上进行访问。Kubernetes系统同默认提供两种方式实现Pod应用在集群外发布服务,一种是基于资源对象Pod的hostPort和hostNetwork方式,另一种是基于资源对象Service的NodePort、LoadBalancer和externalIPs方式。

  • hostPort方式

hostPort方式相当于创建Docker容器时使用"-p"参数提供容器的端口映射,只能通过运行容器的节点IP进行访问,其属于资源对象Pod的运行方式,不支持多个Pod的Service负载均衡等功能。资源配置如下所示。

apiVersion: v1
kind: Pod
metadata:
  name: apps
  labels:
    app: web
spec:
  containers:
  - name: apps
    image: apache
    ports:
    - containerPort: 80
      hostPort: 8080
  • hostNetwork方式

hostNetwork方式相当于创建Docker容器时以主机模式为网络模式的Pod运行方式,该方式运行的容器与所在节点共享网络命名空间,属于资源对象Pod的运行方式,不支持多个Pod的Service负载均衡等功能。资源配置如下所示。

apiVersion: v1
kind: Pod
metadata:
  name: nginx-web
  namespace: default
  labels:
    run: nginx-web
spec:
  hostNetwork: true
  containers:
  - name: nginx-web
    image: nginx
    ports:
    - containerPort: 80
  • NodePort方式

NodePort方式是在集群中每个节点监听固定端口(nodePort)的访问,外部用户对任意节点IP和nodePort的访问,都会被Service负载到后端的Pod,全局nodePort的默认可用范围为30000-32767。NodePort方式访问逻辑如图12-7所示。

Kubernetes探索实践之网络通信

图 12-7 NodePort方式访问逻辑

kube-proxy 初始化时,会对NodePort方式的Service 在Iptables nat表中创建规则链 KUBE-NODEPORTS,用于监听本机nodePort的请求。

外部请求访问节点IP和端口(nodePort)后,被Iptables 规则KUBE-NODEPORTS 匹配后跳转给对应的KUBE-SVC规则链执行负载均衡等操作。

选定Pod后,请求被转发到选定的Pod IP 和目标端口(targetPod)。

NodePort方式资源配置如下所示。

apiVersion: v1
kind: Service
metadata:
  name: nginx-web
  namespace: default
  labels:
    run: nginx-web
spec:
  type: NodePort
  ports:
  - nodePort: 31804
    port: 8080           
    protocol: TCP
    targetPort: 8080
  • LoadBalancer 方式

LoadBalancer 方式是一种通过Kubernetes自动对外发布的解决方案,该方案是将外部负载均衡器作为上层负载,在创建Service时自动与外部负载均衡器互动,完成对Kubernetes Service负载均衡创建的操作,将Service按照外部负载均衡器的负载策略对外提供服务。该方案依赖外部负载均衡器的支持,通常如阿里云、腾讯云的容器云都提供了这个方案的支持。资源配置如下所示。

apiVersion: v1
kind: Service
metadata:
  name: nginx-web
  namespace: default
  labels:
    run: nginx-web
spec:
  type: LoadBalancer
  ports:
  - port: 8080           
    protocol: TCP
    targetPort: 8080

不同的外部负载均衡器需要有对应的负载均衡控制器(Loadbalancer Controller)。

负载均衡控制器实时通过接口服务监听资源对象Service的变化。

LoadBalancer类型的Service被创建时,Kubernetes会为该Service自动分配nodePort。

当监听到LoadBalancer类型的Service 创建时,负载均衡控制器将触发外部负载均衡器(LoadBalancer)创建外部VIP、分配外部IP或将现有节点IP绑定nodePort端口添加到外部负载均衡器的负载均衡池,完成负载均衡的配置。

当外部用户访问负载均衡器的外部VIP时,外部负载均衡器会将流量负载到Kubernetes节点或Kubernetes集群中的Pod(视外部负载均衡器的功能而定)。

不能与NodePort方式同时使用。

  • External IPs方式

External IPs方式提供了一种指定外部IP绑定Service端口的方法,该方法可以指定节点内某几个节点IP地址或绑定外部路由到节点网络的非节点IP对外提供访问。Kubernetes通过externalIPs参数将被指定的IP与Service 端口通过Iptables监听,其使用与Service 一致的端口,相对NodePort方式配置更加简单灵活。由于是直接将Service端口绑定被路由的IP对外暴露服务,使用者需要对整个集群对外服务的端口做好相应的规划,避免端口冲突。资源配置如下所示。

spec:
  externalIPs:
  - 192.168.1.101
  - 192.168.1.102
  ports:
  - name: http
    port: 80
    targetPort: 80
    protocol: TCP
  - name: https
    port: 443
    targetPort: 443
    protocol: TCP

externalIPs设置的IP可以为集群中现有的节点IP,也可以是上层网络设备路由过来的IP。kube-proxy 初始化时,会对externalIPs方式的Service 在Iptables nat表中创建规则链 KUBE-SERVICES,用于访问到externalIPs列表中IP及Service port请求的监听。

外部或本地访问externalIPs列表中IP及port的请求被匹配后,跳转给对应的KUBE-SVC规则链执行负载均衡等操作。

Service中Pod的调度策略

Kubernetes系统中,Pod的部署是随机的,使用者也可通过不同的调度策略进行调整,Pod的调度策略同样对Pod通信存在一定的影响,相关的调度策略有如下两种。

  • 部署调度策略(Affinity)

Kubernetes集群中的Pod 是被随机调度,并创建在集群中的节点上的,在实际使用中,有时需要考虑节点资源的有效利用及不同应用间的访问效率等因素,也需要对这种调度设置相关期望的策略。主要体现在节点与Pod间的关系、同Service下的Pod间关系、不同Service的Pod间关系这3个方面。节点与Pod间关系可以使用“nodeAffinity” 在资源配置文件中设置,其可以在设置Pod资源对象时,将Pod部署到具有指定标签的集群节点上。Pod间关系可通过“podAntiAffinity”的配置尽量把同一Service下的Pod分配到不同的节点,提高自身的高可用性,也可以把互相影响的不同Service的Pod分散到不同的集群节点上。对于Pod间访问比较频繁的应用可以使用“podAffinity”配置,尽量把被配置的Pod部署到同一节点服务器上。

  • 流量调度策略(externalTrafficPolicy)

Service的流量调度策略(externalTrafficPolicy)有两种Pod调度策略,分别是Cluster和Local。Cluster是默认调度策略,其会依据iptables的随机负载算法,将用户请求负载均衡分配给Pod,但该方式会隐藏客户端的源IP。Local策略则会将请求只分配给请求节点IP的Pod,而不会转发给其他节点的Pod,这样就保留了最初的源 IP 地址。但该方式不会对Service的Pod进行负载均衡,同时被访问IP的节点上如果没有该应用的Pod,则会报错。Local策略仅适用于NodePort和LoadBalancer类型的Service。

Kubernetes中Service的Pod是通过Service实现Pod应用访问的,在流量调度策略的Cluster调度策略下,对一个Service的访问请求会被随机分配到Service中的任意Pod,即便该Service与发出请求的Pod在同一节点有可提供服务的Pod,也不一定会被选中。在Kubernetes 计划的1.16版本中增加了服务拓扑感知的流量管理功能,设计了新的Pod定位器(PodLocator)实现了服务的拓扑感知服务路由机制,使Pod总能优先使用“本地访问”的策略找到最近的服务后端,这种拓扑感知服务使“本地访问”具有更广泛的意义,包括节点主机、机架、网络、机房等,这样可以有效的减少网络延迟,提高访问效率及安全性,更加节约成本。

结束语

Ingress 已经属于七层范围,其涵盖的内容也较多,将会另起一章进行分析。

参考文档

https://kubernetes.io/docs/home/