1写在前面
- 工作中遇到,简单整理
- 第一次接触,一些粗浅的思考
- 理解不足小伙伴帮忙指正
对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对大众理想的懦弱回归,是随波逐流,是对内心的恐惧 ——赫尔曼·黑塞《德米安》
部署方面
pod 调度方面
对于 AI 相关项目,一般镜像都比较大,尤其涉及的库比较多,基本都是以 GB 为单位,而且部分项目可能需要专门的硬件支持,所以尽可能的通过 亲和性(affinity)
或者拓扑分布约束(topologySpreadConstraints)
来对调度区域进行限制,减少对整个集群节点存储的占用,方便维护,这里需要分情况考虑,
如果部署 Pod 相对较少,可以考虑通过 亲和性来处理,控制在指定标签的节点上,但是可能存在Pod不会均匀分布的情况,如果希望均匀分布,可以使用拓扑分布约束。
如果部署 Pod 相对较多,考虑通过 拓扑分布约束来限制,限制 Pod 只能调度到指定的拓扑域,但是尽量不要亲和性约束和拓扑分布约束同时使用,维护起来相对不方便。
Pod资源限制
在资源使用方面,如果当前项目和业务项目位于同一集群
,那么需要对所在命名空间做资源配额
,这里可以考虑使用 LimitRange
结合 Resource Quotas
使用.
对于 内存
的限制,根据实际分配情况进行配置,内存属于 不可压缩资源,所以 Cgroup 可以严格控制,对应 CPU 来说,配置需要小于当前约定的配额,CPU 属于可压缩资源,Cgroup 不能像内存那种做成严格限制,超过去只能是少分配处理时间,所以可能会有弹性的变化,已经超出去了,才会限制,不像内存,直接就申请不了那么多起不来,超过去直接 kill 掉。
Pod 数量扩展方面
对于 CPU/GPU 计算密集型
的 Pod 来讲,Pod 不是越多越好,纵向的资源扩展(Pod资源配额)要优于横向的Pod扩展(Pod 数量)
, 多个 Pod 分配很少的资源,处理速度要远远低于 Pod 分配资源较多,但 Pod 数较少。具体情况需要具体分析,实际上还要结合项目需要去考虑,训练和预测可能还存在差异
横向扩展,不能依赖 service 提供的负载均衡能力
测试中发现,使用无状态控制器
来部署项目核心服务提供能力,当 Pod
数量相对较多时, service
往往不能均匀的负载(底层使用 iptable ),存在两级分化
默认情况下 ,SVC 使用轮询模式 RoundRobin
的负载分发策略,实现方式略有不同
当使用 iptables
模式,iptables 负载均衡使用随机模式
,每个对应的 endpoint
会添加一个自定义链,从 SVC 的 服务发布方式(NodePort,ClusterIP,LoadBalancer ) 到 对应的 Pod,当 Pod 比较多,匹配的概率会变的很小,下面为当 Pod 数为 2 的时候,iptables 的匹配概率为 0.50000000000
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.169.89:9090" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-OLNIRCQFCXAN5USW
当使用 ipvs
模式的时候,即依赖 Linux 内核 lvs
特性,使用的负载策略为 rr(轮询调度)
TCP 192.168.26.200:80 rr
-> 192.168.26.153:80 Masq 2 0 0
-> 192.168.26.154:80 Masq 2 0 0
解决办法可以考虑使用流量治理工具,比如 istio
或者对 负载进行划分,降低负载域的范围
架构方面
数据处理 拉(pull)比推(push)更合适
AI 相关项目一般涉及的数据量都比较大,尤其涉及到一些张量
之类的大数组, 实际测试发现,通过一个调度中心把数据推给对应的能力提供 Pod 处理,在速度上要比 对应该的能力提供 Pod 直接从中间件拉取数据处理慢的多的多。
推的方式需要考虑负载问题,以及每个 Pod 实际的饱和状态,而且涉及到多次数据通信,但是拉的话就不需要考虑这么多。具体的处理可以使用K8s
中 job
控制器来实现,根据数据量,创建对应 job
任务,而且每次处理的 Job 也可以分批次控制。
异步不等于快,吞吐量不等于处理速度,网络IO密集型和 CUP 密集型是两种不同场景
当前的 python 框架大多支持异步处理,利用 Python 的协程,基于 asyncio
事件循环,可以有很高的并发量,吞吐量,尤其是 ASGI
标准的框架(fastapi,tomado)等,但是需要注意的是, asyncio
事件循环 的高吞吐 面向 网络/IO
密集型的非阻塞处理,不适用 CPU 密集型,对于 CPU 密集型,任然会阻塞。
即使通过队列等方式做了处理,解决的也只是吞吐量,和处理速度没有关系。往往看上去处理完了,会发现程序内部积累了大量的协程,吃进去了,但是消化不了。
利用中间件交换数据,比通过通信协议直接交换数据 更适合
通过中间件交换数据,解耦了 服务之间的绑定,细化了流程,不需要考虑数据在传输的丢失,二是使用中间件,存储处理的中间数据,更方便测试,可以灵活处理各个阶段对数据的处理