

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 将工作负载分散到节点和可用区中
<a name="spread-workloads"></a>

在可用区和节点等[故障域](https://cluster-api-aws.sigs.k8s.io/topics/failure-domains/)之间分配工作负载可提高组件可用性并降低水平可扩展应用程序的故障几率。以下各节介绍了在节点和可用区之间分配工作负载的方法。

## 使用 pod 拓扑分布约束
<a name="spread-constraints"></a>

[Kubernetes 容器拓扑分布约束](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/)指示 Kubernetes 调度器在不同的故障域（可用区、节点和硬件类型）之间分配由`ReplicaSet``StatefulSet`不同故障域（可用区、节点和硬件类型）管理的 Pod。使用 pod 拓扑分布约束时，可以执行以下操作：
+ 根据应用程序要求将容器分布或集中到不同的故障域中。例如，您可以分发 Pod 以提高弹性，也可以将 pod 集中在网络性能上。
+ 组合不同的条件，例如跨可用区分发和跨节点分配。
+ 如果无法满足条件，请指定首选操作：
  + `whenUnsatisfiable: DoNotSchedule`结合使用`maxSkew`和`minDomains`来为调度程序创建硬性要求。
  + `whenUnsatisfiable: ScheduleAnyway`用于减少`maxSkew`。

如果故障区域变得不可用，则该区域中的 Pod 将变得不健康。Kubernetes 会重新调度 Pod，同时尽可能遵守传播限制。

以下代码显示了跨可用区或跨节点使用 pod 拓扑分布约束的示例：

```
...
spec:
  selector:
    matchLabels:
      app: <your-app-label>
    replicas: 3
    template:
      metadata:
        labels: <your-app-label>
      spec:
        serviceAccountName: <ServiceAccountName>
...
        topologySpreadConstraints:
        - labelSelector:
            matchLabels:
              app: <your-app-label>
          maxSkew: 1
          topologyKey: topology.kubernetes.io/zone # <---spread those pods evenly over all availability zones
          whenUnsatisfiable: ScheduleAnyway
        - labelSelector:
            matchLabels:
              app: <your-app-label>
          maxSkew: 1
          topologyKey: kubernetes.io/hostname # <---spread those pods evenly over all nodes
          whenUnsatisfiable: ScheduleAnyway
```

### 默认集群范围的拓扑分布限制
<a name="default-constraints"></a>

默认情况下，Kubernetes 提供了一[组拓扑分布限制](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/#internal-default-constraints)，用于跨节点和可用区分配 Pod：

```
defaultConstraints:
  - maxSkew: 3
    topologyKey: "kubernetes.io/hostname"
    whenUnsatisfiable: ScheduleAnyway
  - maxSkew: 5
    topologyKey: "topology.kubernetes.io/zone"
    whenUnsatisfiable: ScheduleAnyway
```

**注意**  
需要不同类型拓扑约束的应用程序可以覆盖集群级别的策略。

默认约束设置得很高`maxSkew`，这对于拥有少量 pod 的部署没有用。到目前为止，`KubeSchedulerConfiguration`[无法在 Amazon EKS 中进行更改](https://github.com/aws/containers-roadmap/issues/1468)。如果您需要强制执行其他组拓扑分布约束，请考虑使用变更准入控制器，如下节所示。如果您运行备用调度程序，也可以控制默认的拓扑分布约束。但是，管理自定义调度程序会增加复杂性，并可能对集群弹性和高可用性产生影响。出于这些原因，我们不建议仅对拓扑分布约束使用其他调度器。

### 拓扑分布限制的网守策略
<a name="gatekeeper-policy"></a>

强制执行拓扑分布限制的另一种选择是使用 [Gatekeeper](https://open-policy-agent.github.io/gatekeeper/website/docs/) 项目中的策略。网守策略是在应用程序级别定义的。

以下代码示例演示如何使用`Gatekeeper OPA`策略进行部署。您可以根据需要修改策略。例如，仅将策略应用于带有标签的部署`HA=true`，或者使用不同的策略控制器编写类似的策略。

第一个示例显示了与以下`ConstraintTemplate`内容一起使用`k8stopologyspreadrequired_template.yml`：

```
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8stopologyspreadrequired
spec:
  crd:
    spec:
      names:
        kind: K8sTopologySpreadRequired
      validation:
        openAPIV3Schema:
          type: object
          properties:
            message:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8stopologyspreadrequired

        get_message(parameters, _default) =3D msg {
          not parameters.message
          msg :=_default
        }


        get_message(parameters, _default) =3D msg {
          msg := parameters.message
        }

        violation[{"msg": msg}] {
          input.review.kind.kind ="Deployment"
          not input.review.object.spec.template.spec.topologySpreadConstraint
          def_msg :"Pod Topology Spread Constraints are required for Deployments"
          msg :get_message(input.parameters, def_msg)

        }
```

以下代码显示了 `constraints` YAML 清单`k8stopologyspreadrequired_constraint.yml`：

```
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sTopologySpreadRequired
metadata:
  name: require-topologyspread-for-deployments
spec:
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment"]
    namespaces:  ## Without theses two lines will apply to the whole cluster
      - "example"
```

### 何时使用拓扑分布约束
<a name="when-to-use"></a>

考虑在以下场景中使用拓扑分布约束：
+ 任何水平可扩展的应用程序（例如，无状态 Web 服务）
+ 具有主动-主动或主动-被动副本的应用程序（例如，NoSQL 数据库或缓存）
+ 带有备用副本的应用程序（例如控制器）

例如，可用于水平可扩展场景的系统组件包括以下组件：
+ [集群自动扩缩器和 [Karpent](https://karpenter.sh/) er](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler)（使用和）`replicaCount > 1``leader-elect = true`
+ [AWS Load Balancer 控制器](https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/)
+ [CoreDNS](https://coredns.io/)

## Pod 亲和力和反亲和力
<a name="anti-affinity"></a>

在某些情况下，确保一个节点上运行的特定类型的 pod 不超过一个是有益的。例如，为了避免在同一个节点上调度多个网络密集型的 Pod，您可以使用带有或标签的反关联性规则。`Ingress` `Network-heavy`使用时`anti-affinity`，您还可以使用以下各项的组合：
+ 网络优化节点上的污点
+ 对网络密集型 pod 的相应容忍度
+ 节点亲和性或节点选择器，用于确保网络密集型的 Pod 使用网络优化的实例

以网络密集型的 pod 为例。您可能有不同的要求，例如 GPU、内存或本地存储。有关其他用法示例和配置选项，请参阅 [Kubernetes 文档](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity)。

### 重新平衡吊舱
<a name="rebalance-pods"></a>

本节讨论在 Kubernetes 集群中重新平衡 Pod 的两种方法。第一个使用了 Kubernetes 的解调器。Descheduler 通过强制执行策略来移除违反拓扑分布限制或反关联性规则的 pod，从而帮助维持 pod 分布。第二种方法使用 Karpenter 合并和装箱功能。整合通过将工作负载整合到更少、打包效率更高的节点上，持续评估和优化资源使用情况。

如果你不使用 Karpenter，我们建议你使用 Descheduler。如果您同时使用 Karpenter 和 Cluster Autoscaler，则可以将 Descheduler 与 Cluster Autoscaler 一起用于节点组。

#### 无组节点的解调器
<a name="descheduler"></a>

移除 pod 后，无法保证拓扑约束仍然得到满足。例如，缩小部署规模可能会导致 Pod 分布不平衡。但是，由于 Kubernetes 仅在调度阶段使用 pod 拓扑分布约束，因此 Pod 在故障域中处于不平衡状态。

为了在这种情况下保持平衡的容器分布，你可以使用适用于 Kubernetes 的 [Descheduler](https://github.com/kubernetes-sigs/descheduler)。Descheduler 是一个有用的工具，可用于多种用途，例如强制执行 pod 的最大使用寿命或存活时间 (TTL)，或者改善基础设施的使用。在弹性和高可用性 (HA) 的背景下，考虑以下 Descheduler 策略：
+ [RemovePodsViolatingTopologySpreadConstraint](https://github.com/kubernetes-sigs/descheduler?tab=readme-ov-file#removepodsviolatingtopologyspreadconstraint)
+ [RemovePodsViolatingInterPodAntiAffinity](https://github.com/kubernetes-sigs/descheduler?tab=readme-ov-file#removepodsviolatinginterpodantiaffinity)
+ [RemoveDuplicates](https://github.com/kubernetes-sigs/descheduler?tab=readme-ov-file#removeduplicates)

#### Karpenter 整合和装箱功能
<a name="karpenter"></a>

对于使用 Karpenter 的工作负载，您可以使用整合和垃圾箱打包功能来优化资源利用率并降低 Kubernetes 集群的成本。Karpenter 会持续评估 pod 的放置位置和节点利用率，并尝试尽可能将工作负载整合到更少、打包效率更高的节点上。此过程包括分析资源需求，考虑 Pod 亲和性规则等限制，以及可能在节点之间移动 Pod 以提高集群的整体效率。以下代码提供了一个示例：

```
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  disruption:
    consolidationPolicy: WhenUnderutilized
    expireAfter: 720h
```

对于`consolidationPolicy`，你可以使用`WhenUnderutilized`或`WhenEmpty`：
+ 当设置`consolidationPolicy`为时`WhenUnderutilized`，Karpenter 会考虑所有节点进行合并。当 Karpenter 发现一个空的节点或未充分利用时，Karpenter 会尝试移除或替换该节点以降低成本。
+ 当设置`consolidationPolicy`为时`WhenEmpty`，Karpenter 只考虑不包含工作负载 Pod 的节点进行整合。

Karpenter 整合决策不仅仅基于您在监控工具中可能看到的 CPU 或内存利用率百分比。相反，Karpenter 使用更复杂的算法，该算法基于 pod 资源请求和潜在的成本优化。有关更多信息，请参阅 [Karpenter](https://karpenter.sh/docs/concepts/disruption/#consolidation) 文档。