一、概述
最近 ChatGPT 和其公司 OpenAI 特地火:ChatGPT 3, ChatGPT 3.5, New Bing, ChatGPT 4…
怀着学习的心态,这几天拜访了 OpenAI 的博客, 上边对于 AI 的内容,的确隔行如隔山,齐全看不明确。😂
然而翻看过程中,惊喜发现有 2 篇与 Kubernetes 应用相干的文章:
- 2018 年 1 月:Scaling Kubernetes to 2,500 nodes (openai.com)
- 2021 年 1 月:Scaling Kubernetes to 7,500 nodes (openai.com)
这不碰到老本行了嘛,学习下~
以下为读后笔记,也退出了本人的思考:针对 OpenAI 现状,如何进一步优化监控、镜像拉取、容器编排相干架构。
二、读后笔记
2.1 Dota 2 的 OpenAI 是跑在 Kubernetes 上的
💪💪💪
Dota2 游戏镜像大概是 17GB😛
2.2 OpenAI 如何应用 Kubernetes
2.2.1 用处
Kubernetes 在 OpenAI 次要用于深度学习,次要应用的是 Kubernetes Job.
2.2.2 抉择 Kubernetes 起因
Kubernetes 提供了
- 疾速的迭代周期
- 正当的可扩展性
- 规范样板
2.2.3 Kubernetes 集群规模
- 集群运行在 AZure 上
- 2018 年博文,集群 Node 规模为 2500 多
- 2021 年博文,集群 Node 规模为 7500 多
2.2.4 Kubernetes 超大规模应用过程中遇到的问题
- etcd
- Kube masters
- Docker 镜像拉取
- 网络
- KubeDNS
- 机器的 ARP 缓存
2.2.5 Kubernetes 配套工具
-
监控:
- DataDog(商业监控)
- Prometheus + Grafana
-
日志:
- fluentd
-
网络:
- 刚开始是 Flannel
- 前面是 Azure 的 VMSSes CNI 插件
2.3 Kubernetes 超大规模发现的问题及解决
2.3.1 Etcd
2.3.1.1 问题形容
集群 500 节点后,刚开始是 kubectl 应用卡;另外通过 DataDog 监控发现磁盘写入提早飙升至数百 ms,只管每台机器都应用 30,5 IOPS 的 P000 SSD。
前面解决之后,加到 1000 节点,发现 etcd 提早再次变高;发现 kube-apiservers 从 etcd 读取的速率超过 500MB/s
另一个 1,000 个节点后的故障是达到了 etcd 的硬存储限度(默认为 2GB),这导致它进行承受写入。
2.3.1.2 解决方案
- 将每个节点的 etcd 目录挪动到本地长期磁盘,该长期磁盘是间接连贯到实例的 SSD,而不是网络连接的 SSD。切换到本地磁盘使写入提早达到 200us,etcd 变得衰弱!(依据咱们之前的 Azure 应用教训,其网络 SSD 的确性能不太行😂)
-
1000 节点时呈现的问题,在于
- 启用了审计日志
- 启用了 Prometheus 对 APIServer 的监控
- 这导致呈现了许多迟缓的查问和对事件的 LIST API 的适度调用
- 根本原因:默认设置 fluentd 和 Datadog 的监控过程是从集群中的每个节点查问 API 服务器。” 咱们 ” 只是简略地更改了这些过程,使其轮询不那么激进,并且 apiserver 上的负载再次变得稳固
- 将 Kubernetes Events 存储在一个独自的 etcd 集群中,这样事件创立中的峰值不会影响主 etcd 实例的性能。配置如前面的代码段
- 咱们用标记
--quota-backend-bytes
减少了最大 etcd 大小。
--etcd-servers-overrides=/events#https://0.example.com:2381;https://1.example.com:2381;https://2.example.com:2381
最初,etcd 和 APIServer 都运行在专用节点上。防止相互影响。
在 7500 节点时,有 5 个 etcd 节点。
2.3.2 API Server
在 7500 节点时,有 5 个 API 服务器,并且每个 API 服务器应用的堆内存高达 70GB.
2.3.3 Docker 镜像拉取
2.3.3.1 问题形容
Dota 容器会在一段时间内处于 pending 状态——但对于其余容器也是如此。
解决之后,发现有报错:rpc error: code = 2 desc = net/http: request canceled
, 表明因为不足进度,镜像拉取已被勾销。
还有个问题,OpenAI 的 Kubernetes 组件镜像是默认从 gcr.io
拉取的,然而 gcr.io
可能失败或超出配额(机器用的 NAT 公网 IP 是同一个,很容易超出配额).
2.3.3.2 解决方案
kubelet 有一个 --serialize-image-pulls
默认为 true
的标记,示意 Dota 镜像拉取阻塞了所有其余镜像。
将 --serialize-image-pulls
改为 false
; 将 Docker 根目录挪动到了实例附加的 SSD(而不是网络 SSD)
针对第二个问题,大镜像须要太长时间的 pull 和提取,或者当有大量积压的镜像须要拉取时。为了解决这个问题,咱们将 kubelet 的 --image-pull-progress-deadline
标记设置为 30 分钟,并将 Docker 守护过程的 max-concurrent-downloads
选项设置为 10。第二个选项没有放慢大镜像的提取速度,但容许镜像队列并行拉取。
为了解决这个 gcp.io
失败的问题,” 咱们 ” 通过应用 docker image save -o /opt/preloaded_docker_images.tar
和docker image load -i /opt/preloaded_docker_images.tar
,在 Kubernetes worker 的机器镜像中预装了这些 Docker 镜像。为了进步性能,咱们对常见的 OpenAI 外部镜像如 Dota 镜像的白名单做了同样的解决。
2.3.3.3 🤔笔者思考
对于 OpenAI 碰到的 Docker 镜像拉取的问题,都是十分典型的大规模 Kubernetes 会碰到的问题,其实有更好的解决方案:P2P 镜像解决方案,典型就是 DragonFly.
DragonFly 提供高效、稳固、平安的基于 P2P 技术的文件散发和镜像减速零碎,并且是云原生架构中镜像减速畛域的规范解决方案以及最佳实际。其最大的劣势就是:
- 基于 P2P 的文件散发:通过利用 P2P 技术进行文件传输,它能最大限度地利用每个对等节点(Peer)的带宽资源,以进步下载效率,并节俭大量跨机房带宽,尤其是低廉的跨境带宽。
- 预热:P2P 减速可预热两种类型数据 image 和 file, 用户能够在控制台操作或者间接调用 api 进行预热。
2.3.4 网络
Flannel 在这种超大规模场景下必定是撑不住的,刚开始 OpenAI 采纳了非常简单暴力的解决方案(也适宜他们的应用场景): pod 配置应用 HostNetwork:
...
hostNetwork: true
...
dnsPolicy: ClusterFirstWithHostNet
前面是改为应用 Azure 的 VMSSes CNI 插件。
2.3.4.1 🤔笔者思考
其实 OpenAI 对 Kubernetes 的刚需是:容器编排,网络性能不是刚需,OpenAI 不必 Kubernetes CNI 也能够的。前面会延长讨论一下。
2.3.5 ARP 缓存
另外一个可能常常会疏忽的点是 ARP 缓存问题。
2.3.5.1 问题形容
有一天,一位工程师报告说,他们的 Redis 服务器的 nc -v
须要 30 多秒能力打印出连贯曾经建设。咱们追踪到这个问题是由内核的 ARP 栈引起的。对 Redis pod 主机的初步考察显示,网络出了重大的问题:任何端口的通信都要挂上好几秒,而且无奈通过本地 dnsmasq 守护过程解析 DNS 名称,dig 只是打印了一条神秘的失败信息:socket.c:1915: internal_send: 127.0.0.1#53: Invalid argument
。dmesg 日志的信息量更大:neighbor table overflow!
这意味着 ARP 缓存的空间曾经用完。ARP 是用来将网络地址(如 IPv4 地址)映射到物理地址(如 MAC 地址)的。
2.3.5.2 解决方案
在 /etc/sysctl.conf
中设置选项:
net.ipv4.neigh.default.gc_thresh1 = 80000
net.ipv4.neigh.default.gc_thresh2 = 90000
net.ipv4.neigh.default.gc_thresh3 = 100000
在 Kubernetes 集群中调整这些选项尤其重要,因为每个 pod 都有本人的 IP 地址,会耗费 ARP 缓存的空间。
2.3.6 Prometheus 和 Grafana
因为大量的采集和查问,Prometheus 和 Grafana OOM 的频率也不低。
2.3.6.1 解决方案
-
Grafana: 实质上还是 Prometheus 的高基数问题,我之前介绍过,见这里:
- Prometheus 性能调优 – 什么是高基数问题以及如何解决?- 东风微鸣技术博客 (ewhisper.cn)
-
Prometheus: 重启后,WAL replay 是个问题,在 Prometheus 收集新指标和服务查问之前,通常须要破费数小时能力 replay 所有 WAL 日志。
- 应用
GOMAXPROCS=24
配置,在 WAL replay 期间,Prometheus 试图应用所有的内核,对于领有大量内核的服务器来说,这种抢夺会扼杀所有的性能。
- 应用
2.3.6.2 🤔笔者思考
- Prometheus 近期版本性能会好很多,及时降级到最新版本会对性能问题大有帮忙。比方:高基数,大内存,cpu 耗费较多等都有肯定水平优化。
- Prometheus 在这么大规模集群状况下,倡议创立多个 node role 为 monitoring 的高配机器(也挂本地 SSD), 供 Prometheus 专用。
- 或者更近一步,能够抉择兼容 Prometheus 的其余计划,如:VictoriaMetrics(适宜存储为块存储场景)和 Grafana Labs 公布的 Mimir(适宜存储为对象存储场景).
2.4 OpenAI Kubernetes 的应用场景及举荐的替换计划
这里详细描述一下 OpenAI Kubernetes 的应用场景:
- 次要用到的资源类型是 Job
- 对于 OpenAI 的许多工作负载,单个 Pod 占据了整个节点
- OpenAI 以后的集群具备残缺的平分带宽,不会思考任何机架或网络拓扑
- OpenAI 不太依赖 Kubernetes 负载平衡,HTTPS 流量很少,不须要 A/B 测试,蓝 / 绿或金丝雀
- Pod 通过 SSH 应用 MPI 在其 Pod IP 地址上间接互相通信 (hostNetwork),而不是通过 service endpoint 进行通信
- 服务发现是无限的
- 有一些 PersistentVolume,但 blob 存储的可伸缩性要高得多
- 防止应用 Overlay 网络,因为会影响网络性能
2.4.1 🤔笔者思考
看完 Scaling Kubernetes to 7,500 nodes (openai.com) 这篇,其实会发现 OpenAI 对 Kubernetes 的应用和一般 IT 公司差别还是比拟大的。
OpenAI 最次要用的是:Kubernetes 的 容器编排, 特地是对 Job 的调度能力。
其余 Kubernetes 性能,用的很少或简直没有,如:
- 存储 CSI 用的很少,次要应用的是 blob 存储
- 网络 CNI 用了,但 Pod 次要用的是 hostNetwork
- DNS 用的也不多
- Service 用的很少,Pod 次要用的是 hostNetwork
- Ingress(即上文说的 Kubernetes 负载平衡)用的很少,因为 Kubernetes 集群次要用于试验(当初随着 ChatGPT 的大规模应用可能会用的比之前多一些)
- Kubernetes 的一些高级公布策略,如 A/B 测试,蓝 / 绿或金丝雀也不须要
所以我集体认为(观点仅供参考), Kubernetes 对于 OpenAI 来说,还是有些过于简单和性能冗余的。
OpenAI 真正须要的,是一个纯正的 容器编排 解决方案,特地是对 Job 的调度能力。
所以我感觉啊,不思考用户规模,不思考 Kubernetes 是容器编排畛域的事实上的规范的话,HashiCorp 的 Nomad 反而是更适合的解决方案。以下是具体理由:
- Nomad 是一个易于应用、灵便和高性能的工作负载调度器,能够部署混合的微服务、批处理 、 容器化 和非容器化利用。
- GPU 反对:Nomad 为 GPU 工作负载(如机器学习(ML)和人工智能(AI))提供内置反对。Nomad 应用设施插件来自动检测和利用来自硬件设施(如 GPU、FPGA 和 TPU)的资源。
- 通过验证的可扩展性:Nomad 乐观地并发,可进步吞吐量并缩小工作负载的提早。Nomad 已被证实能够在理论生产环境中扩大到 10K+ 节点的集群。
- 简略性:Nomad 作为单个过程运行,内部依赖性为零。运维人员能够轻松配置、治理和扩大 Nomad。开发人员能够轻松 定义并运行应用程序。
- Nomad 的其中 2 个 调度器: Batch 和 System Batch, 十分符合 OpenAI 的应用场景。
- Nomad 并不附带服务发现及网络的相干性能,只是在 1.3 版本当前, 减少了内置的本地服务发现(SD),使得 Consul 或其余第三方工具非必要。Nomad 的服务发现并不是要取代这些工具。相同,它是一个替代品,能够更容易地测试和部署更简略的架构。
2.5 横向扩大小技巧
另外,惊喜的发现 OpenAI 的博文中居然提到了 Kubernetes 横向扩大的小技巧,OpenAI 将其称为:CPU & GPU balloons.
笔者这里具体向大家介绍一下:
2.5.1 横向扩大的工夫悖论
首先,OpenAI 的横向扩大需要波及 2 个层面:
- Kubernetes 集群层面的 Node 扩大,通过 Cluster AutoScaler 实现(这里 OpenAI 用的是本人开发的,个别各个私有云都提供对应的 Cluster AutoScaler 插件), 为的是疾速横向扩大 Node
- Pod 层面的 横向扩大 (HPA), 为的是疾速横向 Pod 的数量。
然而,在流量或业务量飙升的状况下,Node(也就是云虚拟机)的扩大并不像 Pod 那么迅速,个别是须要几分钟的初始化和启动的工夫,进而影响到 Pod 的横向扩大,导致无奈及时响应业务飙升的需要。
2.5.2 解决方案概述
为此,解决方案就是 CPU & GPU balloons, 具体如下:
在新 Node 上创立新 Pod 所需的工夫由四个次要因素决定:
- HPA(Horizontal Pod Autoscaler) 响应工夫。
- Cluster Autoscaler 响应工夫。
- Node 配置工夫
- Pod 创立工夫
这里次要耗时是 Node 配置的工夫,这次要取决于云提供商。
一个新的计算资源在 3 到 5 分钟 内实现配置是很规范的。
在新 Node 上创立新 Pod 所需的工夫预估须要 7 min 左右。
如果你须要一个新的 Node,你如何调整主动缩放以缩小 7 分钟的缩放工夫?
大部分工夫花在了 Node 配置上,因为你不能扭转云供应商提供资源的工夫,所以就须要一个变通办法:
即:被动创立节点(超配),这样当你须要它们时,它们曾经被配置好了。
始终确保有一个备用节点可用:
- 创立一个节点并将其留空(其实是搁置一个 balloon pod 来占用该节点)。
- 如果空节点中有 Pod(非 balloon pod) 就会创立另一个空节点。
这里,能够运行具备足够 requests 的 deployment 来保留整个节点。
能够将此 pod 视为占位符 — 它旨在保留空间,而不是应用任何资源。
一旦创立了一个真正的 Pod,您就能够逐出占位符并部署 Pod。
具体怎么实现呢?
2.5.3 具体实现
- 具备 requests 的 Pod
- Pod 部署优先级 (PodPriorityClass) 和抢占
如果您的节点实例是 2 vCPU 和 8GB 内存,那么 Pod 的可用空间应该为 1.73 vCPU 和 ~5.9GB 内存 (OS, kubelet, kubeproxy 等须要预留肯定资源),以使得 Pod 独占该 Node。
具体资源需要能够如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: overprovisioning
spec:
replicas: 1
selector:
matchLabels:
run: overprovisioning
template:
metadata:
labels:
run: overprovisioning
spec:
containers:
- name: pause
image: registry.k8s.io/pause
resources:
requests:
cpu: '1739m'
memory: '5.9G'
而后,要确保在创立真正的 Pod 后立刻逐出该 Pod,须要应用 优先级和抢占。
Pod 优先级示意 Pod 绝对于其余 Pod 的重要性。
当无奈调度 Pod 时,调度程序会尝试抢占(逐出)优先级较低的 Pod 来调度挂起的(优先级较高的)Pod。
能够应用 PodPriorityClass 在集群中配置 Pod 优先级:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: overprovisioning
value: -1
globalDefault: false
description: 'Priority class used by overprovisioning.'
因为 Pod 的默认优先级是 0,而超配的 PriorityClass 的值是 -1,所以当集群的空间耗尽时,这个 Pod 会被首先驱赶。
之前的 Deployment 能够调整为:(spec 中 减少 priorityClassName
为 overprovisioning
)
apiVersion: apps/v1
kind: Deployment
metadata:
name: overprovisioning
spec:
replicas: 1
selector:
matchLabels:
run: overprovisioning
template:
metadata:
labels:
run: overprovisioning
spec:
priorityClassName: overprovisioning
containers:
- name: reserve-resources
image: registry.k8s.io/pause
resources:
requests:
cpu: '1739m'
memory: '5.9G'
当集群中没有足够的资源时,占位的 pod 会被抢占,新的 pod 会取代它们的地位。
因为占位 pod 变得不可调度,它迫使 Cluster Autoscaler 向集群增加更多节点。
🎉🎉🎉
三、总结
本文通过学习 OpenAI 博客中 Kubernetes 相干博文,学到了很多超大规模 Kubernetes 集群下的调优技巧。
在这里梳理了 OpenAI 遇到的性能问题及解决方案,同时就横向扩大小技巧(占位🎈) 进行了具体阐明。
同时,联合笔者的教训,也做出一些延长思考:
-
在 Kubernetes 集群中,
- Metrics 监控:举荐应用 VictoriaMetrics 和 Grafana Labs 公布的 Mimir 替换 Prometheus
- 镜像工具:举荐应用 DragonFly, 利用 P2P 和预热性能缓解镜像拉取问题
- 在容器 / 批处理编排调度解决方案中,能够尝试抉择 HashiCorp 的 Nomad 替换 Kubernetes.
以上。
参考文档
- Scaling Kubernetes to 2,500 nodes (openai.com)
- Scaling Kubernetes to 7,500 nodes (openai.com)
- Prometheus 性能调优 – 什么是高基数问题以及如何解决?- 东风微鸣技术博客 (ewhisper.cn)
- 大规模 IoT 边缘容器集群治理的几种架构 -2-HashiCorp 解决方案 Nomad – 东风微鸣技术博客 (ewhisper.cn)
- Architecting Kubernetes clusters — choosing the best autoscaling strategy (learnk8s.io)
- Grafana 系列文章(一):基于 Grafana 的全栈可察看性 Demo(包含 Mimir) – 东风微鸣技术博客 (ewhisper.cn)
三人行, 必有我师; 常识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.