OpenAI Kubernetes 相关博文读后笔记

2023-03-23  本文已影响0人  东风微鸣

一、概述

最近 ChatGPT 和其公司 OpenAI 特别火:ChatGPT 3, ChatGPT 3.5, New Bing, ChatGPT 4...

怀着学习的心态,这几天访问了 OpenAI 的博客, 上边关于 AI 的内容,确实隔行如隔山,完全看不明白。😂

但是翻看过程中,惊喜发现有 2 篇与 Kubernetes 使用相关的文章:

这不碰到老本行了嘛,学习下~

以下为读后笔记,也加入了自己的思考:针对 OpenAI 现状,如何进一步优化监控、镜像拉取、容器编排相关架构。

二、读后笔记

2.1 Dota 2 的 OpenAI 是跑在 Kubernetes 上的

Dota2 Sven

💪💪💪

Dota2 游戏镜像大约是 17GB😛

2.2 OpenAI 如何使用 Kubernetes

2.2.1 用途

Kubernetes 在 OpenAI 主要用于深度学习,主要使用的是 Kubernetes Job.

2.2.2 选择 Kubernetes 原因

Kubernetes 提供了

2.2.3 Kubernetes 集群规模

2.2.4 Kubernetes 超大规模使用过程中遇到的问题

2.2.5 Kubernetes 配套工具

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-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.tardocker image load -i /opt/preloaded_docker_images.tar,在 Kubernetes worker 的机器镜像中预装了这些 Docker 镜像。 为了提高性能,我们对常见的 OpenAI 内部镜像如 Dota 镜像的白名单做了同样的处理。

2.3.3.3 🤔笔者思考

关于 OpenAI 碰到的 Docker 镜像拉取的问题,都是非常典型的大规模 Kubernetes 会碰到的问题,其实有更好的解决方案:P2P 镜像解决方案,典型就是 DragonFly.

DragonFly 提供高效、稳定、安全的基于 P2P 技术的文件分发和镜像加速系统,并且是云原生架构中镜像加速领域的标准解决方案以及最佳实践。其最大的优势就是:

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 解决方案
2.3.6.2 🤔笔者思考
  1. Prometheus 近期版本性能会好很多,及时升级到最新版本会对性能问题大有帮助。比如:高基数,大内存,cpu 消耗较多等都有一定程度优化。
  2. Prometheus 在这么大规模集群情况下,建议创建多个 node role 为 monitoring 的高配机器(也挂本地 SSD), 供 Prometheus 专用。
  3. 或者更近一步,可以选择兼容 Prometheus 的其他方案,如:VictoriaMetrics(适合存储为块存储场景) 和 Grafana Labs 发布的 Mimir(适合存储为对象存储场景).

2.4 OpenAI Kubernetes 的使用场景及推荐的替换方案

这里详细描述一下 OpenAI Kubernetes 的使用场景:

2.4.1 🤔笔者思考

看完 Scaling Kubernetes to 7,500 nodes (openai.com) 这篇,其实会发现 OpenAI 对 Kubernetes 的使用和普通 IT 公司差异还是比较大的。

OpenAI 最主要用的是:Kubernetes 的容器编排, 特别是对 Job 的调度能力。

其他 Kubernetes 功能,用的很少或几乎没有,如:

所以我个人认为(观点仅供参考), Kubernetes 对于 OpenAI 来说,还是有些过于复杂和功能冗余的。

OpenAI 真正需要的,是一个纯粹的容器编排解决方案,特别是对 Job 的调度能力。

所以我觉得啊,不考虑用户规模,不考虑 Kubernetes 是容器编排领域的事实上的标准的话,HashiCorp 的 Nomad 反而是更合适的解决方案。以下是具体理由:

Nomad_PrimaryLogo_FullColor

2.5 横向扩展小技巧

另外,惊喜的发现 OpenAI 的博文中竟然提到了 Kubernetes 横向扩展的小技巧,OpenAI 将其称为:CPU & GPU balloons.

笔者这里详细向大家介绍一下:

2.5.1 横向扩展的时间悖论

首先,OpenAI 的横向扩展需求涉及 2 个层面:

但是,在流量或业务量飙升的情况下,Node (也就是云虚拟机)的扩展并不像 Pod 那么迅速,一般是需要几分钟的初始化和启动的时间,进而影响到 Pod 的横向扩展,导致无法及时响应业务飙升的需求。

2.5.2 解决方案概述

为此,解决方案就是 CPU & GPU balloons, 具体如下:

在新 Node 上创建新 Pod 所需的时间由四个主要因素决定:

这里主要耗时是 Node 配置的时间,这主要取决于云提供商。

一个新的计算资源在 3 到 5 分钟内完成配置是很标准的。

在新 Node 上创建新 Pod 所需的时间预估需要 7 min 左右。

如果你需要一个新的 Node,你如何调整自动缩放以减少 7 分钟的缩放时间?

大部分时间花在了 Node 配置上,由于你不能改变云供应商提供资源的时间,所以就需要一个变通办法:

即:主动创建节点(超配),这样当你需要它们时,它们已经被配置好了。

始终确保有一个备用节点可用

  1. 创建一个节点并将其留空(其实是放置一个 balloon pod 来占用该节点)。
  2. 如果空节点中有 Pod (非 balloon pod) 就会创建另一个空节点。

这里,可以运行具有足够 requests 的 deployment 来保留整个节点。

可以将此 pod 视为占位符 — 它旨在保留空间,而不是使用任何资源。

一旦创建了一个真正的 Pod,您就可以逐出占位符并部署 Pod。

具体怎么实现呢?

2.5.3 具体实现

如果您的节点实例是 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 中 增加 priorityClassNameoverprovisioning)

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 遇到的性能问题及解决方案,同时就横向扩展小技巧(占位🎈) 进行了详细说明。

同时,结合笔者的经验,也做出一些延伸思考:

以上。

参考文档

三人行, 必有我师; 知识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.

上一篇 下一篇

猜你喜欢

热点阅读