【重识云原生】第六章容器基础6.4.9.3节——Service拓扑感知

2022-11-18 19:37:02 浏览数 (1)

1 拓扑感知

1.1 使用拓扑键实现拓扑感知的流量路由特性状态 

Kubernetes v1.21 deprecated

说明:         此功能特性,尤其是 Alpha 阶段的 topologyKeys API,在 Kubernetes v1.21 版本中已被废弃。Kubernetes v1.21 版本中引入的 拓扑感知的提示, 提供类似的功能。

        服务拓扑(Service Topology)可以让一个服务基于集群的 Node 拓扑进行流量路由。 例如,一个服务可以指定流量是被优先路由到一个和客户端在同一个 Node 或者在同一可用区域的端点。

1.2 拓扑感知的流量路由

        默认情况下,发往 ClusterIP 或者 NodePort 服务的流量可能会被路由到服务的任一后端的地址。Kubernetes 1.7 允许将“外部”流量路由到接收到流量的节点上的 Pod。对于 ClusterIP 服务,无法完成同节点优先的路由,你也无法配置集群优选路由到同一可用区中的端点。 通过在 Service 上配置 topologyKeys,你可以基于来源节点和目标节点的标签来定义流量路由策略。

        通过对源和目的之间的标签匹配,作为集群操作者的你可以根据节点间彼此“较近”和“较远” 来定义节点集合。你可以基于符合自身需求的任何度量值来定义标签。 例如,在公有云上,你可能更偏向于把流量控制在同一区内,因为区间流量是有费用成本的,而区内流量则没有。 其它常见需求还包括把流量路由到由 DaemonSet 管理的本地 Pod 上,或者把将流量转发到连接在同一机架交换机的节点上,以获得低延时。

1.3 k8s 亲和性

        service 的就近转发实际就是一种网络的亲和性,倾向于转发到离自己比较近的 endpoint。在此特性之前,已经在调度和存储方面有一些亲和性的设计与实现:

  • 节点亲和性 (Node Affinity): 让 Pod 被调度到符合一些期望条件的 Node 上,比如限制调度到某一可用区,或者要求节点支持 GPU,这算是调度亲和,调度结果取决于节点属性。
  • Pod 亲和性与反亲和性 (Pod Affinity/AntiAffinity): 让一组 Pod 调度到同一拓扑域的节点上,或者打散到不同拓扑域的节点, 这也算是调度亲和,调度结果取决于其它 Pod。
  • 数据卷拓扑感知调度 (Volume Topology-aware Scheduling): 让 Pod 只被调度到符合其绑定的存储所在拓扑域的节点上,这算是调度与存储的亲和,调度结果取决于存储的拓扑域。
  • 本地数据卷 (Local Persistent Volume): 让 Pod 使用本地数据卷,比如高性能 SSD,在某些需要高 IOPS 低时延的场景很有用,它还会保证 Pod 始终被调度到同一节点,数据就不会不丢失,这也算是调度与存储的亲和,调度结果取决于存储所在节点。
  • 数据卷拓扑感知动态创建 (Topology-Aware Volume Dynamic Provisioning):先调度 Pod,再根据 Pod 所在节点的拓扑域来创建存储,这算是存储与调度的亲和,存储的创建取决于调度的结果。

        而 k8s 之前在网络方面还没有亲和性能力,拓扑感知服务路由这个新特性恰好可以补齐这个的空缺,此特性使得 service 可以实现就近转发而不是所有 endpoint 等概率转发。

1.4 实现原理

        我们知道,service 转发主要是 node 上的 kube-proxy 进程通过 watch apiserver 获取 service 对应的 endpoint,再写入 iptables 或 ipvs 规则来实现的;而对于 headless service,主要是通过 kube-dns 或 coredns 动态解析到不同 endpoint ip 来实现的。实现 service 就近转发的关键点就在于如何将流量转发到跟当前节点在同一拓扑域的 endpoint 上,也就是会进行一次 endpoint 筛选,选出一部分符合当前节点拓扑域的 endpoint 进行转发。

        那么如何判断 endpoint 跟当前节点是否在同一拓扑域里呢?只要能获取到 endpoint 的拓扑信息,用它跟当前节点拓扑对比下就可以知道了。那又如何获取 endpoint 的拓扑信息呢?答案是通过 endpoint 所在节点的 label,我们可以使用 node label 来描述拓扑域。

        通常在节点初始化的时候,controller-manager 就会为节点打上许多 label,比如 kubernetes.io/hostname表示节点的 hostname 来区分节点;另外,在云厂商提供的 k8s 服务,或者使用 cloud-controller-manager 的自建集群,通常还会给节点打上 failure-domain.beta.kubernetes.io/zone  和 failure-domain.beta.kubernetes.io/region 以区分节点所在可用区和所在地域,但自 v1.17 开始将会改名成 topology.kubernetes.io/zone 和 topology.kubernetes.io/region,参见 PR 81431。

        如何根据 endpoint 查到它所在节点的这些 label 呢?答案是通过 Endpoint Slice,该特性在v1.16发布了 alpha,在v1.17进入beta,它相当于 Endpoint API 增强版,通过将 endpoint 做数据分片来解决大规模 endpoint 的性能问题,并且可以携带更多的信息,包括 endpoint 所在节点的拓扑信息,拓扑感知服务路由特性会通过 Endpoint Slice 获取这些拓扑信息实现 endpoint 筛选 (过滤出在同一拓扑域的 endpoint),然后再转换为 iptables 或 ipvs 规则写入节点以实现拓扑感知的路由转发。

        之前每个节点上转发 service 的 iptables/ipvs 规则基本是一样的,但启用了拓扑感知服务路由特性之后,每个节点上的转发规则就可能不一样了,因为不同节点的拓扑信息不一样,导致过滤出的 endpoint 就不一样,也正是因为这样,service 转发变得不再等概率,灵活的就近转发才得以实现。

1.5 使用服务拓扑

        如果集群启用了 ServiceTopology 特性门控, 就可以在 Service 规约中设定 topologyKeys 字段,从而控制其流量路由。 此字段是 Node 标签的优先顺序字段,将用于在访问这个 Service 时对端点进行排序。 流量会被定向到第一个标签值和源 Node 标签值相匹配的 Node。 如果这个 Service 没有匹配的后端 Node,那么第二个标签会被使用做匹配,以此类推,直到没有标签。

        如果没有匹配到,流量会被拒绝,就如同这个 Service 根本没有后端。 换言之,系统根据可用后端的第一个拓扑键来选择端点。 如果这个字段被配置了而没有后端可以匹配客户端拓扑,那么这个 Service 对那个客户端是没有后端的,链接应该是失败的。 这个字段配置为 "*" 意味着任意拓扑。 这个通配符值如果使用了,那么只有作为配置值列表中的最后一个才有用。

        如果 topologyKeys 没有指定或者为空,就没有启用这个拓扑约束。

        一个集群中,其 Node 的标签被打为其主机名、区域名和地区名。 那么就可以设置Service的topologyKeys的值,像下面的做法一样定向流量了。

  • 只定向到同一个 Node 上的端点,Node 上没有端点存在时就失败: 配置 "kubernetes.io/hostname"。
  • 偏向定向到同一个 Node 上的端点,回退同一区域的端点上,然后是同一地区, 其它情况下就失败:配置 "kubernetes.io/hostname", "topology.kubernetes.io/zone", "topology.kubernetes.io/region"。 这或许很有用,例如,数据局部性很重要的情况下。
  • 偏向于同一区域,但如果此区域中没有可用的终结点,则回退到任何可用的终结点: 配置 "topology.kubernetes.io/zone", "*"。

1.6 约束条件

  • 服务拓扑和 externalTrafficPolicy=Local 是不兼容的,所以 Service 不能同时使用这两种特性。 但是在同一个集群的不同 Service 上是可以分别使用这两种特性的,只要不在同一个 Service 上就可以。
  • 有效的拓扑键目前只有:kubernetes.io/hostname、topology.kubernetes.io/zone和topology.kubernetes.io/region,但是未来会推广到其它的 Node 标签。
  • 拓扑键必须是有效的标签,并且最多指定16个。
  • 通配符:"*",如果要用,则必须是拓扑键值的最后一个值。

2 实操

2.1 配置示例

        以下是使用服务拓扑功能的常见示例。

2.1.1 仅节点本地端点

        仅路由到节点本地端点的一种服务。如果节点上不存在端点,流量则被丢弃:

代码语言:javascript复制
apiVersion: v1 
kind: Service 
metadata: 
  name: my-service 
spec: 
  selector: 
    app: my-app 
    ports: 
      - protocol: TCP 
        port: 80 
        targetPort: 9376 
    topologyKeys: 
      - "kubernetes.io/hostname"

2.1.2 首选节点本地端点

        首选节点本地端点,如果节点本地端点不存在,则回退到集群范围端点的一种服务:

代码语言:javascript复制
apiVersion: v1 
kind: Service 
metadata: 
  name: my-service 
spec: 
  selector: 
    app: my-app 
  ports: 
    - protocol: TCP 
      port: 80 
      targetPort: 9376 
  topologyKeys: 
    - "kubernetes.io/hostname" 
    - "*"

2.1.3 仅地域或区域端点

        首选地域端点而不是区域端点的一种服务。 如果以上两种范围内均不存在端点, 流量则被丢弃。

代码语言:javascript复制
apiVersion: v1 
kind: Service 
metadata: 
  name: my-service 
spec: 
  selector: 
    app: my-app 
  ports: 
    - protocol: TCP 
      port: 80 
      targetPort: 9376 
  topologyKeys: 
    - "topology.kubernetes.io/zone" 
    - "topology.kubernetes.io/region"

2.1.4 优先选择节点本地端点、地域端点,然后是区域端点

        优先选择节点本地端点,地域端点,然后是区域端点,最后才是集群范围端点的 一种服务。

代码语言:javascript复制
apiVersion: v1 
kind: Service 
metadata: 
  name: my-service 
spec: 
  selector: 
    app: my-app 
  ports: 
    - protocol: TCP 
      port: 80 
      targetPort: 9376 
  topologyKeys: 
    - "kubernetes.io/hostname" 
    - "topology.kubernetes.io/zone" 
    - "topology.kubernetes.io/region" 
    - "*"

2.2 使用实例

        我们创建两个deploment,一个带topologykey的Service,然后来观察创建的ipvs规则是否是按照我们约定的拓扑结构来创建的。

2.2.1 创建deployment

beijing_deploy.yaml

代码语言:javascript复制
apiVersion: apps/v1 
kind: Deployment 
metadata: 
  labels: 
    app: ud-test 
    apps.openyurt.io/pool-name: beijing 
  name: ud-test-beijing-btwbn 
  namespace: default 
spec: 
  progressDeadlineSeconds: 600 
  replicas: 1 
  revisionHistoryLimit: 5 
  selector: 
    matchLabels: 
      app: ud-test 
      apps.openyurt.io/pool-name: beijing 
  strategy: 
    rollingUpdate: 
      maxSurge: 25% 
      maxUnavailable: 25% 
    type: RollingUpdate 
  template: 
    metadata: 
      creationTimestamp: null 
      labels: 
        app: ud-test 
        apps.openyurt.io/pool-name: beijing 
    spec: 
      affinity: 
        nodeAffinity: 
          requiredDuringSchedulingIgnoredDuringExecution: 
            nodeSelectorTerms: 
              - matchExpressions: 
                - key: apps.openyurt.io/nodepool 
                  operator: In 
                  values: 
                    - beijing 
      containers: 
        - image: registry.cn-hangzhou.aliyuncs.com/dice-third-party/nginx:1.14.0 
          imagePullPolicy: Always 
          name: nginx 
          resources: {} 
          terminationMessagePath: /dev/termination-log 
          terminationMessagePolicy: File 
      dnsPolicy: ClusterFirst 
      restartPolicy: Always 
      schedulerName: default-scheduler 
      securityContext: {} 
      terminationGracePeriodSeconds: 30

hangzhou_deploy.yaml

代码语言:javascript复制
apiVersion: apps/v1 
kind: Deployment 
metadata: 
  labels: 
    app: ud-test 
    apps.openyurt.io/pool-name: hangzhou 
  name: ud-test-hangzhou-btwbn 
  namespace: default 
spec: 
  progressDeadlineSeconds: 600 
  replicas: 1 
  revisionHistoryLimit: 5 
selector: 
  matchLabels: 
    app: ud-test 
    apps.openyurt.io/pool-name: hangzhou 
  strategy: 
    rollingUpdate: 
      maxSurge: 25% 
      maxUnavailable: 25% 
    type: RollingUpdate 
  template: 
    metadata: 
      creationTimestamp: null 
      labels: 
        app: ud-test 
        apps.openyurt.io/pool-name: hangzhou 
    spec: 
      affinity: 
        nodeAffinity: 
          requiredDuringSchedulingIgnoredDuringExecution: 
            nodeSelectorTerms: 
              - matchExpressions: 
                - key: apps.openyurt.io/nodepool 
                  operator: In 
                  values: 
                    - hangzhou 
      containers: 
        - image: registry.cn-hangzhou.aliyuncs.com/dice-third-party/nginx:1.14.0 
          imagePullPolicy: Always 
          name: nginx 
          resources: {} 
          terminationMessagePath: /dev/termination-log 
          terminationMessagePolicy: File 
      dnsPolicy: ClusterFirst 
      restartPolicy: Always 
      schedulerName: default-scheduler 
      securityContext: {} 
      terminationGracePeriodSeconds: 30

2.2.2 创建service

        增加了topoloKeys的字段,用于给kube-proxy做拓扑感知使用,kube-proxy会根据自身的该标签来过滤endpointslice中的endpoin信息设置自己的负载均衡策略,所以相应的节点上也需要打上http://topology.kubernetes.io/zone的标签。

代码语言:javascript复制
apiVersion: v1 
kind: Service 
metadata: 
  name: ud-test 
  labels: 
    app: ud-test 
spec: 
  ports: 
    - name: ud-test 
      port: 80 
      targetPort: 80 
  selector: 
    app: ud-test 
  topologyKeys: 
    - "topology.kubernetes.io/zone" 
    # - "*"

2.2.3 观察效果

        创建完成后,我们们可以看到一个service资源,对应两个pod资源,同时会生成一个EndpointSlice资源。

        然后我们登陆到有http://topology.kubernetes.io/zone节点上的去查看对应的ipvs规则。

        可以看到ipvs规则对应的后端pod的ip只有对应节点的标签过滤出来的endpoint信息。

         如果节点没有打http://topology.kubernetes.io/zone呢,那就不会有任何后端信息。这也不合理,所有k8s给了一个*选项,如果没有匹配到标签,则后端是所有的endpoint集合。

参考链接

开启服务拓扑 | Kubernetes

使用拓扑键实现拓扑感知的流量路由 | Kubernetes

我参与的k8s v1.17 新特性: 拓扑感知服务路由

详解K8s资源拓扑感知调度、资源优化策略最佳实践 - 腾讯云开发者社区-腾讯云

如何获取k8s拓扑_k8s从安装到精通--Service 拓扑介绍_weixin_39525243的博客-CSDN博客

Kubernetes 资源拓扑感知调度优化

Kubernetes Service 开启拓扑感知(就近访问)能力_shida_csdn的博客-CSDN博客_k8s 拓扑感知

   《重识云原生系列》专题索引:

  1. 第一章——不谋全局不足以谋一域
  2. 第二章计算第1节——计算虚拟化技术总述
  3. 第二章计算第2节——主流虚拟化技术之VMare ESXi
  4. 第二章计算第3节——主流虚拟化技术之Xen
  5. 第二章计算第4节——主流虚拟化技术之KVM
  6. 第二章计算第5节——商用云主机方案
  7. 第二章计算第6节——裸金属方案
  8. 第三章云存储第1节——分布式云存储总述
  9. 第三章云存储第2节——SPDK方案综述
  10. 第三章云存储第3节——Ceph统一存储方案
  11. 第三章云存储第4节——OpenStack Swift 对象存储方案
  12. 第三章云存储第5节——商用分布式云存储方案
  13. 第四章云网络第一节——云网络技术发展简述
  14. 第四章云网络4.2节——相关基础知识准备
  15. 第四章云网络4.3节——重要网络协议
  16. 第四章云网络4.3.1节——路由技术简述
  17. 第四章云网络4.3.2节——VLAN技术
  18. 第四章云网络4.3.3节——RIP协议
  19. 第四章云网络4.3.4.1-2节——OSPF协议综述
  20. 第四章云网络4.3.4.3节——OSPF协议工作原理
  21. 第四章云网络4.3.4.4节——[转载]OSPF域内路由
  22. 第四章云网络4.3.4.5节——[转载]OSPF外部路由
  23. 第四章云网络4.3.4.6节——[转载]OSPF特殊区域之Stub和Totally Stub区域详解及配置
  24. 第四章云网络4.3.4.7节——OSPF特殊区域之NSSA和Totally NSSA详解及配置
  25. 第四章云网络4.3.5节——EIGRP协议
  26. 第四章云网络4.3.6节——IS-IS协议
  27. 第四章云网络4.3.7节——BGP协议
  28. 第四章云网络4.3.7.2节——BGP协议概述
  29. 第四章云网络4.3.7.3节——BGP协议实现原理
  30. 第四章云网络4.3.7.4节——高级特性
  31. 第四章云网络4.3.7.5节——实操
  32. 第四章云网络4.3.7.6节——MP-BGP协议
  33. 第四章云网络4.3.8节——策略路由
  34. 第四章云网络4.3.9节——Graceful Restart(平滑重启)技术
  35. 第四章云网络4.3.10节——VXLAN技术
  36. 第四章云网络4.3.10.2节——VXLAN Overlay网络方案设计
  37. 第四章云网络4.3.10.3节——VXLAN隧道机制
  38. 第四章云网络4.3.10.4节——VXLAN报文转发过程
  39. 第四章云网络4.3.10.5节——VXlan组网架构
  40. 第四章云网络4.3.10.6节——VXLAN应用部署方案
  41. 第四章云网络4.4节——Spine-Leaf网络架构
  42. 第四章云网络4.5节——大二层网络
  43. 第四章云网络4.6节——Underlay 和 Overlay概念
  44. 第四章云网络4.7.1节——网络虚拟化与卸载加速技术的演进简述
  45. 第四章云网络4.7.2节——virtio网络半虚拟化简介
  46. 第四章云网络4.7.3节——Vhost-net方案
  47. 第四章云网络4.7.4节vhost-user方案——virtio的DPDK卸载方案
  48. 第四章云网络4.7.5节vDPA方案——virtio的半硬件虚拟化实现
  49. 第四章云网络4.7.6节——virtio-blk存储虚拟化方案
  50. 第四章云网络4.7.8节——SR-IOV方案
  51. 第四章云网络4.7.9节——NFV
  52. 第四章云网络4.8.1节——SDN总述
  53. 第四章云网络4.8.2.1节——OpenFlow概述
  54. 第四章云网络4.8.2.2节——OpenFlow协议详解
  55. 第四章云网络4.8.2.3节——OpenFlow运行机制
  56. 第四章云网络4.8.3.1节——Open vSwitch简介
  57. 第四章云网络4.8.3.2节——Open vSwitch工作原理详解
  58. 第四章云网络4.8.4节——OpenStack与SDN的集成
  59. 第四章云网络4.8.5节——OpenDayLight
  60. 第四章云网络4.8.6节——Dragonflow
  61. 第四章云网络4.9.1节——网络卸载加速技术综述
  62. 第四章云网络4.9.2节——传统网络卸载技术
  63. 第四章云网络4.9.3.1节——DPDK技术综述
  64. 第四章云网络4.9.3.2节——DPDK原理详解
  65. 第四章云网络4.9.4.1节——智能网卡SmartNIC方案综述
  66. 第四章云网络4.9.4.2节——智能网卡实现
  67. 第六章容器6.1.1节——容器综述
  68. 第六章容器6.1.2节——容器安装部署
  69. 第六章容器6.1.3节——Docker常用命令
  70. 第六章容器6.1.4节——Docker核心技术LXC
  71. 第六章容器6.1.5节——Docker核心技术Namespace
  72. 第六章容器6.1.6节—— Docker核心技术Chroot
  73. 第六章容器6.1.7.1节——Docker核心技术cgroups综述
  74. 第六章容器6.1.7.2节——cgroups原理剖析
  75. 第六章容器6.1.7.3节——cgroups数据结构剖析
  76. 第六章容器6.1.7.4节——cgroups使用
  77. 第六章容器6.1.8节——Docker核心技术UnionFS
  78. 第六章容器6.1.9节——Docker镜像技术剖析
  79. 第六章容器6.1.10节——DockerFile解析
  80. 第六章容器6.1.11节——docker-compose容器编排
  81. 第六章容器6.1.12节——Docker网络模型设计
  82. 第六章容器6.2.1节——Kubernetes概述
  83. 第六章容器6.2.2节——K8S架构剖析
  84. 第六章容器6.3.1节——K8S核心组件总述
  85. 第六章容器6.3.2节——API Server组件
  86. 第六章容器6.3.3节——Kube-Scheduler使用篇
  87. 第六章容器6.3.4节——etcd组件
  88. 第六章容器6.3.5节——Controller Manager概述
  89. 第六章容器6.3.6节——kubelet组件
  90. 第六章容器6.3.7节——命令行工具kubectl
  91. 第六章容器6.3.8节——kube-proxy
  92. 第六章容器6.4.1节——K8S资源对象总览
  93. 第六章容器6.4.2.1节——pod详解
  94. 第六章容器6.4.2.2节——Pod使用(上)
  95. 第六章容器6.4.2.3节——Pod使用(下)
  96. 第六章容器6.4.3节——ReplicationController
  97. 第六章容器6.4.4节——ReplicaSet组件
  98. 第六章容器基础6.4.5.1节——Deployment概述
  99. 第六章容器基础6.4.5.2节——Deployment配置详细说明
  100. 第六章容器基础6.4.5.3节——Deployment实现原理解析
  101. 第六章容器基础6.4.6节——Daemonset
  102. 第六章容器基础6.4.7节——Job
  103. 第六章容器基础6.4.8节——CronJob
  104. 第六章容器基础6.4.9.1节——Service综述
  105. 第六章容器基础6.4.9.2节——使用 Service 连接到应用
  106. 第六章容器基础6.4.9.3节——Service拓扑感知
  107. 第六章容器基础6.4.10.1节——StatefulSet概述
  108. 第六章容器基础6.4.10.2节——StatefulSet常规操作实操
  109. 第六章容器基础6.4.10.3节——StatefulSet实操案例-部署WordPress 和 MySQL
  110. 第六章容器基础6.4.10.4节——StatefulSet实操案例-使用 StatefulSet 部署Cassandra
  111. 第六章容器基础6.4.10.5节——Statefulset原理剖析
  112. 第六章容器基础6.4.11.1节——Ingress综述

0 人点赞