# Kubernetes 集群规模杂谈

## 节点数量

早在 Kubernetes 1.2 时候，就已经宣布达到 1000 节点的规模了，在 1.6 版本更达到了 5000 节点的规模。各大厂也都有了各自的超大规模单一集群。然而普罗大众的情况是如何呢？ Sysdig 在 2019 年度容器应用报告中得到的结果是，大于 50 节点规模的集群不足 10%，另外一个佐证是 Mohamed Ahmed 的一篇调查报告中也提供了类似的数据。这种情况的一种解释是，目前的应用阶段还比较早期，处于试探期间；然而从一个侧面来说，Sysdig 的调研对象针对的是生产应用，也就是说处于生产应用状态下的集群，绝大多数都是这种小规模集群。根据对 CNCF Landscape 中 Distribution 分类的产品的抽查，也可以看到随处可见的 Kubernetes As Service 类似功能的实现，这也证实了小集群协作方案的落地趋势。相对于少量大集群，多个小集群的差异在于：

### 隔离程度高

虽然现在存在不少沙箱容器实现，然而最易用的、生态最为成熟的方案还是 Docker为代表的传统容器方案，传统容器方案所缺失的隔离能力，通过多租户多集群方式是一个非常自然的思路。

### 实现难度低

国内几个大厂都有自己的大规模 Kubernetes 集群实现方式，然而通常需要对基础组件大动干戈，甚至不惜使用无法回流社区的孤岛版本，虽然部分大企业的研究院等相关部门已经具备了非常强的研发实力，然而对于通常的 To B 场景来说，这并不是一个合适的选择。

### 运管成本高

多个集群很明显会需要更多的运维和管理人力的投入。

### 资源利用率低

多个集群都会有自己的 Master 组件、ETCD 集群、网络组件等，这些都会抢占更多原本属于工作负载的系统资源，客观上降低了资源的总体利用率。

## 节点尺寸

目前很多 Kubernetes 系统都会使用虚拟机来做为节点。那么虚拟机的资源是多分还是少分呢？下表是一个简单的对比：

||大节点|小节点|备注|
|---|---|---|---|
|节点数量|少|多|同样的资源总量情况下，相对来说小资源节点会得到更多的数量。|
|运维成本|低|高|通常情况下，节点的运维成本是和节点数量正相关的。             |
|容错能力|低|高|较大的节点上通常会集中较多的应用，因此在节点出现故障时，可能会带来更大的损失。|
|资源粒度|大|小|单节点资源较大，因此其资源粒度也较大。|
|应用副本数|少|多|同一应用的多个副本，如果调度到同一个节点上的话，对于提高其负载能力和健壮性来说并无裨益。|
|副本规模|大|小|毫无疑问，具备更多资源的大节点，能够运行更大资源需求范围的容器应用。|
|系统开销|少|多|每个虚拟机都会有自己的操作系统、网络等基础开销，因此相对于少量大节点来说，大量的小节点会消耗更多的资源。|
|虚拟机分配难度|高|低|过大的节点资源需求，如果采用虚拟机分配，就需要有更大规模的物理机提供支持。|

除了这些原则性的条目之外，更重要的决策依据就是运行在集群上的应用需求。例如某租户的集群需要支撑 20 个应用，共 300 个 Pod，按照常见的每节点 30-50 Pod 的分布，就需要 6-10 个运算节点（Node）。以 10 节点算，加入系统保留、冗余等计算，可能需要 10 * 120G 的虚拟机实例；然而考虑到故障情况——一个节点的故障，最好的结果也是短期内降低 10% 的算力。如果扩张到 40 个 32G 的虚拟机节点，会大幅降低单节点故障的影响——当然也会提高网络的复杂性和效率要求。

## 应用资源

Java 应用是特别常见的迁移案例，除掉微服务化、网格、分布式等改造要求之外，资源的申请和限制是一个必须要面对的门槛。requests 是个用于调度的定义，Kubernetes 根据这个要求来选择能够满足要求的节点来分配应用；而 limits 则会用于触发 OOM。

众所周知的是，Java 的早期版本是无法识别容器内的内存限制的，因此如果没有限制堆内存上限，又开启了 limits，就会被 Kubernetes 杀掉。因此针对容器中运行的情况，需要进行一些启动参数的设置。

如果允许更新到新版本的 JVM，可以使用新引入的 UseCGroupMemoryLimitForHeap、MaxRAMFraction 参数，让 JVM 直接继承容器的定义。

如果无法直接升级，那么就有必要设置 xmx 和 xms 参数了，这里有几个小建议：

- xmx 和 xms，request 和 limits 建议设成一致，能省掉很多麻烦。
- tmpfs、filemapping 等都是可能的内存大户。
- JVM 并不是唯一的内存消耗者，一般建议 Limit 大于 XMX 25% 以上。
- /sys/fs/cgroup/memory/memory.stat 是你的好朋友。

## Kubernetes 中的 CPU 和内存

Kubernetes 集群中的资源，主要关注的是 CPU 和内存两种。Pod 的定义中会定义对资源需求的声明，声明方式分为 Request 和 Limit。

Request 是一个调度参数，可以理解为基本需求：一个 Pod 中的所有容器的 Request 之和，就是 Pod 对资源的最小需求，调度器根据这个最小需求来选择具备条件的节点，在其上运行被调度的 Pod。

Limit 是一个安全参数，它的值一定大于 Request，顾名思义，它声明的是上限：

CPU是弹性资源，如果容器使用CPU达到Limit，就无法进一步提高运算能力，可能会导致运算速度无法满足需求。

- Memory 是非弹性资源，如果容器使用 Memory 达到 Limit，就会触发 cgroup 的 OOM 事件，导致容器被杀死。

综上所述，Memory超限会对业务产生更大伤害，那么是不是不设限会更安全？答案很显然是否定的：

- 不设置 Limit，一旦引发系统 OOM 或者驱逐事件，宏观来看，都会导致一个不可预知的结果。
- 不设置 Request，Kubernetes 调度器会失去重要的调度标准，会影响负载分布的准确性。

一般来说如果 Limit 大于 Request（称为 Burstable），Kubernetes 会根据 Request 将 Pod 调度到满足 Request 要求的节点上去，然而一旦内存消耗从 Request 向着 Limit 增长的过程中出现了节点内存不足的情况，仍然会引发驱逐问题，因此对于保障级别高的业务，我们强烈建议将 Limit 设置为和 Request 相等。

## 副本和节点数量

目前 Kubernetes 的主流网络模型是基于 iptables 的，很显然 Service、Endpoint 和 Pod 并非越多越好。

而对于应用来说更多的副本数往往意味着更好的容错能力——同样损失一个副本，越多总数意味着业务损失越小。

## 参考资料

- `https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/`

- `https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource/`

- `https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes/`

- `https://dig.sysdig.com/c/pf-2019-container-usage-report?x=u_WFRi&mkt_tok=eyJpIjoiWW1GbVptUmtOakk1T1RVNCIsInQiOiJCUitxTXpSYUpXbVJOUDBUK09sbDh4aDVDNkZURHFXK0UwdUNEbkp6UG43XC9VamJIbm9obzJ6MDdcL3EwYXRHS0dTMVdrQXlJaEZDUFd5WnE0WUpXa1ZNVHZyRFkrYjlTNmhwb3d4cFk0alBSOHBqY09mY0pkaDV1VkZCeCtOaHpnIn0%3D`
