ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI推理服务K8s弹性伸缩实践:HPA配置与扩容避坑指南

AI推理服务K8s弹性伸缩实践:HPA配置与扩容避坑指南 1. 从一次故障复盘说起AI健康业务为什么必须自动扩容先讲一个真实发生在我这边的场景。某个工作日晚间八点半我们的AI虚拟健康系统突然迎来一波流量高峰——用户集中上线做健康评估智能问诊模块的请求量在十几分钟内翻了四倍。当时系统还是固定副本数部署核心推理服务在Kubernetes集群里一共跑着8个Pod正常情况下CPU水位在40%左右。那波流量冲上来之后CPU直接拉满Pod开始频繁OOM健康检查探针连续失败前端用户看到的是服务繁忙请稍后再试客服群里一晚上进来了三十多条同类反馈。那次故障持续了将近四十分钟等我们手动扩容到24个副本、业务恢复平稳的时候已经错过了用户线上问诊的黄金时段。事后复盘时大家达成了共识像AI虚拟健康系统这种面向C端、流量天生带脉冲特性的业务靠人肉扩容是救不回来的。凌晨三点加密的紧急工单、比平时贵好几倍的临时机器成本、用户流失的隐性损失这些东西只要经历过一次就不想再来第二次。所以那之后我们做的第一件事就是把弹性伸缩作为系统的基础能力来建设而不是一个可选的优化项。核心选型很明确Kubernetes做容器编排底座HPAHorizontal Pod Autoscaler水平Pod自动扩缩容做自动扩缩容控制器。这套组合本身不算新鲜真正花心思的地方在于——AI推理负载的特征跟传统Web应用不一样CPU模型不能简单套用GPU资源、推理时延、排队长度、服务预热这些因素叠在一起让自动扩容这件事比看上去要复杂得多。这篇文章不打算整篇堆概念我会直接从架构设计、HPA配置细节、AI负载适配、生产验证这几个维度展开。如果你正在做AI类服务上K8s、或者被流量高峰搞得焦头烂额这篇应该能帮你少踩几个坑。另外说明一点文中涉及的具体数值和配置都是我们基于自身场景调整出来的你可以参考思路和推导方法不必照抄。2. 整体架构设计与技术选型背后的取舍逻辑2.1 系统模块拆解哪些服务需要纳入弹性伸缩范围AI虚拟健康系统不是单个服务而是一组服务的集合。从流量入口到最终响应大致可以分成四层接入层Nginx Ingress负责TLS终止、路由转发、限流业务逻辑层用户服务、健康档案服务、预约服务等无状态APIAI能力层智能问诊推理服务、健康评估引擎、语音交互服务数据与状态层MySQL、Redis、向量数据库、对象存储。最开始我们只对业务逻辑层做了HPAAI服务因为依赖GPU暂时没纳入。结果第二次流量高峰一来业务API层倒是扛住了AI推理服务的GPU利用率却冲到95%以上推理请求在队列里排队平均响应时间从800ms拉长到6秒。用户端的体感就是问一句等半天这等于没解决问题。所以后来我们把弹性伸缩的范围扩大到了AI能力层这也是这套架构跟常规HPA实践最不一样的地方。说白了弹性伸缩不是给某个服务单独做的事而是从接入层到推理层统一规划的事。任何一个环节卡住整条链路都是瓶颈用户感知到的永远是那一个卡住的环节。2.2 为什么选择K8sHPA而不是自研扩缩容系统这里先说个背景我们团队早期用的是云厂商的VM负载均衡弹性伸缩靠的是云平台自带的定时策略CPU阈值策略。当时看起来够用实际跑了半年暴露出几个问题冷启动时间太长。新机器从扩容到流量真正能打进去普遍需要5到10分钟赶上流量陡增根本来不及策略维度太少。云平台的伸缩策略基本只看CPU、内存、带宽拿不到业务自定义指标比如推理队列长度请求排队数跨服务协同弱。我们想让AI推理层和业务层联动扩容在云平台原生方案里实现起来很别扭。换到K8sHPA之后这几个问题基本都解决了。Pod级别的扩容粒度是秒级到分钟级启动一个Java服务Pod从镜像拉取到Ready大约40到60秒跟虚拟机不在一个量级。HPA支持通过API获取自定义指标Prometheus Adapter可以把任何指标暴露给HPA做决策。最关键的是所有编排逻辑都落在代码和声明式配置里整个系统是可控、可审计的。那为什么没有直接用KEDAKubernetes Event-driven Autoscaler我们也评估过。KEDA确实在事件驱动方面更完备对Kafka、RabbitMQ这类消息源有原生支持。但我们的核心流量特征是HTTP突增不是消息队列积压HPA的模型已经够用KEDA反而在日常运维上多了一个组件要维护。所以在第一版架构里我们选择了最直接、团队最熟的HPA方案给KEDA留了扩展接口后续如果引入异步任务体系再切换也不迟。2.3 核心链路从Metrics采集到扩缩容决策的完整流程把HPA的工作链路拆开看其实是一套采集—存储—查询—决策—执行的闭环metrics-server或Prometheus Adapter采集Pod的资源使用数据CPU、内存或自定义指标数据通过聚合API暴露给HPA控制器HPA通过/apis/metrics.k8s.io查询HPA控制器在每个同步周期默认15秒内计算当前副本数与期望副本数如果期望副本数偏离当前副本数超过容忍阈值HPA触发扩容或缩容操作Deployment控制器根据期望副本数创建或销毁Pod。期望副本数是怎么算出来的核心公式是期望副本数 ceil(当前副本数 × (当前指标值 / 期望指标值))举个例子当前8个PodCPU平均利用率是85%我们设的期望值是50%那期望副本数就是ceil(8 × 85/50) ceil(13.6) 14个。之所以不是简简单单的85比50是因为副本数和单Pod负载之间存在线性假设下的等比关系。虽然实际系统里负载均衡不一定完全线性但这个公式在绝大多数场景下能给出合理的结果。这里有一个很多人忽略的点HPA的算法是比例估算迭代逼近不是一步到位精确算出最终副本数。所以流量剧烈变化时HPA可能需要几个同步周期才能稳定到目标副本数这也是我们后续要配合稳定窗口、behavior策略的原因。3. HPA配置从能用到好用稳定窗口、多指标与行为策略3.1 一份能直接落地的HPA配置样例直接贴一份我们生产环境在用的HPA配置针对的是AI智能问诊推理服务。这版配置经过了好几轮压测和故障验证参数不是随手填的下面会逐个解释。apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: ai-inference-hpa namespace: production spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: ai-inference minReplicas: 4 maxReplicas: 32 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 - type: Pods pods: metric: name: inference_queue_depth target: type: AverageValue averageValue: 5 behavior: scaleUp: stabilizationWindowSeconds: 60 policies: - type: Percent value: 100 periodSeconds: 30 - type: Pods value: 8 periodSeconds: 30 selectPolicy: Max scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 20 periodSeconds: 60先说metrics部分。我们同时挂了CPU利用率和自定义指标inference_queue_depth。CPU利用率是保底指标覆盖那些流量没上来但代码死循环把CPU打满的异常场景自定义指标才是主力具体含义是推理服务内部消息队列的积压深度这个指标能更真实地反映当前服务是不是处理不过来了。再说behavior这是很多人容易忽略但价值极大的部分。HPA从v2版本开始支持通过behavior字段精细控制扩缩容行为。扩缩容策略由稳定窗口stabilizationWindowSeconds和扩缩容策略policies组成。我们这里设置的是扩容允许每30秒翻倍或最多增加8个Pod取两者中更激进的selectPolicy: Max缩容则要求维持300秒稳定后每分钟最多缩容20%。这组配置的价值在于既能快速跟上流量突增又不会在流量小波动时频繁抖动。3.2 稳定窗口为什么这个参数直接决定用户体验稳定窗口这个参数很多人第一次看文档会直接忽略但它恰恰是能不能在生产环境用起来的关键。HPA的容忍度逻辑是这样的假设当前期望副本数是20实际是18偏差只有2个副本10%小于默认容忍度HPA就不会调整。只有偏差超过容忍度才触发扩缩容。但是如果指标在50%和90%之间来回跳动HPA就可能在扩容和缩容之间反复横跳每次调整都要创建或销毁Pod引发不必要的资源浪费和服务抖动。稳定窗口的作用就是给决策加一层惯性。扩容侧我们设置了60秒意思是60秒内HPA会保留历史决策的最大值避免刚扩容完又立刻缩回去。缩容侧设置300秒是避免流量稍微降一点系统就急着缩容结果下个高峰又得扩容回来形成震荡。调整稳定窗口时候有个经验扩容稳定窗口可以短一点30到90秒缩容一定要拉长至少3到5分钟。原因是缩容的代价是闲置资源浪费扩容的代价是服务质量下降。两害相权宁可多跑一会闲置资源也别在流量还没稳的时候就把Pod杀了。3.3 多指标组合里的主次关系与判断逻辑多指标同时存在时HPA取的是所有指标计算出的副本数的最大值。比如CPU算出来要10个副本队列深度算出来要16个副本HPA按16个执行。这个设计逻辑很容易理解任何一个指标到了瓶颈都说明系统容量不足应当扩到满足最紧张那个指标的数量。反之缩容时也要等所有指标都满足低于目标的条件才会真正缩容。实际使用中要留意一个问题某些自定义指标容易瞬时尖刺。比如推理队列深度可能在一两秒内因为某次大请求批量进入而暴增触发扩容但10秒后队列又消费完了。这种指标如果不加平滑处理就直接暴露给HPA会导致扩容频率过高。我们的做法是在Prometheus Adapter的查询里做最近1分钟平均通过avg_over_time函数把瞬时毛刺磨平而不是在HPA端增加稳定窗口硬扛。3.4 自定义指标接入Prometheus Adapter的配置要点自定义指标接入HPA走的链路是应用暴露指标 - Prometheus采集 - Prometheus Adapter转换成聚合API - HPA查询。关键配置在Prometheus Adapter的规则里截取一段我们在用的规则rules: - seriesQuery: inference_queue_depth_total{namespace!,pod!} resources: overrides: namespace: { resource: namespace } pod: { resource: pod } name: matches: inference_queue_depth_total as: inference_queue_depth metricsQuery: sum(rate(inference_queue_depth_total{.LabelMatchers}[1m])) by (.GroupBy)这里面的核心思路是先找到原始指标序列seriesQuery然后把namespace和pod标签映射成K8s资源resources最后用PromQL计算出每个Pod的指标值metricsQuery。注意.LabelMatchers和.GroupBy是Adapter的模板变量作用分别是把当前请求的过滤条件和分组维度拼进去这样HPA按Pod查询时Adapter就知道该返回哪个Pod的数据。配置完成后可以用一条命令验证指标是否生效kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/production/pods/*/inference_queue_depth | jq如果返回了每个Pod的指标值说明链路是通的。这一步看似简单其实很多团队卡在这里很久Prometheus有数据但HPA查不到极大概率就是Adapter规则里的标签映射写错了。4. AI推理场景的流量高峰应对GPU、预热与队列协同4.1 AI服务扩容为什么不能只盯CPU常规Web服务的HPA盯CPU或内存就够了因为请求处理是均匀的更多副本数意味着更多吞吐。但AI推理服务不一样它是计算密集型负载而且常常依赖GPU。如果我们把GPU加速的推理服务也按CPU利用率来扩容会遇到几个尴尬情况CPU利用率很低的Pod可能GPU已经打满了。因为大部分推理框架如TensorFlow Serving、TorchServe把计算都卸载到GPUCPU只负责数据预处理和调度GPU显存占用高不一定意味着计算繁忙。显存分配是模型加载时一次性完成的即使没有请求进来显存占用也在那里。拿显存利用率做扩容指标会导致永远扩不上去或者过度扩容推理延迟和吞吐之间存在非线性关系。吞吐到一定程度后每增加一个并发请求延迟会指数级上升而CPU利用率可能才刚到70%。所以AI服务的扩容指标更应该关注请求排队深度和P95推理延迟而不是CPU或显存的绝对值。排队深度直接反映处理能力缺口延迟则反映用户体验。我们在实践里把这两个指标都接进了自定义指标效果比单纯用CPU准确不少。4.2 模型加载与预热HPA扩容成功后的下一道坎HPA扩容成功不等于服务就能立刻承担流量。AI推理服务有一个典型问题新Pod从创建到Ready需要加载模型权重、初始化推理引擎这个时间可能是几十秒甚至几分钟取决于模型规模和硬件。拿我们用的健康评估模型来说模型文件大概1.2GB量化后也有400MB左右。新Pod启动时要把权重从对象存储拉到本地再加载进显存整个过程大约需要30到45秒。如果流量高峰期间HPA扩了10个Pod但这10个Pod在预热完成前不允许接流量那么实际扛峰容量并不是当前副本数而是已完成预热的副本数。这个问题我们用了三个手段协同解决配置就绪探针readinessProbe探针在模型加载完成后才返回成功。K8s只会把流量打给就绪的Pod这样用户请求不会打到半成品Pod上利用HPA的scaleUp策略预先扩一部分副本。我们在压测时发现如果等到队列深度超标再扩容会出现一个尴尬窗口——扩容请求发出去了但新Pod还在预热这段时间服务质量继续恶化。所以后来配合定时策略在业务高峰到来前5分钟先扩到预期水位比如每天19:00自动扩到20个Pod高峰期过了再让HPA慢慢缩回来在推理服务的入口加了等待队列和超时控制Pod预热期间队列深度会短暂上升但不会导致请求直接失败而是在队列里等待有Pod就绪后再消费。这里有个反直觉的经验不要把就绪探针的initialDelaySeconds设成0。应用进程启动后模型加载和引擎初始化都需要时间探针太早探测只会看到未就绪白白重试多次。我们设的是initialDelaySeconds: 10periodSeconds: 5基本上模型加载到一半左右探针开始探测正好能准确感知就绪状态。4.3 流量高峰模式识别与扩容策略配置观察了半年线上流量之后我们总结出AI虚拟健康系统常见的三种流量高峰模式每种模式的应对策略不一样脉冲型突发的、短促的流量尖峰持续时间可能只有几分钟。典型场景是某个健康科普内容在外部渠道爆了用户集中涌入。这种场景要求扩容速度足够快稳定窗口不能太长等流量过去后又要快速缩回来否则资源成本扛不住潮汐型可预测的、有规律的流量起伏。比如工作日早高峰和晚间饭后是用户问诊的高峰时段节假日可能整体偏低。这种场景适合用定时扩缩容打底再用HPA做动态修正持续型活动期间比如健康筛查推广流量连续几天保持在高位。这种场景主要靠把maxReplicas上限调高同时确保底层资源池容量充足。针对这三种模式我们在同一个HPA上通过behavior策略做了统一处理再叠加CronJob来实现在特定时间段预先扩张副本数。CronJob做的事情很简单到点修改Deployment的replicas字段但会被HPA接管覆盖因为HPA的副本数优先级高于手动设置的replicas。这里需要特别提醒HPA开启后不要直接去改Deployment的replicasHPA会在下一个同步周期把你改的值覆盖掉。如果确实需要定时扩容合理做法是写一个临时调整HPA的minReplicas/maxReplicas的小工具。我们实现的定时扩容CronJob逻辑大致如下apiVersion: batch/v1 kind: CronJob metadata: name: scale-out-before-peak spec: schedule: 0 18 * * * jobTemplate: spec: template: spec: containers: - name: scale image: bitnami/kubectl:latest command: - /bin/bash - -c - | kubectl patch hpa ai-inference-hpa -n production \ --type merge \ -p {spec:{minReplicas:20,maxReplicas:32}} restartPolicy: OnFailure每天晚上18点把HPA的minReplicas抬高到20等系统扛过晚间高峰另一个CronJob在23点把minReplicas调回4。这样做既保证了高峰时段有基础容量兜底又不影响HPA在min/max范围内动态调整。5. 踩坑实录三次告警轰炸后的完整排查链路5.1 第一个坑HPA一动不动最终查出是metrics-server版本问题上线初期我们把HPA配置好之后发现一个诡异现象不管流量怎么涨HPA始终显示副本数没变化。kubectl describe hpa里看到的状态一直是Current: 8 pods, Desired: 8 pods完全没有扩缩容动作。先说排查思路。HPA不动作可能出问题的环节有四层指标采集层、指标查询层、HPA计算层、执行层。我们按这个顺序逐层排查先看metrics-server的Pod状态kubectl get pods -n kube-system | grep metrics-server发现Pod在反复重启CrashLoopBackOff看日志kubectl logs -n kube-system metrics-server-pod --previous报错的是insecure skip verify相关的问题查metrics-server版本和K8s集群版本的兼容性才发现集群是1.20版本但metrics-server用的是v0.4.x这个版本对1.20的aggregator兼容性很差需要升到v0.6.x。根因就是版本兼容问题。metrics-server作为指标聚合组件它需要通过kube-aggregator把指标API注册到K8s里版本不匹配时注册失败HPA查询指标的请求就得不到响应。HPA拿不到指标数据时不会报错而是静默保持当前副本数不变——这个静默失败的设计很容易让问题潜伏很久。后来我们建了一个监控定时检查kubectl get --raw /apis/metrics.k8s.io/v1beta1是否正常返回数据如果返回异常就告警。这个检查放在HPA本身之前算是指标的指标。顺带说一下如果你的HPA用的不是metrics-server而是Prometheus Adapter同样需要注意版本和配置。Adapter的--metrics-relist-interval参数控制它多久重新加载一次规则默认是1分钟你改了规则后如果发现指标没有变化多半是还没到重新加载的时间。5.2 第二个坑扩容之后性能反而下降CPU指标解读出了偏差还有一次印象很深的故障流量高峰时HPA确实触发了扩容从8个Pod扩到16个但扩完之后系统整体吞吐量反而下降了P95延迟比扩容前还高。当时第一反应是扩容出的Pod有问题逐个检查新Pod的日志和资源使用情况都没发现异常。后来把监控面板的时间轴拉出来对比才发现问题出在调度上新扩容的8个Pod有6个被调度到了同一台物理节点上。那台节点本来已经跑了其他服务加上这6个推理Pod之后CPU争抢激烈反而拖慢了整体推理速度。根因是集群的节点资源水位不均。我们的集群里有几台老节点配置较低而K8s默认调度器在做Pod调度时虽然会计算节点资源可用量但用的是请求值requests而不是实际用量usage。推理服务的requests设得比较保守2C4G但实际运行时会跑到4C8G甚至更高。调度器看到节点还有资源就把Pod塞上去了结果实际运行时就超卖击穿了。排查链路如下先确认HPA动作正常kubectl describe hpa显示扩容触发了且scaleTargetRef正确指向了Deployment检查新建Pod的分布kubectl get pods -o wide | grep ai-inference发现多个Pod集中在同一节点查看该节点的资源占用kubectl describe node node-name发现Allocated resources已经接近上限对比Pod实际资源使用kubectl top pods | grep ai-inference发现实际使用远超requests。解决思路有两个方向一是把Deployment的requests设置得贴近真实使用量让调度器提前感知资源压力二是给关键服务加PodTopologySpreadConstraints让Pod尽量分散到不同节点避免扎堆扩容。spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: ai-inferencemaxSkew: 1的意思是不同节点上的副本数差异最多1个。这样即使HPA一次性扩出很多Pod调度器也会尽量把它们打散到不同节点而不是一股脑塞到看起来有空闲的那台机器上。这里多说一句whenUnsatisfiable有DoNotSchedule和ScheduleAnyway两个选项。我们用的是DoNotSchedule表示如果无法满足打散要求这个Pod就不调度宁可Pending等待也不要扎堆。缺点是有时候会造成Pod长时间Pending所以需要配合合理的maxReplicas上限和节点池规划确保集群整体容量是够的。5.3 第三个坑扩缩容抖动导致资源反复创建销毁最终靠behavior和阈值拯救第三个坑是在某个促销活动期间遇到的。那几天流量波动特别剧烈系统在扩容-缩容-扩容之间来回折腾。我们统计了一下一个下午HPA触发了二十多次扩缩容操作Pod频繁创建和销毁镜像仓库拉取压力大集群节点也一直在调度Pod-回收Pod的循环里很多Pod还出现启动到一半被杀掉的情况。问题出在哪里两个原因叠加。第一流量本身波动大峰谷交替密集第二也是更关键的我们把缩容稳定窗口设得太短了只有60秒。流量稍微一降HPA觉得低负载持续了60秒就开始缩容结果流量刚下去又起来又得扩容。这次之后我们把behavior参数调成了上面配置里那组扩容稳定窗口60秒、缩容稳定窗口300秒同时缩容策略限制为每分钟最多20%。注意这里不是只改一个数字那么简单而是想清楚了两个问题缩容的代价是资源浪费扩容的代价是服务不可用。在C端用户体验优先的场景里我们宁可浪费资源也不能让服务在高峰时处于还在扩容途中的亚健康状态限制缩容速率能让刚缩完又要扩的窗口大幅缩短。就算流量确实长期下降了缩容只是慢一点不会造成成本失控。另外还加了一道保险scaleDown策略里把selectPolicy设成了Min虽然上面示例里扩缩容都只写了一个策略默认不冲突但如果你配置了多个策略建议缩容用Min扩容用Max避免缩容策略叠加出激进效果。5.4 关于排错给一个通用的排查顺序经历了这几次故障我们整理了一份HPA异常的排查顺序团队新人对着一路查就行指标数据存在吗检查metrics-server或Prometheus Adapter是否正常返回数据用kubectl top pods和kubectl get --raw分别验证HPA状态怎么说kubectl describe hpa看Events和Conditions重点看FailedGetResourceMetric、FailedComputeFullReplicaCount这类异常原因指标值合理吗对比监控面板确认HPA拿到的指标值和Prometheus里看到的实际值一致扩缩容动作执行了吗看Deployment的副本数和kubectl get events里的ScaledUp/ScaledDown事件新Pod正常吗检查新Pod的启动日志、就绪探针、资源实际占用调度分布合理吗用kubectl get pods -o wide检查Pod是否均匀分布在节点上。这套排查顺序不能说覆盖所有情况但90%的HPA问题都能通过这六步定位到根因。6. 生产验证方案与扩容后稳定性加固6.1 压测方案设计怎么验证弹性伸缩真的可靠配置写好了总得用数据说话。我们做的验证分成两轮一轮是功能性验证确认HPA能扩能缩另一轮是以扩容速度和稳定性为目标的压测。压测工具用的可以是wrk、k6这类常见负载生成器。考虑到我们的场景是AI问答接口请求特征是单次请求耗时长、对响应时间敏感所以我们采用并发阶梯式压测每5分钟增加50个并发一直加到600保持30分钟观察HPA的响应曲线。压力测试期间的几项关键指标指标意义参考值我们的场景首次扩容触发时间从流量升高到HPA首次扩Pod的时间30秒内可接受扩容完成时间从触发到新增Pod全部Ready60秒内较理想扩容期间P95延迟扩容过程中用户体验的恶化程度不超过正常值的1.5倍缩容后的资源消耗流量回落后多久能降到正常水位10分钟以内压测下来我们这套HPA配置的表现还不错流量上来后大约40秒触发扩容60到90秒内新Pod全部就绪P95延迟从正常时的1200ms涨到1600ms左右但没出现请求大量超时的情况。重点改进在于把minReplicas周期性抬高和Prometheus Adapter的平滑查询结合起来后扩容触发的灵敏度有了明显改善。6.2 扩容之后的三座大山连接池、缓存击穿与分布式锁HPA扩容成功只是第一步扩容之后稳定性能不能保证才是真正决定上线成败的因素。这里说三个我们踩过、也坑了很多团队的点。连接池打满。推理服务每个Pod都维持着到MySQL和Redis的连接池。扩容前8个Pod连接池总共占用80个MySQL连接扩容后32个Pod连接池翻了四倍。如果MySQL的max_connections没预留够连接就会在DB层堆积直接拖垮数据库。我们的处理方法是给推理服务单独建了一个只读账号连接数上限按最大副本数×单Pod连接池大小来预留同时在代码层面接入了连接池动态调整逻辑Pod数增加时连接池初始大小也随之调整避免默认值过大导致启动期就占满连接。缓存击穿。健康评估服务的部分结果是带缓存的缓存Key设计上带了用户维度。流量高峰期间大量新用户涌入缓存全部Miss请求直接打到数据库上。如果不做保护扩容等于把流量压力从应用层转移到了DB层。我们的方案是两层一是对热点评估结果做进程内短时缓存TTL设30秒二是对数据库查询做了单飞逻辑同一时间同一评估Key只有第一个请求真正落库其他请求等待缓存回填。这个单飞模式在高峰期能挡住90%以上的DB穿透请求。分布式锁与任务重复执行。AI健康评估流程里有一些异步任务比如生成健康报告、调用外部服务做数据补充。这些任务如果在多个Pod里并发执行会造成严重的系统性能浪费。我们用Redis分布式锁做了互斥锁的粒度细化到任务实例ID而不是锁全局。这个点看起来和弹性伸缩无关但扩容后副本数变多并发冲突概率会指数级上升必须提前做好隔离。6.3 扩容后的优雅下线不能让流量打到正在销毁的Pod上缩容是弹性伸缩的另一半缩得不好同样会引发故障。Pod被删除时如果负载均衡还在往这个Pod转发请求用户就会遇到连接重置或请求超时。K8s提供了terminationGracePeriodSeconds和PreStop钩子来处理优雅下线。我们的配置思路是接到SIGTERM信号后先停掉服务注册从负载均衡摘除不再接收新请求等待几秒让已接收的请求处理完毕再退出进程。实现上可以给Deployment加上PreStop钩子执行一条sleep命令spec: template: spec: terminationGracePeriodSeconds: 60 containers: - name: ai-inference lifecycle: preStop: exec: command: [/bin/sh, -c, sleep 10]这个sleep的10秒时间就是给Ingress和Service的endpoints更新留出时间窗口。别小看这个动作没有PreStop的话新Pod和销毁Pod的节奏错位常常导致请求成功率下降0.5到1个百分点。对于C端系统来说这个损失不算小。6.4 巡检脚本自动化把看HPA面板从手工操作里解放出来最后分享一个我们内部的小工具一个巡检脚本写在CI里定时跑或者在故障时手动执行一次可以快速定位HPA相关异常。核心逻辑也很简单就是把我前面说的排查顺序固化成命令#!/bin/bash # 检查HPA状态 kubectl get hpa -n production | grep ai-inference # 检查metrics-server健康 kubectl get --raw /apis/metrics.k8s.io/v1beta1 | head -n 20 # 检查Pod分布 kubectl get pods -n production -o wide | grep ai-inference | wc -l kubectl get pods -n production -o wide | grep ai-inference # 检查节点水位 kubectl top nodes输出结构化信息人工扫一眼基本就能判断问题出在哪个环节。7. 上线半年之后的复盘与几个值得调整的方向系统上线半年多经历了几轮真实的流量高峰验证整体是稳的。这里复盘几个我们后续想继续优化的方向也给你们做参考。第一预测性扩容值得探索。现在我们是反应式扩容——先有压力再扩容中间难免有几十秒的服务质量下降。要想做到流量还没到Pod已经就位就得引入预测。我们规划了两个路径一个是用历史流量数据做时序预测比如基于Prophet或LSTM训练流量模型每天定时把预测结果同步给HPA另一个是结合业务侧的突发事件信号比如运营后台确认要推一个健康专题时主动触发预扩容。React式扩缩容保底预测式扩缩容提体验两者结合才是比较理想的方案。第二当前自定义指标只覆盖了推理队列和P95延迟其实还可以把用户排队等待时长纳入指标。这个是业务侧的真实体感比队列深度更贴近用户感受。因为队列深度相同的情况下如果队列里的请求都是长耗时请求用户等待时间会更长。把这个指标引入后HPA就能照顾到更多真实体验维度的变化。第三GPU层面的弹性。现在的架构里GPU节点是固定的HPA扩出来的Pod如果对GPU有需求调度器会优先往GPU节点塞。但如果GPU节点池已经满了新Pod只能Pending。下一步我们打算引入GPU资源共享方案或者规划自动扩缩容的GPU节点池让推理层的弹性伸缩更加彻底。第四多集群容灾。目前单集群架构能扛住单节点故障但扛不住整个集群的故障。我们已经在规划第二集群用Federation或者直接在Ingress层做流量切换。HPA的配置需要跨集群同步这里肯定还有新的坑要踩等落地后再来分享。8. 一点个人体会弹性伸缩是一把手工程不是加个HPA就完事最后说一句个人的感受。很多人以为用了K8s和HPA弹性伸缩就自动搞定了。但实际操作下来你会发现HPA只是打开了自动扩容的开关真正决定系统能不能扛住流量高峰的是你对业务负载特征的理解深度——AI服务和Web服务不一样GPU和CPU不一样长耗时推理和短请求不一样每次扩容都意味着服务能力翻倍同时依赖的系统压力翻倍。我们在生产环境踩过的每一个坑背后都对应一个之前没想清楚的问题CPU指标和真实负载之间的关系、扩容后新Pod能不能立刻接流量、调度分布是否均匀、缩容会不会太激进、数据库连接池够不够、缓存会不会被打穿。这些问题一个个解决了HPA才真正从一个配置变成了一个可靠的机制。如果你的系统正在经历类似的流量之痛我建议别急着上来就写HPA配置先花点时间把自己的业务负载特征、指标结构、历史流量曲线梳理清楚。设计弹性伸缩方案的时候把下面几条原则刻在脑子里扩容要快缩容要慢稳定窗口是防抖的关键多指标取最大值不要让单一指标成为盲区扩容的目标是新Pod能立刻干活而不是新Pod存在扩出来的流量最终都会打到下游数据库、缓存扩容前先确认下游扛得住每一条HPA策略都应该经过压测验证别只做功能测试就上线。把这几条想明白了你再去配置HPA会发现那些文档里一笔带过的参数每一个都有它存在的理由。这套架构现在还在持续迭代后面有新的实践和踩坑我再来更新分享。
返回列表