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节——主流虚拟化技术之VMare ESXi
- 第二章计算第3节——主流虚拟化技术之Xen
- 第二章计算第4节——主流虚拟化技术之KVM
- 第二章计算第5节——商用云主机方案
- 第二章计算第6节——裸金属方案
- 第三章云存储第1节——分布式云存储总述
- 第三章云存储第2节——SPDK方案综述
- 第三章云存储第3节——Ceph统一存储方案
- 第三章云存储第4节——OpenStack Swift 对象存储方案
- 第三章云存储第5节——商用分布式云存储方案
- 第四章云网络第一节——云网络技术发展简述
- 第四章云网络4.2节——相关基础知识准备
- 第四章云网络4.3节——重要网络协议
- 第四章云网络4.3.1节——路由技术简述
- 第四章云网络4.3.2节——VLAN技术
- 第四章云网络4.3.3节——RIP协议
- 第四章云网络4.3.4.1-2节——OSPF协议综述
- 第四章云网络4.3.4.3节——OSPF协议工作原理
- 第四章云网络4.3.4.4节——[转载]OSPF域内路由
- 第四章云网络4.3.4.5节——[转载]OSPF外部路由
- 第四章云网络4.3.4.6节——[转载]OSPF特殊区域之Stub和Totally Stub区域详解及配置
- 第四章云网络4.3.4.7节——OSPF特殊区域之NSSA和Totally NSSA详解及配置
- 第四章云网络4.3.5节——EIGRP协议
- 第四章云网络4.3.6节——IS-IS协议
- 第四章云网络4.3.7节——BGP协议
- 第四章云网络4.3.7.2节——BGP协议概述
- 第四章云网络4.3.7.3节——BGP协议实现原理
- 第四章云网络4.3.7.4节——高级特性
- 第四章云网络4.3.7.5节——实操
- 第四章云网络4.3.7.6节——MP-BGP协议
- 第四章云网络4.3.8节——策略路由
- 第四章云网络4.3.9节——Graceful Restart(平滑重启)技术
- 第四章云网络4.3.10节——VXLAN技术
- 第四章云网络4.3.10.2节——VXLAN Overlay网络方案设计
- 第四章云网络4.3.10.3节——VXLAN隧道机制
- 第四章云网络4.3.10.4节——VXLAN报文转发过程
- 第四章云网络4.3.10.5节——VXlan组网架构
- 第四章云网络4.3.10.6节——VXLAN应用部署方案
- 第四章云网络4.4节——Spine-Leaf网络架构
- 第四章云网络4.5节——大二层网络
- 第四章云网络4.6节——Underlay 和 Overlay概念
- 第四章云网络4.7.1节——网络虚拟化与卸载加速技术的演进简述
- 第四章云网络4.7.2节——virtio网络半虚拟化简介
- 第四章云网络4.7.3节——Vhost-net方案
- 第四章云网络4.7.4节vhost-user方案——virtio的DPDK卸载方案
- 第四章云网络4.7.5节vDPA方案——virtio的半硬件虚拟化实现
- 第四章云网络4.7.6节——virtio-blk存储虚拟化方案
- 第四章云网络4.7.8节——SR-IOV方案
- 第四章云网络4.7.9节——NFV
- 第四章云网络4.8.1节——SDN总述
- 第四章云网络4.8.2.1节——OpenFlow概述
- 第四章云网络4.8.2.2节——OpenFlow协议详解
- 第四章云网络4.8.2.3节——OpenFlow运行机制
- 第四章云网络4.8.3.1节——Open vSwitch简介
- 第四章云网络4.8.3.2节——Open vSwitch工作原理详解
- 第四章云网络4.8.4节——OpenStack与SDN的集成
- 第四章云网络4.8.5节——OpenDayLight
- 第四章云网络4.8.6节——Dragonflow
- 第四章云网络4.9.1节——网络卸载加速技术综述
- 第四章云网络4.9.2节——传统网络卸载技术
- 第四章云网络4.9.3.1节——DPDK技术综述
- 第四章云网络4.9.3.2节——DPDK原理详解
- 第四章云网络4.9.4.1节——智能网卡SmartNIC方案综述
- 第四章云网络4.9.4.2节——智能网卡实现
- 第六章容器6.1.1节——容器综述
- 第六章容器6.1.2节——容器安装部署
- 第六章容器6.1.3节——Docker常用命令
- 第六章容器6.1.4节——Docker核心技术LXC
- 第六章容器6.1.5节——Docker核心技术Namespace
- 第六章容器6.1.6节—— Docker核心技术Chroot
- 第六章容器6.1.7.1节——Docker核心技术cgroups综述
- 第六章容器6.1.7.2节——cgroups原理剖析
- 第六章容器6.1.7.3节——cgroups数据结构剖析
- 第六章容器6.1.7.4节——cgroups使用
- 第六章容器6.1.8节——Docker核心技术UnionFS
- 第六章容器6.1.9节——Docker镜像技术剖析
- 第六章容器6.1.10节——DockerFile解析
- 第六章容器6.1.11节——docker-compose容器编排
- 第六章容器6.1.12节——Docker网络模型设计
- 第六章容器6.2.1节——Kubernetes概述
- 第六章容器6.2.2节——K8S架构剖析
- 第六章容器6.3.1节——K8S核心组件总述
- 第六章容器6.3.2节——API Server组件
- 第六章容器6.3.3节——Kube-Scheduler使用篇
- 第六章容器6.3.4节——etcd组件
- 第六章容器6.3.5节——Controller Manager概述
- 第六章容器6.3.6节——kubelet组件
- 第六章容器6.3.7节——命令行工具kubectl
- 第六章容器6.3.8节——kube-proxy
- 第六章容器6.4.1节——K8S资源对象总览
- 第六章容器6.4.2.1节——pod详解
- 第六章容器6.4.2.2节——Pod使用(上)
- 第六章容器6.4.2.3节——Pod使用(下)
- 第六章容器6.4.3节——ReplicationController
- 第六章容器6.4.4节——ReplicaSet组件
- 第六章容器基础6.4.5.1节——Deployment概述
- 第六章容器基础6.4.5.2节——Deployment配置详细说明
- 第六章容器基础6.4.5.3节——Deployment实现原理解析
- 第六章容器基础6.4.6节——Daemonset
- 第六章容器基础6.4.7节——Job
- 第六章容器基础6.4.8节——CronJob
- 第六章容器基础6.4.9.1节——Service综述
- 第六章容器基础6.4.9.2节——使用 Service 连接到应用
- 第六章容器基础6.4.9.3节——Service拓扑感知
- 第六章容器基础6.4.10.1节——StatefulSet概述
- 第六章容器基础6.4.10.2节——StatefulSet常规操作实操
- 第六章容器基础6.4.10.3节——StatefulSet实操案例-部署WordPress 和 MySQL
- 第六章容器基础6.4.10.4节——StatefulSet实操案例-使用 StatefulSet 部署Cassandra
- 第六章容器基础6.4.10.5节——Statefulset原理剖析
- 第六章容器基础6.4.11.1节——Ingress综述