• K8S学习之Service实现服务发现原理分析与实践


    前言

    问题列表

    k8s的service解决什么问题?

    • 简答

      Service为了给客户端提供固定的访问端点,因此在客户端和服务端提供了中间层叫Service,Service名称解析是强依赖于Dns解析组件的,部署完k8s还需要部署CoreDns

    service和pod怎么关联的?

    • 简答

      Service 通过标签来选取服务后端,一般配合 ReplicaSet(替换原来的资源 ReplicationController) 或者 Deployment 来保证后端容器的正常运行。

      pod与service之间的关系

    部署在k8s集群的服务如何对外提供访问?

    • 简答

      通过Service直接对外提供服务

      ServiceType设置为NodePort、LoadBalancer或External Name方式,进行相关配置

      配置Ingress结合Service对外提供服务

      结合Ingress 控制器(如Nginx Ingress 控制器)

    什么是Service

    • 简述

      Service 是 k8s 中为多个 pod 公开网络服务的抽象方法。在 k8s 中,每个 pod 都有自己的 ip 地址,而且 Service 可以为一组 pod 提供相同的 DNS(Domain Name System根据域名查出IP地址) ,使得多个 pod 之间可以相互通讯,k8s 可以在这些 pod 之间进行负载均衡。

    • k8s与传统物理概念对比

      k8s概念传统物理概念说明备注
      pod单台物理机实例Pod代表的是集群上处于运行状态的一组容器
      workload应用内置workload工作负载包括:无状态、有状态、守护进程和批处理
      service(服务发现)负载均衡
      负载均衡网关配置

    为什么需要Service

    • 简述

      一方面是因为 Pod 的 IP 不是固定的,另一方面则是因为一组 Pod 实例之间总会有负载均衡的需求

    • 问题举例

      如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能, 那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?

    应用举例

    定义Service

    • 内容介绍

      Service 在 Kubernetes 中是一个 REST 对象,可以基于 POST 方式,请求 API server 创建新的实例。

      
          
          apiVersion: v1
          kind: Service
          metadata:
            name: hostnames
          spec:
            selector:
              app: hostnames
            ports:
              - name: default
                protocol: TCP
                port: 80
                targetPort: 9376
      
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      上述配置,创建一个名称为 “hostnames” 的 Service 对象,通过selector选择算符声明这个 Service 只代理携带了 “app=hostnames” 标签的 Pod,并且它会将请求代理到使用 TCP 端口 9376。

      Kubernetes 为该服务分配一个 IP 地址(有时称为 “集群IP”),该 IP 地址由服务代理使用。

    创建应用的Deployment

    • 内容介绍

      定义一个Deployment,声明创建了一个 ReplicaSet,负责启动三个 hostnames Pods

      
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: hostnames
          spec:
            selector:
              matchLabels:
                app: hostnames
            replicas: 3
            template:
              metadata:
                labels:
                  app: hostnames
              spec:
                containers:
                - name: hostnames
                  image: k8s.gcr.io/serve_hostname
                  ports:
                  - containerPort: 9376
                    protocol: TCP
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • 操作步骤

    1. 创建 Deployment
         kubectl apply -f ${路径}/hostnames-deployment.yaml
    
    • 1
    1. 查看Deployment上线状态
    
         kubectl rollout status deployment/hostnames
    
    
    • 1
    • 2
    • 3

    输出类似于:

    
         Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
         deployment "hostnames" successfully rolled out
    
    
    • 1
    • 2
    • 3
    • 4
    1. 获取Deployment基本信息
    
         kubectl get deployments
    
    
    • 1
    • 2
    • 3

    输出类似于:

    
         NAME               READY   UP-TO-DATE   AVAILABLE   AGE
         hostnames           3/3     3            3           18s
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 查看Deployment创建的ReplicaSet(rs)信息
    
         kubectl get rs
    
    
    
    • 1
    • 2
    • 3
    • 4

    输出类似于:

    
         NAME                          DESIRED   CURRENT   READY   AGE
         hostnames-75675f5897          3         3         3       18s
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意 ReplicaSet 的名称始终被格式化为[Deployment名称]-[随机字符串]。 其中的随机字符串是使用 pod-template-hash 作为种子随机生成的。

    1. 查看每个pod自动生成的标签
    
         kubectl get pods --show-labels
    
    
    • 1
    • 2
    • 3

    输出类似:

    
         NAME                                READY     STATUS    RESTARTS   AGE       LABELS
         hostnames-75675f5897-7ci7o          1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
         hostnames-75675f5897-kzszj          1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
         hostnames-75675f5897-qqcnn          1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    所创建的 ReplicaSet 确保总是存在三个 hostnames Pod。

    • 注意事项

      你必须在 Deployment 中指定适当的选择算符和 Pod 模板标签(在本例中为 app: hostnames)。 标签或者选择算符不要与其他控制器(包括其他 Deployment 和 StatefulSet)重叠。 Kubernetes 不会阻止你这样做,但是如果多个控制器具有重叠的选择算符, 它们可能会发生冲突执行难以预料的操作。

    • pod-template-hash标签

      不要更改此标签

      Deployment 控制器将 pod-template-hash 标签添加到 Deployment 所创建或收留的每个 ReplicaSet 。

      此标签可确保 Deployment 的子 ReplicaSets 不重叠。 标签是通过对 ReplicaSet 的 PodTemplate 进行哈希处理。 所生成的哈希值被添加到 ReplicaSet 选择算符、Pod 模板标签,并存在于在 ReplicaSet 可能拥有的任何现有 Pod 中。

    访问验证

    • Endpoints(端点)

      被 selector 选中的 Pod,就称为 Service 的 Endpoints。

      
      
          kubectl get endpoints hostnames
      
      
      • 1
      • 2
      • 3
      • 4

      输出类似:

      
          
          NAME        ENDPOINTS                                          AGE
          hostnames   10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376    1h
      
      
      • 1
      • 2
      • 3
      • 4
      • 5

      需要注意的是,只有处于 Running 状态,且 readinessProbe 检查通过的 Pod,才会出现在 Service 的 Endpoints 列表里。

    • VIP(虚拟IP)

      通过 Service 的 VIP 地址访问到它所代理的 Pod

      
          
          $ kubectl get svc hostnames
          NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
          hostnames   ClusterIP   10.0.1.175   <none>        80/TCP    5s
      
          $ curl 10.0.1.175:80
          hostnames-0uton
      
          $ curl 10.0.1.175:80
          hostnames-yp2kp
      
          $ curl 10.0.1.175:80
          hostnames-bvc05
      
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      这个 VIP 地址 10.0.1.175 是 Kubernetes 自动为 Service 分配的。Service 提供的是 Round Robin 方式的负载均衡。对于这种方式,我们称为:ClusterIP 模式的 Service

    Service工作原理

    • kube-proxy 组件 + iptables

      在Kubernetes集群中每个Node运行一个kube-proxy进程,kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式

      创建Service -> Kubernetes -> kube-proxy -> Service的Informer -> 创建宿主机iptables规则

    iptables 代理模式

    • 访问流程

      iptables代理模式

    • VIP跳转

      登录宿主机查询iptables规则

      
          # svc相关的iptables都在nat表里面
          iptables-save -t nat
          
      
      • 1
      • 2
      • 3
      • 4

      输出类似:

      
          -A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
      
      
      
      • 1
      • 2
      • 3
      • 4

      这条 iptables 规则的含义是:凡是目的地址是 10.0.1.175、目的端口是 80 的 IP 包,都应该跳转到另外一条名叫 KUBE-SVC-NWV5X2332I4OT4T3 的 iptables 链进行处理

      10.0.1.175 正是这个 Service 的 VIP。所以这一条规则,就为这个 Service 设置了一个固定的入口地址。并且,由于 10.0.1.175 只是一条 iptables 规则上的配置,并没有真正的网络设备,所以你 ping 这个地址,是不会有任何响应的。

    • iptables链追踪

      跳转到的 KUBE-SVC-NWV5X2332I4OT4T3 规则,实际上是一组规则的集合,这一组规则,实际上是一组随机模式(–mode random)的 iptables 链

      
      
          
          -A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
          -A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
          -A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -j KUBE-SEP-57KPRZ3JQVENLNBR
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      随机转发的目的地,分别是 KUBE-SEP-WNBA2IHDGP2BOBGZ、KUBE-SEP-X3P2623AGDH6CDF3 和 KUBE-SEP-57KPRZ3JQVENLNBR。

      这三条链指向的最终目的地,其实就是这个 Service 代理的三个 Pod。所以这一组规则,就是 Service 实现负载均衡的位置

    • iptables规则匹配

      iptables 规则的匹配是从上到下逐条进行的,所以为了保证上述三条规则每条被选中的概率都相同,我们应该将它们的 probability 字段的值分别设置为 1/3(0.333…)、1/2 和 1

      匹配原理:第一条规则被选中的概率就是 1/3;而如果第一条规则没有被选中,那么这时候就只剩下两条规则了,所以第二条规则的 probability 就必须设置为 1/2;类似地,最后一条就必须设置为 1。

      
      
          
          -A KUBE-SEP-57KPRZ3JQVENLNBR -s 10.244.3.6/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
          -A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.3.6:9376
      
          -A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 10.244.1.7/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
          -A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.1.7:9376
      
          -A KUBE-SEP-X3P2623AGDH6CDF3 -s 10.244.2.3/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
          -A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.2.3:9376
      
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      这三条链,其实是三条 DNAT 规则。但在 DNAT 规则之前,iptables 对流入的 IP 包还设置了一个“标志”(–set-xmark)。

      “标志”作用

      宿主机器的iptables只对 NodePort Service转发出来的 IP 包进行SNAT(否则普通的 IP 包就被影响了)。而 iptables 做这个判断的依据,就是查看该 IP 包是否有一个“0x4000”的“标志”。

      DNAT 规则作用

      在 PREROUTING 检查点(路由)之前,将流入 IP 包的目的地址和端口,改成–to-destination 所指定的新的目的地址和端口。可以看到,这个目的地址和端口,正是被代理 Pod 的 IP 地址和端口

    • 总结

      访问 Service VIP 的 IP 包经过上述 iptables 处理之后,就已经变成了访问具体某一个后端 Pod 的 IP 包了。不难理解,这些 Endpoints 对应的 iptables 规则,正是 kube-proxy 通过监听 Pod 的变化事件,在宿主机上生成并维护的

      基于 iptables 的 Service 实现,是制约 Kubernetes 项目承载更多量级的 Pod 的主要障碍。kube-proxy 通过 iptables 处理 Service 的过程,其实需要在宿主机上设置相当多的 iptables 规则。而且,kube-proxy 还需要在控制循环里不断地刷新这些规则来确保它们始终是正确的

    IPVS 代理模式

    • 访问流程

      ipvs代理模式

    • 工作原理

    1. 当创建了前面的 Service 之后,kube-proxy 首先会在宿主机上创建一个虚拟网卡(叫作:kube-ipvs0),并为它分配 Service VIP 作为 IP 地址
    
    
         
         # ip addr
         ...
         73:kube-ipvs0:<BROADCAST,NOARP>  mtu 1500 qdisc noop state DOWN qlen 1000
         link/ether  1a:ce:f5:5f:c1:4d brd ff:ff:ff:ff:ff:ff
         inet 10.0.1.175/32  scope global kube-ipvs0
         valid_lft forever  preferred_lft forever
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. kube-proxy 就会通过 Linux 的 IPVS 模块,为这个 IP 地址设置三个 IPVS 虚拟主机,并设置这三个虚拟主机之间使用轮询模式 (rr) 来作为负载均衡策略。
    
    
         
         # ipvsadm -ln
         IP Virtual Server version 1.2.1 (size=4096)
         Prot LocalAddress:Port Scheduler Flags
             ->  RemoteAddress:Port           Forward  Weight ActiveConn InActConn     
         TCP  10.102.128.4:80 rr
             ->  10.244.3.6:9376    Masq    1       0          0         
             ->  10.244.1.7:9376    Masq    1       0          0
             ->  10.244.2.3:9376    Masq    1       0          0
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    输出结果可见,这三个 IPVS 虚拟主机的 IP 地址和端口,对应的正是三个被代理的 Pod,任何发往 10.102.128.4:80 的请求,就都会被 IPVS 模块转发到某一个后端 Pod 上。

    • 注意事项

      IPVS 模块只负责上述的负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,还是要靠 iptables 来实现。只不过,这些辅助性的 iptables 规则数量有限,也不会随着 Pod 数量的增加而增加

      要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 之前使 IPVS 在节点上可用。

      当 kube-proxy 以 IPVS 代理模式启动时(设置–proxy-mode=ipvs 来开启这个功能),它将验证 IPVS 内核模块是否可用。 如果未检测到 IPVS 内核模块,则 kube-proxy 将退回到以 iptables 代理模式运行。

    • 总结

      IPVS代理模式基于类似于 iptables 模式的 netfilter 挂钩函数的 NAT 模式, 但是使用哈希表作为基础数据结构,并且在内核空间中工作。 这意味着,与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy 重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。 与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量

      “将重要操作放入内核态”是提高性能的重要手段

    Service与DNS的关系

    • 前言

      在 Kubernetes 里,/etc/hosts 文件是单独挂载的,这也是为什么 kubelet 能够对 hostname 进行修改并且 Pod 重建后依然有效的原因。这跟 Docker 的 Init 层是一个原理。

    DNS

    • 建议使用附加组件 为 Kubernetes 集群设置 DNS 服务

      支持集群的 DNS 服务器(例如 CoreDNS)监视 Kubernetes API 中的新服务,并为每个服务创建一组 DNS 记录(从域名解析 IP 的记录)。 如果在整个集群中都启用了 DNS,则所有 Pod 都应该能够通过其 DNS 名称自动解析服务。

    ClusterIP模式

    • ClusterIP Service DNS

      对于 ClusterIP 模式的 Service 来说(比如我们上面的例子),它的 DNS 记录的格式是:…svc.cluster.local。当你访问这条 DNS 记录的时候,它解析到的就是该 Service 的 VIP 地址

    • ClusterIP Service 代理的Pod DNS

      代理的 Pod 被自动分配的 DNS 记录的格式是:…pod.cluster.local(podName.namespace.pod.cluster.local)。这条记录指向 Pod 的 IP 地址。

    Headless Service(无头服务)

    • 内容介绍

      Headless Service 为你提供的,则是一个 Pod 的稳定的 DNS 名字,并且,这个名字是可以通过 Pod 名字和 Service 名字拼接出来的。

    • Headless Service DNS

      对于指定了 clusterIP=None 的 Headless Service 来说,它的 DNS 记录的格式也是:…svc.cluster.local。但是,当你访问这条 DNS 记录的时候,它返回的是所有被代理的 Pod 的 IP 地址的集合。当然,如果你的客户端没办法解析这个集合的话,它可能会只会拿到第一个 Pod 的 IP 地址。

    • Headless Service 代理的Pod DNS

      代理的 Pod 被自动分配的 DNS 记录的格式是:…svc.cluster.local(如:myPodName.myServiceName.myNameSpace.svc.cluster.local)。这条记录也指向 Pod 的 IP 地址。

    • 样例

      如果你为 Pod 指定了 Headless Service,并且 Pod 本身声明了 hostname 和 subdomain 字段,那么这时候 Pod 的 DNS 记录就会变成:【pod的hostname】…svc.cluster.local(hostname.subdomain.myNameSpace.svc.cluster.local),比如:

      
      
      
           apiVersion: v1
           kind: Service
           metadata:
             name: default-subdomain
           spec:
             selector:
               name: busybox
             clusterIP: None
             ports:
             - name: foo
               port: 1234
               targetPort: 1234
           ---
           apiVersion: v1
           kind: Pod
           metadata:
             name: busybox1
             labels:
               name: busybox
           spec:
             hostname: busybox-1
             subdomain: default-subdomain
             containers:
             - image: busybox
               command:
                 - sleep
                 - "3600"
               name: busybox
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32

      在上面这个 Service 和 Pod 被创建之后,你就可以通过 busybox-1.default-subdomain.default.svc.cluster.local 解析到这个 Pod 的 IP 地址了。

    其他

    SNAT与DNAT简介

    • SNAT

      SNAT(Source Network Address Translation,源地址转换)是Linux防护墙的一种地址转换操作,也是iptables命令中的一种数据包控制类型,其作用是根据指定条件修改数据包的源IP地址

    • DNAT

      DNAT(Destination Network Address Translation,目标地址转换)是Linux防火墙的另一种地址转换操作,同样也是iptables命令中的一种数据包控制类型,其作用是根据指定条件修改数据包的目标IP地址和目标端口

    参考链接

  • 相关阅读:
    什么是js的闭包,它是如何产生的
    Flink的ResourceManager详解(一)
    使用WPS自动化转换办公文档: 将Word, PowerPoint和Excel文件转换为PDF
    【图像处理笔记】图像分割之聚类和超像素
    【pandas小技巧】--统计值作为新列
    SpringBoot 2.X 快速掌握
    HDFS集成Kerberos并使用Python调用
    MongoDB 权限管理
    【牛客网】两个有序数组间相加和的Topk问题 [H](堆)
    Java进阶笔记(面向对象后, 持续更新)
  • 原文地址:https://blog.csdn.net/weixin_42586723/article/details/125546262