利用拓扑感知路由控制Kubernetes中的流量

2024/11/5 21:03:24

本文主要是介绍利用拓扑感知路由控制Kubernetes中的流量,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

你需要了解的简单却强大的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-aroc-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-1k3d-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   运行中
创建一个具有拓扑感知的服务

启用拓扑感知路由非常简单:只需将 Serviceservice.kubernetes.io/topology-mode 注解设置为 AutoEndpointSlice 控制器将尝试为 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 的 ServiceEvents,并确认拓扑感知路由功能已开启:

    $ 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工作负载的日志,确认拓扑感知路由是否按预期工作。

我们这里需要画个拓扑图。

首先列出如下这几个工作负载 DeploymentPods。我有一个 Podroc-a 区域的节点上运行(curl-5fcbf7c896-fxx56),另外有两个 Podroc-b 区域的节点上运行(curl-5fcbf7c896-22qbmcurl-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中进行基本的流量控制。虽然它并不适用于所有工作负载,但在具有均匀分布区域流量的环境中,它会更加有用。了解如何利用像拓扑感知路由这样的功能来管理流量,可以帮助您降低成本并提高应用程序性能。

本文中,你将有机会了解、配置并验证基于示例工作负载的拓扑感知路由。在确定实施策略时,你应该用自己环境中的真实流量模式测试此功能。希望你能找到使用拓扑感知路由的方法来提高应用程序的成本效益、性能和可用性。



这篇关于利用拓扑感知路由控制Kubernetes中的流量的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程