

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

# 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 API 的各种运算符，例如网络和存储驱动程序（在图的右侧）。尽管 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/enhancements/issues/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)


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

### 不必要的错误的代价
<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 服务器，因为它允许我们查看客户端应用程序或控制平面是否存在问题。