你需要了解的简单却强大的Kubernetes流量控制功能简介。
Kubernetes网络是我最喜爱的话题之一,同时也是使用和管理Kubernetes集群中最复杂的部分之一。服务的网络提供了强大的功能,可以轻松部署高可用和分布式的应用,但理解这些高级功能所依赖的网络基础也是非常重要的。特别是在云环境中,流量路径对成本会产生重大影响。
拓扑感知路由 是一个功能,旨在将服务流量保持在同一区域内。例如,一个应用程序的 Pod
可能会通过其 Service
的 Cluster IP 地址连接到一个只读数据库 Pod
。拓扑感知路由会尝试将此数据库流量发送到同一区域内的 Pods
。由于 Cluster IP 地址由多个端点提供支持,kube-proxy 可以尝试仅将流量发送到同一区域内的端点。
有很多理由让流量保持在区域内,这取决于你的架构。如果你使用云服务,那么一个区域可能代表一个可用区。许多云服务商会对跨越可用区边界的流量收取费用。这样可以减少费用。如果你在本地部署 Kubernetes,你可能会用机架边界来表示区域以进行性能考虑。让流量在同一机架内流动可以为客户端流量提供更优质的服务。
拓扑感知路由 并不总是适用,特别是在流量分布不均衡地跨越区域边界时。官方文档解释了在实现拓扑感知路由时应关注的重要保护措施和限制。
在这篇文章里,我会带您一步步分析一个使用拓扑感知路由功能的示例工作负载。您将看到如何配置、使用和验证 Kubernetes 网络堆栈中的这个有用的功能。
配置环境你可以使用运行任何较新版本(≥ 1.27)的 Kubernetes 的任何 Kubernetes 集群。在本文中,我使用 k3d 快速搭建了一个 K3s 集群。你至少需要三个非控制平面节点。这三节点被分成两个区域。
拓扑结构图 — k3d 集群架构
如果你使用 k3d 创建集群,注意截至2023年11月,你需要手动指定 image,以确保拓扑感知路由能正常工作,需要使用更新版本的 K3s 容器。
执行以下命令来创建一个包含6个代理的k3d集群:
$ k3d cluster create --agents 6 --image=docker.io/rancher/k3s:v1.28.3-rc3-k3s2
一旦集群运行起来,使用 topology.kubernetes.io/zone
标签将工作节点分成两个区。我来自罗切斯特,所以我通常会定义两个区:roc-a
和 roc-b
。然而,一个区可以根据您的环境来定义,例如地理位置、建筑或者甚至数据中心的机架。请根据您的具体情况进行适当的命名。如果您使用的是云提供商的 Kubernetes 发行版,这个区域标签可能已经为您自动设置了。
给节点k3d-k3s-default-agent-0打上标签,标记其拓扑区域为roc-a。
然后给k3d-k3s-default-agent-1和k3d-k3s-default-agent-2也打上同样的标签。
接下来,我们把k3d-k3s-default-agent-3, k3d-k3s-default-agent-4和k3d-k3s-default-agent-5的区域标签设为roc-b。
这些命令的目的是将不同节点标记到不同的区域中。
部署一个示例任务有一个工作负载会很有帮助,来理解拓扑感知路由的效果。这个工作负载应该让你能够轻松观察到流量保持在特定区域内。Nginx 提供了一个简单的方法来做到这一点:你可以在 Nginx 响应头中返回 Pod
主机名。
创建一个配置映射(ConfigMap),包含必要的 Nginx 配置信息。此配置为每个 HTTP 响应设置一个 X-Server-Hostname
响应头,该头包含 Pod
的主机名。虽然你不会在实际生产环境中使用这个配置,但它非常适合这样的练习:它允许你将 HTTP 响应与特定的 Nginx Pod
相关联起来。
---
apiVersion: v1
kind: ConfigMap
元数据:
名称: nginx.conf
数据:
默认.conf: |
服务器 {
监听 80;
监听 [::]:80;
服务器名称 _;
添加头部 X-Server-主机名 $hostname;
位置 / {
根目录 /usr/share/nginx/html;
索引 index.html index.htm;
}
错误页面 500 502 503 504 /50x.html;
位置 = /50x.html {
根目录 /usr/share/nginx/html;
}
}
接下来,创建一个 Nginx 的 Deployment
,使其在拓扑结构中均匀分布到各个区域。为此,指定 6 个副本,以匹配集群中工作节点的数量,并将 topologySpreadConstraint
设置为使用 topology.kubernetes.io/zone
作为 topologyKey
。这将使 Pods
尽可能均匀地分布在每个区域。
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
# `spec` 部分定义了部署的副本数量和其他一些约束。
replicas: 6
selector:
matchLabels:
app: nginx
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
# `topologySpreadConstraints` 用于控制 Pod 在不同拓扑区域的分布。
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
# 当条件无法满足时,不允许调度。
labelSelector:
matchLabels:
app: nginx
containers:
- image: docker.io/nginx:latest
name: nginx
ports:
- containerPort: 80
# `containerPort` 表示容器的端口。
volumeMounts:
- name: config
mountPath: "/etc/nginx/conf.d"
readOnly: true
volumes:
- name: config
configMap:
name: nginx.conf
# `items` 表示配置映射中的项。
items:
- key: default.conf
path: default.conf
注意:在非技术读者阅读时,上述术语可能需要进一步解释。
确认 Pods
正在运行,并且均匀分布在各个区域(zone)。在我的环境中,可以看到在 roc-a
区域的 k3d-k3s-default-agent-1
和 k3d-k3s-default-agent-2
节点上有 3 个 Pods
在运行,其余的 Pods
则在 roc-b
区域的节点上:
使用kubectl获取pod信息
$ kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,STATUS:.status.phase --sort-by .spec.nodeName
NAME 节点 状态
nginx-5455fc8796-vjq78 k3d-k3s-default-agent-1 运行中
nginx-5455fc8796-wtrqv k3d-k3s-default-agent-1 运行中
nginx-5455fc8796-4qgrp k3d-k3s-default-agent-2 运行中
nginx-5455fc8796-sv9cg k3d-k3s-default-agent-3 运行中
nginx-5455fc8796-wx4dk k3d-k3s-default-agent-4 运行中
nginx-5455fc8796-rtdwt k3d-k3s-default-agent-5 运行中
创建一个具有拓扑感知的服务
启用拓扑感知路由非常简单:只需将 Service
的 service.kubernetes.io/topology-mode
注解设置为 Auto
。EndpointSlice
控制器将尝试为 EndpointSlice
中的每个端点分配提示信息,以将流量引导到给定区域内的各个 Pods
。正如前面提到的,了解 EndpointSlice
控制器和 kube-proxy 实现的安全措施和限制很重要,尤其是当路由不符合预期时。
创建一个新的 Service
来实现基于拓扑的路由功能。
---
apiVersion: v1
kind: Service
metadata:
annotations:
service.kubernetes.io/topology-mode: Auto
name: nginx
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: 集群IP
一旦创建了Service
,你应该确认每个EndpointSlice
中的提示已显示在每个端点地址上。这些提示被kube-proxy使用,并用于决定如何路由流量。在这种情况下,当kube-proxy看到发往NginxService
的流量时,它会尽量将流量留在同一区域内的。
首先,确保检查 Nginx 的 Service
的 Events
,并确认拓扑感知路由功能已开启:
$ kubectl events --for service/nginx
最后看到 类型 原因 对象 消息
28s 正常 拓扑感知提示已启用 Service/nginx 已启用拓扑感知提示,地址类型为:IPv4
$
请检查与 Service
相关的 EndpointSlice
,并确认每个端点的 hints.forZones
值是否正确地指定了区域名,确保其指向正确的区域。
# 列出端点切片列表
$ kubectl get endpointslice (命令)
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
kubernetes IPv4 6443 10.89.1.90 24m
nginx-vq9k2 IPv4 80 10.42.1.3,10.42.4.4,10.42.1.4 + 3 more... 73s
# 检查 Nginx 的端点切片,这里只展示一个端点作为示例。
$ kubectl get endpointslice nginx-vq9k2 -o yaml (输出)
地址类型为 IPv4
apiVersion: discovery.k8s.io/v1
端点如下:
- 地址:
- 10.42.1.3
条件:
ready: true
serving: true
terminating: false
提示:
forZones:
- name: roc-a
节点名称为:k3d-k3s-default-agent-1
目标引用详情如下:
kind: Pod
name: nginx-5455fc8796-wtrqv
namespace: default
唯一标识符为:efd865ed-ebb3-4f13-b39e-2dafeed6fc36
控制平面在拓扑感知路由决策上非常谨慎。如果遇到问题,例如 Service
事件中的错误信息,或者看到 EndpointSlice
中没有填充提示,那么你应该检查你的拓扑标签是否已经正确应用,并且确认 Nodes
是否报告了可分配的 CPU 资源。
现在一切就绪,拓扑感知路由可以开始工作了。你可以通过一个简单的测试工作负载来确认流量是否按预期流动。一个简单的方法是使用一个不断查询Nginx Service
并只保留 X-Server-Hostname
头信息的 curl
容器。
下面的 Deployment
定义创建了一个工作负载,该工作负载每秒使用 curl
连接到 Nginx,并通过 grep
过滤 X-Server-Hostname
响应头。这个循环每秒执行一次,以模拟对服务的持续工作负载。之后,您可以检查日志以确认流量是否仍然在同一个区域中。
你也希望确保这份工作负载在两个区域间均匀分布。你可以再次使用 topologySpreadConstraints
来确保每个副本放置在不同的区域中。设置副本数量为 3,这样可以确保每个区域中至少有一个 Pod
,因为 topologySpreadConstraint
最多容忍一个 Pod
的差异。
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: curl
spec:
replicas: 3
selector:
matchLabels:
app: curl
template:
metadata:
creationTimestamp: null
labels:
app: curl
spec:
topologySpreadConstraints:
# 拓扑扩散约束,用于控制部署在集群中的Pod分布
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
# 当不满足拓扑扩散约束时,不允许调度Pod
labelSelector:
matchLabels:
app: curl
# 匹配标签,用于选择具有特定标签的应用程序
containers:
- name: curl
image: docker.io/curlimages/curl:latest
args:
- sh
- -c
- 'while true; do curl -s -v nginx 2>&1 | grep X-Server-Hostname; sleep 1; done;'
# 这是一个循环执行curl命令并抓取nginx服务器主机名的shell脚本。
看看结果
你现在有一个Nginx工作负载,以及一个与之对应的使用拓扑感知路由的Service
。你还在每个区域部署了一个简单的客户端。你可以查看curl
工作负载的日志,确认拓扑感知路由是否按预期工作。
我们这里需要画个拓扑图。
首先列出如下这几个工作负载 Deployment
的 Pods
。我有一个 Pod
在 roc-a
区域的节点上运行(curl-5fcbf7c896-fxx56
),另外有两个 Pod
在 roc-b
区域的节点上运行(curl-5fcbf7c896-22qbm
和 curl-5fcbf7c896-7s5z7
)
$ kubectl get pods -l app=curl -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,STATUS:.status.phase --sort-by .spec.nodeName
# 运行以下命令以获取名为 'curl' 的 pod 的信息:
NAME NODE STATUS
curl-5fcbf7c896-fxx56 k3d-k3s-default-agent-0 Running # 运行状态
curl-5fcbf7c896-22qbm k3d-k3s-default-agent-3 Running # 运行状态
curl-5fcbf7c896-7s5z7 k3d-k3s-default-agent-4 Running # 运行状态
# 注:名称、节点和状态分别是 pod 的名称、运行节点名和当前状态。这些名称和节点名是占位符,在实际环境中可能有所不同。
名称 节点 状态
首先,查看 us-east-1a
可用区中的某个 Pod
的日志:
$ kubectl logs curl-5fcbf7c896-fxx56 | head # 使用 kubectl 查看 curl-5fcbf7c896-fxx56 的日志,并显示前几行
< X-Server-Hostname: nginx-5455fc8796-vjq78 # 服务器主机名是 nginx-5455fc8796-vjq78
< X-Server-Hostname: nginx-5455fc8796-4qgrp # 服务器主机名是 nginx-5455fc8796-4qgrp
< X-Server-Hostname: nginx-5455fc8796-wtrqv # 服务器主机名是 nginx-5455fc8796-wtrqv
Note: Removed redundant lines to enhance fluency and provided explanations for clarity.
你应该有许多日志行记录,具体数量取决于你的工作负载运行的时间长短。使用一个简单的 bash 一行命令来过滤这些日志,仅显示唯一 Pod
主机名的个数。
$ kubectl logs curl-5fcbf7c896-fxx56 | cut -f 2 -d ':' | sort | uniq -c # 查看curl-5fcbf7c896-fxx56的日志并处理数据
2296 nginx-5455fc8796-4qgrp
2313 nginx-5455fc8796-vjq78
2326 nginx-5455fc8796-wtrqv # 以下是每个nginx容器的调用次数统计
尽管在Deployment
中有6个可用pod,所有的流量都被发送到了3个Nginx pod。确认这些pod都在us-east-1a
可用区的Nodes
上。
$ kubectl get pods nginx-5455fc8796-4qgrp nginx-5455fc8796-vjq78 nginx-5455fc8796-wtrqv -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName (显示指定pod的名称和节点)
NAME NODE
nginx-5455fc8796-4qgrp k3d-k3s-default-agent-2
nginx-5455fc8796-vjq78 k3d-k3s-default-agent-1
nginx-5455fc8796-wtrqv k3d-k3s-default-agent-1
看看 us-east-1b
可用区中的 Pod
日志:
$ kubectl logs curl-5fcbf7c896-22qbm | cut -f 2 -d ':' | sort | uniq -c
2324 nginx-5455fc8796-rtdwt
2369 nginx-5455fc8796-sv9cg
2242 nginx-5455fc8796-wx4dk
请注意,所有流量只发送给3个Nginx Pods
(注:Pods
是容器编排中的术语)。确认这3个Pods
是否都在us-east-1b
区域。
$ kubectl get pods nginx-5455fc8796-rtdwt nginx-5455fc8796-sv9cg nginx-5455fc8796-wx4dk -o custom-columns=名称:.metadata.name,节点:.spec.nodeName
名称 节点
nginx-5455fc8796-rtdwt k3d-k3s-default-agent-5
nginx-5455fc8796-sv9cg k3d-k3s-default-agent-3
nginx-5455fc8796-wx4dk k3d-k3s-default-agent-4:
根据这次实验,拓扑感知路由正在正确运作:客户端到 Nginx Service
的流量只会被发送到与客户端位于同一区域的 Pods
。比如在 AWS 中,跨可用区的流量会产生费用,那么你就可以省下这些费用。如果你将你的本地环境按机架划分成不同的区域,那么你可能会从这种配置中获得性能上的好处。
拓扑感知路由是一种简单却有效的流量管理方式,可以在Kubernetes中进行基本的流量控制。虽然它并不适用于所有工作负载,但在具有均匀分布区域流量的环境中,它会更加有用。了解如何利用像拓扑感知路由这样的功能来管理流量,可以帮助您降低成本并提高应用程序性能。
本文中,你将有机会了解、配置并验证基于示例工作负载的拓扑感知路由。在确定实施策略时,你应该用自己环境中的真实流量模式测试此功能。希望你能找到使用拓扑感知路由的方法来提高应用程序的成本效益、性能和可用性。