Kubernetes网络原理及方案

前置知识

Docker的网络模型
img.png

名词解释

1、网络的命名空间:Linux在网络栈中引入网络命名空间,将独立的网络协议栈隔离到不同的命令空间中,彼此间无法通信;docker利用这一特性,实现不容器间的网络隔离。

2、Veth设备对:也叫虚拟网络接口对。Veth设备对的引入是为了实现在不同网络命名空间的通信。

3、Iptables/Netfilter:Netfilter负责在内核中执行各种挂接的规则(过滤、修改、丢弃等),运行在内核 模式中;Iptables模式是在用户模式下运行的进程,负责协助维护内核中Netfilter的各种规则表;通过二者的配合来实现整个Linux网络协议栈中灵活的数据包处理机制。

4、网桥:网桥是一个二层网络设备,通过网桥可以将linux支持的不同的端口连接起来,并实现类似交换机那样的多对多的通信。

5、路由:Linux系统包含一个完整的路由功能,当IP层在处理数据发送或转发的时候,会使用路由表来决定发往哪里。

kubernetes网络模型

Kubernetes 网络设计的一个基本原则是:每个Pod 都有一个全局唯一的IP,  而且假定所有的Pod 都在一个可以直接连通的,扁平的网络空间中,Pod 之间可以跨主机通信,按照这种网络原则抽象出来的一个Pod 对应一个IP的设计模型也被称作IP-per-Pod 模型。

相比于Docker 原生的NAT方式来说,这样使得容器在网络层面更像虚拟机或者物理机,复杂度整体降低,更加容易实现服务发现,迁移,负载均衡等功能。

img_4.png

容器间的通信

同一个Pod的容器共享同一个网络命名空间,它们之间的访问可以用localhost地址 + 容器端口就可以访问。
img_1.png

这种情况下,同一个pod内共享网络命名空间,容器之间通过访问127.0.0.1:(端口)即可。图中的veth*即指veth对的一端(另一端未标注,但实际上是成对出现),该veth对是由Docker Daemon挂载在docker0网桥上,另一端添加到容器所属的网络命名空间,图上显示是容器中的eth0。

图中演示了bridge模式下的容器间通信。docker1向docker2发送请求,docker1,docker2均与docker0建立了veth对进行通讯。

当请求经过docker0时,由于容器和docker0同属于一个子网,因此请求经过docker2与docker0的veth*对,转发到docker2,该过程并未跨节点,因此不经过eth0。

Pod 间的通信

  • 同一Node中Pod间通信:
    同一Node中Pod的默认路由都是docker0的地址,由于它们关联在同一个docker0网桥上,地址网段相同,所有它们之间应当是能直接通信的。
    img_2.png

    由于Pod内共享网络命名空间(由pause容器创建),所以本质上也是同节点容器间的通信。同时,同一Node中Pod的默认路由都是docker0的地址,由于它们关联在同一个docker0网桥上,地址网段相同,所有它们之间应当是能直接通信的。来看看实际上这一过程如何实现。如上图,Pod1中容器1和容器2共享网络命名空间,因此对pod外的请求通过pod1和Docker0网桥的veth对(图中挂在eth0和ethx上)实现。

  • 不同Node中Pod间通信:
    不同Node中Pod间通信要满足2个条件: Pod的IP不能冲突; 将Pod的IP和所在的Node的IP关联起来,通过这个关联让Pod可以互相访问。

跨节点通信

CNI:容器网络接口

CNI 是一种标准,它旨在为容器平台提供网络的标准化。不同的容器平台(比如目前的 kubernetes、mesos 和 rkt)能够通过相同的接口调用不同的网络组件。

目前kubernetes支持的CNI组件种类很多,例如:bridge calico calico-ipam dhcp flannel host-local ipvlan loopback macvlan portmap ptp sample tuning vlan。在docker中,主流的跨主机通信方案主要有一下几种:

1)基于隧道的overlay网络:按隧道类型来说,不同的公司或者组织有不同的实现方案。docker原生的overlay网络就是基于vxlan隧道实现的。ovn则需要通过geneve或者stt隧道来实现的。flannel最新版本也开始默认基于vxlan实现overlay网络。

2)基于包封装的overlay网络:基于UDP封装等数据包包装方式,在docker集群上实现跨主机网络。典型实现方案有weave、flannel的早期版本。

3)基于三层实现SDN网络:基于三层协议和路由,直接在三层上实现跨主机网络,并且通过iptables实现网络的安全隔离。典型的方案为Project Calico。同时对不支持三层路由的环境,Project Calico还提供了基于IPIP封装的跨主机网络实现

img_3.png

集群内跨节点通信涉及到不同的子网间通信,仅靠docker0无法实现,这里需要借助CNI网络插件来实现。图中展示了使用flannel实现跨节点通信的方式。

简单说来,flannel的用户态进程flanneld会为每个node节点创建一个flannel.1的网桥,根据etcd或apiserver的全局统一的集群信息为每个node分配全局唯一的网段,避免地址冲突。同时会为docker0和flannel.1创建veth对,docker0将报文丢给flannel.1,。

Flanneld维护了一份全局node的网络表,通过flannel.1接收到请求后,根据node表,将请求二次封装为UDP包,扔给eth0,由eth0出口进入物理网路发送给目的node。

在另一端以相反的流程。Flanneld解包并发往docker0,进而发往目的Pod中的容器。

Pod 到Service 通信

当一个 Service 对象在 Kubernetes 集群中被定义出来时,集群内的客户端应用就可以
通过服务 IP 访问到 体的 Pod 容器提供 的服务了。从服务 IP 到后 Pod 的负载均衡机制,
则是由每个 Node 上的 kube-proxy 负责实现的。

kube-proxy

Kube-proxy是一个简单的网络代理和负载均衡器,它的作用主要是负责Service的实现,具体来说,就是实现了内部从Pod到Service和外部的从NodePort向Service的访问。

实现方式:

  • userspace是在用户空间,通过kuber-proxy实现LB的代理服务,这个是kube-proxy的最初的版本,较为稳定,但是效率也自然不太高。
  • iptables是纯采用iptables来实现LB,是目前kube-proxy默认的方式。

kube-proxy 通过设置 Linux Kernel的 iptables 规则,实现从 Service
到后端 Endpoint 列表的负载分发规则,效率很高 但是,如果某个后端 Endpoint
在转发时不可用,此次客户端请求就会得到失败的响应,相对于 userspace 模式来
说更不可靠。 此时应该通过为 Pod 设置 readinessprobe (服务可用性健康检查)来
保证只有达到 ready 状态的 Endpoint 才会被设置为 Service 的后端 Endpoint.

  • ipvs 模式:在 Kubernetes 11 本中达到 Stable 阶段, kube-proxy 通过设置 Linux
    Kernel netlink 接口设置 IPVS 规则,转发效率和支持的吞吐率都是最高的。 ipvs
    模式要求 Linux Kernel 启用 IPVS 模块,如果操作系统未启用 IPVS 内核模块,
    kube-proxy 则会自动切换至 iptables 模式 同时, ipvs 模式支持更多的负载均衡策
    略,如下所述
    • rr: round-robi n, 轮询
    • le: least connection, 最小连接数
    • dh: destination hashing, 目的地址哈希
    • sh: source has ing, 源地址哈希
    • sed: shortest expected delay 最短期望延时
    • nq: never queue, 永不排队。
      下面是iptables模式下Kube-proxy的实现方式:

img_7.png

  • 在这种模式下,kube-proxy监视Kubernetes主服务器添加和删除服务和端点对象。对于每个服务,它安装iptables规则,捕获到服务的clusterIP(虚拟)和端口的流量,并将流量重定向到服务的后端集合之一。对于每个Endpoints对象,它安装选择后端Pod的iptables规则。

  • 默认情况下,后端的选择是随机的。可以通过将service.spec.sessionAffinity设置为“ClientIP”(默认为“无”)来选择基于客户端IP的会话关联。

  • 与用户空间代理一样,最终结果是绑定到服务的IP:端口的任何流量被代理到适当的后端,而客户端不知道关于Kubernetes或服务或Pod的任何信息。这应该比用户空间代理更快,更可靠。然而,与用户空间代理不同,如果最初选择的Pod不响应,则iptables代理不能自动重试另一个Pod,因此它取决于具有工作准备就绪探测。

外部到内部的通信

从集群外访问集群有多种方式,比如loadbalancer,Ingress,nodeport,nodeport和loadbalancer是service的两个基本类型,是将service直接对外暴露的方式,ingress则是提供了七层负载均衡,其基本原理将外部流量转发到内部的service,再转发到后端endpoints,在平时的使用中,我们可以依据具体的业务需求选用不同的方式。

Kubernetes 支持两种对外服务的Service 的Type 定义:NodePort 和LoadBalancer。

NodePort:在每个Node 上打开一个端口并且每个Node 的端口都是一样的,通过:NodePort 的方式,Kubernetes 集群外部的程序可以访问Service;
LoadBalancer:通过外部的负载均衡器来访问。

Ingress

Ingress是推荐在生产环境使用的方式,它起到了七层负载均衡器和Http方向代理的作用,可以根据不同的url把入口流量分发到不同的后端Service。外部客户端只看到http://foo.bar.com这个服务器,屏蔽了内部多个Service的实现方式。采用这种方式,简化了客户端的访问,并增加了后端实现和部署的灵活性,可以在不影响客户端的情况下对后端的服务部署进行调整。

img_6.png

其部署的yaml可以参考如下模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: test.name.com
http:
paths:
- path: /test
backend:
serviceName: service-1
servicePort: 8118
- path: /name
backend:
serviceName: service-2
servicePort: 8228

这里我们定义了一个ingress模板,定义通过http://test.name.com来访问服务,在虚拟主机http://test.name.com下面定义了两个Path,其中/test被分发到后端服务s1,/name被分发到后端服务s2。

集群中可以定义多个ingress,来完成不同服务的转发,这里需要一个ingress controller来管理集群中的Ingress规则。Ingress Contronler 通过与 Kubernetes API 交互,动态的去感知集群中 Ingress 规则变化,然后读取它,按照自定义的规则,规则就是写明了哪个域名对应哪个service,生成一段 Nginx 配置,再写到 Nginx-ingress-control的 Pod 里,这个 Ingress Contronler 的pod里面运行着一个nginx服务,控制器会把生成的nginx配置写入/etc/nginx.conf文件中,然后 reload使用配置生效。

Kubernetes提供的Ingress Controller模板如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
backend:
serviceName: s1
servicePort: 80
- path: /bar
backend:
serviceName: s2
servicePort: 80

Kube-dns介绍

Kube-dns用来为kubernetes service分配子域名,在集群中可以通过名称访问service;通常kube-dns会为service赋予一个名为“service名称.namespace.svc.cluster.local”的A记录,用来解析service的clusterip。

Kube-dns组件:

  • 在Kubernetes v1.4版本之前由“Kube2sky、Etcd、Skydns、Exechealthz”四个组件组成。
  • 在Kubernetes v1.4版本及之后由“Kubedns、dnsmasq、exechealthz”三个组件组成。
    img_8.png

Kubedns

  • 接入SkyDNS,为dnsmasq提供查询服务。

  • 替换etcd容器,使用树形结构在内存中保存DNS记录。

  • 通过K8S API监视Service资源变化并更新DNS记录。

  • 服务10053端口。

    Dnsmasq

  • Dnsmasq是一款小巧的DNS配置工具。

  • 在kube-dns插件中的作用是:

    • 通过kubedns容器获取DNS规则,在集群中提供DNS查询服务
    • 提供DNS缓存,提高查询性能
    • 降低kubedns容器的压力、提高稳定性
  • Dockerfile在GitHub上Kubernetes组织的contrib仓库中,位于dnsmasq目录下。

  • 在kube-dns插件的编排文件中可以看到,dnsmasq通过参数–server=127.0.0.1:10053指定upstream为kubedns。

    Exechealthz

  • 在kube-dns插件中提供健康检查功能。

  • 源码同样在contrib仓库中,位于exec-healthz目录下。

  • 新版中会对两个容器都进行健康检查,更加完善。

Flannel容器网络:

Flannel之所以可以搭建kubernets依赖的底层网络,是因为它可以实现以下两点:

  • 它给每个node上的docker容器分配相互不想冲突的IP地址;

  • 它能给这些IP地址之间建立一个覆盖网络,同过覆盖网络,将数据包原封不动的传递到目标容器内。

    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转发。
    img_9.png

Calico容器网络:

Calico介绍

  • Calico是一个纯3层的数据中心网络方案,而且无缝集成像OpenStack这种IaaS云架构,能够提供可控的VM、容器、裸机之间的IP通信。Calico不使用重叠网络比如flannel和libnetwork重叠网络驱动,它是一个纯三层的方法,使用虚拟路由代替虚拟交换,每一台虚拟路由通过BGP协议传播可达信息(路由)到剩余数据中心。
  • Calico在每一个计算节点利用Linux Kernel实现了一个高效的vRouter来负责数据转发,而每个vRouter通过BGP协议负责把自己上运行的workload的路由信息像整个Calico网络内传播——小规模部署可以直接互联,大规模下可通过指定的BGP route reflector来完成。
  • Calico节点组网可以直接利用数据中心的网络结构(无论是L2或者L3),不需要额外的NAT,隧道或者Overlay Network。
  • Calico基于iptables还提供了丰富而灵活的网络Policy,保证通过各个节点上的ACLs来提供Workload的多租户隔离、安全组以及其他可达性限制等功能。

    Calico架构图

    img_10.png
    img_11.png

参考文章

评论