k8s Service详解(概念、原理、流量分析、代码) - sucre_tan - 博客园

补充说明:

ClusterIP方式原理

kubernetes中kube-proxy的工作原理是什么 - 云计算 - 亿速云

Kubernetes中的负载均衡原理————以iptables模式为例 | CITAHub技术团队

kube-proxy & service

  • kube-proxy其实就是管理service的访问入口,包括集群内Pod到Service的访问和集群外访问service。

  • kube-proxy管理sevice的Endpoints,该service对外暴露一个Virtual IP,也成为Cluster IP, 集群内通过访问这个Cluster IP:Port就能访问到集群内对应的serivce下的Pod。

  • service是通过Selector选择的一组Pods的服务抽象,其实就是一个微服务,提供了服务的LB和反向代理的能力,而kube-proxy的主要作用就是负责service的实现。

  • service另外一个重要作用是,一个服务后端的Pods可能会随着生存灭亡而发生IP的改变,service的出现,给服务提供了一个固定的IP,而无视后端Endpoint的变化。

服务发现

k8s提供了两种方式进行服务发现:

  • 环境变量: 当你创建一个Pod的时候,kubelet会在该Pod中注入集群内所有Service的相关环境变量。需要注意的是,要想一个Pod中注入某个Service的环境变量,则必须Service要先比该Pod创建。这一点,几乎使得这种方式进行服务发现不可用。

    比如,一个ServiceName为redis-master的Service,对应的ClusterIP:Port为10.0.0.11:6379,则其对应的环境变量为:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
  • DNS:这也是k8s官方强烈推荐的方式。可以通过cluster add-on的方式轻松的创建KubeDNS来对集群内的Service进行服务发现。更多关于KubeDNS的内容,请查看:Kubernetes DNS Service技术研究 - 爱码网

服务发布类型ServiceType

k8s原生的,一个Service的ServiceType决定了其发布服务的方式。

  • ClusterIP集群内发布服务,这是k8s默认的ServiceType。通过集群内的ClusterIP在内部发布服务,只能够在集群内部访问服务。

  • ExternalName用于集群内不同命名空间内的服务通过名称可直接访问,以及将在k8s集群外部的服务引入到k8s集群内通过服务名称访问场景。需要借助KubeDNS(version >= 1.7)的支持,就是用KubeDNS将该service和ExternalName做一个Map,KubeDNS返回一个CNAME记录。
    ExternalName的用途(业务场景),有两个:
    (1)将k8s集群内在某个namespace内的服务暴露到另外一个namespace内,实现两个不同的namespace之间的不同pod可以通过name的形式访问。假如集群内有两个服务服务a和b,a服务访问b服务。若两个服务都定义在同一个namespaceA下,则a可以通过b的名称http://b/访问b服务,k8s会将http://b/转为http://b.namespaceA.svc.cluster.local/ svc.cluster.local 是整个k8s集群的域),若将b定义在namespaceB访问,则A无法直接通过b的名称http://b/访问服务b,因为b的namespace不是namespaceA。若想仍然通过http://b访问,可以在namespaceA下定义一个type为ExternalName的服务b,其externalName属性的值为b.namespaceB.svc.cluster.local即可,相当于在namespaceA下为定义在namespaceB下的服务b建了一个链接,连接名为b。namespaceA下的ExternalName服务b定义如下:

    apiVersion: v1
    kind: Service
    metadata:
      name: b
      namespace: namespaceA
    spec:
      type: ExternalName
      externalName: b.namespaceB.svc.cluster.local

      (2)将k8s集群外的服务引入到k8s内部,供内部的pod访问,就像使用集群内的服务一样使用外部服务,利用ExternalName的Service访问的外部服务地址必须是域名,不支持IP地址。举例参见下方k8s集群内部与外部之间互联互通#通过ExternalName Service访问域名方式的外部服务

  • NodePort用来对集群外暴露k8s内部的Service。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建,同时,k8s会在集群内所有node上,为集群内的所有NodePort类型的服务暴露其NodePort,这样集群外部可以连接到集群内任何一个node的ip:nodePort即可以访问该服务。这种方式的缺点比较多:每个端口只能是一种服务;端口范围只能是 30000-32767,不推荐采用这种方式对集群外暴露服务NodePort 需要借助真实存在的ip,是一个公共的ip,任何人都可以访问,而ClusterIP可以理解成不对外开放,仅限于集群内的节点之间特定的一个范围。
  • LoadBalancer: 也是用来对集群外暴露k8s内部的Service。使用云提供商的负载局衡器,可以路由到 NodePort 服务和 ClusterIP 服务,这个需要结合具体的云厂商进行操作。所有通往指定的端口的流量都会被转发到对应的服务。它没有过滤条件,没有路由等。这意味着你几乎可以发送任何种类的流量到该服务,像 HTTP,TCP,UDP,Websocket,gRPC 或其它任意种类。这个方式的最大缺点是每一个用 LoadBalancer 暴露的服务都会有它自己的 IP 地址,每个用到的 LoadBalancer 都需要付费,这将是非常昂贵的。

kube-proxy负载均衡原理

k8s借助kube-proxy实现负载均衡。

kube-proxy实现了三种代理模式:

  • userspace代理模式:v1.0及之前版本的默认模式,来回在用户空间和内核空间切换,存在严重的性能问题,不推荐
  • iptables代理模式:从v1.1版本中开始增加了iptables mode,在v1.2版本中正式替代userspace模式成为默认模式。将k8s整个集群的service和endpoints路由都写到iptables中,当service数量较大时,同样存在严重的性能问题,且负载均衡能力比较简单,不推荐
  • IPVS代理模式:v1.9之后引入,通过维护ipvs规则和ipset两种高效的数据结构,使得iptables规则不再随着service数量增加而增加,这也是ipvs模式在大规模场景下比iptables模式性能更优越的原因,且支持丰富的负载均衡算法。推荐。

以上三种方式,都是利用客户端的OS的网络内核(iptables规则)拦截到ClusterIP:ClusterPort的请求,因此是客户端实现的负载均衡。

userspace 代理模式

userspace是在用户空间,通过kube-proxy来实现service的代理服务,其原理如下如图所示:

iptables代理模式

这种方式完全利用内核iptables来实现service的代理和LB。是v1.2及之后版本默认模式,其原理图如下所示:

说明:

  • k8s集群中所有node的iptables规则中均配置了整个k8s集群中所有的Service的ClusterIP:ClusterPort到对应的endpoint列表的路由规则。
  • iptables规则会拦截client端发起的到ClusertIP:ClusterPort的请求,实现了请求转发到某个具体的Endpoint,同时实现了负载均衡和会话保持功能。
  • iptables规则借助statistic模块实现负载均衡,以random方式均衡的分发流量,也即负载均衡模式为简单的轮询无法实现复杂的如最小链接分发方式,鉴于此,K8S 1.9后续版本调整了kube-proxy服务,其可通过ipvs实现Service负载均衡功能
  • iptables规则借助recent模块,实现会话保持功能。可以通过Service对象的sessionAffinity属性配置 session 亲和,目前可以有两种取值,一种是 None,也是默认值,表示没有,会直接轮询 Pod。一种是 ClientIP,表示根据客户端 IP 亲和,同一个客户端 IP,会被发送到同一个 Pod 上。
  • 我们提到了kube-proxy iptables模式在大规模场景下会生成众多的iptables规则,而这些iptables规则是链式顺序匹配的,这样会大大降低网络访问性能。

iptables最初也不是为了负载均衡而设计,而纯粹是为了防火墙而设计。为了解决iptables模式下的性能问题,kube-proxy引入了一个更适合负载均衡场景且有更高效数据结构(散列表)的模块——ipvs。

IPVS代理模式

https://www.jianshu.com/p/d10bf4086644?utm_campaign=maleskine...&utm_content=note&utm_medium=seo_notes

kube-proxy ipvs模式原理分析 - 墨天轮

说明:

  • 通过创建kube-ipvs0 dummy网卡并绑定service ip,使得从外部进来的流量在PREROUTING后的路由决策中发往INPUT,从而能到达ipvs模块。

  • ipvs模式下通过维护ipvs规则和ipset两种高效的数据结构,使得iptables规则不再随着service数量增加而增加,这也是ipvs模式在大规模场景下比iptables模式性能更优越的原因。

  • 外部访问service ip的链路为:PREROUTING->route->INPUT->ipvs(DNAT)->OUTPUT->route->POSTROUTING

  • 宿主机上访问service ip的链路为:OUTPUT->ipvs(DNAT)->OUTPUT->route->POSTROUTING

  • ipvs只做了DNAT,部分场景下仍需要iptables做SNAT。

k8s集群内部与外部之间互联互通

从k8s集群外部访问k8s

可通过NodePort、LoadBalancer和Ingeress方式,从k8s集群外部访问k8s内部的服务。

区别参见:K8S ClusterIP、 NodePort、LoadBalancer和Ingress 区别 - 王叫兽 - 博客园

将k8s集群外部的服务接入k8s集群内部

 参见:https://www.jianshu.com/p/758cfafcf80d 

有以下几种方式:

  • 利用ConfigMap+环境变量访问外部服务:ConfigMap配置外部服务的地址(ip或域名均可),在源端容器中定义环境变量,并从ConfigMap映射。这种方式的问题:需要编写额外的程序代码以从环境变量中读取外部服务地址,另外如果外部服务地址发生变化,需要重启所有正在运行的容器以获取更新后的服务地址,故不推荐这种方案
  • 通过ExternalName Service访问域名方式的外部服务。通过ExternalName Service定义k8s内部服务名到外部服务名的映射,即可在k8s内部访问外部服务,无需编写额外的程序代码读取环境变量,地址发生变更后,只需要ExternalName Service配置即可动态生效,不需要重启容器。这种方式的问题:外部服务名必须是域名,不支持IP地址。

    举例:以下配置可在集群内通过https://baidu-service/访问https://www.baidu.com/:
    apiVersion: v1
    kind: Service
    metadata:
      name: baidu-service
      namespace: namespaceA
    spec:
      type: ExternalName
      externalName: www.baidu.com
  • 通过无Pod选择器的Service+Endpoints访问外部服务。当外部服务的访问地址是ip地址时,无法通过ExternalName service访问外部服务。此时需要创建一个没有 Pod 选择器的Service和一个Endpoints,Endpoints对象通过subsets指向外部服务的ip和端口(若外部服务是一个集群,即有多个服务实例,均可以定义在subsets中,k8s会在所有服务地址之间进行流量的负载平衡)。

    例如一个外部游戏服务,其访问地址为http://192.168.3.175::8091/,想引入k8s集群内部,且希望通过http://games/访问(pod通过名称访问游戏服务,而非写死且可能会发生变更的ip地址),则对应的Headless Service和Endpoint定义如下:
     
    #Service定义,可以是Headless Service,也可以是普通的Service。
    #注意这里没有pod选择器,因此它不知道往哪里转发流量。
    #因此需手动创建一个将从此服务接收流量的 Endpoints 对象(见下方Endpoints定义)
    apiVersion: v1
    kind: Service
    metadata:
      name: games
    spec:
      #clusterIP: None表示headless service
      #clusterIP: None 
      type: ClusterIP
      ports:
      - port: 80
        targetPort: 8091
    
    
    #EndPoints定义,接收上方Service转发的流量(name要与Service的name相同)
    #并将流量转发到真正的外部服务192.168.3.175:8091上
    #若相同的外部服务有多个ip:port,则可以都定义在subsets中
    #k8s会在所有服务地址之间进行流量的负载平衡
    apiVersion: v1
    kind: Endpoints
    metadata:
      name: games
    subsets:
      - addresses:
          - ip: 192.168.3.175
        ports:
          - port: 8091
    

client Pod访问服务的处理过程如下:

  1. clientPod访问serviceName:servicePort
  2. 服务发现:k8s的cube-dns服务,将serviceName:servicePort解析为serviceIP:servicePort
  3. 请求被cube-proxy

Logo

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

更多推荐