二、Service网络

1. 服务集群

使用k8s部署服务,所有的服务都部署在pod内部的容器中,服务集群就需要多个pod副本实现。
思考:那么多个pod副本实现负载均衡访问,该如何实现呢???
在这里插入图片描述
解决方案: 使用nginx实现负载均衡??
在这里插入图片描述

nginx也在一个pod中

使用nginx实时多个pod副本直接负载均衡访问。

问题:
Pod是一个服务进程,有生命周期,并且pod随时可能宕机,k8s立马对pod进行重建,但是此时重建的pod的ip,hostname都发生了变化(无状态的pod)。此时nginx还能访问重建的pod吗??

总结:nginx作为pod副本的负载均衡服务器,无法发现k8s重建,或者新建的pod,也就是说k8s重建的,新建的pod,使用nginx无法访问。因此使用nginx作为pod副本负载均衡访问服务,是无法实现的。

2. Service

2.1 什么是Service

Service是一个抽象的概念。它通过一个虚拟的IP的形式(VIPs),映射出来指定的端口,通过代理客户端发来的请求转发到后端一组Pods中的一台(也就是endpoint)

Service定义了Pod逻辑集合和访问该集合的策略,是真实服务的抽象。Service提供了统一的服务访问入口以及服务代理和发现机制,关联多个相同Label的Pod,用户不需要了解后台Pod是如何运行。

外部系统访问Service的问题:

  • 首先需要弄明白Kubernetes的三种IP这个问题
    • Node IP:Node节点的IP地址
    • Pod IP: Pod的IP地址
    • Cluster IP:Service的IP地址
  • 首先,Node IP是Kubernetes集群中节点的物理网卡IP地址,所有属于这个网络的服务器之间都能通过这个网络直接通信。这也表明Kubernetes集群之外的节点访问Kubernetes集群之内的某个节点或者TCP/IP服务的时候,必须通过Node IP进行通信
  • 其次,Pod IP是每个Pod的IP地址,他是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络。
  • 最后Cluster IP是一个虚拟的IP,但更像是一个伪造的IP网络,原因有以下几点:
    • Cluster IP仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配P地址
    • Cluster IP无法被ping,他没有一个“实体网络对象”来响应
    • Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备通信的基础,并且他们属于Kubernetes集群这样一个封闭的空间。
    • Kubernetes集群之内,Node IP网、Pod IP网于Cluster IP网之间的通信,采用的是Kubernetes自己设计的一种编程方式的特殊路由规则。

2.2 Service VIP(ClusterIP)

Client只需通过ClusterIP就可以访问背后的Pod集群,不需要关心集群中的具体Pod数量和PodIP,即使是PodIP发生变化也会被ClusterIP所屏蔽。注意,这里的ClusterIP实际是个虚拟IP,也称Virtual IP(VIP)。

Service VIP 是 k8s 提供一个虚拟IP, Service 就是一个虚拟ip的资源对象。Service VIP就相当于是服务网关,所有的请求都要被service VIP进行拦截,然后进行转发,它屏蔽了底层 pod Ip,hostname变化所造成的影响,使得用户不需要关心pod在底层到底是如何变化的,或者不需要关心pod的ip,hostname是如何变化。
在这里插入图片描述
注意:service VIP 一旦创建就不会发生变化,service VIP高可用性由etcd来保证,service VIP作为一组pod业务的统一网关入口。

Service VIP 是一个服务、进程,运行在每个node节点上,是一个独立的网关入口,它的高可用 由etcd保证,这个服务用的映射数据是存在etcd上的

2.3 Service网络原理

在这里插入图片描述

在K8s平台的每个Worker节点上,都部署有两个组件,一个叫Kubelet,另外一个叫Kube-Proxy,这两个组件+Master是K8s实现服务注册和发现的关键。下面我们看下简化的服务注册发现流程。

  • 首先,在服务Pod实例发布时(可以对应K8s发布中的Kind: Deployment),Kubelet会负责启动Pod实例,启动完成后,Kubelet会把服务的PodIP列表汇报注册到Master节点。
  • 其次,通过服务Service的发布(对应K8s发布中的Kind: Service),K8s会为服务分配ClusterIP,相关信息也记录在Master上。
  • 第三,在服务发现阶段,Kube-Proxy会监听Master并发现服务ClusterIP和PodIP列表映射关系,并且修改本地的linux iptables转发规则,指示iptables在接收到目标为某个ClusterIP请求时,进行负载均衡并转发到对应的PodIP上。
  • 运行时,当有消费者Pod需要访问某个目标服务实例的时候,它通过ClusterIP发起调用,这个ClusterIP会被本地iptables机制截获,然后通过负载均衡,转发到目标服务Pod实例上。

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

实际消费者Pod也并不直接调服务的ClusterIP,而是先调用服务名,因为ClusterIP也会变(例如针对TEST/UAT/PROD等不同环境的发布,ClusterIP会不同),只有服务名一般不变。为了屏蔽ClusterIP的变化,K8s在每个Worker节点上还引入了一个KubeDNS组件,它也监听Master并发现服务名和ClusterIP之间映射关系,这样, 消费者Pod通过KubeDNS可以间接发现服务的ClusterIP。

VIP 其实现原理主要是靠TCP/IP的ARP协议。因为ip地址只是一个逻辑地址,在以太网中MAC地址才是真正用来进行数据传输的物理地址,每台主机中都有一个ARP高速缓存,存储同一个网络内的IP地址与MAC地址的对应关 系,以太网中的主机发送数据时会先从这个缓存中查询目标IP对应的MAC地址,会向这个MAC地址发送数据。操作系统会自动维护这个缓存,这就是整个实现 的关键。

2.3.1 pod负载均衡流程

K8s 核心资源对象几种IP地址分类:
1、NODE IP —– 物理机节点IP地址
2、POD IP —– POD 进程IP地址
3、cluster IP —– 虚拟机IP,由k8s抽象出的service资源对象具有的IP地址,此ip地址类型默认就是clusterIP, 此类型只能在局域网进行通信,无法对外网提供服务。

在这里插入图片描述

图中 ServiceVIP 实际上是每个node节点内部的一个进程,抽出来是为了方便理解、画图
而ServiceVIP中的映射数据是存在etcd的

iptables中 10.11.16.20:80 这个地址映射了四个pod地址
物理机node开辟一个端口,外网访问这个物理机地址对应的端口,则将请求转发给该端口对应的映射的Service VIP,再由Service VIP通过iptables实现负载均衡、路由

1、在物理机开辟一个端口,实现数据包(请求)转发
2、创建service VIP 资源对象,实现物理端口和service VIP 端口关系映射,从而从物理机端口转发的请求自动转发给service VIP
3、service 资源对象通过iptables,根据转发策略,把请求转发给相应的Pod.

K8s中的代理转发是iptables转发,虽然K8s中有Kube-Proxy,但它只是负责服务发现和修改iptables(或ipvs)规则,实际请求是不穿透Kube-Proxy的。

2.3.2 Service关联pod

Service和pod如何关联起来的,service是通过标签选择器选择一组相关的pod。通常来说service只对一组相关的pod副本提供服务。多个业务pod,就应该有多个service。
在这里插入图片描述
一个业务组pod副本,对应一个service:
在这里插入图片描述

一个Service VIP对应一个虚拟地址

2.3.3 Pod服务发现

Pod宕机,当k8s重建pod后,pod的ip地址,hostname发现了变化,service VIP 可以感知到pod ip,hostname已经发生了变化。K8s node节点中提供一个核心组件:kube-proxy,这个代理组件主要用来实现服务发现,路由规则改写。

kube-proxy 进程 可以书写负载均衡的规则,实际上就去修改etcd

在这里插入图片描述

每个节点中都有 一个 kube-proxy 组件,即每个节点都会运行一个 kube-proxy 进程,它会做两件事:

  • 1.监听pod
  • 2.一但pod发生变化,就会修改ServiceVIP 映射iP的规则,即endpoints资源对象,每个endpoint对应一个pod

PS:一个node中所有pod的端口是一样的,ip不一样

endpoints是一个数组,维护了pod的ip地址

注意: kube-proxy在每一个node节点都存在一个进程,每一个node节点中kube-proxy都会监控pod(不只监听本地节点的)的ip地址变化,发现pod的ip地址发生了变化,把变化的ip的更新到service资源对象的endpoints. 使得service vip可以及时感知到pod ip的变化,从而可以实现更好的,及时的,稳定的访问。

总结: k8s所有的资源对象都存储在etcd, 所有的资源对象都可以用yaml资源配置文件方式进行表示,因此这些资源以yaml方式存储。

3. kube-proxy的三种代理模式

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

3.1 User space模式

在这种模式下,kube-proxy通过观察Kubernetes中service和endpoint对象的变化,当有新的service创建时,所有节点的kube-proxy在node节点上随机选择一个端口,在iptables中追加一条把访问service的请求重定向到这个端口的记录,并开始监听这个端口的连接请求。

比如说创建一个service,对应的IP:1.2.3.4,port:8888,kube-proxy随机选择的端口是32890,则会在iptables中追加

-A PREROUTING -j KUBE-PORTALS-CONTAINER

-A KUBE-PORTALS-CONTAINER -d 1.2.3.4/32 -p tcp --dport 8888 -j REDIRECT --to-ports 32890

KUBE-PORTALS-CONTAINER 是kube-proxy在iptable中创建的规则链,在PREROUTING阶段执行

执行过程:

  • 当有请求访问service时,在PREROUTING阶段,将请求jumpKUBE-PORTALS-CONTAINER
  • KUBE-PORTALS-CONTAINER中的这条记录起作用,把请求重定向到kube-proxy刚监听的端口32890上,数据包进入kube-proxy进程内,
  • 然后kube-proxy会从这个service对应的endpoint中根据SessionAffinity来选择一个作为真实服务响应请求。

在这里插入图片描述
这种模式的缺点就是,请求数据需要到kube-proxy中,就是用户空间中,才能决定真正要转发的实际服务地址,性能会有损耗。并且在应用执行过程中,kube-proxy的可用性也会影响系统的稳定

3.2 iptables 模式(目前kube-proxy默认的工作模式)

Iptables模式为Services的默认代理模式。在iptables 代理模式中,kube-proxy不在作为反向代理的在VIPs 和backend Pods之间进行负载均衡的分发。这个工作交给了在四层的iptables来实现。iptables 和netfilter紧密集成,密切合作,都在kernelspace 就实现了包的转发。

在这个模式下,kube-proxy 主要有这么几步来实现实现报文转发:

  • 通过watching kubernetes集群 cluster API, 获取新建、删除Services或者Endpoint Pod指令。
  • kube-proxy 在node上设置iptables规则,当有请求转发到Services的 ClusterIP上后,会立即被捕获,并重定向此Services对应的一个backend的Pod。
  • kube-proxy会在node上为每一个Services对应的Pod设置iptables 规则,选择Pod默认算法是随机策略。

在iptables模式中,kube-proxy把流量转发和负载均衡的策略完全委托给iptables/netfiter 来做,这些转发操作都是在kernelspace 来实现,比userspace 快很多。
在这里插入图片描述
在iptables 中kube-proxy 只做好watching API 同步最新的数据信息这个角色。路由规则信息和转发都放在了kernelspace 的iptables 和netfiter 来做了。但是,这个这个模式不如userspace模式的一点是,在usersapce模式下,kube-proxy做了负载均衡,如果选择的backend 一台Pod没有响应,kube-proxy可以重试,在iptables模式下,就是一条条路由规则,要转发的backend Pod 没有响应,且没有被K8S 摘除,可能会导致转发到此Pod请求超时,需要配合K8S探针一起使用。

在这种模式下,kube-proxy通过观察Kubernetes中service和endpoint对象的变化,当有servcie创建时,kube-proxy在iptables中追加新的规则。对于service的每一个endpoint,会在iptables中追加一条规则,设定动作为DNAT,将目的地址设置成真正提供服务的pod地址;再为servcie追加规则,设定动作为跳转到对应的endpoint的规则上。

默认情况下,kube-proxy随机选择一个后端的服务,可以通过iptables中的 -m recent 模块实现session affinity功能,通过 -m statistic 模块实现负载均衡时的权重功能

比如说创建了一个service,对应的IP:1.2.3.4,port:8888,对应一个后端地址:10.1.0.8:8080,则会在iptables中追加(主要规则):

-A PREROUTING -j KUBE-SERVICES

-A KUBE-SERVICES -d 1.2.3.4/32 -p tcp –dport 8888 -j KUBE-SVC-XXXXXXXXXXXXXXXX

-A KUBE-SVC-XXXXXXXXXXXXXXXX -j KUBE-SEP-XXXXXXXXXXXXXXXX

-A KUBE-SEP-XXXXXXXXXXXXXXXX -p tcp -j DNAT –to-destination 10.1.0.8:8080

KUBE-SERVICES 是kube-proxy在iptable中创建的规则链,在PREROUTING阶段执行

执行过程:

  • 当请求访问service时,iptables在prerouting阶段,将讲求jump到KUBE-SERVICES,
  • 在KUBE-SERVICES 中匹配到上面的第一条准则,继续jump到KUBE-SVC-XXXXXXXXXXXXXXXX,
  • 根据这条准则jump到KUBE-SEP-XXXXXXXXXXXXXXXX,
  • 在KUBE-SEP-XXXXXXXXXXXXXXXX规则中经过DNAT动做,访问真正的pod地址10.1.0.8:8080。

在这里插入图片描述
在这种逻辑下,数据转发都在系统内核层面做,提升了性能,并且即便kube-proxy不工作了,已经创建好的服务还能正常工作 。但是在这种模式下,如果选中的第一个pod不能响应,请求就会失败,不能像userspace模式,请求失败后kube-proxy还能对其他endpoint进行重试。

这就要求我们的应用(pod)要提供readiness probes功能,来验证后端服务是否能正常提供服务,kube-proxy只会将readiness probes测试通过的pod写入到iptables规则中,以此来避免将请求转发到不正常的后端服务中。

3.2.1 负载均衡的方式

在Linux中使用iptables完成tcp的负载均衡有两种模式:随机、轮询

The statistic module support two different modes:

random:(随机)
the rule is skipped based on a probability
nth:(轮询)
the rule is skipped based on a round robin algorithm

3.2.2 随机方式

下面以一个example说明iptables两种LB方式的具体实现:
系统中提供3个servers,下面我们通过配置iptables使流量均衡访问这3台server。

# 随机:(Random balancing)
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode random --probability 0.33  -j DNAT --to-destination 10.0.0.2:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode random --probability 0.5 -j DNAT --to-destination 10.0.0.3:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017  -j DNAT --to-destination 10.0.0.4:1234

rules说明:

第一条规则中,指定–probability 0.33 ,则说明该规则有33%的概率会命中,

第二条规则也有33%的概率命中,因为规则中指定 --probability 0.5。 则命中的概率为:50% * (1 - 33%)=0.33

第三条规则中,没有指定 --probability 参数,因此意味着当匹配走到第三条规则时,则一定命中,此时走到第三条规则的概率为:1 - 0.33 -0.33 ≈ 0.33。

由上可见,三条规则命中的几率一样的。此外,如果我们想修改三条规则的命中率,可以通过 --probability 参数调整。

假设有n个server,则可以设定n条rule将流量均分到n个server上,其中 --probability 参数的值可通过以下公式计算得到:

其中 i 代表规则的序号(第一条规则的序号为1)
n 代表规则/server的总数
p 代表第 i 条规则中 --probability 的参数值

p=1/(n−i+1)

注意:因为iptables中,规则是按顺序匹配的,由上至下依次匹配,因此设计iptables规则时,要严格对规则进行排序。因此上述三条规则的顺序也不可以调换,不然就无法实现LB均分了。

3.2.3 轮询方式

轮询算法中有两个参数:

  • n: 指每 n 个包
  • p:指第 p 个包

在规则中 n 和 p 代表着: 从第 p 个包开始,每 n 个包执行该规则。

这样可能有点绕口,直接看栗子吧:

还是上面的例子,有3个server,3个server轮询处理流量包,则规则配置如下:

#every:每n个包匹配一次规则
#packet:从第p个包开始
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 10.0.0.2:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode nth --every 2 --packet 0  -j DNAT --to-destination 10.0.0.3:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -j DNAT --to-destination 10.0.0.4:1234

3.3 IPVS

3.3.1 什么是IPVS

由于在iptables模式中,kube-proxy需要为每一个服务,每一个endpoint都生成相应的iptables规则,当服务规模很大时,性能也将显著下降,因此kubernetes在1.8引入了IPVS模式,1.9版本中变成beta,在1.11版本中成为GA。

在IPVS模式下,kube-proxy观察Kubernetes中service和endpoint对象的变化,通过调用netlink接口创建相应的IPVS规则,并周期性的对Kubernetes的service、endpoint和IPVS规则进行同步,当访问一个service时,IPVS负责选择一个真实的后端提供服务。

IPVS模式也是基于netfilter的hook功能(INPUT阶段),这点和iptables模式是一样的,但是用的是内核工作空间的hash表作为存储的数据结构,在这种模式下,iptables可通过ipset来验证具体请求是否满足某条规则。在service变更时,只用更新ipset中的记录,不用改变iptables的规则链,因此可以保证iptables中的规则不会随着服务的增加变多,在超大规模服务集群的情况下提供一致的性能效果。

在对规则进行同步时的性能也要高于iptables(只用对特定的一个hash表进行更新,而不是像iptables模式下对整个规则表进行操作),所以能提供更高的网络流量。

IPVS在对后端服务的选择上也提供了更多灵活的算法:

  • rr: round-robin(轮询算法)
  • lc: least connection (最少连接数算法)
  • dh: destination hashing(目的hash算法)
  • sh: source hashing(原地址hash算法)
  • sed: shortest expected delay(最短延迟算法)
  • nq: never queue(无需队列等待算法)

在这里插入图片描述

IPVS(IP虚拟服务器)实现传输层负载平衡,通常称为第4层LAN交换,是Linux内核的一部分。

IPVS在主机上运行,在真实服务器集群前充当负载均衡器。 IPVS可以将对基于TCP和UDP的服务的请求定向到真实服务器,并使真实服务器的服务在单个IP地址上显示为虚拟服务。

3.3.2 IPVS vs. IPTABLES

IPVS模式在Kubernetes v1.8中引入,并在v1.9中进入了beta。 IPTABLES模式在v1.1中添加,并成为自v1.2以来的默认操作模式。 IPVS和IPTABLES都基于netfilter。 IPVS模式和IPTABLES模式之间的差异如下:

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

我们都知道,在Linux 中iptables设计是用于防火墙服务的,对于比较少规则的来说,没有太多的性能影响。但是对于,一个K8S集群来说,会有上千个Services服务,当然也会转发到Pods,每个都是一条iptables规则,对集群来说,每个node上会有大量的iptables规则,简直是噩梦。

同样IPVS可以解决可能也会遇见这样大规模的网络转发需求,但是IPVS用hash tabels来存储网络转发规则,比iptables 在这上面更有优势,而且它主要工作在kernelspace,减少了上下文切换带来的开销。

3.3.3 IPVS负载步骤

kube-proxy和IPVS在配置网络转发中,有这么几步:

  • 通过watching kubernetes集群 cluster API, 获取新建、删除Services或者Endpoint Pod指令,有新的Service建立,kube-proxy回调网络接口,构建IPVS规则。
  • 同时,kube-proxy会定期同步 Services和backend Pods的转发规则,确保失效的转发能被更新修复。
  • 有请求转发到后端的集群时,IPVS的负载均衡直接转发到backend Pod。

3.3.4 IPVS负载算法

IPVS支持的负载均衡算法有这么几种:

  • rr: 轮询
  • lc: 最小连接数
  • dh: 目的地址hash
  • sh: 源地址hash
  • sed: 最短期望延迟
  • nq: 无须队列等待

在node上通过 “–ipvs-scheduler”参数,指定kube-proxy的启动算法。

4. 总结

  1. K8s的Service网络构建于Pod网络之上,它主要目的是解决服务发现(Service Discovery)和负载均衡(Load Balancing)问题。
  2. K8s通过一个ServiceName+ClusterIP统一屏蔽服务发现和负载均衡,底层技术是在DNS+Service Registry基础上发展演进出来。
  3. K8s的服务发现和负载均衡是在客户端通过Kube-Proxy + iptables转发实现,它对应用无侵入,且不穿透Proxy,没有额外性能损耗。
  4. K8s服务发现机制,可以认为是现代微服务发现机制和传统Linux内核机制的优雅结合。

有了Service抽象,K8s中部署的应用都可以通过一个抽象的ClusterIP进行寻址访问,并且消费方不需要关心这个ClusterIP后面究竟有多少个Pod实例,它们的PodIP是什么,会不会变化,如何以负载均衡方式去访问等问题。但是,K8s的Service网络只是一个集群内可见的内部网络,集群外部是看不到Service网络的,也无法直接访问。而我们发布应用,有些是需要暴露出去,要让外网甚至公网能够访问的,这样才能对外提供服务。K8s如何将内部服务暴露出去?

Logo

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

更多推荐