

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

# EKS 可扩展性最佳实践
<a name="scalability"></a>

**提示**  
 通过 Amazon EKS 研讨会@@ [探索](https://aws-experience.com/emea/smb/events/series/get-hands-on-with-amazon-eks?trk=4a9b4147-2490-4c63-bc9f-f8a84b122c8c&sc_channel=el)最佳实践。

本指南为扩展 EKS 集群提供了建议。扩展 EKS 集群的目标是最大限度地提高单个集群可以执行的工作量。与使用多个集群相比，使用单个大型 EKS 集群可以减少运营负载，但在多区域部署、租户隔离和集群升级等方面需要权衡取舍。在本文档中，我们将重点介绍如何使用单个集群实现最大的可扩展性。

## 如何使用本指南
<a name="_how_to_use_this_guide"></a>

本指南适用于负责在 AWS 中创建和管理 EKS 集群的开发人员和管理员。[它侧重于一些通用的 Kubernetes 扩展实践，但它没有具体说明自行管理的 Kubernetes 集群或使用 EKS Anywhere 在 AWS 区域之外运行的集群。](https://anywhere.eks.amazonaws.com/)

每个主题都有简要概述，然后是大规模运营 EKS 集群的建议和最佳实践。无需按特定顺序阅读主题，未经测试和验证建议在您的集群中是否有效，则不应应用这些建议。

## 了解缩放维度
<a name="_understanding_scaling_dimensions"></a>

可扩展性不同于性能和[可靠性](https://aws.github.io/aws-eks-best-practices/reliability/docs/)，在规划集群和工作负载需求时，应将这三者都考虑在内。随着集群的扩展，需要对其进行监控，但本指南不涵盖监控最佳实践。EKS 可以扩展到大规模，但你需要计划如何将集群扩展到 300 个节点或 5,000 个 Pod 以上。这些数字不是绝对数字，但它们来自于与多个用户、工程师和支持专业人员合作使用本指南。

Kubernetes 中的扩展是多维的，没有适用于每种情况的特定设置或建议。我们可以为扩展提供指导的主要领域包括：

 E@@ **KS 集群中的 Kubernetes 控制平面**包括 AWS 为你自动运行和扩展的所有服务（例如 Kubernetes API 服务器）。扩展控制平面是 AWS 的责任，但负责任地使用控制平面是您的责任。

 **Kubernetes 数据平面**扩展处理集群和工作负载所需的 AWS 资源，但它们不在 EKS 控制平面之内。包括 EC2 实例、kubelet 和存储在内的资源都需要随着集群的扩展而进行扩展。

 **集群服务**是在集群内部运行的 Kubernetes 控制器和应用程序，可为您的集群和工作负载提供功能。这些可以是 [EKS 附加组件](https://docs.aws.amazon.com/eks/latest/userguide/eks-add-ons.html)，也可以是您为合规性和集成而安装的其他服务或 Helm 图表。这些服务通常依赖于工作负载，随着工作负载的扩展，您的集群服务将需要随之扩展。

 **工作负载**是您拥有集群的原因，并且应该与集群一起水平扩展。Kubernetes 中工作负载的集成和设置可以帮助集群扩展。Kubernetes 抽象也需要考虑架构问题，例如命名空间和服务。

## 超大缩放比例
<a name="_extra_large_scaling"></a>

如果您要将单个集群扩展到 1,000 个节点或 50,000 个 Pod 以上，我们很乐意与您交谈。我们建议您联系您的支持团队或技术客户经理，与专家取得联系，他们可以帮助您规划和扩展本指南中提供的信息。如果您被选中入门，Amazon EKS 可以在单个集群中支持多达 100,000 个节点。

# Kubernetes 控制平面
<a name="scale-control-plane"></a>

**提示**  
 通过 Amazon EKS 研讨会@@ [探索](https://aws-experience.com/emea/smb/events/series/get-hands-on-with-amazon-eks?trk=4a9b4147-2490-4c63-bc9f-f8a84b122c8c&sc_channel=el)最佳实践。

Kubernetes 控制平面由 Kubernetes API 服务器、Kubernetes 控制器管理器、调度器和 Kubernetes 运行所需的其他组件组成。这些组件的可扩展性限制因您在集群中运行的内容而异，但对扩展影响最大的领域包括 Kubernetes 版本、利用率和单个节点扩展。

## 限制工作负载和节点爆发
<a name="_limit_workload_and_node_bursting"></a>

**重要**  
为了避免在控制平面上达到 API 限制，您应该限制一次将集群大小增加两位数百分比的扩展峰值（例如，1000 个节点增加到 1100 个节点，或者同时增加 4000 到 4500 个 pod）。

EKS 控制平面将随着集群的增长而自动扩展，但是它的扩展速度有限制。首次创建 EKS 集群时，控制平面将无法立即扩展到数百个节点或数千个 Pod。要详细了解 EKS 如何进行扩展改进，请参阅[此博客文章](https://aws.amazon.com/blogs/containers/amazon-eks-control-plane-auto-scaling-enhancements-improve-speed-by-4x/)。

扩展大型应用程序需要基础设施适应以完全准备就绪（例如，变暖负载均衡器）。要控制扩展速度，请确保根据应用程序的正确指标进行扩展。CPU 和内存缩放可能无法准确预测您的应用程序限制，在 Kubernetes 水平容器自动扩缩器 (HPA) 中使用自定义指标（例如每秒请求数）可能是更好的扩展选项。

要使用自定义指标，请参阅 [Kubernetes](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics) 文档中的示例。如果您有更高级的扩展需求或需要根据外部来源（例如 AWS SQS 队列）进行扩展，请使用 [KEDA](https://keda.sh) 进行基于事件的工作负载扩展。

## 安全地向下缩放节点和 pod
<a name="_scale_nodes_and_pods_down_safely"></a>

### 替换长时间运行的实例
<a name="_replace_long_running_instances"></a>

定期更换节点可以避免配置偏差和只有在延长正常运行时间后才会出现的问题（例如缓慢的内存泄漏），从而保持集群的健康。自动替换将为您提供良好的节点升级和安全补丁流程和实践。如果定期更换集群中的每个节点，那么维护单独的进程以进行持续维护所需的辛勤工作就会减少。

使用 Karpenter 的[生存时间 (TTL)](https://docs.aws.amazon.com/eks/latest/best-practices/karpenter.html#_creating_nodepools) 设置在实例运行了指定时间后替换这些实例。自我管理的节点组可以使用该`max-instance-lifetime`设置自动循环节点。托管节点组目前没有此功能，但您可以[在此](https://github.com/aws/containers-roadmap/issues/1190)处跟踪请求 GitHub。

### 移除未充分利用的节点
<a name="_remove_underutilized_nodes"></a>

当节点没有正在运行的工作负载时，你可以使用 Kubernetes 集群自动扩缩器中的缩减阈值将其移除，[https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#how-does-scale-down-work](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#how-does-scale-down-work)或者在 Karpenter 中使用配置器设置。`ttlSecondsAfterEmpty`

### 使用 pod 中断预算和安全关闭节点
<a name="_use_pod_disruption_budgets_and_safe_node_shutdown"></a>

从 Kubernetes 集群中移除 Pod 和节点需要控制器对多个资源进行更新（例如）。 EndpointSlices当更改传播到控制器时，频繁或过快地执行此操作可能会导致 API 服务器限制和应用程序中断。当集群中的节点被移除或重新调度时，[Pod 中断预算](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/)是减慢流失速度以保护工作负载可用性的最佳实践。

## 运行 Kubectl 时使用客户端缓存
<a name="_use_client_side_cache_when_running_kubectl"></a>

如果使用 kubectl 命令效率低下，可能会给 Kubernetes API 服务器增加额外的负载。你应该避免运行重复使用 kubectl 的脚本或自动化（例如在 for 循环中），或者在没有本地缓存的情况下运行命令。

 `kubectl`具有客户端缓存，用于缓存来自集群的发现信息，以减少所需的 API 调用量。默认情况下，缓存处于启用状态，并且每 10 分钟刷新一次。

如果您从容器或没有客户端缓存的情况下运行 kubectl，则可能会遇到 API 限制问题。建议通过挂载来保留您的集群缓存，`--cache-dir`以避免进行不必要的 API 调用。

## 禁用 kubectl 压缩
<a name="_disable_kubectl_compression"></a>

在 kubeconfig 文件中禁用 kubectl 压缩可以降低 API 和客户端 CPU 的使用率。默认情况下，服务器将压缩发送到客户端的数据以优化网络带宽。这会增加每个请求在客户端和服务器上的 CPU 负载，如果您有足够的带宽，禁用压缩可以减少开销和延迟。要禁用压缩，你可以在 kubeconfig 文件`disable-compression: true`中使用或设置该`--disable-compression=true`标志。

```
apiVersion: v1
clusters:
- cluster:
    server: serverURL
    disable-compression: true
  name: cluster
```

## 分片集群自动扩缩器
<a name="_shard_cluster_autoscaler"></a>

[Kubernetes 集群自动扩缩器已经过测试，可以扩展](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/proposals/scalability_tests.md)到 1000 个节点。在节点超过 1000 个的大型集群上，建议在分片模式下运行集群自动扩缩程序的多个实例。每个 Cluster Autoscaler 实例都配置为扩展一组节点组。以下示例显示了为每个 4 级节点组配置的 2 个集群自动缩放配置。

ClusterAutoscaler-1

```
autoscalingGroups:
- name: eks-core-node-grp-20220823190924690000000011-80c1660e-030d-476d-cb0d-d04d585a8fcb
  maxSize: 50
  minSize: 2
- name: eks-data_m1-20220824130553925600000011-5ec167fa-ca93-8ca4-53a5-003e1ed8d306
  maxSize: 450
  minSize: 2
- name: eks-data_m2-20220824130733258600000015-aac167fb-8bf7-429d-d032-e195af4e25f5
  maxSize: 450
  minSize: 2
- name: eks-data_m3-20220824130553914900000003-18c167fa-ca7f-23c9-0fea-f9edefbda002
  maxSize: 450
  minSize: 2
```

ClusterAutoscaler-2

```
autoscalingGroups:
- name: eks-data_m4-2022082413055392550000000f-5ec167fa-ca86-6b83-ae9d-1e07ade3e7c4
  maxSize: 450
  minSize: 2
- name: eks-data_m5-20220824130744542100000017-02c167fb-a1f7-3d9e-a583-43b4975c050c
  maxSize: 450
  minSize: 2
- name: eks-data_m6-2022082413055392430000000d-9cc167fa-ca94-132a-04ad-e43166cef41f
  maxSize: 450
  minSize: 2
- name: eks-data_m7-20220824130553921000000009-96c167fa-ca91-d767-0427-91c879ddf5af
  maxSize: 450
  minSize: 2
```

## API 优先级和公平性
<a name="_api_priority_and_fairness"></a>

![\[APF\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/APF.jpg)


### 概述
<a name="_overview"></a>

为了防止自己在请求增加期间过载，API 服务器限制了在给定时间内可能未完成的飞行中请求的数量。超过此限制后，API 服务器将开始拒绝请求，并将 “请求过多” 的 429 HTTP 响应代码返回给客户端。服务器丢弃请求并让客户端稍后再试比服务器端对请求数量没有限制和控制平面过载更可取，后者可能会导致性能下降或不可用。

Kubernetes 用来配置如何在不同请求类型之间划分这些机上请求的机制称为 [API](https://kubernetes.io/docs/concepts/cluster-administration/flow-control/) 优先级和公平性。API 服务器通过将和标志指定的值相加，来配置它可以接受的机上请求总数。`--max-requests-inflight` `--max-mutating-requests-inflight`EKS 对这些标志使用默认值 400 和 200 个请求，允许在给定时间总共发送 600 个请求。但是，由于它会将控制平面扩展到更大的大小以应对利用率的增加和工作负载流失，因此它相应地将机上请求配额一直增加到2000年（可能会发生变化）。APF 规定了如何在不同的请求类型之间进一步细分这些机上请求配额。请注意，EKS 控制平面高度可用，每个集群至少注册了 2 个 API 服务器。这意味着您的集群可以处理的飞行中请求总数是每个 kube-apiserver 设置的机上配额的两倍（如果进一步横向扩展，则更高）。在最大的EKS集群 requests/second 上，这相当于数千个。

两种名为 “ PriorityLevelConfigurations 和 FlowSchemas” 的 Kubernetes 对象配置如何在不同的请求类型之间分配请求总数。这些对象由 API 服务器自动维护，EKS 使用给定的 Kubernetes 次要版本中这些对象的默认配置。 PriorityLevelConfigurations 仅占允许请求总数的一小部分。例如，在总共 600 个请求中 PriorityLevelConfiguration ，分配了工作负载最高的 98 个。分配给所有请求的总和 PriorityLevelConfigurations 将等于 600（或略高于 600，因为如果向给定级别授予请求的一小部分，API 服务器将向上舍入）。要检查集群 PriorityLevelConfigurations 中的以及分配给每个集群的请求数，可以运行以下命令。以下是 EKS 1.32 的默认设置：

```
$ kubectl get --raw /metrics | grep apiserver_flowcontrol_nominal_limit_seats
apiserver_flowcontrol_nominal_limit_seats{priority_level="catch-all"} 13
apiserver_flowcontrol_nominal_limit_seats{priority_level="exempt"} 0
apiserver_flowcontrol_nominal_limit_seats{priority_level="global-default"} 49
apiserver_flowcontrol_nominal_limit_seats{priority_level="leader-election"} 25
apiserver_flowcontrol_nominal_limit_seats{priority_level="node-high"} 98
apiserver_flowcontrol_nominal_limit_seats{priority_level="system"} 74
apiserver_flowcontrol_nominal_limit_seats{priority_level="workload-high"} 98
apiserver_flowcontrol_nominal_limit_seats{priority_level="workload-low"} 245
```

第二种物体是 FlowSchemas。具有给定属性集的 API 服务器请求被归入同一组属性下 FlowSchema。这些属性包括经过身份验证的用户或请求的属性，例如 API 组、命名空间或资源。A FlowSchema 还指定应将 PriorityLevelConfiguration 此类请求映射到哪个类型。这两个对象一起说：“我希望这种类型的请求计入机上请求的份额。” 当请求到达 API 服务器时，它将检查每个请求， FlowSchemas 直到找到一个与所有必需属性相匹配的请求。如果多个请求 FlowSchemas 匹配，API 服务器将选择匹配优先级最小的，该优先级被指定为对象中的一个属性。 FlowSchema 

 PriorityLevelConfigurations 可以使用以下命令查看 FlowSchemas 到的映射：

```
$ kubectl get flowschemas
NAME                           PRIORITYLEVEL     MATCHINGPRECEDENCE   DISTINGUISHERMETHOD   AGE     MISSINGPL
exempt                         exempt            1                    <none>                7h19m   False
eks-exempt                     exempt            2                    <none>                7h19m   False
probes                         exempt            2                    <none>                7h19m   False
system-leader-election         leader-election   100                  ByUser                7h19m   False
endpoint-controller            workload-high     150                  ByUser                7h19m   False
workload-leader-election       leader-election   200                  ByUser                7h19m   False
system-node-high               node-high         400                  ByUser                7h19m   False
system-nodes                   system            500                  ByUser                7h19m   False
kube-controller-manager        workload-high     800                  ByNamespace           7h19m   False
kube-scheduler                 workload-high     800                  ByNamespace           7h19m   False
kube-system-service-accounts   workload-high     900                  ByNamespace           7h19m   False
eks-workload-high              workload-high     1000                 ByUser                7h14m   False
service-accounts               workload-low      9000                 ByUser                7h19m   False
global-default                 global-default    9900                 ByUser                7h19m   False
catch-all                      catch-all         10000                ByUser                7h19m   False
```

PriorityLevelConfigurations 可以有 “队列”、“拒绝” 或 “豁免” 的类型。对于 Queue 和 Reject 类型，将对该优先级的最大机上请求数施加限制，但是，当达到该限制时，行为会有所不同。例如，workload-high PriorityLevelConfiguration 使用 Queue 类型，有 98 个请求可供控制器管理器、端点控制器、调度器、eks 相关控制器以及在 kube-system 命名空间中运行的 pod 使用。由于使用了 Queue 类型，API 服务器将尝试将请求保留在内存中，并希望在这些请求超时之前将正在运行的请求数量降至 98 以下。如果给定请求在队列中超时，或者已经排队的请求太多，API 服务器别无选择，只能放弃该请求并向客户端返回 429。请注意，排队可能会阻止请求接收 429，但需要权衡请求 end-to-end延迟时间增加。

现在考虑一下映射到包罗万 FlowSchema 象的 “拒绝” 类型 PriorityLevelConfiguration 。如果客户端达到 13 个机上请求的限制，API 服务器将不会进行排队，而是使用 429 响应代码立即丢弃请求。最后，映射到类型为 “豁免” PriorityLevelConfiguration 的请求将永远不会收到 429，并且始终会立即分发。这用于高优先级请求，例如 healthz 请求或来自 system: masters 组的请求。

### 监控 APF 和已删除的请求
<a name="_monitoring_apf_and_dropped_requests"></a>

要确认是否有任何请求因 APF 而被丢弃，`apiserver_flowcontrol_rejected_requests_total`可以监控 API 服务器指标以检查受影响的 FlowSchemas 和 PriorityLevelConfigurations。例如，此指标显示，由于请求在工作负载较低的队列中超时，来自服务帐号的 100 个请求 FlowSchema 被丢弃：

```
% kubectl get --raw /metrics | grep apiserver_flowcontrol_rejected_requests_total
apiserver_flowcontrol_rejected_requests_total{flow_schema="service-accounts",priority_level="workload-low",reason="time-out"} 100
```

要检查给定对象与接收 429 的 PriorityLevelConfiguration 距离有多近，或者由于排队而出现延迟增加，您可以比较并发限制和正在使用的并发数之间的差异。在此示例中，我们有一个包含 100 个请求的缓冲区。

```
% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_nominal_limit_seats.*workload-low'
apiserver_flowcontrol_nominal_limit_seats{priority_level="workload-low"} 245

% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_current_executing_seats.*workload-low'
apiserver_flowcontrol_current_executing_seats{flow_schema="service-accounts",priority_level="workload-low"} 145
```

要检查给定对象 PriorityLevelConfiguration 是否遇到排队但请求不一定被丢弃的情况，`apiserver_flowcontrol_current_inqueue_requests`可以参考以下指标：

```
% kubectl get --raw /metrics | grep 'apiserver_flowcontrol_current_inqueue_requests.*workload-low'
apiserver_flowcontrol_current_inqueue_requests{flow_schema="service-accounts",priority_level="workload-low"} 10
```

其他有用的 Prometheus 指标包括：
+ apiserver\$1flowcontrol\$1dispatched\$1request\$1t
+ apiserver\$1flowcontrol\$1requestion\$1exection
+ apiserver\$1flowcontrol\$1request\$1wait\$1duration\$1se

有关 [APF 指标](https://kubernetes.io/docs/concepts/cluster-administration/flow-control/#observability)的完整列表，请参阅上游文档。

### 防止请求被丢弃
<a name="_preventing_dropped_requests"></a>

#### 通过更改工作负载来防止 429 秒
<a name="_prevent_429s_by_changing_your_workload"></a>

当 APF 因给定请求 PriorityLevelConfiguration 超出其允许的最大机上请求数而丢弃请求时，受影响的客户端 FlowSchemas 可以减少在给定时间执行的请求数。这可以通过减少在发生 429 的时段内发出的请求总数来实现。请注意，长时间运行的请求（例如昂贵的列表调用）尤其成问题，因为它们在整个执行期间都算作飞行中请求。减少这些昂贵的请求的数量或优化这些列表调用的延迟（例如，通过减少每个请求获取的对象数量或切换到使用监视请求）可以帮助减少给定工作负载所需的总并发量。

#### 通过更改 APF 设置来防止 429 秒
<a name="_prevent_429s_by_changing_your_apf_settings"></a>

**警告**  
只有在知道自己在做什么时才更改默认 APF 设置。APF 设置配置不当可能会导致 API 服务器请求丢失和工作负载严重中断。

防止请求被丢弃的另一种方法是更改默认值 FlowSchemas 或在 EKS 集群上 PriorityLevelConfigurations 安装的设置。EKS 为 FlowSchemas 给定的 Kubernetes 次要版本安装上游默认设置。 PriorityLevelConfigurations 如果修改了这些对象，API 服务器会自动将这些对象协调回其默认值，除非这些对象的以下注释设置为 false：

```
  metadata:
    annotations:
      apf.kubernetes.io/autoupdate-spec: "false"
```

简而言之，可以将 APF 设置修改为：
+ 为您关心的请求分配更多机上容量。
+ 隔离不必要或昂贵的请求，这些请求可能会导致其他请求类型的容量不足。

这可以通过更改默认值 FlowSchemas 和 PriorityLevelConfigurations 或通过创建这些类型的新对象来实现。操作员可以增加相关 PriorityLevelConfigurations 对象 assuredConcurrencyShares 的值，以增加分配给它们的飞行中请求的比例。此外，如果应用程序能够处理因请求在调度之前排队而造成的额外延迟，则在给定时间可以排队的请求数量也可以增加。

或者，可以创建特定于客户工作负载的新 PriorityLevelConfigurations 对象 FlowSchema 和对象。请注意， assuredConcurrencyShares 向现有 PriorityLevelConfigurations 或新存储桶分配更多请求 PriorityLevelConfigurations 将导致其他存储桶可以处理的请求数量减少，因为每个 API 服务器的总体限制将保持在 600 个正在进行中。

更改 APF 默认值时，应在非生产集群上监控这些指标，以确保更改设置不会导致意外的 429：

1. `apiserver_flowcontrol_rejected_requests_total`应监控所有人的指标， FlowSchemas 以确保没有存储桶开始丢弃请求。

1. `apiserver_flowcontrol_current_executing_seats`应比较`apiserver_flowcontrol_nominal_limit_seats`和的值，以确保使用的并发不会因为突破该优先级的限制而面临风险。

定义新的 and 的一个常见用例 PriorityLevelConfiguration 是 FlowSchema 隔离。假设我们想将来自 pod 的长时间运行的列表事件调用隔离到它们自己的请求份额。这将防止来自使用现有服务帐户 FlowSchema 的 pod 的重要请求接收 429 并导致请求容量不足。回想一下，飞行中请求的总数是有限的，但是，此示例显示可以修改 APF 设置以更好地划分给定工作负载的请求容量：

用于隔离列表事件请求的示例 FlowSchema 对象：

```
apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
  name: list-events-default-service-accounts
spec:
  distinguisherMethod:
    type: ByUser
  matchingPrecedence: 8000
  priorityLevelConfiguration:
    name: catch-all
  rules:
  - resourceRules:
    - apiGroups:
      - '*'
      namespaces:
      - default
      resources:
      - events
      verbs:
      - list
    subjects:
    - kind: ServiceAccount
      serviceAccount:
        name: default
        namespace: default
```
+ 这将 FlowSchema 捕获默认命名空间中服务帐号发出的所有列表事件调用。
+ 匹配优先级 8000 低于现有服务帐户使用的值 9000， FlowSchema 因此这些列表事件调用将匹配 list-events-default-service-accounts 而不是服务帐户。
+ 我们正在使用包罗万象 PriorityLevelConfiguration 来隔离这些请求。此存储桶仅允许这些长时间运行的列表事件调用使用 13 个飞行中请求。当 Pod 尝试同时发出超过 13 个这样的请求时，它们就会开始收到 429。

## 检索 API 服务器中的资源
<a name="_retrieving_resources_in_the_api_server"></a>

对于任何规模的集群，从 API 服务器获取信息都是一种预期行为。在扩展集群中的资源数量时，请求频率和数据量很快就会成为控制平面的瓶颈，并导致 API 延迟和缓慢。根据延迟的严重程度，如果不小心，它会导致意外停机。

了解您的要求以及避免此类问题的第一步是多久一次。以下是根据扩展最佳实践限制查询量的指南。本节中的建议是按顺序提供的，从已知可以最佳扩展的选项开始。

### 使用共享举报人
<a name="_use_shared_informers"></a>

在构建与 Kubernetes API 集成的控制器和自动化时，您通常需要从 Kubernetes 资源中获取信息。如果您定期轮询这些资源，可能会导致 API 服务器负载过重。

使用 client-go 库中的[告密器](https://pkg.go.dev/k8s.io/client-go/informers)可以让你根据事件监视资源的变化，而不是轮询更改。告密者通过对事件和更改使用共享缓存来进一步减少负载，因此监视相同资源的多个控制器不会增加额外的负载。

控制器应避免在没有标签和字段选择器的情况下轮询集群范围内的资源，尤其是在大型集群中。每个未过滤的民意调查都需要通过 API 服务器从 etcd 发送大量不必要的数据，然后由客户端进行过滤。通过根据标签和命名空间进行筛选，可以减少 API 服务器为完成发送到客户端的请求和数据而需要执行的工作量。

### 优化 Kubernetes API 的使用情况
<a name="_optimize_kubernetes_api_usage"></a>

使用自定义控制器或自动化调用 Kubernetes API 时，请务必将调用限制在所需的资源上。没有限制，您可能会在 API 服务器和 etcd 上造成不必要的负载。

建议尽可能使用 watch 参数。如果没有参数，则默认行为是列出对象。要使用监视而不是列表，您可以在 API 请求`?watch=true`的末尾追加。例如，要使用手表获取默认命名空间中的所有 pod，请使用：

```
/api/v1/namespaces/default/pods?watch=true
```

如果您要列出对象，则应限制所列内容的范围和返回的数据量。您可以通过在请求中添加`limit=500`参数来限制返回的数据。`fieldSelector`参数和`/namespace/`路径对于确保列表的范围根据需要缩小范围很有用。例如，要仅列出默认命名空间中正在运行的 Pod，请使用以下 API 路径和参数。

```
/api/v1/namespaces/default/pods?fieldSelector=status.phase=Running&limit=500
```

或者列出所有正在使用以下命令运行的 pod：

```
/api/v1/pods?fieldSelector=status.phase=Running&limit=500
```

限制监视调用或列出的对象的另一种选择是使用它 [`resourceVersions`，你可以在 Kubernetes 文档中阅读相关内容](https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions)。如果不带任何`resourceVersion`参数，您将收到最新的可用版本，该版本需要 etcd 法定读取，这是数据库中最昂贵和最慢的读取。ResourceVersion 取决于你要查询的资源，这些资源可以在现场找到。`metadata.resourseVersion`如果使用监视通话，而不仅仅是列出通话，也建议这样做

有一种特殊功能`resourceVersion=0`可以从 API 服务器缓存中返回结果。这可以减少 etcd 的负载，但它不支持分页。

```
/api/v1/namespaces/default/pods?resourceVersion=0
```

建议使用 watch，将 ResourceVersion 设置为从其先前的列表或监视中收到的最新已知值。这是在 client-go 中自动处理的。但是，如果您使用的是其他语言的 k8s 客户端，建议您仔细检查一下。

```
/api/v1/namespaces/default/pods?watch=true&resourceVersion=362812295
```

如果你在没有任何参数的情况下调用 API，它将是 API 服务器和 etcd 资源最密集的。此调用将在不进行分页或限制范围的情况下获取所有命名空间中的所有 pod，并且需要从 etcd 读取法定人数。

```
/api/v1/pods
```

### 防止 DaemonSet 雷鸣般的牛群
<a name="_prevent_daemonset_thundering_herds"></a>

A DaemonSet 可确保所有（或部分）节点都运行 Pod 的副本。当节点加入集群时，守护程序集控制器会为这些节点创建 Pod。当节点离开集群时，这些 Pod 会被垃圾回收。删除 a DaemonSet 会清理它创建的 pod。

a 的一些典型用法 DaemonSet 是：
+ 在每个节点上运行集群存储守护程序
+ 在每个节点上运行日志收集守护程序
+ 在每个节点上运行节点监视守护程序

在拥有数千个节点的集群上 DaemonSet，创建新节点 DaemonSet、更新节点或增加节点数量可能会导致控制平面上的负载过高。如果 DaemonSet Pod 在 Pod 启动时发出昂贵的 API 服务器请求，则大量并发请求可能会导致控制层面的资源使用率过高。

在正常操作中，您可以使用`RollingUpdate`来确保逐步推出新 DaemonSet pod。使用`RollingUpdate`更新策略，在你更新 DaemonSet 模板后，控制器会以受控的方式杀死旧 DaemonSet 的 p DaemonSet od 并自动创建新的 pod。在整个更新过程中 DaemonSet ，每个节点上最多只能运行一个 pod。您可以通过将设置`maxUnavailable`为 1、0 和 `minReadySeconds` 60 `maxSurge` 来执行逐步部署。如果您未指定更新策略，Kubernetes 将默认为创建一个，其值`RollingUpdate``maxUnavailable`为 1、`maxSurge`为 0 和 `minReadySeconds` 0。

```
minReadySeconds: 60
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 0
    maxUnavailable: 1
```

如果已经创建了新的 DaemonSet pod，并且所有节点上 DaemonSet 的 pod 数量都达到预期，则`RollingUpdate`可以确保逐步推出新`Ready`的 pod。在战略未涵盖的某些条件下，可能会出现雷鸣般的牛群问题。`RollingUpdate`

#### 防止在创建时出现雷鸣般的牛群 DaemonSet
<a name="_prevent_thundering_herds_on_daemonset_creation"></a>

默认情况下，无论`RollingUpdate`配置如何，在创建新节点时，中的守护程序集控制器 kube-controller-manager都会同时为所有匹配的节点创建 pod。 DaemonSet要在创建 pod 后强制逐步推出 pod DaemonSet，你可以使用`NodeSelector`或`NodeAffinity`。这将创建一个匹配零个节点的 DaemonSet ，然后你可以逐渐更新节点，使它们有资格以受控的速率从中 DaemonSet 运行 Pod。你可以遵循这种方法：
+ 为的所有节点添加标签`run-daemonset=false`。

```
kubectl label nodes --all run-daemonset=false
```
+ 使用 DaemonSet 与任何没有`run-daemonset=false`标签的节点相匹配的`NodeAffinity`设置来创建。最初，这将导致你 DaemonSet 没有相应的 pod。

```
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: run-daemonset
          operator: NotIn
          values:
          - "false"
```
+ 以受控的速率从节点上移除`run-daemonset=false`标签。你可以用这个 bash 脚本作为示例：

```
#!/bin/bash

nodes=$(kubectl get --raw "/api/v1/nodes" | jq -r '.items | .[].metadata.name')

for node in ${nodes[@]}; do
   echo "Removing run-daemonset label from node $node"
   kubectl label nodes $node run-daemonset-
   sleep 5
done
```
+ （可选）从 DaemonSet 对象中移除该`NodeAffinity`设置。请注意，由于 DaemonSet 模板已更改，这也会触发`RollingUpdate`并逐渐替换所有现有的 DaemonSet pod。

#### 防止节点横向扩展时出现雷鸣般的牛群
<a name="_prevent_thundering_herds_on_node_scale_outs"></a>

与 DaemonSet 创建类似，快速创建新节点可能会导致大量 DaemonSet Pod 同时启动。您应该以受控的速率创建新节点，以便控制器以相同的速率创建 DaemonSet Pod。如果无法做到这一点，则可以使用使新节点最初不符合现有 DaemonSet 节点的资格。`NodeAffinity`接下来，你可以逐渐为新节点添加标签，这样 daemonset-controller 就可以以受控的速率创建 Pod。你可以遵循这种方法：
+ 为所有现有节点添加标签 `run-daemonset=true` 

```
kubectl label nodes --all run-daemonset=true
```
+  DaemonSet 使用设置更新您的`NodeAffinity`设置，以匹配任何带有`run-daemonset=true`标签的节点。请注意，由于 DaemonSet 模板已更改，这也会触发`RollingUpdate`并逐渐替换所有现有的 DaemonSet pod。您应该等`RollingUpdate`到完成后再进入下一步。

```
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: run-daemonset
          operator: In
          values:
          - "true"
```
+ 在集群中创建新节点。请注意，这些节点没有`run-daemonset=true`标签，因此 DaemonSet 不会与这些节点匹配。
+ 以受控的速率将`run-daemonset=true`标签添加到您的新节点（当前没有`run-daemonset`标签）。你可以用这个 bash 脚本作为示例：

```
#!/bin/bash

nodes=$(kubectl get --raw "/api/v1/nodes?labelSelector=%21run-daemonset" | jq -r '.items | .[].metadata.name')

for node in ${nodes[@]}; do
   echo "Adding run-daemonset=true label to node $node"
   kubectl label nodes $node run-daemonset=true
   sleep 5
done
```
+ 或者，从 DaemonSet 对象中移除`NodeAffinity`设置并从所有节点上移除`run-daemonset`标签。

#### 防止更新时出现雷鸣般的牛群 DaemonSet
<a name="_prevent_thundering_herds_on_daemonset_updates"></a>

`RollingUpdate`策略只会尊重符合条件的 DaemonSet pod 的`maxUnavailable`设置`Ready`。如果 a 只 DaemonSet 有 `NotReady` pod 或很大比例的 `NotReady` pod，并且您更新了其模板，则守护程序控制器将同时为任何 pod 创建新的 pod。`NotReady`如果有大量的 `NotReady` pod，这可能会导致雷鸣般的牛群问题，例如，如果 pod 持续崩溃循环或无法拉取图像。

要在更新 a DaemonSet 且有 pod 时强制逐步推出 `NotReady` pod，可以暂时将更新策略 DaemonSet 从 from 更改`RollingUpdate`为`OnDelete`。在更新 DaemonSet 模板后`OnDelete`，控制器会在您手动删除旧的 pod 之后创建新的 pod，这样您就可以控制新 pod 的推出了。你可以遵循这种方法：
+ 检查您的`NotReady`吊舱里是否有吊舱 DaemonSet。
+ 如果不是，您可以放心地更新 DaemonSet 模板，该`RollingUpdate`策略将确保逐步推出。
+ 如果是，则应先更新您的策略 DaemonSet 以使用该`OnDelete`策略。

```
updateStrategy:
  type: OnDelete
```
+ 接下来，使用所需的更改更新您的 DaemonSet 模板。
+ 更新后，您可以通过以受控的速率发出删除 pod 请求来删除旧 DaemonSet pod。你可以用这个 bash 脚本作为示例，其中 kube-system 命名空间中的 DaemonSet 名字是 fluentd-elasticsearch：

```
#!/bin/bash

daemonset_pods=$(kubectl get --raw "/api/v1/namespaces/kube-system/pods?labelSelector=name%3Dfluentd-elasticsearch" | jq -r '.items | .[].metadata.name')

for pod in ${daemonset_pods[@]}; do
   echo "Deleting pod $pod"
   kubectl delete pod $pod -n kube-system
   sleep 5
done
```
+ 最后，你可以更新 DaemonSet 到之前的`RollingUpdate`策略。

# Kubernetes 数据平面
<a name="scale-data-plane"></a>

选择 EC2 实例类型可能是客户面临的最艰难的决定之一，因为在具有多个工作负载的集群中。没有万能 one-size-fits的解决办法。以下是一些技巧，可帮助您避免在扩展计算时遇到的常见陷阱。

## 自动节点自动缩放
<a name="_automatic_node_autoscaling"></a>

我们建议您使用节点自动缩放，这样可以减少工作量并与 Kubernetes 深度集成。对于大型@@ [集群，建议使用托管节点组](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html)和 [Karpenter](https://karpenter.sh/)。

托管节点组将为您提供 Amazon EC2 Auto Scaling 组的灵活性，并在托管升级和配置方面带来更多好处。它可以使用 [Kubernetes 集群自动扩缩器进行扩展](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler)，对于有各种计算需求的集群来说，这是一种常见的选择。

Karpenter 是一款由 AWS 创建的开源、工作负载原生节点自动扩缩器。它根据资源（例如 GPU）的工作负载要求以及污点和容忍度（例如区域分布）来扩展集群中的节点，而无需管理节点组。节点直接从 EC2 创建，这避免了默认的节点组配额（每组 450 个节点），并提供了更大的实例选择灵活性，同时减少了运营开销。我们建议客户尽可能使用 Karpenter。

## 使用许多不同的 EC2 实例类型
<a name="_use_many_different_ec2_instance_types"></a>

每个 AWS 区域每种实例类型的可用实例数量有限。如果您创建的集群仅使用一种实例类型，并且将节点数量扩展到该区域的容量之外，则会收到一条错误消息，提示没有可用的实例。为避免此问题，您不应任意限制可在集群中使用的实例类型。

默认情况下，Karpenter 将使用一系列兼容的实例类型，并将在配置时根据待处理的工作负载要求、可用性和成本选择实例。您可以扩大`karpenter.k8s.aws/instance-category`密钥中使用的实例类型的列表[NodePools](https://karpenter.sh/docs/concepts/nodepools/#instance-types)。

Kubernetes 集群自动扩缩器要求节点组的大小必须相似，这样它们才能持续扩展。您应该根据 CPU 和内存大小创建多个组，并分别对其进行扩展。使用 [ec2-instance-selector](https://github.com/aws/amazon-ec2-instance-selector) 来识别您的节点组大小相似的实例。

```
ec2-instance-selector --service eks --vcpus-min 8 --memory-min 16
a1.2xlarge
a1.4xlarge
a1.metal
c4.4xlarge
c4.8xlarge
c5.12xlarge
c5.18xlarge
c5.24xlarge
c5.2xlarge
c5.4xlarge
c5.9xlarge
c5.metal
```

## 最好使用更大的节点来减少 API 服务器的负载
<a name="_prefer_larger_nodes_to_reduce_api_server_load"></a>

在决定使用哪些实例类型时，较少的大型节点会减少 Kubernetes 控制平面的负载，因为运行的 kubelet 会更少。 DaemonSets 但是，大型节点可能无法像较小的节点那样得到充分利用。应根据您的工作负载可用性和规模要求评估节点大小。

一个包含三个 u-24tb1.metal 实例（24 TB 内存和 448 个内核）的集群有 3 个 kubelet，默认情况下每个节点的容量限制为 110 个。如果你的 pod 每个使用 4 个内核，那么这可能是预料之中的（4 个内核 x 110 = 440 个内核/节点）。对于 3 节点集群，您处理实例事件的能力会很低，因为 1 个实例中断可能会影响 1/3 的集群。你应该在工作负载中指定节点要求和 Pod 分布，这样 Kubernetes 调度器就可以正确放置工作负载。

工作负载应通过污点、容忍度和，来定义他们需要的资源和所需的可用性。[PodTopologySpread](https://kubernetes.io/blog/2020/05/introducing-podtopologyspread/)他们应该选择可以充分利用并满足可用性目标的最大节点，以减少控制平面负载、降低操作和降低成本。

如果资源可用，Kubernetes 调度器将自动尝试将工作负载分散到可用区和主机上。如果没有可用容量，Kubernetes 集群自动扩缩程序将尝试在每个可用区中均匀添加节点。除非工作负载指定了其他要求，否则 Karpenter 将尝试以尽可能快和低廉的价格添加节点。

要强制工作负载随调度器分散并跨可用区创建新节点，应使用 topologySpreadConstraints：

```
spec:
  topologySpreadConstraints:
    - maxSkew: 3
      topologyKey: "topology.kubernetes.io/zone"
      whenUnsatisfiable: ScheduleAnyway
      labelSelector:
        matchLabels:
          dev: my-deployment
    - maxSkew: 2
      topologyKey: "kubernetes.io/hostname"
      whenUnsatisfiable: ScheduleAnyway
      labelSelector:
        matchLabels:
          dev: my-deployment
```

## 使用相似的节点大小以获得一致的工作负载性能
<a name="_use_similar_node_sizes_for_consistent_workload_performance"></a>

工作负载应定义它们需要在多大小的节点上运行，以实现稳定的性能和可预测的扩展。请求 500 米 CPU 的工作负载在具有 4 个内核的实例和具有 16 个内核的实例上的性能会有所不同。避免使用 CPUs 像 T 系列实例那样使用可突发性能的实例类型。

为了确保您的工作负载获得稳定的性能，工作负载可以使用[支持的 Karpenter 标签](https://karpenter.sh/docs/concepts/scheduling/#labels)来定位特定的实例大小。

```
kind: deployment
...
spec:
  template:
    spec:
    containers:
    nodeSelector:
      karpenter.k8s.aws/instance-size: 8xlarge
```

使用 Kubernetes 集群自动扩缩器在集群中调度的工作负载应根据标签匹配将节点选择器与节点组进行匹配。

```
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: eks.amazonaws.com/nodegroup
            operator: In
            values:
            - 8-core-node-group    # match your node group name
```

## 高效使用计算资源
<a name="_use_compute_resources_efficiently"></a>

计算资源包括 EC2 实例和可用区。有效使用计算资源将提高您的可扩展性、可用性和性能，并降低总成本。在具有多个应用程序的自动缩放环境中，很难预测资源的有效使用情况。[Karpenter](https://karpenter.sh/) 旨在根据工作负载需求按需配置实例，从而最大限度地提高利用率和灵活性。

Karpenter 允许工作负载声明其所需的计算资源类型，而无需先创建节点组或为特定节点配置标签污点。有关更多信息，请参阅 [Karpenter 最佳实践](https://docs.aws.amazon.com/eks/latest/best-practices/karpenter.html)。考虑在 Karpenter 配置器中启用[整合](https://docs.aws.amazon.com/eks/latest/best-practices/karpenter.html#_scheduling_pods)，以替换未充分利用的节点。

## 自动更新亚马逊系统映像 (AMI)
<a name="_automate_amazon_machine_image_ami_updates"></a>

使工作节点组件保持最新状态将确保您拥有最新的安全补丁和与 Kubernetes API 兼容的功能。更新 kubelet 是 Kubernetes 功能最重要的组件，但是自动化操作系统、内核和本地安装的应用程序补丁将减少在扩展时的维护工作。

建议你为节点映像使用最新的[亚马逊 EKS 优化的亚马逊 Linux 2](https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami.html) 或[亚马逊 EKS 优化的 Bottlerocket AM](https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami-bottlerocket.html) I。Karpenter 将自动使用[最新的可用的 AMI](https://karpenter.sh/docs/concepts/nodepools/#instance-types) 在集群中配置新节点。托管节点组将在[节点组更新期间更新](https://docs.aws.amazon.com/eks/latest/userguide/update-managed-node-group.html) AMI，但不会在节点配置时更新 AMI ID。

对于托管节点组，当补丁版本可用 IDs 时，您需要使用新 AMI 更新 Auto Scaling 组 (ASG) 启动模板。AMI 次要版本（例如 1.23.5 到 1.24.3）将作为节点组的[升级](https://docs.aws.amazon.com/eks/latest/userguide/update-managed-node-group.html)在 EKS 控制台和 API 中提供。补丁发布版本（例如 1.23.5 到 1.23.6）不会作为节点组的升级提供。如果您想让您的节点组与 AMI 补丁版本保持同步，则需要创建新的启动模板版本，并让节点组用新的 AMI 版本替换实例。

您可以从[此页面](https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami.html)找到最新的可用的 AMI，也可以使用 AWS CLI。

```
aws ssm get-parameter \
  --name /aws/service/eks/optimized-ami/1.24/amazon-linux-2/recommended/image_id \
  --query "Parameter.Value" \
  --output text
```

## 为容器使用多个 EBS 卷
<a name="_use_multiple_ebs_volumes_for_containers"></a>

EBS 卷的 input/output （I/O）配额取决于卷的类型（例如 gp3）和磁盘的大小。如果您的应用程序与主机共享一个 EBS 根卷，则可能会耗尽整个主机的磁盘配额，并导致其他应用程序等待可用容量。如果应用程序将文件写入其覆盖分区，从主机装载本地卷，以及登录到标准输出 (STDOUT)，则会写入磁盘，具体取决于所使用的日志代理。

为避免磁盘 I/O 耗尽，您应该将第二个卷挂载到容器状态文件夹（例如 /run/containerd），使用单独的 EBS 卷存储工作负载，并禁用不必要的本地日志记录。

要使用 [eksctl](https://eksctl.io/) 将第二个卷挂载到您的 EC2 实例，您可以使用具有以下配置的节点组：

```
managedNodeGroups:
  - name: al2-workers
    amiFamily: AmazonLinux2
    desiredCapacity: 2
    volumeSize: 80
    additionalVolumes:
      - volumeName: '/dev/sdz'
        volumeSize: 100
    preBootstrapCommands:
    - |
      "systemctl stop containerd"
      "mkfs -t ext4 /dev/nvme1n1"
      "rm -rf /var/lib/containerd/*"
      "mount /dev/nvme1n1 /var/lib/containerd/"
      "systemctl start containerd"
```

如果你使用 terraform 来配置节点组，请参阅 [EKS 蓝图](https://aws-ia.github.io/terraform-aws-eks-blueprints/patterns/stateful/#eks-managed-nodegroup-w-multiple-volumes)中适用于 terraform 的示例。如果您使用 Karpenter 来配置节点，则可以使用[https://karpenter.sh/docs/concepts/nodeclasses/#specblockdevicemappings](https://karpenter.sh/docs/concepts/nodeclasses/#specblockdevicemappings)节点用户数据来添加其他卷。

要将 EBS 卷直接挂载到您的容器中，您应该使用 [AWS EBS CSI 驱动程序](https://github.com/kubernetes-sigs/aws-ebs-csi-driver)并使用具有存储类的卷。

```
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ebs-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ebs-sc
  resources:
    requests:
      storage: 4Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: public.ecr.aws/docker/library/nginx
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: ebs-claim
```

## 如果工作负载使用 EBS 卷，请避开 EBS 连接限制较低的实例
<a name="_avoid_instances_with_low_ebs_attach_limits_if_workloads_use_ebs_volumes"></a>

EBS 是工作负载获得持久存储的最简单方法之一，但它也存在可扩展性限制。每种实例类型都有可[连接的最大数量的 EBS 卷](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/volume_limits.html)。工作负载需要声明它们应该在哪些实例类型上运行，并限制带有 Kubernetes 污染的单个实例上的副本数量。

## 禁用不必要的磁盘日志记录
<a name="_disable_unnecessary_logging_to_disk"></a>

不要在生产环境中运行带有调试日志的应用程序，并禁用频繁读写磁盘的日志记录，从而避免不必要的本地日志记录。Journald 是一种本地日志服务，它在内存中保留日志缓冲区并定期刷新到磁盘。Journald 比 syslog 更受青睐，后者会立即将每行记录到磁盘。禁用 syslog 还可以降低所需的存储总量，并避免需要复杂的日志轮换规则。要禁用 syslog，您可以将以下代码段添加到 cloud-init 配置中：

```
runcmd:
  - [ systemctl, disable, --now, syslog.service ]
```

## 当需要操作系统更新速度时，将实例修补到位
<a name="_patch_instances_in_place_when_os_update_speed_is_a_necessity"></a>

**重要**  
只有在需要时才应在原地修补实例。Amazon 建议将基础设施视为不可变的，并像应用程序一样彻底测试在较低环境中推广的更新。当无法做到这一点时，本节适用。

在不中断容器化工作负载的情况下，在现有 Linux 主机上安装软件包需要几秒钟。无需封锁、耗尽或更换实例即可安装和验证软件包。

要替换实例，您首先需要创建、验证和分发新实例 AMIs。该实例需要创建替代实例，并且需要封锁和排空旧实例。然后，需要在新实例上创建工作负载，对其进行验证，然后重复所有需要修补的实例。在不中断工作负载的情况下安全更换实例需要数小时、数天或数周的时间。

Amazon 建议使用基于自动声明式系统构建、测试和升级的不可变基础设施，但是如果您需要快速修补系统，则需要对现有系统进行修补并在新 AMIs 系统问世时进行更换。由于修补和更换系统之间的时间差异很大，我们建议在需要时使用 [AWS Systems Manager Patch Manager 自动修补](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-patch.html)节点。

在 AMI 更新后，通过修补节点，您可以快速推出安全更新并定期更换实例。如果您使用的操作系统具有只读根文件系统，例如 [Flatcar Container Linux](https://flatcar-linux.org/) 或 [Bottlerocket OS](https://github.com/bottlerocket-os/bottlerocket)，我们建议您使用适用于这些操作系统的更新运算符。[Flatcar Linux 更新操作员](https://github.com/flatcar/flatcar-linux-update-operator)和 [Bottlerocket 更新操作员](https://github.com/bottlerocket-os/bottlerocket-update-operator)将重启实例，使节点自动保持最新状态。

# 集群服务
<a name="scale-cluster-services"></a>

集群服务在 EKS 集群内运行，但它们不是用户工作负载。如果您使用的是 Linux 服务器，则通常需要运行 NTP、syslog 和容器运行时等服务来支持您的工作负载。集群服务类似，支持服务可帮助您实现集群的自动化和运营。在 Kubernetes 中，它们通常在 kube-system 命名空间中运行，有些则以 kube-system 命名空间运行。[DaemonSets](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/)

集群服务预计会有较长的正常运行时间，并且通常在停机期间和故障排除中起着至关重要的作用。如果核心群集服务不可用，您可能会无法访问有助于恢复或防止中断的数据（例如磁盘利用率高）。它们应在专用计算实例（例如单独的节点组或 AWS Fargate）上运行。这将确保共享实例上的集群服务不会受到可能正在扩展或使用更多资源的工作负载的影响。

## 扩展 CoreDNS
<a name="scale-coredns"></a>

扩展 CoreDNS 有两种主要机制。减少对 CoreDNS 服务的调用次数并增加副本数量。

### 通过降低 ndots 来减少外部查询
<a name="_reduce_external_queries_by_lowering_ndots"></a>

ndots 设置指定域名中有多少句点（又名 “点”）被认为足以避免查询 DNS。如果您的应用程序的 ndots 设置为 5（默认），并且您从外部域（例如 api.example.com）请求资源（2 个点），则系统将针对/etc/resolv.conf 中为更具体的域名定义的每个搜索域查询 CoreDNS。默认情况下，在发出外部请求之前将搜索以下域。

```
api.example.<namespace>.svc.cluster.local
api.example.svc.cluster.local
api.example.cluster.local
api.example.<region>.compute.internal
```

`namespace`和`region`值将替换为您的工作负载命名空间和计算区域。根据您的集群设置，您可能还有其他搜索域。

您可以通过降低工作负载的 nd [ots 选项来减少 CoreDNS 的请求数量，或者通过](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config)添加尾随来完全限定域名请求。 （例如`api.example.com.`）。如果您的工作负载通过 DNS 连接到外部服务，我们建议将 ndots 设置为 2，这样工作负载就不会在集群内进行不必要的集群 DNS 查询。如果工作负载不需要访问集群内部的服务，则可以设置不同的 DNS 服务器和搜索域。

```
spec:
  dnsPolicy: "None"
  dnsConfig:
    options:
      - name: ndots
        value: "2"
      - name: edns0
```

如果您将 ndots 降低到过低的值，或者您要连接的域名不包括足够的特异性（包括尾随字符。），那么 DNS 查找可能会失败。请务必测试此设置将如何影响您的工作负载。

### 水平扩展 CoreDNS
<a name="_scale_coredns_horizontally"></a>

CoreDNS 实例可以通过向部署中添加其他副本来进行扩展。建议您使用 [NodeLocal DNS](https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/) 或[集群比例自动扩缩器来扩展](https://github.com/kubernetes-sigs/cluster-proportional-autoscaler) CoreDNS。

NodeLocal DNS 将要求每个节点运行一个实例， DaemonSet这需要集群中的更多计算资源，但它可以避免 DNS 请求失败并缩短集群中 DNS 查询的响应时间。集群比例自动扩缩器将根据集群中的节点或内核数量扩展 CoreDNS。这与请求查询没有直接关系，但根据您的工作负载和集群大小，这可能会很有用。默认的比例比例是为集群中每 256 个内核或 16 个节点添加一个额外的副本，以先发生者为准。

[如果使用 [CoreDNS EKS](https://docs.aws.amazon.com/eks/latest/userguide/managing-coredns.html) 附加组件，请考虑启用自动缩放选项。](https://docs.aws.amazon.com/eks/latest/userguide/coredns-autoscaling.html)CoreDNS 自动扩缩器通过监控节点数和 CPU 内核来动态调整 CoreDNS 副本的数量，其公式取最大值（nodes^16）或（CPU cores^256），在需要时立即向上扩展，然后逐渐向下扩展以保持稳定性。

## 垂直扩展 Kubernetes 指标服务器
<a name="_scale_kubernetes_metrics_server_vertically"></a>

Kubernetes 指标服务器支持水平和垂直扩展。通过水平扩展 Metrics Server，它将具有很高的可用性，但它不会水平扩展以处理更多的集群指标。将节点和收集的指标添加到集群后[，您需要根据指标服务器的建议](https://kubernetes-sigs.github.io/metrics-server/#scaling)垂直扩展。

指标服务器将其收集、聚合和提供的数据保存在内存中。随着集群的增长，Metrics Server 存储的数据量也会增加。在大型集群中，Metrics Server 需要的计算资源将超过默认安装中指定的内存和 CPU 预留量。你可以使用 Vertic [al Pod Autoscaler](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler) (VPA) 或 [Addon Resizer 来扩展 Metrics Server](https://github.com/kubernetes/autoscaler/tree/master/addon-resizer)。Addon Resizer 根据工作节点成比例垂直扩展，VPA 基于 CPU 和内存使用率进行扩展。

## CoreDNS lameduck 时长
<a name="_coredns_lameduck_duration"></a>

Pod 使用该`kube-dns`服务进行名称解析。Kubernetes 使用目标 NAT (DNAT) 将`kube-dns`流量从节点重定向到 CoreDNS 后端容器。在扩展 CoreDNS 部署时，会更新节点上的 iptables 规则和链`kube-proxy`，将 DNS 流量重定向到 CoreDNS 容器。向上扩展时传播新的终端节点和缩小规模 CoreDNS 时删除规则可能需要 1 到 10 秒的时间，具体取决于集群的大小。

当 CoreDNS 容器被终止但该节点的 iptables 规则尚未更新时，这种传播延迟可能会导致 DNS 查找失败。在这种情况下，该节点可能会继续向已终止的 CoreDNS 容器发送 DNS 查询。

你可以通过在 CoreDNS 容器中设置 l [ameduck](https://coredns.io/plugins/health/) 时长来减少 DNS 查询失败。在 lameduck 模式下，CoreDNS 将继续响应机上请求。设置 lameduck 持续时间会延迟 CoreDNS 的关闭过程，从而让节点有时间更新 iptables 规则和链。

我们建议将 CoreDNS lameduck 持续时间设置为 30 秒。

## CoreDNS 就绪情况调查
<a name="_coredns_readiness_probe"></a>

我们建议使用`/ready`代替CoreDNS的就绪性探测。`/health`

与之前关于将 lameduck 持续时间设置为 30 秒的建议一致，为节点的 iptables 规则在吊舱终止之前提供充足的时间进行更新，使用`/ready`而不是用于 CoreDNS 就绪探测可确保 CoreDNS 容器在启动时做好充分准备，可以迅速响应 DNS 请求。`/health`

```
readinessProbe:
  httpGet:
    path: /ready
    port: 8181
    scheme: HTTP
```

有关 CoreDNS Ready 插件的更多信息，请参阅 https://coredns。 io/plugins/ready/

## 记录和监视代理
<a name="_logging_and_monitoring_agents"></a>

日志和监控代理可能会给集群控制平面增加大量负载，因为代理会查询 API 服务器以使用工作负载元数据丰富日志和指标。节点上的代理只能访问本地节点资源来查看容器和进程名称之类的内容。查询 API 服务器可以添加更多详细信息，例如 Kubernetes 部署名称和标签。这对于故障排除非常有用，但不利于扩展。

由于有许多不同的日志和监控选项，因此我们无法为每个提供商展示示例。在 f [luentbi](https://docs.fluentbit.io/manual/pipeline/filters/kubernetes) t 中，我们建议启用 Use\$1Kubelet 从本地 kubelet 而不是 Kubernetes API 服务器获取元数据，并将其设置为`Kube_Meta_Cache_TTL`一个在可以缓存数据时减少重复调用的数字（例如 60）。

扩展监控和日志记录有两个常规选项：
+ 禁用集成
+ 采样和过滤

禁用集成通常不是一种选择，因为您会丢失日志元数据。这消除了 API 扩展问题，但由于在需要时没有所需的元数据，还会带来其他问题。

采样和过滤可以减少收集的指标和日志的数量。这将减少对 Kubernetes API 的请求量，并减少收集的指标和日志所需的存储量。降低存储成本将降低整个系统的成本。

配置采样的能力取决于代理软件，可以在不同的摄取点实现。在尽可能靠近代理的地方添加采样很重要，因为这可能是 API 服务器调用发生的地方。请联系您的提供商，了解有关采样支持的更多信息。

如果您使用的是 CloudWatch 和 CloudWatch 日志，则可以使用[文档中描述](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html)的模式添加代理筛选。

为避免丢失日志和指标，您应将数据发送到能够缓冲数据的系统，以防接收端点出现故障。有了 fluentbit，你可以使用 [Amazon Kinesis Data](https://docs.fluentbit.io/manual/pipeline/outputs/firehose) Firehose 来临时保留数据，从而减少最终数据存储位置过载的机会。

# 工作负载
<a name="scale-workloads"></a>

工作负载会影响集群的扩展规模。 APIs 大量使用 Kubernetes 的工作负载会限制您在单个集群中可以拥有的工作负载总量，但是您可以更改一些默认值以帮助减少负载。

Kubernetes 集群中的工作负载可以访问与 Kubernetes API 集成的功能（例如 Secrets 和 ServiceAccounts），但是这些功能并不总是必需的，如果不使用这些功能，则应将其禁用。限制工作负载访问和对 Kubernetes 控制平面的依赖将增加你可以在集群中运行的工作负载数量，并通过消除对工作负载的不必要访问和实施最低权限做法来提高集群的安全性。请阅读[安全最佳实践](https://docs.aws.amazon.com/eks/latest/best-practices/security.html)，了解更多信息。

## 用 IPv6 于 pod 联网
<a name="_use_ipv6_for_pod_networking"></a>

您无法将 VPC 从过渡 IPv4 到 IPv6 ，因此 IPv6 在配置集群之前启用非常重要。如果您在 VPC IPv6 中启用，并不意味着您必须使用它，如果您的 pod 和服务使用了， IPv6 您仍然可以路由往返 IPv4 地址的流量。有关更多信息，请参阅 [EKS 网络最佳实践](https://docs.aws.amazon.com/eks/latest/best-practices/networking.html)。

[IPv6 在集群中使用](https://docs.aws.amazon.com/eks/latest/userguide/cni-ipv6.html)可以避免一些最常见的集群和工作负载扩展限制。 IPv6 避免因没有 IP 地址可用而无法创建 Pod 和节点的 IP 地址耗尽。它还提高了每个节点的性能，因为通过减少每个节点的 ENI 连接数量，Pod 可以更快地接收 IP 地址。在 [VPC CNI 中使用IPv4 前缀模式](https://docs.aws.amazon.com/eks/latest/best-practices/prefix-mode-linux.html)可以实现类似的节点性能，但仍需要确保 VPC 中有足够的 IP 地址可用。

## 限制每个命名空间的服务数量
<a name="_limit_number_of_services_per_namespace"></a>

[一个命名空间中的最大服务数为 5,000，集群中的最大服务数为](https://github.com/kubernetes/community/blob/master/sig-scalability/configs-and-limits/thresholds.md) 10,000。为了帮助组织工作负载和服务、提高性能并避免对命名空间范围内的资源造成级联影响，我们建议将每个命名空间的服务数量限制在 500 个。

使用 kube-proxy 为每个节点创建的 IP 表规则数量会随着集群中服务总数的增加而增长。生成成千上万个 IP 表规则并通过这些规则路由数据包会影响节点的性能并增加网络延迟。

只要每个命名空间的服务数量低于 500，即可创建包含单个应用程序环境的 Kubernetes 命名空间。这将使服务发现足够小，以避免服务发现限制，还可以帮助您避免服务命名冲突。应用程序环境（例如开发、测试、生产）应使用单独的 EKS 集群而不是命名空间。

## 了解 Elastic 负载均衡器配额
<a name="_understand_elastic_load_balancer_quotas"></a>

创建服务时，请考虑将使用哪种类型的负载平衡（例如网络负载均衡器 (NLB) 或应用程序负载均衡器 (ALB)）。每种负载均衡器类型提供不同的功能和[不同的配额](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-limits.html)。有些默认配额可以调整，但有些配额上限无法更改。要查看您的账户配额和使用情况，请查看 AWS 控制台中的 S [ervice Quotas 控制面板](https://console.aws.amazon.com/servicequotas)。

例如，ALB 的默认目标是 1000。如果您的服务拥有超过 1000 个终端节点，则需要增加配额或将服务拆分为多个终端节点， ALBs 或者使用 Kubernetes Ingress。NLB 的默认目标是 3000，但每个可用区限制为 500 个目标。如果您的集群为 NLB 服务运行的容量超过 500 个，则需要使用多个容器 AZs 或请求提高配额限制。

使用与服务耦合的负载均衡器的另一种方法是使用[入口控制器](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/)。AWS Load Balancer 控制器可以 ALBs 为入口资源创建，但您可以考虑在集群中运行专用控制器。集群内入口控制器允许您通过在集群内运行反向代理来从单个负载均衡器公开多个 Kubernetes 服务。控制器具有不同的功能，例如支持 [Gateway API](https://gateway-api.sigs.k8s.io/)，根据您的工作负载的数量和规模，这些功能可能会带来好处。

## 使用 Route 53、Global Accelerator 或 CloudFront
<a name="_use_route_53_global_accelerator_or_cloudfront"></a>

要将使用多个负载均衡[器的服务作为单个终端节点使用，您需要使用[亚马逊 CloudFront](https://aws.amazon.com/cloudfront/)、AWS Global A](https://aws.amazon.com/global-accelerator/) ccelerator 或 A [mazon Route 53](https://aws.amazon.com/route53/) 将所有负载均衡器作为单个面向客户的终端节点公开。每个选项都有不同的好处，可以根据需要单独使用或一起使用。

Route 53 可以用一个通用名称公开多个负载均衡器，并且可以根据分配的权重向每个负载均衡器发送流量。您可以在[文档中阅读有关 DNS 权重的更多信息，也可以在](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-values-weighted.html#rrsets-values-weighted-weight) AWS Load B [alancer Controller 文档中阅读如何使用 [Kubernetes 外部 DNS 控制器](https://github.com/kubernetes-sigs/external-dns)实现这些权重。](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/integrations/external_dns/#usage)

全球加速器可以根据请求的 IP 地址将工作负载路由到最近的区域。这对于部署到多个区域的工作负载可能很有用，但它并不能改善到单个区域中单个集群的路由。将 Route 53 与全球加速器结合使用还有其他好处，例如运行状况检查和在可用区不可用时自动进行故障转移。您可以[在这篇博客文章](https://aws.amazon.com/blogs/containers/operating-a-multi-regional-stateless-application-using-amazon-eks/)中看到在 Route 53 上使用全球加速器的示例。

CloudFront 可以与 Route 53 和全球加速器一起使用，也可以单独使用将流量路由到多个目的地。 CloudFront 缓存从原始来源提供的资产，这可能会降低带宽需求，具体取决于您提供的服务。

## 使用 EndpointSlices 代替终端节点
<a name="_use_endpointslices_instead_of_endpoints"></a>

在发现与服务标签匹配的 pod 时，应使用[EndpointSlices](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/)而不是终端节点。端点是小规模公开服务的简单方法，但是自动扩展或有更新的大型服务会导致 Kubernetes 控制平面上的大量流量。 EndpointSlices 有自动分组，可以启用拓扑感知提示之类的东西。

并非所有控制器都 EndpointSlices 默认使用。您应该验证您的控制器设置并在需要时将其启用。对于 [AWS Load Balancer 控制器](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/deploy/configurations/#controller-command-line-flags)，您应该启用要使用的`--enable-endpoint-slices`可选标志 EndpointSlices。

## 如果可能，请使用不可变的和外部的机密
<a name="_use_immutable_and_external_secrets_if_possible"></a>

kubelet 会保留当前密钥和密钥值的缓存，这些密钥用于该节点上 Pod 的卷中。kubelet 会监视秘密以检测变化。随着集群的扩展，监视数量的增加可能会对 API 服务器性能产生负面影响。

有两种策略可以减少 Secrets 上的观看次数：
+ 对于不需要访问 Kubernetes 资源的应用程序，你可以通过设置 Token: false 来禁用自动挂载的服务账号密钥 automountServiceAccount
+ 如果您的应用程序的密钥是静态的，并且将来不会被修改，请将该[密钥标记为不可变](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable)。kubelet 不维护 API 监视不可变的机密。

要禁用自动将服务帐户挂载到 pod，可以在工作负载中使用以下设置。如果特定工作负载需要服务帐号，则可以覆盖这些设置。

```
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app
automountServiceAccountToken: true
```

在集群中的密钥数量超过 10,000 个限制之前对其进行监控。您可以使用以下命令查看集群中的密钥总数。您应该通过集群监控工具监控此限制。

```
kubectl get secrets -A | wc -l
```

您应该设置监控，以便在达到此限制之前提醒集群管理员。考虑使用外部机密管理选项，例如带有 Secrets [St](https://secrets-store-csi-driver.sigs.k8s.io/) ore CSI 驱动程序[的 AWS 密钥管理服务 (AWS KMS)](https://aws.amazon.com/kms/) 或 [Hashicorp Vault](https://www.vaultproject.io/)。

## 限制部署历史记录
<a name="_limit_deployment_history"></a>

创建、更新或删除 Pod 时可能会很慢，因为集群中仍会跟踪旧对象。你可以减少[部署以清理较旧`revisionHistoryLimit`的部署](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#clean-up-policy) ReplicaSets ，这将降低到 Kubernetes 控制器管理器跟踪的对象总数。10 中部署的默认历史记录限制。

如果您的集群通过 CronJobs 或其他机制创建了大量任务对象，则应使用该[`ttlSecondsAfterFinished`设置](https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/)自动清理集群中的旧 pod。这将在指定时间后从任务历史记录中删除成功执行的作业。

## enableServiceLinks 默认情况下禁用
<a name="_disable_enableservicelinks_by_default"></a>

当 Pod 在节点上运行时，kubelet 会为每个活动服务添加一组环境变量。Linux 进程的环境大小有上限，如果命名空间中有太多服务，则可以达到该大小。每个命名空间的服务数量不应超过 5,000。此后，服务环境变量的数量超过了 shell 限制，导致 Pod 在启动时崩溃。

Pod 不应使用服务环境变量进行服务发现还有其他原因。环境变量名称冲突、泄漏的服务名和环境总大小不一而足。您应该使用 CoreDNS 来发现服务端点。

## 限制每个资源的动态准入 webhook
<a name="_limit_dynamic_admission_webhooks_per_resource"></a>

 [动态准入 Webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) 包括准入 webhook 和变异 Webhook。它们是 API 端点，不是 Kubernetes 控制平面的一部分，当资源发送到 Kubernetes API 时，它们会按顺序调用。每个 webhook 的默认超时时间为 10 秒，如果您有多个 webhook 或其中任何一个 webhook 超时，则可以增加 API 请求所花费的时间。

确保您的 webhook 具有高可用性（尤其是在可用区事件期间），并正确设置 Fa [ilurePolicy 以拒绝资源或忽略故障](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy)。通过允许--dry-run kubectl 命令绕过 webhook，不要在不需要时调用 webhook。

```
apiVersion: admission.k8s.io/v1
kind: AdmissionReview
request:
  dryRun: False
```

改变 webhook 可以频繁地连续修改资源。如果你有 5 个变异 webhook 并部署 50 个资源，etcd 将存储每种资源的所有版本，直到每隔 5 分钟进行一次压缩，以移除修改后的资源的旧版本。在这种情况下，当 etcd 删除被取代的资源时，将从 etcd 中删除 200 个资源版本，根据资源的大小，可能会在 etcd 主机上占用大量空间，直到每 15 分钟进行一次碎片整理。

这种碎片整理可能会导致 etcd 暂停，这可能会对 Kubernetes API 和控制器产生其他影响。应避免频繁修改大型资源或快速连续修改数百个资源。

## 比较多个集群中的工作负载
<a name="_compare_workloads_across_multiple_clusters"></a>

如果您有两个集群的性能应该相似但却没有，请尝试比较指标以找出原因。

例如，比较集群延迟是一个常见问题。这通常是由于 API 请求量的差异造成的。你可以运行以下 CloudWatch LogInsight 查询来了解其中的区别。

```
filter @logStream like "kube-apiserver-audit"
| stats count(*) as cnt by objectRef.apiGroup, objectRef.apiVersion, objectRef.resource, userAgent, verb, responseStatus.code
| sort cnt desc
| limit 1000
```

您可以添加其他过滤器来缩小范围。例如，将重点放在来自的所有列表请求上`foo`。

```
filter @logStream like "kube-apiserver-audit"
| filter verb = "list"
| filter user.username like "foo"
| stats count(*) as cnt by objectRef.apiGroup, objectRef.apiVersion, objectRef.resource, responseStatus.code
| sort cnt desc
| limit 1000
```

# Kubernetes 缩放理论
<a name="kubernetes_scaling_theory"></a>

## 节点与流失率
<a name="_nodes_vs_churn_rate"></a>

通常，当我们讨论 Kubernetes 的可扩展性时，我们会根据单个集群中有多少节点来讨论这个问题。有趣的是，这很少是理解可扩展性的最有用的指标。例如，拥有大量但固定数量的 pod 的 5,000 个节点的集群在初始设置后不会给控制平面带来太大的压力。但是，如果我们选择一个 1,000 个节点的集群，并尝试在不到一分钟的时间内创建 10,000 个短暂的工作岗位，那将给控制平面带来巨大的持续压力。

仅仅使用节点数量来理解扩展可能会产生误导。最好从特定时间段内发生的变化率来考虑（让我们使用 5 分钟的时间间隔进行讨论，因为这是 Prometheus 查询通常默认使用的）。让我们探讨一下为什么用变化率来描述问题可以让我们更好地了解要调整什么才能达到我们想要的规模。

## 每秒查询次数中的思考
<a name="_thinking_in_queries_per_second"></a>

Kubernetes 为每个组件（Kubelet、调度器、Kube 控制器管理器和 API 服务器）提供了多种保护机制，以防止 Kubernetes 链中的下一个链接不堪重负。例如，Kubelet 有一个标志，可以按一定速率限制对 API 服务器的调用。这些保护机制通常但并非总是以每秒允许的查询或 QPS 来表达。

更改这些 QPS 设置时必须格外小心。移除一个瓶颈，例如 Kubelet 上的每秒查询次数，将对其他下游组件产生影响。这可能会而且将会超过一定速度使系统不堪重负，因此，了解和监控服务链的每个部分是成功扩展 Kubernetes 上工作负载的关键。

**注意**  
API 服务器的系统更加复杂，引入了 API 优先级和公平性，我们将单独讨论。

**注意**  
注意，有些指标看起来很合适，但实际上是在衡量其他指标。举个例子，它只与 Kubelet 中的指标服务器`kubelet_http_inflight_requests`有关，而不是从 Kubelet 向 apiserver 请求的请求数量。这可能会导致我们错误配置 Kubelet 上的 QPS 标志。查询特定 Kubelet 的审计日志将是检查指标的更可靠方法。

## 扩展分布式组件
<a name="_scaling_distributed_components"></a>

由于 EKS 是一项托管服务，让我们将 Kubernetes 组件分为两类：AWS 托管组件，包括 etcd、Kube 控制器管理器和调度器（在图表的左侧），以及客户可配置的组件，例如 Kubelet、容器运行时以及调用 AWS 的各种运算符， APIs 例如网络和存储驱动程序（在图的右侧）。尽管 API 服务器由 AWS 管理，但我们仍将其置于中间，因为 API 优先级和公平性设置可以由客户配置。

![\[Kubernetes 组件\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/k8s-components.png)


## 上游和下游瓶颈
<a name="_upstream_and_downstream_bottlenecks"></a>

当我们监控每项服务时，重要的是要双向查看指标，以寻找瓶颈。让我们以 Kubelet 为例，学习如何做到这一点。Kubelet 会与 API 服务器和容器运行时进行交谈；我们需要**如何以及如何**监控**哪些**组件来检测任何一个组件是否遇到问题？

### 每个节点有多少 Pod
<a name="_how_many_pods_per_node"></a>

当我们查看缩放数字（例如一个节点上可以运行多少个 Pod）时，我们可以按面值计算上游支持的每个节点 110 个 pod。

**注意**  
https://kubernetes。 io/docs/setup/best-practices/cluster-large/

但是，您的工作负载可能比在上游的可扩展性测试中测试的要复杂得多。为了确保我们可以为要在生产环境中运行的 pod 数量提供服务，让我们确保 Kubelet “跟上” 了 Containerd 运行时的步伐。

![\[跟上\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/keeping-up.png)


简而言之，Kubelet 从容器运行时（在我们的例子中为 Containerd）获取 pod 的状态。如果我们有太多的 pod 过快地改变状态会怎样？ 如果更改率太高，[对容器运行时] 的请求可能会超时。

**注意**  
Kubernetes 在不断发展，这个子系统目前正在发生变化。 https://github.com/kubernetes/增强功能/问题/3386

![\[流\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/flow.png)


![\[PLEG 持续时间\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/PLEG-duration.png)


在上图中，我们看到一条扁线，表示我们刚刚达到了 pod 生命周期事件生成持续时间指标的超时值。如果你想在自己的集群中看到这个，你可以使用以下 PromQL 语法。

```
increase(kubelet_pleg_relist_duration_seconds_bucket{instance="$instance"}[$__rate_interval])
```

如果我们目睹了这种超时行为，我们就知道我们已经将节点推到了其所能达到的极限。在继续操作之前，我们需要先修复超时的原因。这可以通过减少每个节点的 pod 数量或寻找可能导致大量重试（从而影响流失率）的错误来实现。重要的一点是，与使用固定数字相比，指标是了解节点是否能够处理分配的 pod 流失率的最佳方式。

## 按指标缩放
<a name="_scale_by_metrics"></a>

虽然使用指标来优化系统的概念是一个古老的概念，但在人们开始他们的 Kubernetes 之旅时，它经常被忽视。我们没有关注特定的数字（即每个节点 110 个 pod），而是将精力集中在寻找可以帮助我们发现系统瓶颈的指标上。了解这些指标的正确阈值可以使我们对系统配置最优有信心。

### 变化的影响
<a name="_the_impact_of_changes"></a>

可能给我们带来麻烦的一种常见模式是将注意力集中在第一个看起来可疑的指标或日志错误上。当我们看到 Kubelet 早些时候超时时，我们可以随机尝试一些东西，比如提高允许 Kubelet 发送的每秒速率等。但是，明智的做法是查看我们首先发现的错误下游所有内容的全貌。*每一次更改都要有目的并以数据为后盾*。

Kubelet 的下游将是 Containerd 运行时（容器错误）， DaemonSets 例如与 EC2 API 通信的存储驱动程序 (CSI) 和网络驱动程序 (CNI) 等。

![\[Flow 插件\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/flow-addons.png)


让我们继续我们之前的 Kubelet 跟不上运行时间的示例。在很多情况下，我们可以将一个节点装得如此密集，以至于它会触发错误。

![\[瓶颈\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/bottlenecks.png)


在为我们的工作负载设计合适的节点大小时，这些 easy-to-overlook信号可能会给系统带来不必要的压力，从而限制我们的规模和性能。

### 不必要的错误的代价
<a name="_the_cost_of_unnecessary_errors"></a>

Kubernetes 控制器擅长在出现错误情况时进行重试，但这是有代价的。这些重试可能会增加 Kube 控制器管理器等组件的压力。监控此类错误是规模测试的重要组成部分。

当发生的错误较少时，就更容易发现系统中的问题。通过在重大操作（例如升级）之前定期确保我们的集群没有错误，我们可以简化发生不可预见的事件时的故障排除日志。

#### 扩大我们的视野
<a name="_expanding_our_view"></a>

在拥有 1,000 个节点的大型集群中，我们不想单独寻找瓶颈。在 PromQL 中，我们可以使用名为 topk 的函数找到数据集中的最高值；K 是一个变量，我们放置了我们想要的项目数。在这里，我们使用三个节点来了解集群中的所有 Kubelet 是否都已饱和。到目前为止，我们一直在研究延迟，现在让我们看看 Kubelet 是否在丢弃事件。

```
topk(3, increase(kubelet_pleg_discard_events{}[$__rate_interval]))
```

分解这句话。
+ 我们使用 Grafana `$__rate_interval` 变量来确保它获得所需的四个样本。这就绕过了使用简单变量进行监控的复杂话题。
+  `topk`只给我们最好的结果，数字 3 将这些结果限制为三个。对于集群范围的指标，这是一个有用的函数。
+  `{}`告诉我们没有过滤器，通常你会输入任何抓取规则的任务名称，但是由于这些名称各不相同，我们将其留空。

#### 将问题一分为二
<a name="_splitting_the_problem_in_half"></a>

为了解决系统的瓶颈，我们将采取一种方法来寻找一个指标，该指标可以向我们显示上游或下游存在问题，因为这使我们能够将问题一分为二。这也将是我们如何显示指标数据的核心原则。

开始这个过程的一个不错的地方是 API 服务器，因为它允许我们查看客户端应用程序或控制平面是否存在问题。

# 控制平面监控
<a name="control_plane_monitoring"></a>

## API 服务器
<a name="_api_server"></a>

在查看我们的 API 服务器时，请务必记住，其功能之一是限制入站请求以防止控制平面过载。看似在 API 服务器级别的瓶颈实际上可能是保护它免受更严重问题的侵害。我们需要考虑增加通过系统的请求量的利弊。为了确定是否应增加 API 服务器的值，以下是我们需要注意的事项的小示例：

1. 在系统中传输的请求的延迟是多少？

1. 那是 API 服务器本身的延迟，还是像 etcd 这样的 “下游”？

1. API 服务器队列深度是造成这种延迟的一个因素吗？

1. API 优先级和公平性 (APF) 队列是否针对我们想要的 API 调用模式设置正确？

## 问题出在哪里？
<a name="_where_is_the_issue"></a>

首先，我们可以使用 API 延迟指标来深入了解 API 服务器为请求提供服务所花费的时间。让我们使用下面的 PromQL 和 Grafana 热图来显示这些数据。

```
max(increase(apiserver_request_duration_seconds_bucket{subresource!="status",subresource!="token",subresource!="scale",subresource!="/healthz",subresource!="binding",subresource!="proxy",verb!="WATCH"}[$__rate_interval])) by (le)
```

**注意**  
要详细了解如何使用本文中使用的 API 控制面板监控 API 服务器，请参阅以下[博客](https://aws.amazon.com/blogs/containers/troubleshooting-amazon-eks-api-servers-with-prometheus/) 

![\[API 请求时长热图\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/api-request-duration.png)


这些请求都在一秒钟以内，这很好地表明控制平面正在及时处理请求。但是，如果不是这样呢？

我们在上述 API 请求持续时间中使用的格式是热图。热图格式的好处在于，它可以告诉我们默认情况下 API 的超时值（60 秒）。但是，我们真正需要知道的是，在达到超时阈值之前，这个值应该在什么阈值上引起关注。[有关可接受阈值的粗略指导，我们可以使用上游 Kubernetes SLO，可以在这里找到](https://github.com/kubernetes/community/blob/master/sig-scalability/slos/slos.md#steady-state-slisslos) 

**注意**  
注意到这个语句上的 max 函数了吗？ 使用聚合多台服务器（EKS 上默认为两台 API 服务器）的指标时，重要的是不要将这些服务器放在一起进行平均值。

### 非对称流量模式
<a name="_asymmetrical_traffic_patterns"></a>

如果一个 API 服务器 [pod] 加载得很轻，而另一个负载很重，会怎么样？ 如果我们将这两个数字相加求平均值，我们可能会误解正在发生的事情。例如，这里有三个 API 服务器，但所有负载都在其中一个 API 服务器上。通常，在投资规模和性能问题时，任何具有多台服务器（例如etcd和API服务器）的东西都应进行细分。

![\[机上请求总数\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/inflight-requests.png)


随着转向 API 优先级和公平性，系统上的请求总数只是检查 API 服务器是否超额订阅的一个因素。由于系统现在可以处理一系列队列，因此我们必须查看这些队列中是否有任何队列已满，以及该队列的流量是否被丢弃。

让我们用以下查询来看看这些队列：

```
max without(instance)(apiserver_flowcontrol_nominal_limit_seats{})
```

**注意**  
有关 API A&F 工作原理的更多信息，请参阅以下[最佳实践指南](https://docs.aws.amazon.com/eks/latest/best-practices/scale-control-plane.html#_api_priority_and_fairness) 

在这里，我们可以看到集群中默认出现的七个不同的优先级组

![\[共享并发\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/shared-concurrency.png)


接下来，我们想看看该优先级组中使用了多少百分比，这样我们就可以了解某个优先级是否已饱和。将请求限制在工作负载低级别可能是可取的，但是降低领导人选举级别则不是。

API 优先级与公平性 (APF) 系统有许多复杂的选项，其中一些选项可能会产生意想不到的后果。我们在现场看到的一个常见问题是将队列深度增加到开始增加不必要的延迟的程度。我们可以使用`apiserver_flowcontrol_current_inqueue_request`指标来监控这个问题。我们可以使用来检查掉落情况`apiserver_flowcontrol_rejected_requests_total`。如果任何存储桶超过其并发度，则这些指标将为非零值。

![\[正在使用的请求\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/requests-in-use.png)


增加队列深度会使 API 服务器成为延迟的重要来源，因此应谨慎行事。我们建议谨慎对待创建的队列数量。例如，EKS 系统上的共享数量为 600，如果我们创建的队列太多，这可能会减少需要吞吐量的重要队列（例如领导者选举队列或系统队列）中的份额。创建过多的额外队列会使正确调整这些队列的大小变得更加困难。

为了专注于您可以在APF中进行的简单而有影响力的更改，我们只需从未充分利用的存储桶中提取份额，然后增加处于最大使用量的存储桶的大小。通过在这些桶之间智能地重新分配股份，您可以降低下跌的可能性。

有关更多信息，请访问《EKS 最佳实践指南》中的 [API 优先级和公平性设置](https://docs.aws.amazon.com/eks/latest/best-practices/scale-control-plane.html#_api_priority_and_fairness)。

### API 与 etcd 延迟
<a name="_api_vs_etcd_latency"></a>

我们如何使用 API 服务器 metrics/logs 的来确定 API 服务器是否存在问题、API 服务器问题，或者两者兼而有之。 upstream/downstream 为了更好地理解这一点，让我们来看看 API Server 和 etcd 是如何关联的，以及对错误的系统进行故障排除有多容易。

在下图中，我们看到了 API 服务器延迟，但我们也看到其中大部分延迟与 etcd 服务器相关，因为图表中的条形显示了 etcd 级别的大部分延迟。如果同时有 15 秒的 etcd 延迟，则有 20 秒的 API 服务器延迟，则大部分延迟实际上处于 etcd 级别。

通过查看整个流程，我们发现明智的做法是不要只关注 API 服务器，还要寻找表明 etcd 受到胁迫的信号（即缓慢的应用计数器增加）。只需一眼就能快速移动到正确的问题区域是仪表板的强大之处。

**注意**  
部分中的仪表板可在 https://github.com/RiskyAdventure/故障排除 Dashboards/blob/main/api-疑难解答.json 中找到

![\[ETCD 胁迫\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/etcd-duress.png)


### 控制平面与客户端问题
<a name="_control_plane_vs_client_side_issues"></a>

在此图表中，我们正在寻找在此期间完成时间最长的 API 调用。在本例中，我们看到自定义资源 (CRD) 正在调用 APPLY 函数，这是 05:40 时间范围内最潜在的调用。

![\[最慢的请求\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/slowest-requests.png)


有了这些数据，我们就可以在该时间段内使用临时 PromQL 或 CloudWatch Insights 查询从审计日志中提取列表请求，以查看这可能是哪个应用程序。

### 通过以下方式寻找来源 CloudWatch
<a name="_finding_the_source_with_cloudwatch"></a>

指标最好用于查找我们要查看的问题区域，并缩小问题的时间范围和搜索参数。有了这些数据后，我们想过渡到日志，以获取更详细的时间和错误。为此，我们将使用 Logs [Insights 将CloudWatch 日志](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html)转换为指标。

例如，为了调查上述问题，我们将使用以下 L CloudWatch ogs Insights 查询来提取 userAgent 和 requestURI，这样我们就可以确定哪个应用程序导致了这种延迟。

**注意**  
需要使用适当的计数，以免手表 List/Resync 出现正常行为。

```
fields @timestamp, @message
| filter @logStream like "kube-apiserver-audit"
| filter ispresent(requestURI)
| filter verb = "list"
| parse requestReceivedTimestamp /\d+-\d+-(?<StartDay>\d+)T(?<StartHour>\d+):(?<StartMinute>\d+):(?<StartSec>\d+).(?<StartMsec>\d+)Z/
| parse stageTimestamp /\d+-\d+-(?<EndDay>\d+)T(?<EndHour>\d+):(?<EndMinute>\d+):(?<EndSec>\d+).(?<EndMsec>\d+)Z/
| fields (StartHour * 3600 + StartMinute * 60 + StartSec + StartMsec / 1000000) as StartTime, (EndHour * 3600 + EndMinute * 60 + EndSec + EndMsec / 1000000) as EndTime, (EndTime - StartTime) as DeltaTime
| stats avg(DeltaTime) as AverageDeltaTime, count(*) as CountTime by requestURI, userAgent
| filter CountTime >=50
| sort AverageDeltaTime desc
```

通过此查询，我们发现两个不同的代理正在运行大量高延迟列表操作。Splunk 和 CloudWatch 特工。有了这些数据，我们就可以决定移除、更新这个控制器，或者用另一个项目替换这个控制器。

![\[查询结果\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/query-results.png)


**注意**  
有关此主题的更多详细信息，请参阅以下[博客](https://aws.amazon.com/blogs/containers/troubleshooting-amazon-eks-api-servers-with-prometheus/) 

## 调度器
<a name="_scheduler"></a>

由于 EKS 控制平面实例是在单独的 AWS 账户中运行的，因此我们将无法抓取这些组件来获取指标（API 服务器是个例外）。但是，由于我们可以访问这些组件的审计日志，因此我们可以将这些日志转换为指标，以查看是否有任何子系统导致了扩展瓶颈。让我们使用 Logs CloudWatch Insights 来查看调度器队列中有多少未调度的 pod。

### 调度器日志中未调度的 pod
<a name="_unscheduled_pods_in_the_scheduler_log"></a>

如果我们可以直接在自我管理的 Kubernetes（例如 Kops）上抓取调度器指标，我们将使用以下 PromQL 来了解调度器待办事项。

```
max without(instance)(scheduler_pending_pods)
```

由于我们无法在 EKS 中访问上述指标，因此我们将使用以下 Lo CloudWatch gs Insights 查询来查看在特定时间段内有多少 pod 无法取消调度，从而查看积压情况。然后，我们可以进一步研究高峰时段的消息，以了解瓶颈的本质。例如，节点的旋转速度不够快，或者调度器本身的速率限制器。

```
fields timestamp, pod, err, @message
| filter @logStream like "scheduler"
| filter @message like "Unable to schedule pod"
| parse @message  /^.(?<date>\d{4})\s+(?<timestamp>\d+:\d+:\d+\.\d+)\s+\S*\s+\S+\]\s\"(.*?)\"\s+pod=(?<pod>\"(.*?)\")\s+err=(?<err>\"(.*?)\")/
| count(*) as count by pod, err
| sort count desc
```

在这里，我们看到了调度程序的错误，说由于存储 PVC 不可用，Pod 没有部署。

![\[CloudWatch 日志查询\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/cwl-query.png)


**注意**  
必须在控制平面上开启审计日志才能启用此功能。限制日志保留也是一种最佳做法，以免随着时间的推移不必要地增加成本。使用下面的 EKSCTL 工具开启所有日志功能的示例。

```
cloudWatch:
  clusterLogging:
    enableTypes: ["*"]
    logRetentionInDays: 10
```

## Kube 控制器管理器
<a name="_kube_controller_manager"></a>

与所有其他控制器一样，Kube 控制器管理器对一次可以执行的操作数量有限制。让我们通过查看可以在其中设置这些参数的 KOPS 配置来回顾一下其中一些标志是什么。

```
  kubeControllerManager:
    concurrentEndpointSyncs: 5
    concurrentReplicasetSyncs: 5
    concurrentNamespaceSyncs: 10
    concurrentServiceaccountTokenSyncs: 5
    concurrentServiceSyncs: 5
    concurrentResourceQuotaSyncs: 5
    concurrentGcSyncs: 20
    kubeAPIBurst: 30
    kubeAPIQPS: "20"
```

这些控制器的队列在集群流失率高时会被填满。在本例中，我们看到 replicaset 集控制器的队列中有大量待办事项。

![\[队列\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/queues.png)


我们有两种不同的方法可以解决这种情况。如果运行自我管理，我们可以简单地增加并发 goroutine，但是这会在 KCM 中处理更多数据，从而对 etcd 产生影响。另一种选择是减少部署中使用的`.spec.revisionHistoryLimit`副本集对象的数量，以减少我们可以回滚的副本集对象的数量，从而减轻该控制器的压力。

```
spec:
  revisionHistoryLimit: 2
```

可以调整或关闭 Kubernetes 的其他 Kubernetes 功能，以减轻高流失率系统中的压力。例如，如果我们 pod 中的应用程序不需要直接与 k8s API 通信，那么关闭这些 pod 中的投影密将减少负载。 ServiceaccountTokenSyncs如果可能，这是解决此类问题的更理想方法。

```
kind: Pod
spec:
  automountServiceAccountToken: false
```

在我们无法访问指标的系统中，我们可以再次查看日志来检测争用情况。如果我们想查看每个控制器或汇总级别上正在处理的请求数量，我们可以使用以下 L CloudWatch ogs Insights 查询。

### KCM 处理的总容量
<a name="_total_volume_processed_by_the_kcm"></a>

```
# Query to count API qps coming from kube-controller-manager, split by controller type.
# If you're seeing values close to 20/sec for any particular controller, it's most likely seeing client-side API throttling.
fields @timestamp, @logStream, @message
| filter @logStream like /kube-apiserver-audit/
| filter userAgent like /kube-controller-manager/
# Exclude lease-related calls (not counted under kcm qps)
| filter requestURI not like "apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-controller-manager"
# Exclude API discovery calls (not counted under kcm qps)
| filter requestURI not like "?timeout=32s"
# Exclude watch calls (not counted under kcm qps)
| filter verb != "watch"
# If you want to get counts of API calls coming from a specific controller, uncomment the appropriate line below:
# | filter user.username like "system:serviceaccount:kube-system:job-controller"
# | filter user.username like "system:serviceaccount:kube-system:cronjob-controller"
# | filter user.username like "system:serviceaccount:kube-system:deployment-controller"
# | filter user.username like "system:serviceaccount:kube-system:replicaset-controller"
# | filter user.username like "system:serviceaccount:kube-system:horizontal-pod-autoscaler"
# | filter user.username like "system:serviceaccount:kube-system:persistent-volume-binder"
# | filter user.username like "system:serviceaccount:kube-system:endpointslice-controller"
# | filter user.username like "system:serviceaccount:kube-system:endpoint-controller"
# | filter user.username like "system:serviceaccount:kube-system:generic-garbage-controller"
| stats count(*) as count by user.username
| sort count desc
```

这里的关键要点是，在研究可扩展性问题时，在进入详细的故障排除阶段之前，要先查看路径中的每个步骤（API、调度程序、KCM 等）。通常在生产环境中，你会发现需要对 Kubernetes 的多个部分进行调整，才能让系统以最佳性能运行。很容易无意中对瓶颈大得多的症状（例如节点超时）进行故障排除。

## ETCD
<a name="_etcd"></a>

etcd 使用内存映射文件有效地存储键值对。有一种保护机制可以设置此可用内存空间的大小，通常设置为 2、4 和 8GB 的限制。数据库中较少的对象意味着在更新对象和需要清理旧版本时，etcd 需要做的清理工作更少。这种清理对象旧版本的过程称为压缩。经过多次压缩操作后，会有一个后续的过程来恢复可用空间空间，称为碎片整理，该过程发生在超过一定阈值或固定时间表上。

我们可以做一些与用户相关的项目来限制 Kubernetes 中对象的数量，从而减少压缩和碎片整理过程的影响。例如，Helm 保持高位`revisionHistoryLimit`。这样可以使系统 ReplicaSets 上的旧对象能够进行回滚。通过将历史记录限制设置为 2，我们可以将对象（比如 ReplicaSets）的数量从十个减少到两个，这反过来又会减少系统的负载。

```
apiVersion: apps/v1
kind: Deployment
spec:
  revisionHistoryLimit: 2
```

从监控的角度来看，如果系统延迟峰值以设定的模式出现，以小时为间隔，则检查此碎片整理过程是否是源头可能会有所帮助。我们可以通过使用 L CloudWatch ogs 来看到这一点。

如果您想查看碎片整理 start/end 时间，请使用以下查询：

```
fields @timestamp, @message
| filter @logStream like /etcd-manager/
| filter @message like /defraging|defraged/
| sort @timestamp asc
```

![\[碎片整理查询\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/defrag.png)


# 节点和工作负载效率
<a name="node_and_workload_efficiency"></a>

提高工作负载和节点的效率可以减少工作负载和节点， complexity/cost 同时提高性能和规模。在规划这种效率时，有许多因素需要考虑，最容易从权衡角度来考虑，而不是为每个功能设置一个最佳实践设置。让我们在下一节中深入探讨这些权衡。

## 节点选择
<a name="_node_selection"></a>

使用稍大一点的节点大小（4-12xlarge）会增加我们用于运行 Pod 的可用空间，因为这样可以减少用于 “开销”（例如[DaemonSets](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/)系统组件的[预](https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/)留空间）的节点百分比。在下图中，我们看到了 2xlarge 系统上的可用空间与只有中等数量的 8xlarge 系统的可用空间之间的区别。 DaemonSets

**注意**  
由于 k8s 通常水平扩展，因此对于大多数应用程序来说，考虑 NUMA 大小节点对性能的影响是没有意义的，因此建议使用低于该节点大小的范围。

![\[节点大小\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/node-size.png)


较大的节点大小允许我们在每个节点上拥有更高的可用空间百分比。但是，通过在节点上打包太多 pod 以至于会导致错误或使节点饱和，可以将此模型发挥到极致。监控节点饱和度是成功使用更大节点大小的关键。

节点选择很少是一个 one-size-fits-all命题。通常，最好将流失率截然不同的工作负载分成不同的节点组。流失率高的小批量工作负载最好由 4xlarge 系列实例来处理，而 12xlarge 系列可以更好地为像 Kafka 这样的占用 8 个 vCPU 且流失率低的大型应用程序提供服务。

![\[流失率\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/churn-rate.png)


**注意**  
对于非常大的节点大小，需要考虑的另一个因素是，CGROUPS 不会向容器化应用程序隐藏 vCPU 的总数。动态运行时通常会产生意外数量的操作系统线程，从而造成难以排除故障的延迟。对于这些应用程序，建议使用 [CPU 固定](https://kubernetes.io/docs/tasks/administer-cluster/cpu-management-policies/#static-policy)。要更深入地探讨话题，请 https://www.youtube.com观看以下视频 /watch？ v= \$1 NqtfDy KAqg

## 节点装箱
<a name="_node_bin_packing"></a>

### Kubernetes 与 Linux 规则
<a name="_kubernetes_vs_linux_rules"></a>

在 Kubernetes 上处理工作负载时，我们需要注意两组规则。Kubernetes 调度器的规则，它使用请求值在节点上调度 Pod，然后在调度 pod 之后会发生什么，这是 Linux 的领域，而不是 Kubernetes 的领域。

Kubernetes 调度器完成后，一套新的规则取而代之，即 Linux 完全公平的调度器 (CFS)。关键要点是 Linux CFS 没有核心的概念。我们将讨论为什么在内核中思考会导致优化工作负载以实现规模化时出现重大问题。

### 在核心中思考
<a name="_thinking_in_cores"></a>

混乱之所以开始，是因为 Kubernetes 调度器确实有内核的概念。从 Kubernetes 调度器的角度来看，如果我们看一个包含 4 个 NGINX Pod 的节点，每个节点都有一个核心集的请求，那么该节点将如下所示。

![\[核心数 1\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/cores-1.png)


但是，让我们做一个思想实验，看看从 Linux CFS 的角度来看，这看起来有何不同。使用 Linux CFS 系统时要记住的最重要的一点是：繁忙的容器 (CGROUPS) 是唯一计入共享系统的容器。在这种情况下，只有第一个容器处于繁忙状态，因此允许它使用节点上的所有 4 个内核。

![\[核心数 2\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/cores-2.png)


为什么这很重要？ 假设我们在一个开发集群中进行了性能测试，其中 NGINX 应用程序是该节点上唯一繁忙的容器。当我们将应用程序移至生产环境时，会发生以下情况：NGINX 应用程序需要 4 个 vCPU 资源，但是，由于节点上的所有其他 pod 都处于繁忙状态，因此我们的应用程序的性能受到限制。

![\[核心数 3\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/cores-3.png)


这种情况会导致我们不必要地添加更多容器，因为我们不允许我们的应用程序扩展到其 “最佳位置”。让我们更详细地探讨一下这个重要`"sweet spot"`的 a 概念。

### 正确调整应用程序大小
<a name="_application_right_sizing"></a>

每个应用程序都有一个特定的点，它无法再占用流量。超过此点可能会增加处理时间，甚至在远远超过此点时会降低流量。这被称为应用程序的饱和点。为避免缩放问题，我们应该尝试**在应用程序达到饱和点之前对其进行**缩放。让我们称这一点为最佳位置。

![\[最佳位置\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/sweet-spot.png)


我们需要测试每个应用程序以了解其最佳位置。由于每个应用程序都不同，因此这里没有通用的指导。在这次测试中，我们试图了解显示应用程序饱和点的最佳指标。通常，利用率指标被用来表示应用程序已饱和，但这很快就会导致扩展问题（我们将在后面的章节中详细探讨这个主题）。一旦我们有了这个 “最佳位置”，我们就可以用它来有效地扩展我们的工作负载。

相反，如果我们在最佳位置之前就扩大规模并创建不必要的吊舱，会发生什么？ 让我们在下一节中探讨这个问题。

### 吊舱蔓延
<a name="_pod_sprawl"></a>

要了解创建不必要的 pod 会如何很快失控，让我们看一下左边的第一个示例。每秒处理 100 个请求时，此容器的正确垂直缩CPUs 放会占用大约 2 v 的利用率。但是，如果我们通过将请求设置为半个内核来少调请求值，那么现在我们实际需要的每个 pod 需要 4 个 pod。更糟糕的是，如果我们的 [HPA](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) 设置为默认的50％CPU，则这些吊舱将缩放一半为空，从而产生 8:1 的比例。

![\[缩放比例\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/scaling-ratio.png)


放大这个问题，我们可以很快看到这个问题是如何失控的。部署十个最佳位置设置不正确的 pod 可能会迅速上升到 80 个 pod 以及运行它们所需的额外基础架构。

![\[糟糕的甜点\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/bad-sweetspot.png)


既然我们已经了解了不允许应用程序在其最佳位置运行的影响，那么让我们回到节点级别，问一下为什么 Kubernetes 调度器和 Linux CFS 之间的这种区别如此重要？

使用 HPA 向上和向下扩展时，我们可能会遇到这样一种情况：我们有足够的空间来分配更多 pod。这将是一个错误的决定，因为左边所示的节点已经达到了 100% 的 CPU 利用率。在一个不切实际但理论上可能的场景中，我们可能会遇到另一个极端，即我们的节点完全已满，但我们的 CPU 利用率为零。

![\[hpa 利用率\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/hpa-utilization.png)


### 设置请求
<a name="_setting_requests"></a>

将请求设置为该应用程序的 “最佳位置” 值很诱人，但这会导致效率低下，如下图所示。这里我们将请求值设置为 2 个 vCPU，但是这些 pod 的平均利用率大部分时间仅运行 1 个 CPU。这种设置会导致我们浪费 50% 的 CPU 周期，这是不可接受的。

![\[请求数 1\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/requests-1.png)


这使我们想到了问题的复杂答案。容器利用率不能在真空中考虑；必须考虑节点上运行的其他应用程序。在以下示例中，本质上是爆发的容器与两个可能受内存限制的 CPU 利用率低的容器混合在一起。通过这种方式，我们允许容器在不对节点造成负担的情况下达到最佳位置。

![\[请求数 2\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/requests-2.png)


要从这一切中汲取的重要概念是，使用 Kubernetes 调度程序的内核概念来理解 Linux 容器性能可能会导致决策不佳，因为它们并不相关。

**注意**  
Linux CFS 有其优点。对于 I/O 基于的工作负载尤其如此。但是，如果您的应用程序使用不带边车的完整内核，并且没有 I/O 要求，则CPU固定可以消除此过程中的大量复杂性，因此建议您注意这些警告。

## 利用率与饱和度
<a name="_utilization_vs_saturation"></a>

应用程序扩展中的一个常见错误是仅将 CPU 利用率作为扩展指标。在复杂的应用程序中，这几乎总是一个很差的指标，表明应用程序实际上已经充满了请求。在左边的示例中，我们看到我们所有的请求实际上都在发送 Web 服务器，因此 CPU 利用率在饱和度方面表现良好。

在现实世界的应用程序中，其中一些请求很可能会由数据库层或身份验证层等提供服务。在这种更常见的情况下，请注意 CPU 没有跟踪饱和度，因为请求是由其他实体提供服务的。在这种情况下，CPU 是衡量饱和度的一个非常差的指标。

![\[实用程序与饱和度 1\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/util-vs-saturation-1.png)


在应用程序性能中使用错误的指标是 Kubernetes 中出现不必要和不可预测的扩展的首要原因。在为所使用的应用程序类型选择正确的饱和度指标时，必须格外小心。值得注意的是，没有可以给出 “一刀切” 的建议。根据所使用的语言和相关应用程序的类型，有多种饱和度指标。

我们可能认为这个问题只出在 CPU 利用率上，但是其他常见指标（例如每秒请求数）也可能属于与上面讨论的完全相同的问题。请注意，请求也可能转到数据库层、身份验证层，而不是由我们的 Web 服务器直接提供服务，因此衡量 Web 服务器本身的真实饱和度是一个很差的指标。

![\[实用程序与饱和度 2\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/util-vs-saturation-2.png)


不幸的是，在选择正确的饱和度指标时，没有简单的答案。以下是一些需要考虑的准则：
+ 了解您的语言运行时——具有多个操作系统线程的语言的反应会与单线程应用程序不同，因此对节点的影响也不同。
+ 了解正确的垂直缩放比例——在缩放新 pod 之前，你想在应用程序的垂直比例中放多少缓冲区？
+ 哪些指标能真正反映应用程序的饱和度-Kafka Producer 的饱和度指标将与复杂的 Web 应用程序大不相同。
+ 节点上的所有其他应用程序如何相互影响-应用程序性能不是在真空中完成的，节点上的其他工作负载会产生重大影响。

为了结束本节，很容易将上述内容视为过于复杂和不必要。我们经常会遇到问题，但我们没有意识到问题的真正本质，因为我们看错了指标。在下一节中，我们将探讨如何发生这种情况。

### 节点饱和度
<a name="_node_saturation"></a>

现在我们已经探讨了应用程序饱和度，让我们从节点的角度来看同样的概念。让我们以两个 CPUs 100% 利用率为例，看看利用率与饱和度之间的区别。

左边的 vCPU 利用率为 100%，但是没有其他任务等待在这个 vCPU 上运行，因此从纯粹的理论意义上讲，这非常高效。同时，在第二个示例中，我们有 20 个单线程应用程序等待由 vCPU 处理。现在，所有 20 个应用程序在等待轮到 vCPU 处理时都会遇到某种类型的延迟。换句话说，右边的 vCPU 已饱和。

如果我们只看利用率，我们不仅不会看到这个问题，而且我们可能会将这种延迟归因于不相关的东西，例如网络，这会导致我们走上错误的道路。

![\[节点饱和度\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/node-saturation.png)


在任何给定时间增加节点上运行的 Pod 总数时，重要的是要查看饱和度指标，而不仅仅是利用率指标，因为我们很容易忽略节点过饱和的事实。对于此任务，我们可以使用压力失速信息指标，如下图所示。

PromQL-I/O 停滞不前

```
topk(3, ((irate(node_pressure_io_stalled_seconds_total[1m])) * 100))
```

![\[io 停滞不前\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/stalled-io.png)


**注意**  
有关压力失速指标的更多信息，请参阅 https://facebookmicrosites.github。 io/psi/docs/overview\$1

通过这些指标，我们可以判断线程是否在 CPU 上等待，或者即使盒子上的每个线程都停滞不前，等待内存等资源还是在 1 分钟I/O. For example, we could see what percentage every thread on the instance was stalled waiting on I/O内停滞不前。

```
topk(3, ((irate(node_pressure_io_stalled_seconds_total[1m])) * 100))
```

使用这个指标，我们可以在上面的图表中看到，盒子上的每个线程在最高水位等候的时间中有45％的时间 I/O 处于停滞状态，这意味着我们在那一分钟内浪费了所有CPU周期。了解这种情况的发生可以帮助我们节省大量的 vCPU 时间，从而提高扩展效率。

### HPA V2
<a name="_hpa_v2"></a>

建议使用 HPA API 的自动缩放/v2 版本。在某些边缘情况下，旧版本的 HPA API 可能会卡住扩展。它还被限制为 pod 在每个扩展步骤中只能翻一番，这给需要快速扩展的小型部署带来了问题。

AutoScaling/v2 使我们能够更灵活地包含多个标准来进行扩展，并在使用自定义和外部指标（非 K8s 指标）时为我们提供了极大的灵活性。

例如，我们可以按三个值中最高的值进行缩放（见下文）。如果所有 pod 的平均利用率超过 50%，如果自定义指标每秒入口的数据包平均超过 1,000 个，或者入口对象超过每秒 1 万个请求，我们就会进行扩展。

**注意**  
这只是为了展示自动缩放 API 的灵活性，我们建议不要使用在生产中难以排除故障的过于复杂的规则。

```
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
  - type: Pods
    pods:
      metric:
        name: packets-per-second
      target:
        type: AverageValue
        averageValue: 1k
  - type: Object
    object:
      metric:
        name: requests-per-second
      describedObject:
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        name: main-route
      target:
        type: Value
        value: 10k
```

但是，我们了解了将此类指标用于复杂的 Web 应用程序的危险。在这种情况下，使用能够准确反映应用程序饱和度与利用率相比的自定义或外部指标可以更好地为我们服务。 HPAv2 通过能够根据任何指标进行扩展来实现这一点，但是我们仍然需要找到该指标并将其导出到 Kubernetes 以供使用。

例如，我们可以查看 Apache 中的活动线程队列数。这通常会创建一个 “更平滑” 的缩放配置文件（稍后会详细介绍该术语）。如果线程处于活动状态，则无论该线程是在数据库层等待还是在本地处理请求，如果所有应用程序线程都在使用中，则很好地表明应用程序已饱和。

我们可以使用这种线程耗尽作为信号，创建一个线程池完全可用的新 pod。这也使我们能够控制在流量繁忙时要在应用程序中吸收多大的缓冲区。例如，如果我们的线程池总数为 10，则按使用的 4 个线程而不是使用 8 个线程进行扩展将对我们在扩展应用程序时可用的缓冲区产生重大影响。对于需要在重负载下快速扩展的应用程序来说，设置为 4 是有意义的，如果我们有足够的时间进行扩展，因为请求数量的增长缓慢而不是随着时间的推移而急剧增加，那么如果我们有足够的时间进行扩展，那么设置为 8 会更有效地利用我们的资源。

![\[线程池\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/thread-pool.png)


在缩放方面，“平滑” 一词是什么意思？ 请注意下图，其中我们使用 CPU 作为指标。此部署中的 pod 将在短时间内激增，从 50 个 pod 一直到 250 个 pod，然后立即再次缩小。这是效率极低的扩展是集群流失的主要原因。

![\[尖锐的缩放\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/spiky-scaling.png)


请注意，在我们更改为反映应用程序正确最佳位置的指标（图表的中间部分）之后，我们如何能够平稳地进行扩展。现在，我们的缩放效率很高，我们的 pod 可以根据我们通过调整请求设置提供的余量进行完全扩展。现在，一小群吊舱正在做以前数百个吊舱所做的工作。现实世界的数据表明，这是 Kubernetes 集群可扩展性的首要因素。

![\[平滑缩放\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/smooth-scaling.png)


关键要点是 CPU 利用率只是应用程序和节点性能的一个维度。使用 CPU 利用率作为节点和应用程序的唯一运行状况指标会带来扩展、性能和成本方面的问题，这些都是紧密联系的概念。应用程序和节点的性能越高，需要扩展的次数就越少，这反过来又会降低成本。

通过查找和使用正确的饱和度指标来扩展您的特定应用程序，您还可以监控该应用程序的真正瓶颈并发出警报。如果跳过这一关键步骤，将很难甚至不可能理解有关性能问题的报告。

## 设置 CPU 限制
<a name="_setting_cpu_limits"></a>

为了完善本节关于被误解的话题，我们将介绍 CPU 限制。简而言之，限制是与容器关联的元数据，该容器具有每 100 毫秒重置一次的计数器。这有助于 Linux 跟踪特定容器在 100 毫秒的时间段内在节点范围内使用了多少 CPU 资源。

![\[CPU 限制\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/cpu-limits.png)


设置限制时的一个常见错误是假设应用程序是单线程的，并且只能在其 “`分配的” vCPU上运行。在上一节中，我们了解到 CFS 不分配内核，实际上，运行大型线程池的容器将在盒子上所有可用的 vCPU 上进行调度。

如果 64 个操作系统线程在 64 个可用内核上运行（从 Linux 节点的角度来看），则在将所有 64 个内核上的运行时间相加后，我们将在将 100 毫秒内的 CPU 使用时间总账单计算得相当大。由于这可能只发生在垃圾收集过程中，因此很容易错过这样的事情。这就是为什么在尝试设置限制之前，有必要使用指标来确保随着时间的推移我们有正确的使用情况。

幸运的是，我们可以准确地看到应用程序中所有线程使用了多少 vCPU。我们将为此使用该指标`container_cpu_usage_seconds_total`。

由于限制逻辑每 100 毫秒发生一次，并且该指标是每秒的指标，因此我们将 PromQL 与这个 100 毫秒周期相匹配。如果您想深入了解这个 PromQL 语句的工作原理，请参阅以下[博客](https://aws.amazon.com/blogs/containers/using-prometheus-to-avoid-disasters-with-kubernetes-cpu-limits/)。

PromQL 查询：

```
topk(3, max by (pod, container)(rate(container_cpu_usage_seconds_total{image!="", instance="$instance"}[$__rate_interval]))) / 10
```

![\[cpu 1\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/cpu-1.png)


一旦我们觉得自己有正确的价值，我们就可以限制生产。然后，有必要查看我们的应用程序是否由于意想不到的事情而受到限制。我们可以通过看一下来做到这一点 `container_cpu_throttled_seconds_total` 

```
topk(3, max by (pod, container)(rate(container_cpu_cfs_throttled_seconds_total{image!=``""``, instance=``"$instance"``}[$__rate_interval]))) / 10
```

![\[cpu 2\]](http://docs.aws.amazon.com/zh_cn/eks/latest/best-practices/images/scalability/cpu-2.png)


### 内存
<a name="_memory"></a>

内存分配是另一个很容易将 Kubernetes 调度行为与 Linux 行为混为一谈的例子。 CGroup [这是一个更加细致入微的话题，因为 CGroup v2 在 Linux 中处理内存的方式发生了重大变化，而 Kubernetes 也更改了语法以反映这一点；请阅读此博客了解更多详情。](https://kubernetes.io/blog/2021/11/26/qos-memory-resources/)

与 CPU 请求不同，在调度过程完成后，内存请求将处于未使用状态。这是因为我们无法像使用 CPU 那样在 CGroup v1 中压缩内存。这只给我们留下了内存限制，这些限制旨在通过完全终止 pod 来保护内存泄漏。这是一个全有要么全有要么全无的风格主张，但是我们现在有了解决这个问题的新方法。

首先，重要的是要明白，为容器设置正确的内存量并不像看起来那样简单。Linux 中的文件系统将使用内存作为缓存来提高性能。这种缓存会随着时间的推移而增长，很难知道有多少内存可以用来存放缓存，但可以在不对应用程序性能产生重大影响的情况下进行回收。这通常会导致对内存使用情况的误解。

具有 “压缩” 内存的能力是 CGroup v2 背后的主要驱动力之一。有关为何需要 CGroup V2 的更多历史记录，请参阅 Chris Down 的[演讲](https://www.youtube.com/watch?v=kPMZYoRxtmg)，他在演讲中介绍了为什么无法正确设置最小内存是促使他创建 CGroup v2 和压力失速指标的原因之一。 LISA21 

幸运的是，Kubernetes 现在有了 “和” 之下的概念。`memory.min` `memory.high` `requests.memory`这使我们可以选择主动释放缓存的内存供其他容器使用。一旦容器达到内存上限，内核就可以积极回收该容器的内存，最高可达设置为的值。`memory.min`因此，当节点承受内存压力时，我们可以获得更大的灵活性。

关键问题变成了，`memory.min`要设置为什么值？ 这就是内存压力失速指标发挥作用的地方。我们可以使用这些指标来检测容器级别的内存 “抖动”。然后，我们可以使用诸如 [fbtax](https://facebookmicrosites.github.io/cgroup2/docs/fbtax-results.html) 之类的控制器`memory.min`通过查找内存抖动来检测正确的值，并将该`memory.min`值动态设置为该设置。

### Summary
<a name="_summary"></a>

总而言之，可以很容易地将以下概念混为一谈：
+ 利用率和饱和度
+ 使用 Kubernetes 调度器逻辑的 Linux 性能规则

必须格外小心，将这些概念分开。性能和规模在深层次上是相互关联的。不必要的缩放会造成性能问题，进而造成扩展问题。

# Kubernetes 上游 SLOs
<a name="kubernetes_upstream_slos"></a>

Amazon EKS 运行的代码与上游 Kubernetes 版本相同，并确保 EKS 集群在 Kubernetes 社区 SLOs 定义的范围内运行。Kubernetes [可扩展性特别兴趣小组 (SIG)](https://github.com/kubernetes/community/tree/master/sig-scalability) 定义了可扩展性目标，并通过和调查性能瓶颈。 SLIs SLOs

SLIs 是我们衡量系统的方式，例如可用于确定系统运行的 “良好” 的指标或衡量标准，例如请求延迟或计数。 SLOs 定义系统运行 “良好” 时的预期值，例如请求延迟保持在 3 秒以内。Kubernetes SLOs SLIs 侧重于 Kubernetes 组件的性能，完全独立于注重 EKS 集群终端节点可用性的亚马逊 EKS 服务 SLAs 。

Kubernetes 具有许多功能，允许用户使用自定义插件或驱动程序扩展系统，例如 CSI 驱动程序、准入 webhook 和自动缩放器。这些扩展可能会以不同的方式极大地影响 Kubernetes 集群的性能，也就是说，如果 webhook 目标不可用，则带有准入 webhook 的 webhook `failurePolicy=Ignore` 可能会增加 K8s API 请求的延迟。Kubernetes 可扩展性 SIG 使用 [“你承诺，我们承诺](https://github.com/kubernetes/community/blob/master/sig-scalability/slos/slos.md#how-we-define-scalability)” 的框架来定义可扩展性：

如果您承诺：-正确配置您的集群-“合理” 使用可扩展性功能-将集群中的负载保持在[建议](https://github.com/kubernetes/community/blob/master/sig-scalability/configs-and-limits/thresholds.md)的限制之内 

那么我们保证你的集群会扩展，即：-所有 SLOs 都得到满足。

## Kubernetes SLOs
<a name="_kubernetes_slos"></a>

Kubernetes SLOs 没有考虑到所有可能影响集群的插件和外部限制，例如工作节点扩展或准入 webhook。它们 SLOs 侧重于 [Kubernetes 组件](https://kubernetes.io/docs/concepts/overview/components/)，并确保 Kubernetes 的操作和资源按预期运行。它们 SLOs 可以帮助 Kubernetes 开发人员确保对 Kubernetes 代码的更改不会降低整个系统的性能。

[Kuberntes 可扩展性 SIG 定义了以下官方](https://github.com/kubernetes/community/blob/master/sig-scalability/slos/slos.md) SLO/。SLIsAmazon EKS 团队定期在 EKS 集群上运行可扩展性测试， SLOs/SLIs 以监控随着更改和新版本发布而出现的性能下降。


| 目标 | 定义 | SLO | 
| --- | --- | --- | 
|  API 请求延迟（变更）  |  处理每对（资源、动词）的单个对象的变异 API 调用的延迟，以过去 5 分钟内的第 99 个百分位数来衡量  |  在默认的 Kubernetes 安装中，对于每对（资源、动词），不包括虚拟资源和聚合资源以及自定义资源定义，每个集群日的第 99 个百分位数 <= 1s  | 
|  API 请求延迟（只读）  |  处理每个（资源、范围）对的非流式只读 API 调用的延迟，以过去 5 分钟内的第 99 个百分位数来衡量  |  在默认 Kubernetes 安装中，对于每对（资源、范围），不包括虚拟和聚合资源以及自定义资源定义，每个集群日的第 99 个百分位数：(a) 如果 (b) <= 1s 否则为 <= 30s`scope=resource`（如果或）`scope=namespace``scope=cluster`  | 
|  Pod 启动延迟  |  可调度的无状态 pod 的启动延迟，不包括拉取镜像和运行 init 容器的时间，从 pod 创建时间戳到所有容器都报告为已启动并通过 watch 观察的时间来衡量，以过去 5 分钟内的第 99 个百分位数来衡量  |  在默认的 Kubernetes 安装中，每个集群日的第 99 个百分位数 <= 5s  | 

### API 请求延迟
<a name="_api_request_latency"></a>

`kube-apiserver`已`--request-timeout`定义为`1m0s`默认值，这意味着请求最多可以运行一分钟（60 秒），然后才会被超时和取消。L SLOs atency 的定义按正在发出的请求类型进行细分，可以是变异请求或只读请求：

#### 变异
<a name="_mutating"></a>

Kubernetes 中的变异请求会对资源进行更改，例如创建、删除或更新。这些请求非常昂贵，因为在返回更新的对象之前，必须将这些更改写入 [etcd 后端](https://kubernetes.io/docs/concepts/overview/components/#etcd)。[Etcd](https://etcd.io/) 是一种分布式键值存储，用于所有 Kubernetes 集群数据。

这种延迟是以 Kubernetes 资源对（资源、动词）在 5 分钟内的第 99 个百分位来衡量的，例如，这将衡量 Create Pod 请求和更新节点请求的延迟。请求延迟必须为 <= 1 秒才能满足 SLO。

#### Read-only
<a name="_read_only"></a>

只读请求会检索单个资源（例如 Get Pod X）或集合（例如 “从命名空间 X 获取所有 Pod”）。`kube-apiserver`维护对象的缓存，因此可以从缓存中返回请求的资源，或者可能需要先从 etcd 中检索它们。这些延迟也是以 5 分钟内的第 99 个百分位数来衡量的，但是只读请求可以有不同的范围。SLO 定义了两个不同的目标：
+ 对于针对*单个*资源（即`kubectl get pod -n mynamespace my-controller-xxx`）发出的请求，请求延迟应保持 <= 1 秒。
+ 对于对命名空间或集群（例如`kubectl get pods -A`）中的多个资源发出的请求，延迟应保持 <= 30 秒

对于不同的请求范围，SLO 具有不同的目标值，因为对 Kubernetes 资源列表发出的请求需要在 SLO 中返回请求中所有对象的详细信息。在大型集群或大型资源集合上，这可能会导致响应大小过大，可能需要一些时间才能返回。例如，在运行成千上万个 Pod 且每个 Pod 以 JSON 编码时大约 1 KiB 的集群中，返回集群中的所有 Pod 将包含 10MB 或更多。Kubernetes 客户端可以使用分[ APIList块检索大量资源来帮助缩小响应大](https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks)小。

### Pod 启动延迟
<a name="_pod_startup_latency"></a>

这个 SLO 主要关注从 Pod 创建 Pod 到该 Pod 中的容器实际开始执行所花费的时间。为了衡量这一点，计算了与 Pod 上记录的创建时间戳以及该 Pod [上的 WAT](https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes) CH 报告容器已启动时的差异（不包括容器镜像拉取和初始化容器执行时间）。为了满足 SLO，此 Pod 启动延迟中每个集群日的第 99 个百分位数必须保持 <=5 秒。

请注意，此 SLO 假设此集群中已经存在工作节点，处于可以调度 Pod 的就绪状态。此 SLO 不考虑图像拉取或初始化容器执行，还将测试限制在不利用持久存储插件的 “无状态 pod” 上。

## Kubernetes SLI 指标
<a name="_kubernetes_sli_metrics"></a>

Kubernetes 还通过 SLIs 向随时间跟踪这些指标的 Kubernetes 组件中添加 Prometheus [指标](https://prometheus.io/docs/concepts/data_model/)来改善可观察性。 SLIs 使用 [Prometheus 查询语言 (PromQL)，我们可以构建查询，在 Prom](https://prometheus.io/docs/prometheus/latest/querying/basics/) etheus 或 Grafana 仪表板等工具中显示一段时间内的 SLI 性能，以下是上述的一些示例。 SLOs 

### API 服务器请求延迟
<a name="_api_server_request_latency"></a>


| 指标 | 定义 | 
| --- | --- | 
|  apiserver\$1request\$1sli\$1duration\$1seconds  |  每个动词、组、版本、资源、子资源、范围和组件的响应延迟分布（不包括 webhook 持续时间以及优先级和公平性队列等待时间），以秒为单位。  | 
|  apiserver\$1request\$1持续时间\$1秒  |  每个动词、试运行值、组、版本、资源、子资源、范围和组件的响应延迟分布（以秒为单位）。  | 

**注意**  
该`apiserver_request_sli_duration_seconds`指标从 Kubernetes 1.27 开始可用。

您可以使用这些指标来调查 API 服务器的响应时间以及 Kubernetes 组件或其他插件/组件中是否存在瓶颈。以下查询基于[社区 SLO 控制面板](https://github.com/kubernetes/perf-tests/tree/master/clusterloader2/pkg/prometheus/manifests/dashboards)。

 **API 请求延迟 SLI（变异）**-此时间*不*包括 webhook 的执行或队列中的等待时间。 `histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"CREATE|DELETE|PATCH|POST|PUT", subresource!~"proxy|attach|log|exec|portforward"}[5m])) by (resource, subresource, verb, scope, le)) > 0`

 **API 请求延迟总计（变更）**-这是请求在 API 服务器上花费的总时间，此时间可能比 SLI 时间长，因为它包括 webhook 执行以及 API 优先级和公平性等待时间。 `histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"CREATE|DELETE|PATCH|POST|PUT", subresource!~"proxy|attach|log|exec|portforward"}[5m])) by (resource, subresource, verb, scope, le)) > 0`

在这些查询中，我们排除了不会立即返回的流式传输 API 请求，例如`kubectl port-forward`或`kubectl exec`请求 (`subresource!~"proxy|attach|log|exec|portforward"`)，并且我们只筛选修改对象的 Kubernetes 动词 ()。`verb=~"CREATE|DELETE|PATCH|POST|PUT"`然后，我们将计算过去 5 分钟内延迟的第 99 个百分位数。

我们可以对只读 API 请求使用类似的查询，我们只需修改要筛选的动词以包含只读操作`LIST`和`GET`。根据请求的范围，也存在不同的 SLO 阈值，即获取单个资源或列出多个资源。

 **API 请求延迟 SLI（只读）**-此时间*不*包括 webhook 的执行或队列中的等待时间。对于单个资源（范围=资源，阈值=1s）`histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"GET", scope=~"resource"}[5m])) by (resource, subresource, verb, scope, le))` 

对于同一命名空间中的资源集合（scope=namespace，threshold=5s）`histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"LIST", scope=~"namespace"}[5m])) by (resource, subresource, verb, scope, le))` 

对于整个集群中的资源集合（scope=cluster，threshold=30s）`histogram_quantile(0.99, sum(rate(apiserver_request_sli_duration_seconds_bucket{verb=~"LIST", scope=~"cluster"}[5m])) by (resource, subresource, verb, scope, le))` 

 **API 请求延迟总计（只读）**-这是请求在 API 服务器上花费的总时间，此时间可能比 SLI 时间长，因为它包括 webhook 的执行和等待时间。对于单个资源（范围=资源，阈值=1s）`histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"GET", scope=~"resource"}[5m])) by (resource, subresource, verb, scope, le))` 

对于同一命名空间中的资源集合（scope=namespace，threshold=5s）`histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"LIST", scope=~"namespace"}[5m])) by (resource, subresource, verb, scope, le))` 

对于整个集群中的资源集合（scope=cluster，threshold=30s）`histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{verb=~"LIST", scope=~"cluster"}[5m])) by (resource, subresource, verb, scope, le))` 

SLI 指标通过排除请求在 API 优先级和公平性队列中等待所花费的时间、通过准入 webhook 或其他 Kubernetes 扩展程序运行所花费的时间，从而深入了解 Kubernetes 组件的性能。总指标可以提供更全面的视图，因为它反映了您的应用程序等待 API 服务器响应的时间。比较这些指标可以深入了解请求处理延迟在哪里引入的。

### Pod 启动延迟
<a name="_pod_startup_latency_2"></a>


| 指标 | 定义 | 
| --- | --- | 
|  kubelet\$1pod\$1start\$1sli\$1sli\$1duration\$1seconds  |  启动 Pod 的持续时间（以秒为单位），不包括拉取图像和运行 init 容器的时间，从 pod 创建时间戳到其所有容器都报告为已启动并通过 watch 进行观察的时间进行测量  | 
|  kubelet\$1pod\$1start\$1duration\$1seconds  |  从 kubelet 第一次看到 pod 到 pod 开始运行的持续时间（以秒为单位）。这不包括调度 Pod 或扩展工作节点容量的时间。  | 

**注意**  
 `kubelet_pod_start_sli_duration_seconds`从 Kubernetes 1.27 开始可用。

与上面的查询类似，你可以使用这些指标来深入了解与 Kubelet 操作相比，节点缩放、图像拉取和初始化容器延迟 pod 启动的时间有多长。

 **Pod 启动延迟 SLI-** 这是从创建 Pod 到应用程序容器报告运行的时间。这包括工作节点容量可用和调度 pod 所花费的时间，但这不包括拉取映像或初始化容器运行所花费的时间。 `histogram_quantile(0.99, sum(rate(kubelet_pod_start_sli_duration_seconds_bucket[5m])) by (le))`

 **Pod 启动延迟总计-** 这是 kubelet 首次启动 pod 所花费的时间。这是从 kubelet 通过 WATCH 收到 pod 的时间开始计算的，其中不包括工作节点扩展或调度的时间。这包括拉取映像和初始化容器以供运行的时间。 `histogram_quantile(0.99, sum(rate(kubelet_pod_start_duration_seconds_bucket[5m])) by (le))`

## SLOs 在你的集群上
<a name="_slos_on_your_cluster"></a>

如果您正在从 EKS 集群中的 Kubernetes 资源中收集 Prometheus 指标，则可以更深入地了解 Kubernetes 控制平面组件的性能。

[性能测试存储库包含](https://github.com/kubernetes/perf-tests/) Grafana 仪表板，用于显示测试期间集群的延迟和关键性能指标。性能测试配置利用了配置为收集 Kubernetes 指标的开源项目，但你也可以使用亚马逊[托管 Prometheus 和亚马逊托管](https://aws-observability.github.io/terraform-aws-observability-accelerator/eks/) Grafana。[kube-prometheus-stack](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack)

如果您使用的是`kube-prometheus-stack`或类似的 Prometheus 解决方案，则可以安装相同的控制面板来实时观察集群 SLOs 上的情况。

1. 您首先需要安装控制面板中使用的 Prometheus 规则。`kubectl apply -f prometheus-rules.yaml`你可以在这里下载规则的副本：p https://github.com/kubernetes/ erf--tests/blob/master/clusterloader2/pkg/prometheus/manifests/prometheus rules.yaml

   1. 请务必检查文件中的命名空间是否与您的环境相匹配

   1. 如果您使用的是，请验证标签是否与 `prometheus.prometheusSpec.ruleSelector` helm 值匹配 `kube-prometheus-stack` 

1. 然后，您可以在 Grafana 中安装控制面板。这里提供了 json 仪表板和生成它们的 python 脚本： https://github.com/kubernetes/perf-tests/tree/master/clusterloader2/pkg/prometheus/manifests/dashboards

   1.  [`slo.json`控制面板](https://github.com/kubernetes/perf-tests/blob/master/clusterloader2/pkg/prometheus/manifests/dashboards/slo.json)显示集群与 Kubernetes 相关的性能 SLOs

考虑一下， SLOs 它们侧重于集群中 Kubernetes 组件的性能，但是您可以查看其他指标，这些指标可以为集群提供不同的视角或见解。像 [K](https://github.com/kubernetes/kube-state-metrics/tree/main) 这样的 Kubernetes 社区项目ube-state-metrics可以帮助你快速分析集群中的趋势。Kubernetes 社区中大多数常见的插件和驱动程序也会发出 Prometheus 指标，允许你调查诸如自动扩缩程序或自定义调度器之类的内容。

《[可观察性最佳实践指南》提供了](https://aws-observability.github.io/observability-best-practices/guides/containers/oss/eks/best-practices-metrics-collection/#control-plane-metrics)其他 Kubernetes 指标的示例，您可以使用这些指标来获得进一步的见解。

# 已知限制和 Service Quotas
<a name="known_limits_and_service_quotas"></a>

**提示**  
 通过 Amazon EKS 研讨会@@ [探索](https://aws-experience.com/emea/smb/events/series/get-hands-on-with-amazon-eks?trk=4a9b4147-2490-4c63-bc9f-f8a84b122c8c&sc_channel=el)最佳实践。

Amazon EKS 可用于各种工作负载，可以与各种 AWS 服务进行交互，而且我们已经看到客户工作负载会遇到类似的 AWS 服务配额和其他阻碍可扩展性的问题。

您的 AWS 账户具有默认配额（您的团队可以申请的每个 AWS 资源数量的上限）。每个 AWS 服务都定义自己的配额，配额通常是特定于区域的。您可以请求增加某些配额（软限制），而其他配额则无法提高（硬限制）。在设计应用程序时，您应该考虑这些值。请考虑定期查看这些服务限制，并在应用程序设计期间将其纳入其中。

您可以在 AWS S [ervice Quotas 控制台或使用 AWS CLI 查看账户中的使用情况并提出增加配额](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html#request-increase)[的](https://repost.aws/knowledge-center/request-service-quota-increase-cli)请求。请参阅相应的 AWS 服务的 AWS 文档，了解有关服务配额的更多详细信息以及有关增加配额的任何其他限制或通知。

**注意**  
 [Amazon EKS Ser](https://docs.aws.amazon.com/eks/latest/userguide/service-quotas.html) vice Quotas 列出了服务配额，并提供了请求增加配额的链接（如果有）。

## 其他 AWS Service Quotas
<a name="_other_aws_service_quotas"></a>

我们已经看到 EKS 客户受到下面列出的其他 AWS 服务配额的影响。其中一些可能仅适用于特定的用例或配置，但是您可以考虑您的解决方案在扩展时是否会遇到任何这些用例或配置。[配额是按服务组织的，每个配额都有一个格式为 L-XXXXXXXX 的 ID，你可以用它在 AWS Service Quotas 控制台中查找](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html#request-increase) 


| 服务 | 配额 (l-xxxxx) |  **Impact**  |  **身份证 (l-xxxxx)**  | 默认 | 
| --- | --- | --- | --- | --- | 
|  IAM  |  每个账户的角色数：  |  可以限制账户中集群或 IRSA 角色的数量。  |  L-FE177 D64  |  1000  | 
|  IAM  |  OpenId 按账户连接提供商  |  可以限制每个账户的集群数量，IRSA 使用 OpenID Connect  |  L-858F3967  |  100  | 
|  IAM  |  角色信任策略长度  |  可以限制 IRSA 的 IAM 角色关联的集群数量  |  L-C07B4B0D  |  2,048  | 
|  VPC  |  每个网络接口的安全组数  |  可能会限制集群对网络的控制或连接  |  L-2 AFB9258  |  5  | 
|  VPC  |  IPv4 每个 VPC 的 CIDR 块数  |  可以限制 EKS 工作节点的数量  |  L-83 CA0 A9D  |  5  | 
|  VPC  |  每个路由表的路由数  |  可能会限制集群对网络的控制或连接  |  L-93826ACB  |  50  | 
|  VPC  |  每个 VPC 的活动 VPC 对等连接  |  可能会限制集群对网络的控制或连接  |  L-7E9ECCDB  |  50  | 
|  VPC  |  每个安全组的入站或出站规则。  |  可能会限制集群对网络的控制或连接，EKS 中的某些控制器会创建新规则  |  L-0 F EA8095  |  50  | 
|  VPC  |  VPCs 按地区划分  |  可以限制每个账户的集群数量，也可以限制集群的网络控制或连接  |  L-F678F1CE  |  5  | 
|  VPC  |  每个区域的互联网网关数  |  可以限制每个账户的集群数量，也可以限制集群的网络控制或连接  |  L-A4707A72  |  5  | 
|  VPC  |  每个区域的网络接口数  |  可以限制 EKS 工作节点的数量，也可以限制 Impact EKS 控制平面 scaling/update 活动的数量。  |  L-DF5 E4 CA3  |  5000  | 
|  VPC  |  网络地址使用情况  |  可以限制每个账户的集群数量，也可以限制集群的网络控制或连接  |  L-BB24 F6E5  |  64,000  | 
|  VPC  |  对等网络地址的使用情况  |  可以限制每个账户的集群数量，也可以限制集群的网络控制或连接  |  L-CD17 FD4 B  |  128,000  | 
|  ELB  |  每个网络负载均衡器的侦听器数  |  可以限制对集群流量入口的控制。  |  L-57A373D6  |  50  | 
|  ELB  |  每个区域的目标组数  |  可以限制对集群流量入口的控制。  |  L-22855CB  |  3000  | 
|  ELB  |  每个 Application Load Balancer 的目标数  |  可以限制对集群流量入口的控制。  |  L-7E6692B2  |  1000  | 
|  ELB  |  每个网络负载均衡器的目标数  |  可以限制对集群流量入口的控制。  |  L-EEF1 AD04  |  3000  | 
|  ELB  |  每个 Network Load Balancer 每个可用区的目标数  |  可以限制对集群流量入口的控制。  |  L-B211E961  |  500  | 
|  ELB  |  每个区域每个目标组的目标数  |  可以限制对集群流量入口的控制。  |  L-A0D0B863  |  1000  | 
|  ELB  |  每个区域的应用程序负载均衡器数  |  可以限制对集群流量入口的控制。  |  L-53 DA6 B97  |  50  | 
|  ELB  |  每个区域的经典负载均衡器数  |  可以限制对集群流量入口的控制。  |  L-E9E9831D  |  20  | 
|  ELB  |  每个区域的网络负载均衡器数  |  可以限制对集群流量入口的控制。  |  L-69A177A2  |  50  | 
|  EC2  |  运行按需标准版（A、C、D、H、I、M、R、T、Z）实例（作为最大 vCPU 数量）  |  可以限制 EKS 工作节点的数量  |  L-1216C47A  |  5  | 
|  EC2  |  所有标准（A、C、D、H、I、M、R、T、Z）竞价型实例请求（作为最大 vCPU 数量）  |  可以限制 EKS 工作节点的数量  |  L-34B43A08  |  5  | 
|  EC2  |  EC2-VPC 弹性 IPs  |  可以限制 NAT 的数量 GWs （从而限制 VPCs），这可能会限制区域中的群集数量  |  L-0263D0A3  |  5  | 
|  EBS  |  每个区域的快照数  |  可以限制有状态工作负载的备份策略  |  L-309 BACF6  |  100000  | 
|  EBS  |  通用型 SSD（gp3）卷存储（单位 TiB）  |  可以限制 EKS 工作节点的数量或 PersistentVolume 存储空间  |  L-7A658B76  |  50  | 
|  EBS  |  通用型 SSD（gp2）卷存储（单位 TiB）  |  可以限制 EKS 工作节点的数量或 PersistentVolume 存储空间  |  L-D18 FCD1 D  |  50  | 
|  ECR  |  已注册的存储库  |  可以限制集群中的工作负载数量  |  L-CFEB8 E8D  |  100000  | 
|  ECR  |  每个存储库的镜像数  |  可以限制集群中的工作负载数量  |  L-03A36 CE1  |  20000  | 
|  SecretsManager  |  每个区域的秘密  |  可以限制集群中的工作负载数量  |  L-2F66C23C  |  500,000  | 

## AWS 请求限制
<a name="_aws_request_throttling"></a>

AWS 服务还实施请求限制，以确保它们保持高性能并可供所有客户使用。与 Service Quotas 类似，每个 AWS 服务都有自己的请求限制阈值。如果您的工作负载需要快速发出大量 API 调用，或者您注意到应用程序中存在请求限制错误，请考虑查看相应的 AWS 服务文档。

在大型集群中或集群大幅扩展时，围绕配置 EC2 网络接口或 IP 地址的 EC2 API 请求可能会遇到请求限制。下表显示了我们看到的客户遇到请求限制的一些 API 操作。您可以在[关于速率限制的 EC2 文档中查看 EC2 速率限制默认值以及请求提高速率限制](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/throttling.html)的步骤。


| 变异动作 | 只读操作 | 
| --- | --- | 
|  AssignPrivateIpAddresses  |  DescribeDhcpOptions  | 
|  AttachNetworkInterface  |  DescribeInstances  | 
|  CreateNetworkInterface  |  DescribeNetworkInterfaces  | 
|  DeleteNetworkInterface  |  DescribeSecurityGroups  | 
|  DeleteTags  |  DescribeTags  | 
|  DetachNetworkInterface  |  DescribeVpcs  | 
|  ModifyNetworkInterfaceAttribute  |  DescribeVolumes  | 
|  UnassignPrivateIpAddresses  |  | 

## 其他已知限制
<a name="_other_known_limits"></a>
+  [Route 53 对 Route 53 API 的速率限制也相当低，为每秒 5 个请求](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests)。如果您有大量域名需要使用诸如外部 DNS 之类的项目进行更新，则更新域名时可能会出现速率限制和延迟。
  + 某些 [Nitro 实例类型的卷连接限制为 28 个](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/volume_limits.html#instance-type-volume-limits)，由 Amazon EBS 卷、网络接口和 NVMe 实例存储卷共享。如果您的工作负载正在安装大量 EBS 卷，则使用这些实例类型可以实现的 pod 密度可能会受到限制
  + 每个 Ec2 实例可跟踪的连接数有上限。[如果您的工作负载正在处理大量连接，则可能会出现通信失败或错误，因为已达到该最大值。](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-connection-tracking.html#connection-tracking-throttling)您可以使用`conntrack_allowance_available`和`conntrack_allowance_exceeded`[网络性能指标来监控 EKS 工作节点上跟踪的连接数](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-network-performance-ena.html)。
  + 在 EKS 环境中，根据上游指南，etcd 存储限制[为](https://etcd.io/docs/v3.5/dev-guide/limit/#storage-size-limit) **8 GiB**。请监控指标`apiserver_storage_size_bytes`以跟踪 etcd db 大小。您可以参考[警报规则](https://github.com/etcd-io/etcd/blob/main/contrib/mixin/mixin.libsonnet#L213-L240)`etcdBackendQuotaLowSpace``etcdExcessiveDatabaseGrowth`并设置此监控。