通过 Ingress 配置容器原生负载均衡


本页面介绍了如何在 Google Kubernetes Engine (GKE) 中使用容器原生负载均衡。利用容器原生负载均衡,负载均衡器能够直接定位 Kubernetes Pod,还能将流量均匀分发给 Pod。

如需详细了解容器原生负载均衡的优势、要求和限制,请参阅容器原生负载均衡

准备工作

在开始之前,请确保您已执行以下任务:

  • 启用 Google Kubernetes Engine API。
  • 启用 Google Kubernetes Engine API
  • 如果您要使用 Google Cloud CLI 执行此任务,请安装初始化 gcloud CLI。 如果您之前安装了 gcloud CLI,请运行 gcloud components update 以获取最新版本。

使用容器原生负载均衡

以下几个部分将引导您完成 GKE 上的容器原生负载均衡配置。

创建 VPC 原生集群

如需使用容器原生负载均衡,GKE 集群必须启用别名 IP

例如,以下命令会创建具有自动预配的子网的 GKE 集群 neg-demo-cluster

  • 对于 Autopilot 模式,别名 IP 地址默认处于启用状态:

    gcloud container clusters create-auto neg-demo-cluster \
        --location=COMPUTE_LOCATION
    

    COMPUTE_LOCATION 替换为集群的 Compute Engine 位置

  • 对于 Standard 模式,在创建集群时启用别名 IP 地址:

    gcloud container clusters create neg-demo-cluster \
        --enable-ip-alias \
        --create-subnetwork="" \
        --network=default \
        --location=us-central1-a
    

创建 Deployment

以下示例 Deployment neg-demo-app 运行容器化 HTTP 服务器的单个实例。我们建议您使用利用 Pod 就绪性反馈的工作负载。

使用 Pod 就绪性反馈

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: neg-demo-app # Label for the Deployment
  name: neg-demo-app # Name of Deployment
spec:
  selector:
    matchLabels:
      run: neg-demo-app
  template: # Pod template
    metadata:
      labels:
        run: neg-demo-app # Labels Pods from this Deployment
    spec: # Pod specification; each Pod created by this Deployment has this specification
      containers:
      - image: registry.k8s.io/serve_hostname:v1.4 # Application to run in Deployment's Pods
        name: hostname # Container name
        ports:
        - containerPort: 9376
          protocol: TCP
  

使用硬编码延迟

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: neg-demo-app # Label for the Deployment
  name: neg-demo-app # Name of Deployment
spec:
  minReadySeconds: 60 # Number of seconds to wait after a Pod is created and its status is Ready
  selector:
    matchLabels:
      run: neg-demo-app
  template: # Pod template
    metadata:
      labels:
        run: neg-demo-app # Labels Pods from this Deployment
    spec: # Pod specification; each Pod created by this Deployment has this specification
      containers:
      - image: registry.k8s.io/serve_hostname:v1.4 # Application to run in Deployment's Pods
        name: hostname # Container name
      # Note: The following line is necessary only on clusters running GKE v1.11 and lower.
      # For details, see https://cloud.google.com/kubernetes-engine/docs/how-to/container-native-load-balancing#align_rollouts
        ports:
        - containerPort: 9376
          protocol: TCP
      terminationGracePeriodSeconds: 60 # Number of seconds to wait for connections to terminate before shutting down Pods
  

在此 Deployment 中,每个容器都运行一个 HTTP 服务器。该 HTTP 服务器返回应用服务器的主机名(运行服务器所在的 Pod 的名称)作为响应。

将此清单保存为 neg-demo-app.yaml,然后创建 Deployment:

kubectl apply -f neg-demo-app.yaml

为容器原生负载均衡器创建 Service

创建 Deployment 后,需要将其 Pod 分组到 Service 中。

以下示例 Service neg-demo-svc 会定位您在上一个部分中创建的示例 Deployment:

apiVersion: v1
kind: Service
metadata:
  name: neg-demo-svc # Name of Service
spec: # Service's specification
  type: ClusterIP
  selector:
    run: neg-demo-app # Selects Pods labelled run: neg-demo-app
  ports:
  - name: http
    port: 80 # Service's port
    protocol: TCP
    targetPort: 9376

在为该 Service 创建 Ingress 之前,不会创建负载均衡器。

将此清单保存为 neg-demo-svc.yaml,然后创建 Service:

kubectl apply -f neg-demo-svc.yaml

为 Service 创建 Ingress

以下示例 Ingress neg-demo-ing 会定位您创建的 Service:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: neg-demo-ing
spec:
  defaultBackend:
    service:
      name: neg-demo-svc # Name of the Service targeted by the Ingress
      port:
        number: 80 # Should match the port used by the Service

将此清单保存为 neg-demo-ing.yaml,然后创建 Ingress:

kubectl apply -f neg-demo-ing.yaml

创建 Ingress 后,系统会在项目中创建应用负载均衡器,并在集群运行的每个可用区中创建网络端点组 (NEG)。NEG 中的端点和 Service 的端点会保持同步。

验证 Ingress

部署了工作负载,将其 Pod 分组到 Service 中,并为 Service 创建了 Ingress 后,您应验证 Ingress 是否已成功预配容器原生负载均衡器。

检索 Ingress 的状态:

kubectl describe ingress neg-demo-ing

输出包括 ADDCREATE 事件:

Events:
Type     Reason   Age                From                     Message
----     ------   ----               ----                     -------
Normal   ADD      16m                loadbalancer-controller  default/neg-demo-ing
Normal   Service  4s                 loadbalancer-controller  default backend set to neg-demo-svc:32524
Normal   CREATE   2s                 loadbalancer-controller  ip: 192.0.2.0

测试负载均衡器

以下几个部分说明如何测试容器原生负载均衡器的功能。

访问 Ingress 的 IP 地址

等待几分钟,让应用负载均衡器完成配置。

您可以通过访问 Ingress 的 IP 地址来验证容器原生负载均衡器是否正常运行。

如需获取 Ingress 的 IP 地址,请运行以下命令:

kubectl get ingress neg-demo-ing

在命令输出中,Ingress 的 IP 地址显示在 ADDRESS 列中。在网络浏览器中访问 IP 地址。

检查后端服务的运行状况

您还可以获取负载均衡器的后端服务的运行状况。

  1. 获取项目中运行的后端服务的列表:

    gcloud compute backend-services list
    

    记录包含 Service 名称(例如 neg-demo-svc)的后端服务的名称。

  2. 获取后端服务的运行状况:

    gcloud compute backend-services get-health BACKEND_SERVICE_NAME --global
    

    BACKEND_SERVICE_NAME 替换为后端服务的名称。

测试 Ingress

您可以采用另一种方法来测试负载均衡器是否按预期运行,也就是扩缩示例 Deployment,将测试请求发送到 Ingress,并验证响应的副本数量是否正确。

  1. neg-demo-app Deployment 从一个实例扩容到两个实例:

    kubectl scale deployment neg-demo-app --replicas 2
    

    此命令可能需要几分钟才能完成。

  2. 验证发布是否已完成:

    kubectl get deployment neg-demo-app
    

    输出内容应包含两个可用的副本:

    NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    neg-demo-app   2         2         2            2           26m
    
  3. 获取 Ingress IP 地址:

    kubectl describe ingress neg-demo-ing
    

    如果此命令返回 404 错误,请等待几分钟,以便负载均衡器启动,然后重试。

  4. 计算来自负载均衡器的不同响应的数量:

    for i in `seq 1 100`; do \
      curl --connect-timeout 1 -s IP_ADDRESS && echo; \
    done  | sort | uniq -c
    

    IP_ADDRESS 替换为 Ingress IP 地址。

    输出类似于以下内容:

    44 neg-demo-app-7f7dfd7bc6-dcn95
    56 neg-demo-app-7f7dfd7bc6-jrmzf
    

    在此输出中,不同响应的数量与副本的数量相同,这表明所有后端 Pod 都在处理流量。

清理

完成本页面上的任务后,请按照以下步骤移除资源,以防止您的账号产生不必要的费用:

删除集群

gcloud

gcloud container clusters delete neg-demo-cluster

控制台

  1. 前往 Google Cloud 控制台中的 Google Kubernetes Engine 页面。

    转到 Google Kubernetes Engine

  2. 选择 neg-demo-cluster,然后点击 删除

  3. 当系统提示您确认时,点击删除

问题排查

请使用以下技巧验证您的网络配置。以下几个部分说明如何解决与容器原生负载均衡相关的具体问题。

  • 如需了解如何列出您的网络端点组,请参阅负载均衡文档

  • 您可以在服务的 neg-status 注释中找到与服务对应的 NEG 的名称和地区。请使用以下命令获取 Service 规范:

    kubectl get svc SVC_NAME -o yaml
    

    metadata:annotations:cloud.google.com/neg-status 注释列出了服务的对应 NEG 的名称和地区。

  • 您可以使用以下命令检查与 NEG 相对应的后端服务的运行状况:

    gcloud compute backend-services --project PROJECT_NAME \
        get-health BACKEND_SERVICE_NAME --global
    

    后端服务的名称与其 NEG 的名称相同。

  • 如需输出服务的事件日志,请运行以下命令:

    kubectl describe svc SERVICE_NAME
    

    服务的名称字符串包含相应 GKE Service 的名称和命名空间。

无法使用别名 IP 地址创建集群

表现

当您尝试使用别名 IP 地址创建集群时,可能会遇到以下错误:

ResponseError: code=400, message=IP aliases cannot be used with a legacy network.
潜在原因

如果您尝试使用别名 IP 创建的集群同时也使用旧版网络,则会遇到此错误。

解决方法

确保未在同时启用别名 IP 和旧版网络的情况下创建集群。如需详细了解如何使用别名 IP,请参阅创建 VPC 原生集群

流量未到达端点

表现
502/503 错误或连接遭拒错误。
潜在原因

将新端点连接到负载均衡器后,只要新端点响应健康检查,通常就可以访问新端点。如果流量无法到达端点,您可能会遇到 502 错误或连接遭拒错误。

502 错误和连接遭拒错误也可能是由不处理 SIGTERM 的容器引起的。如果容器未明确处理 SIGTERM,则会立即终止并停止处理请求。负载均衡器会继续将传入的流量发送到已终止的容器,从而导致错误。

容器原生负载均衡器只有一个后端端点。在滚动更新期间,旧端点会在新端点编程之前进行去编程。

预配容器原生负载均衡器后,后端 pod 首次部署到新可用区中。当可用区中至少有一个端点时,负载均衡器基础架构会在可用区中进行编程。将新端点添加到可用区时,负载均衡器基础架构会进行编程并导致服务中断。

解决方法

将容器配置为处理 SIGTERM 并在整个终止宽限期(默认为 30 秒)内继续响应请求。将 Pod 配置为在收到 SIGTERM 时开始让健康检查失败。这样可以通知负载均衡器在端点正在终止运行的过程中停止向 Pod 发送流量。

如果您的应用在收到 SIGTERM 后没有正常关闭并停止响应请求,则可以使用 preStop 钩子来处理 SIGTERM,并在进行端点去编程时继续处理流量。

lifecycle:
  preStop:
    exec:
      # if SIGTERM triggers a quick exit; keep serving traffic instead
      command: ["sleep","60"]

请参阅有关 Pod 终止的文档

如果您的负载均衡器后端只有一个实例,请配置发布策略,以避免在新实例完全编程之前删除唯一实例。对于 Deployment 工作负载管理的应用 Pod,可以通过配置发布策略并将 maxUnavailable 参数设置为 0 来实现。

strategy:
  rollingUpdate:
    maxSurge: 1
    maxUnavailable: 0

如需排查流量无法到达端点的问题,请验证防火墙规则是否允许传入的 TCP 流量传输到 130.211.0.0/2235.191.0.0/16 范围内的端点。如需了解详情,请参阅 Cloud Load Balancing 文档中的添加健康检查

查看项目中的后端服务。相关后端服务的名称字符串包含相应 GKE Service 的名称和命名空间:

gcloud compute backend-services list

从后端服务检索后端运行状况:

gcloud compute backend-services get-health BACKEND_SERVICE_NAME

如果所有后端的运行状况都不佳,则防火墙、Ingress 或 Service 可能配置错误。

如果某些后端在短时间内运行状况不佳,则可能是网络编程延迟的原因。

如果某些后端未出现在后端服务列表中,则可能是编程延迟的原因。您可以通过运行以下命令来验证这一点,其中 NEG_NAME 是后端服务的名称。 (NEG 和后端服务共用同一个名称):

gcloud compute network-endpoint-groups list-network-endpoints NEG_NAME

检查所有预期的端点是否都在 NEG 中。

如果容器原生负载均衡器选择了少量后端(例如 1 个 Pod),请考虑增加副本数量并将后端 Pod 分布到 GKE 集群跨越的所有可用区中。这会确保底层负载均衡器基础架构经过完全编程。否则,请考虑将后端 Pod 限制在单个可用区。

如果您为端点配置网络政策,请确保允许来自代理专用子网的入站流量。

停止发布

表现
发布更新 Deployment 的操作处于停止状态,并且最新副本的数量与所需的副本数量不匹配。
潜在原因

部署的健康检查失败。容器映像可能已损坏,或者健康检查的配置可能有误。Pod 的滚动替换正在等待新启动的 Pod 通过其 Pod 就绪性门控。仅当 Pod 正在响应负载均衡器健康检查时,才会出现这种情况。如果 Pod 没有响应,或者健康检查的配置有误,则无法满足就绪性门控条件,并且无法继续发布。

如果您使用的是 kubectl 1.13 或更高版本,则可以使用以下命令检查 Pod 的就绪性门控状态:

kubectl get pod POD_NAME -o wide

检查 READINESS GATES 列。

kubectl 1.12 及更低版本中不存在此列。标记为 READY 状态的 Pod 的就绪性门控可能已失败。如需就此进行验证,请使用以下命令:

kubectl get pod POD_NAME -o yaml

输出中会列出就绪性门控及其状态。

解决方法

验证 Deployment 的 Pod 规范中的容器映像是否正常运行,以及是否能够响应健康检查。验证健康检查是否已正确配置。

降级模式错误

表现

从 GKE 1.29.2-gke.1643000 版开始,当 NEG 更新时,您可能会在 Logs Explorer 中收到有关服务的以下警告:

Entering degraded mode for NEG <service-namespace>/<service-name>-<neg-name>... due to sync err: endpoint has missing nodeName field
潜在原因

这些警告表明,GKE 在基于 EndpointSlice 对象的 NEG 更新期间检测到端点错误配置,从而触发了称为降级模式的更深入的计算过程。GKE 会继续尽量更新 NEG,方法是更正错误配置或从 NEG 更新中排除无效端点。

一些常见错误包括:

  • endpoint has missing pod/nodeName field
  • endpoint corresponds to an non-existing pod/node
  • endpoint information for attach/detach operation is incorrect
解决方法

通常,临时状态会导致这些事件,它们会自行解决。不过,由自定义 EndpointSlice 对象中的错误配置导致的事件仍未解决。如需了解错误配置,请检查与服务对应的 EndpointSlice 对象:

kubectl get endpointslice -l kubernetes.io/service-name=<service-name>

根据事件中的错误验证每个端点。

如需解决此问题,您必须手动修改 EndpointSlice 对象。此更新会触发 NEG 再次更新。配置错误不再存在后,输出类似于以下内容:

NEG <service-namespace>/<service-name>-<neg-name>... is no longer in degraded mode

已知问题

GKE 上的容器原生负载均衡存在以下已知问题:

NEG 就绪情况门控竞态条件

在某些情况下,就绪状态门可能会在入站健康检查报告健康状态之前返回“假正例”就绪状态,从而在 Ingress 对象上生成如下错误事件:

NEG is not attached to any BackendService with health checking. Marking condition "cloud.google.com/load-balancer-neg-ready" to True.

表现

此问题会导致负载均衡器在 GKE 对部署工作负载执行滚动更新时,向流量报告 503 错误 (failed_to_pick_backend)。

原因

虽然 GKE NEG 控制器依赖于 Compute Engine NEG 健康检查信息来报告端点是否健康,但请考虑以下事件序列:

  1. 系统会创建一个新的 Pod 来进行滚动更新。
  2. GKE NEG 控制器会将此新的 Pod IP 地址添加到网络端点组。
  3. GKE NEG 控制器请求新添加的端点的健康状况。
  4. Compute Engine NEG 服务尚无健康状况信息,并返回空状态。
  5. GKE NEG 控制器会假定空状态表示未配置健康检查,并将 Pod 标记为“就绪”。
  6. GKE 会移除旧 Pod,因为系统认为新 Pod 已准备好处理流量。
  7. 如果新 Pod 是负载均衡器剩下的唯一后端,则负载均衡器会返回 503 Service Unavailable 响应。
  8. 当 Pod 开始通过健康检查后,负载均衡器将开始按预期返回 200 OK 响应。

GKE NEG 控制器无法区分两种不同的健康检查状态:missing-health-check-because-not-attached-to-backend 和 missing-health-check-because-health-check-is-not-yet-programmed。

由于 NEG 控制器无法区分这两种情况,因此 GKE NEG 控制器必须假设,如果 NEG 的任何端点都没有健康检查信息,则该 NEG 不得属于任何 BackendService。

虽然这种情况不太可能发生,但与 NEG 数量相比,Pod 数量相对较少(例如 2 个)会增加出现此竞态条件的风险。请注意,系统会为每个区域创建 NEG,每个可用区一个 NEG,因此通常会创建三个 NEG。

推论是,如果 Pod 数量相对较多,使得每个 NEG 在滚动更新开始之前始终有多个 Pod,则触发此竞态条件的可能性非常小。

解决方法:

防止出现此竞态条件(并最终防止在滚动更新期间出现 503 Service Unavailable 响应)的最佳方法是在网络端点组中添加更多后端。

确保配置了滚动更新策略,以确保始终至少有 1 个 Pod 在运行。

例如,如果只有 2 个 Pod 正常运行,则示例配置可能如下所示:

strategy:
   type: RollingUpdate
   rollingUpdate:
     maxUnavailable: 0
     maxSurge: 1

上例是一个建议。您必须根据多种因素(例如副本数量)更新策略。

垃圾回收不完整

GKE 每两分钟对容器原生负载均衡器执行一次垃圾回收操作。如果在完全移除负载均衡器之前删除了集群,则需要手动删除负载均衡器的 NEG。

通过运行以下命令查看项目中的 NEG:

gcloud compute network-endpoint-groups list

在命令输出中,查找相关的 NEG。

如需删除 NEG,请运行以下命令,并将 NEG_NAME 替换为 NEG 的名称:

gcloud compute network-endpoint-groups delete NEG_NAME

使工作负载发布与端点传播保持一致

将工作负载部署到集群或更新现有工作负载时,容器原生负载均衡器传播新端点所需的时间可能比完成工作负载发布所需的时间长。您在本指南中部署的示例 Deployment 使用两个字段来使其发布与端点的传播保持一致:terminationGracePeriodSecondsminReadySeconds

terminationGracePeriodSeconds 允许 Pod 等待连接在某个 Pod 被安排删除后终止,从而能够正常关闭。

minReadySeconds 会在创建 Pod 后添加延迟时段。您需要指定新的 Pod 在被视为可用前应处于 Ready 状态(没有任何容器崩溃)的秒数下限。

您应将工作负载的 minReadySecondsterminationGracePeriodSeconds 值配置为 60 秒或更长时间,以确保服务不会因为工作负载发布而中断。

terminationGracePeriodSeconds 在所有 Pod 规范中均可用;minReadySeconds 可用于 Deployment 和 DaemonSet。

如需详细了解如何微调发布,请参阅 RollingUpdateStrategy

未遵循 Pod readinessProbe 中的 initialDelaySeconds

您可能希望容器原生负载均衡器遵循 Pod 的 readinessProbe 中的 initialDelaySeconds 配置;但是,readinessProbe 由 kubelet 实现,并且 initialDelaySeconds 配置(而不是容器原生负载均衡器)控制 kubelet 健康检查。容器原生负载均衡有自己的负载均衡健康检查。

后续步骤