如何用 Kubernetes 管理超过 2500 个节点的集群

Posted K8sMeetup

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何用 Kubernetes 管理超过 2500 个节点的集群相关的知识,希望对你有一定的参考价值。

 | 为  |  容  |  器  |  技  |  术  |  而  |  生  |

翻译:夏天

校对:郭维


两年来,我们一直在使用 Kubernetes 进行深度学习方面的研究。虽然我们大量的 workloads 可以直接运行在云虚拟机上,但是因为 Kubernetes 目前仍在快速迭代,而且拥有比较好的扩展性,也没有过多的约束,因此 Kubernetes 成为了我们最理想的集群管理工具。目前,我们已经有好几个正在运行的 Kubernetes 集群了(有些运行在云虚拟机上,有些则直接运行在物理机上)。其中最大的一个集群运行在 Azure 上,由 D15v2 和 NC24 两种类型的虚拟机组成,目前已经超过 2500 个节点。


在达到 2500 节点规模的过程中,许多的系统组件都出现了问题,包括 etcd,Kube Master,Docker 拉取镜像,网络,Kube DNS,机器上的 ARP 缓存。接下来将分享我们所遇到的问题和解决方案,希望对大家有所帮助。


etcd


在集群中添加了超过 500 个节点的时候,我们的研究人员发现使用 kubectl 操作集群的时候会发生超时的情况。于是我们尝试添加更多的 Kube Master 节点(运行 kube-apiserver 的节点)。这样做临时解决了这个问题,但当我们添加了 10 个 Master 节点后,我们发现这只是在表面上解决问题,并没有找到这个问题的根本原因(作为对比,GKE 只使用了一台 32 核的虚拟机作为 Master 就能管理 500 个节点。


etcd 存储了 Kubernetes 集群的所有状态数据,因此我们强烈怀疑是 etcd 集群出现了问题。通过查看 Datadog,我们发现虽然我们的每台机器都使用了支持 5000 IOPS 的 P30 SSD,但是运行了 etcd 的 DS15v2 虚拟机的写延迟在某些时间段猛增至数百毫秒。

如何用 Kubernetes 管理超过 2500 个节点的集群

这么高的写延迟阻塞了整个集群!

在使用 fio 进行性能测试之后,我们发现 etcd 的 I/O 性能只能达到 I/O 上限的 10% 左右 。这主要是因为单次写延迟为 2ms,而 etcd 又使用了串行 I/O,导致 I/O 之间的延迟叠加(latency-bound)。


然后,我们将每个节点上的 etcd 的目录从网络磁盘移到本地 SSD 磁盘上。移动之后,写延迟降低到了 200 微秒,etcd 终于正常了。


我们的集群一直运行良好,直到我们增加到了 1000 个节点。这个时候我们再次发现 etcd 出现了很高的延迟。这一次,我们注意到 kube-apiservers 从 etcd 读取数据的速度超过了 500MB/s。我们搭建了 Prometheus 用于监控 kube-apiservers,还设置了  --audit-log-path--audit-log-maxbackup 选项来保存更多的日志数据。通过日志,我们发现了许多慢查询请求和大量的对 Events 的 List API 的请求。


我们找到了问题的根本原因,我们发现部分程序会频繁去 kube-apiservers 查询各种信息(比如 Fluentd 和 Datdog 会去 kube-apiservers 查询每个节点信息)。我们修改了这些程序的设置,降低它们对 kube-apiservers 的查询频率,然后 kube-apiservers 的负载就降下来了:

如何用 Kubernetes 管理超过 2500 个节点的集群

etcd 的出口速度从 500MB/s 左右降低到了接近 0MB/s(上图中负值表示出口速度)


另一个有效的改动是把 Kubernetes Events 存储在一个单独的 etcd 集群里,这样对 Events 的操作就不会影响到主 etcd 集群的正常工作。能做到这一点,我们只需要将这 --etcd-servers-overrides 设置为

如何用 Kubernetes 管理超过 2500 个节点的集群

另一个超过 1000 节点后的故障是触发了 etcd 的硬盘存储上限(默认是 2GB),于是 etcd 不再接受写请求。这直接导致了一连串的问题:所有 kube node 的健康检查都失败了,我们的 autoscaler 决定删除所有的 workers。于是我们通过 etcd 的  --quota-backend-bytes 增大了存储大小。另外还让 autoscaler 支持基础(sanity)检查,在发现要终止集群里超过 50% 的 workers 的时候,放弃这个操作。

Kube Master


我们在同一台机器上安装了 kube-apiserverkube-controller-managerkube-scheduler processes。为了高可用,我们一直有至少两个 masters,并将 --apiserver-count 设置为我们正在运行的 apiservers 数量(否则 Prometheus 可能会混淆这些实例)。


我们主要使用 Kubernetes 作为批量调度系统,并依靠 autoscaler 动态地伸缩我们的集群。这使我们可以显著地降低空闲节点的成本,在快速迭代的同时提供低延迟。默认的 kube-scheduler 策略是将负载均匀分散在节点之间,但这与我们的期望相悖,我们希望可以终止未使用的节点,同时也可以快速调度大的 pod。所以我们切换到下面的策略:

如何用 Kubernetes 管理超过 2500 个节点的集群

我们使用 KubeDNS 实现服务发现,但是在我们使用新的调度策略后,很快它就出现了一些可靠性问题。我们发现故障只在 KubeDNS 的 Pods 中出现。在新的调度规则影响下,有些机器上运行了十多个 KubeDNS 实例,这就导致了这些机器成为了热点,它们收到的 DNS 查询超过了 200 QPS(这是 Azure 虚拟机对 DNS 查询的上限值)。

如何用 Kubernetes 管理超过 2500 个节点的集群


Docker 拉取镜像


我们的 Dota 项目刚开始运行在 Kubernetes 上的时候,一旦它开始扩容,我们就注意到一些新的节点上有很多 Pods 处于 Pending 状态,而且持续很长时间。Dota 的镜像大小为 17GB,把这个镜像拉取到一个新的节点上需要大约 30 分钟的时间。因此我们知道了 Dota 的 Pod 处于 Pending 的原因,但是同一个节点上的其他比较小的 Pod 也出现了相同的情况。


随着调查的深入,我们发现 kubelet 的 --serialize-image-pulls 默认为 true,这意味着拉取 Dota 镜像的时候,其他镜像的拉取都会被阻塞住。把这个选项修改为 false 需要让 Docker 使用 overlay2 文件系统而不是 AUFS。为了加快拉取速度,我们把 Docker 的根存储目录迁移到了机器的本地 SSD 上,就像 etcd 那样。


在优化了拉取速度之后,我们仍然发现 Pods 出现了奇怪的错误:error message: rpc error: code = 2 desc = net/http: request canceled。由于进度不足,Kubelet 和 Docker 的日志里也指出了镜像拉取被取消了。我们究其根源,发现当有很多积压的镜像拉取任务,或者有些大镜像花费很长的时间都没有完成拉取和解压工作的时候,就会出现这种错误。为了解决这个问题,我们将 Kubelet 的 --image-pull-progress-deadline 设置为 30 分钟, 并且设置了 Docker 的  max-concurrent-downloads 为 10。(第二个选项不会加速大镜像的解压工作,但是可以让拉取镜像的工作并行执行 。)


我们最后的 Docker 镜像拉取问题源自 Google Container Registry。默认情况下, Kubelet 会自动从 gcr.io 拉取一个特殊的镜像(可由 --pod-infra-container-image 控制),这个镜像用于启动新的容器。如果拉取失败,这个节点就不能启动任何 Pod 。由于我们的节点没有公网 IP,只能通过 NAT 访问 gcr.io,一旦超过了单个 IP 的配额上限,就会拉取失败。为了解决这个问题,我们通过预加载镜像直接将镜像部署到机器上。通过  docker image save -o /opt/preloaded_docker_images.tardocker image load -i /opt/preloaded_docker_images.tar 完成这个工作。为了提升性能,我们对其他公共镜像比如 OpenAI-internal 和 Dota 镜像也做了相同的事情。


联网


随着我们的实验越来越大,我们的系统也逐渐变成了重度依赖网络的复杂的分布式系统。当我们第一次开始分布式实验的时候,立即就发现了我们的网络不是很好。机器之间的吞吐量大约在 10-15Gbit/s, 但是我们基于 Flannel 的 Pods 之间的最大吞吐量仅仅只有 2Gbit/s。


Machine Zone 的公开性能测试也显示了类似的结果,这意味着这个问题未必是由配置不好导致的,很有可能是我们的环境中隐藏了一些问题(机器上的 Flannel 应该不会增加这么大的开销)。


为了解决这个问题,用户可以使用两个不同的设置来让 Pods 绕过 Flannel 的网络:hostNetwork 设置为 true,dnsPolicy 设置为 ClusterFirstWithHostNet(在做这件事情之前请先阅读 Kubernetes 中的警告)。


ARP 缓存


这个修改在 HPC 集群中非常常见,而此时在 Kubernetes 中这个选项也非常重要,因为每个 Pod 都有自己的 IP,而每个 IP 都需要占用 ARP 缓存空间。


参考文献:

https://blog.openai.com/scaling-kubernetes-to-2500-nodes/?utm_source=digg


推荐阅读:




END



线上课堂


1 月 31 日的 K8sMeetup 中国社区【线上课堂】开始报名!


时间:2018 年 1 月 31 日 20:00

话题:容器集群性能优化之路》

讲师:华为 CloudBU 资深工程师 钟成


以上是关于如何用 Kubernetes 管理超过 2500 个节点的集群的主要内容,如果未能解决你的问题,请参考以下文章

如何用 Helm 管理 Jenkins,看这篇就够了

2.5T及以上的硬盘如何使用如何用parted创建分区安装CentOS7

如何用女朋友能懂的方式解释Kubernetes

Istio技术与实践05:如何用istio实现流量管理

(十三):如何用 Loki 收集查看 Kubernetes Events

如何用 Ubuntu 和 Xen 来设置 Kubernetes