ARTICLE DETAIL

资讯详情

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

Knative + ACK:云原生弹性伸缩从固定资源池到按需智变

Knative + ACK:云原生弹性伸缩从固定资源池到按需智变 流量曲线跟账单之间的账做过后端的人多半都心里有数。你的业务一天里峰值可能是低峰的十倍甚至几十倍但Kubernetes集群里的Pod却只能按峰值预留常驻。结果就是大促过去Deployment还在那里烧钱凌晨三四点没人访问的时候三副本照样稳稳地运行着。这套模式在业务早期没毛病可真到了要精细化运营、要降本增效的时候固定资源池的短板就暴露得很彻底。我在这篇文章里要聊的就是用Knative 阿里云ACK容器服务Kubernetes版这套组合把弹性伸缩这件事从能用做到智变。核心解决三类问题一是按需缩容到零让没流量的应用不再占用成本二是基于并发数的精准扩容比传统的CPU、内存指标响应更快、更贴合在线服务场景三是通过ACK的弹性容器实例ECI在真正高峰时快速弹出资源做到既不浪费又有底气。这个方案适合谁如果你的服务有明显的潮汐特征——定时任务、消息推送回调、运营活动页、夜间低峰的内部系统——又或者你已经被K8s的固定节点池和居高不下的账单搞得头疼那这篇文章值得你从头到尾看完。我会把架构原理、部署步骤、参数调优和真实场景下的成本测算一次讲透。1. 核心难题固定资源池与潮汐流量之间的账怎么算1.1 为什么固定副本模式在降本面前不堪一击先抛一个很多团队实际遇到过的情况。某个业务服务每天凌晨到早上八点几乎没有请求白天平均QPS在200左右但每到晚高峰或者运营推活动时会瞬间冲到2000以上的QPS。过去我们的做法很直接为了保证高峰不宕机把Deployment的replicas设为20个Pod常驻每Pod分配2C4G。这样确实扛住了高峰但账单也很真实——20个Pod每天24小时在跑哪怕凌晨只有零零散散几个请求占用的资源和成本一分钱不会少。有人会说那我用HPAHorizontal Pod Autoscaler不就行了按CPU或者内存指标自动扩缩容。问题是HPA的伸缩逻辑有几个硬伤首先它的扩容依据是资源使用率但CPU和内存到达瓶颈再到扩容完成中间存在明显的滞后。其次HPA默认的缩容行为偏保守为了稳定可能会在低峰期还保留大量副本。更重要的是HPA能做的只是把Deployment的副本数从20缩到5、再到10它没法做到缩到0——因为Pod都缩没了谁来处理请求从架构上讲HPA本身不具备流量代理的能力它天生只负责保持一定数量的副本至于流量是否打进来了、打进来多少HPA并不关心。这就引出了固定副本模式的两大矛盾扩容的滞后性和缩容的极限性。前者导致你在突发流量面前只能靠提前预留资源来兜底后者导致你在低谷期只能看着资源白白空转。这两个矛盾叠加在一起成本自然就下不来。1.2 Knative真正解决的三件事按需伸缩、精确扩容、流量无缝切换为什么偏偏是Knative能解开这个结因为它解决的不只是伸缩这一个点而是把整个流量入口到后端实例的链路重新设计了一遍。第一件事缩容到零。Knative Serving在检测到没有流量时可以把某个服务的副本数量降到0。这时候请求进来怎么办Knative引入了一个叫Activator的组件它平时接管所有目标Pod的访问流量。当服务缩到0时Activator会收到进来的请求然后立刻通知Autoscaler拉起Pod等Pod Ready之后再把请求转发过去。对调用方来说多了一次冷启动的等待但对系统来说低谷期的资源占用直接归零。第二件事基于并发数的精确扩容。Knative默认的伸缩控制器KPAKnative Pod Autoscaler不是看CPU而是看每个Pod同时正在处理的请求并发数。比如你设定单个Pod的并发目标是100当实时并发数涨到120、150、200时KPA会主动计算出需要增加的副本数并迅速执行。这种以请求数作为伸缩信号的思路比看CPU指标更贴近在线服务的真实负载曲线。第三件事流量路由与灰度能力。Knative Serving天然支持把一个版本的流量按照权重同时分给多个Revision版本也就是说你可以先切10%流量到新版本验证没问题再逐步放量。这在弹性伸缩场景里非常有用——当你缩容到零后重新拉起新副本时它天然就带着这套路由规则不需要额外再叠一套Istio或者网关配置。再强调一下我在实际项目中的体会Knative和K8s原生的HPA不是替代关系而是互补关系。HPA解决的是普通微服务按照资源使用率水平伸缩Knative解决的是无状态在线服务按照实时请求量弹性伸缩并且允许缩到零。搞清楚这个边界你就知道什么业务适合上了。2. 架构原理拆解Knative在ACK上如何做到弹性智变2.1 Serving组件请求链路Activator与Queue-Proxy的分工在ACK集群里部署好Knative之后一个典型请求的完整链路是这样的流量先从入口网关比如ALB Ingress或者Istio Gateway进来被路由到Knative Service对应的路由规则随后请求交给Activator或者直接转发到已有的Pod。这里的关键在于Knative跑了两个旁路组件来支撑伸缩决策。第一个是Activator。它本质上是所有缩容到零服务的默认接收者。当服务处于0副本状态时新请求打过来Activator会把请求挂住hold住同时向Autoscaler发出扩容信号。Autoscaler完成扩容、Pod状态变为Ready之后Activator才将请求转发给业务Pod并且后续的流量会绕过Activator直连Pod。这个设计解决了一个很麻烦的问题缩到0之后怎么知道有新请求来了答案就是Activator在守着。第二个是Queue-Proxy。每个Knative业务Pod里其实有两个容器一个是你的业务镜像另一个是自动注入的queue-proxy边车容器。这个边车会统计打进来的并发请求数定期把指标上报给Autoscaler。同时也承担限流职责——如果并发数超过了Pod设定的目标值queue-proxy会先挡住一部分请求防止单Pod被打爆。这个边车的开销很低实测中大概只占Pod CPU的5毫核左右可以忽略不计。2.2 KPA与HPA的本质差异为什么CPU指标不够用KPA和HPA的区别我在实际生产里感受最深的是扩容依据完全不同。HPA的输入是资源指标CPU使用率、内存使用量它通过Metrics Server周期性获取数据而Metrics Server又依赖cAdvisor从节点上采集。这意味着从流量涨上来到反映到CPU使用率上再到HPA控制器检测到并修改副本数中间往往隔着几十秒甚至几分钟。对一个大促秒杀场景来说这几十秒的延迟足够让用户体验到明显的卡顿和超时。KPA的输入是每个Pod上的并发请求数数据由Queue-Proxy上报是彻底的正中业务要害的指标。它不会去猜测CPU涨了是否代表流量涨了而是直接告诉你当前有多少请求正在被处理、是否需要扩容。KPA的扩容计算也很直接当前总并发数或平均并发数除以设定的单Pod目标并发数得到预期的副本数再和当前副本数做比较决定扩缩方向。不过这里要说明一点KPA的目标并发数是理想状态下的软限制它允许瞬时飙高但会通过快速扩容去消化。系统在计算时默认单Pod副本的流量不会超过目标值太多如果连续多个窗口都超过一定阈值就会触发panic模式进入激进扩容。KPA本身设计得相当聪明但你要理解它的运作逻辑才能在参数上调对这一点后面专门展开讲。2.3 ECI弹性容器在伸缩中扮演的角色Knative解决了何时伸缩、伸缩多少的问题但还有一个物理层面的问题Pod要缩到零也要能再弹出来。如果集群里节点数不够Pod就会因为资源不足而一直Pending。这时候就轮到ACK提供的一个关键能力登场——ECI弹性容器实例或者说ACK的虚拟节点。ECI的本质是Serverless容器它不占用集群里的固定节点资源。当Knative需要扩容时扩出来的Pod可以调度到ACK集群的虚拟节点Virtual Node上这个虚拟节点背后就是ECI按秒计费、不预留资源、用完即走。这样Knative和ACK就形成了一个完美的配合常态高峰用集群里已有的节点兜底真正的突发流量交给ECI去扛扛完自动缩掉不再产生额外费用。我在生产环境中的做法是这样的把一般业务的Knative服务默认调度到普通节点池把对冷启动要求不高的异步任务比如消息推送、数据处理调度到虚拟节点。这样即使ECI拉起的实例需要多几秒的启动时间也不会影响核心在线请求链路。你需要做的只是在Knative Service的定义里加上一个nodeSelector或者tolerations把Pod引到虚拟节点上即可。3. 落地部署从控制台到YAML的完整实操路径3.1 环境准备的关键前提在ACK上部署Knative之前有几个前置条件必须先确认否则后面会浪费大量排查时间。集群版本建议1.24及以上原因很简单Knative Serving对Kubernetes的CRD和APIVersion有版本要求老版本集群装新版Knative会出现接口不兼容的问题。如果集群是ACK托管版只要在创建集群时选择较新的K8s版本一般没什么坑。第二个前提是入口网关。Knative Serving会把流量转发到名为envoy的网关组件上实际落地时建议直接使用ACK上集成的ALB Ingress作为流量入口再用Knative本身的域名和路由功能把请求分发到Service。这样做的好处是ALB作为云上负载均衡器自带一定的DDoS防护能力和高可用属性不需要单独再维护一套网关集群。第三个前提是建议提前开通弹性容器实例ECI服务并完成虚拟节点的配置。这一步在ACK控制台的节点池管理里可以直接操作创建虚拟节点池即可大概两三分钟就能就绪。3.2 在ACK上安装Knative的两种方式第一种方式用ACK应用目录。登录ACK控制台在左侧菜单找到应用下的应用目录搜索knative就可以看到官方封装好的Chart。点击安装时你可以选择是否开启事件组件Eventing、是否集成Kafka等。如果只做弹性伸缩场景只装Serving组件就够了Eventing留到后面有事件驱动需求时再补。这种方式的好处是安装器会自动帮你处理好manifest版本与集群版本的关系出错概率很小。第二种方式手动部署Knative官方YAML。这种方式适合你对版本有固定要求、或者需要做深度定制的情况。比如安装某个老版本内核时你需要先执行kubectl apply -f https://storage.googleapis.com/knative-releases/serving/latest/serving.yaml再配置DNS和网络插件。这种方式步骤多一点但对于需要离线安装或内网部署的团队来说反而更可控。我个人推荐普通团队直接用第一种方式因为你可能只踩过一次手动装完发现Requested backoff的苦头就会明白官方Chart的价值。3.3 用YAML部署第一个Serverless服务安装完成后如何验证Knative真正工作正常最快的办法是部署一个简单的HTTP服务然后观察它的伸缩行为。下面这份YAML是我在实际项目里反复用到的模板你可以在自己集群里直接修改镜像后执行apiVersion: serving.knative.dev/v1 kind: Service metadata: name: demo-serverless-service namespace: default spec: template: metadata: annotations: # 单Pod目标并发数按需调整 autoscaling.knative.dev/target: 20 # 允许缩容到0 autoscaling.knative.dev/minScale: 0 # 最大副本数避免无限扩容 autoscaling.knative.dev/maxScale: 20 spec: containers: - name: app image: registry.cn-hangzhou.aliyuncs.com/hz-gaosheng/demo-server:v1.0 ports: - name: http1 containerPort: 8080执行kubectl apply -f demo.yaml之后观察Pod状态。如果Knative一切正常你会看到Pod先被创建并运行起来然后在desired state里看到缩放状态。接下来用一段脚本持续每秒发送一个请求来模拟真实流量观察Pod数量变化。你会发现当并发数持续低于目标时Pod数量会逐渐缩小停掉请求几分钟后Pod数量会变为0。这里建议设置一个合理的冷却时间不要测试完立刻看结果就下结论Knative判断无流量需要经过多个统计窗口一般约30~60秒。3.4 端到端压测小工具验证伸缩要想真正验证弹性智变而不只是能装起来我建议做一轮简单的压测。压测工具可以不用很复杂直接用hey或者wrk这类轻量级工具比如# 先启动压测持续发送100并发请求共1万次 hey -z 2m -c 100 http://demo-serverless-service.default.example.com压测期间去观察Pod数量和对应的网络吞吐。你会看到Knative自动从0扩容到若干Pod高峰期结束、压力停止后又逐步缩容到0。这个过程如果你用kubectl get pod -w在另一个终端持续观察能非常直观地体会到Knative的核心能力。另一件要重点确认的事是并发指标的采集是否正常。可用以下命令查看Knative的Autoscaler日志确认里面能看到metrics输入kubectl logs -n knative-serving deploy/autoscaler如果日志里出现类似metric not found或者No metrics were reported的报错大概率是Queue-Proxy边车没有正常启动或者Pod的服务端口和你配置的不一致。排查时先检查Service里的containerPort是否和实际监听端口一致这是我在排查中遇到最多的原因没有之一。4. 伸缩策略调优并发数、缩容阈值与冷启动的取舍4.1 并发目标值不是越大越好很多第一次用Knative的人会把autoscaling.knative.dev/target设得很大总想着一个Pod扛的请求越多越省钱。这个想法有一定道理但代价是延迟和稳定性。举个例子如果你的每个请求平均耗时为200ms并发目标是100那么最理想情况下单个Pod能扛起的QPS大概是100 / 0.2 500。这是合理利用资源的表现。但如果你的请求耗时波动很大或者存在部分慢请求并发目标设得过高会导致这些慢请求占住Pod的并发额度新请求只能长时间排队最终表现为P99延迟飙升。我一般建议从20~50之间起步调优。如果是内部API或者RPC类调用耗时在几十毫秒级别可以把目标调到50左右。如果是比较重的业务逻辑比如涉及数据库聚合、文件处理后返回响应目标值保守一点设在20~30比较稳。然后再通过压测和线上监控逐步上调观察P99延迟是否有明显劣化。4.2 scale-to-zero的配置窗口缩容到零不是一有零流量立刻发生的。Knative设计了一个稳定窗口机制在连续一段时间内没有流量后才会真正把Pod数量缩到0。这个时间窗口主要由两个参数控制scale-to-zero-pod-retention-period默认是30秒以及Knative Autoscaler的稳定窗口默认是60秒。两者叠加意味着从流量清零到Pod缩掉大约需要90秒左右。如果你希望系统更快地缩到0可以把稳定窗口调小比如设成30秒。但这里有个权衡窗口太小如果流量是脉冲式的比如每40秒来一次请求Pod会被频繁拉起又销毁冷启动成本和调度压力反而不划算。我见过有些团队把这个时间调成0结果是生产环境一天内出现几百次Pod重建。个人建议保留默认值必要时再调。4.3 冷启动如何把首请求延迟从秒级压到毫秒级Knative缩容到零之后首个请求注定要经历一次冷启动。这个过程包括Activator持有请求Autoscaler扩容Kubernetes调度Pod拉取镜像如果无缓存启动容器Queue-Proxy就绪然后请求被转发。整体延迟通常从几百毫秒到几秒不等具体取决于镜像大小、节点上是否有缓存、以及ECI的初始化速度。应对冷启动有几个常用手段。第一尽量精简镜像把基础镜像换成更轻量的Alpine或Distroless版本减少镜像拉取时间。第二开启ACK的镜像缓存功能让常用镜像在节点上预热这样Pod创建时不需要重新拉取。第三针对延迟敏感的核心服务可以设置minScale为1让它始终保持1个Pod其他副本继续缩到0。这种热备1个按需扩的模式适用于那些不能容忍首请求秒级延迟、又不能承受高峰期全量常驻成本的服务。4.4 panic模式突发流量下KPA的应激反应KPA本身有应对突发流量的内置机制——panic模式。在常规的稳定模式下Autoscaler使用60秒的统计窗口判断流量偏向保守。一旦检测到持续两个窗口内并发数都超过目标值默认panic阈值是2倍就会进入panic模式把统计窗口缩短到6秒并立刻按当前并发数进行扩容而不是等下一个完整窗口。这个机制的直观表现就是秒杀刚开始那几秒Pod数量会非常激进地拉升。你可能看到它在30秒内从2个Pod直接冲到20个Pod哪怕平均流量其实只需要15个。峰值过去后KPA会在稳定窗口内逐渐把多余Pod缩掉。对这种应激反应要有心理预期它的目的是保SLA代价是一定的超量资源花费。如果是价格敏感型业务建议把maxScale显式设一个上限避免极端情况下扩容失控。5. 真实账单复盘降本稳流的量化收益与实际边界5.1 用三个典型业务曲线测算成本用真实一点的数字来算一笔账。假设我有一个消息回调服务单Pod配2C4G在ACK普通节点池的压力测试结果是单Pod稳定扛50并发对应大约500 QPS。业务特征为每天白天10小时有稳定流量早晚各有一小时高峰需要8个Pod其余时间基本没有请求。固定模式下为了保证高峰8个Pod我只能常驻8个Pod。按当时ACK通用型节点池价格粗略估算2C4G的Pod含资源预留和系统组件摊销大约0.1元/小时8个Pod一天的固定费用为0.1×8×2419.2元。换成Knative ACK的Serverless模式白天10个小时跑3个Pod早晚高峰各1小时跑8个Pod低峰14小时缩容到0。一天的资源费用约为0.1×3×10 0.1×8×2 4.6元。相比19.2元降幅约76%。如果把ECI弹性实例的按秒计费考虑进去费用可能还会更低一点因为ECI在创建时才产生费用缩容后立即停止计费。这个对比已经足够直观流量潮汐越明显的业务固定模式下浪费的资源越多Knative的降本效果就越突出。5.2 收益不只在账单上成本收益之外还有两块容易被忽略的好处。一是运维心智的简化。以前我们要预估业务容量要在活动前一周提前扩容节点池要写一大堆弹性脚本。现在这部分工作基本交给Knative和ACK节点池的自动伸缩去做值班同学只需关注关键指标有没有偏离预期而不是天天盯着副本数。二是发布过程的稳定性提升。Knative自带的按流量灰度能力让很多团队可以直接用Revision级别做A/B测试和滚动升级。它天然和弹性伸缩能力整合在一起新版本上线后如果出现严重报错回滚就是一个命令的事情不需要额外搭建灰度发布平台。5.3 不适合的场景与边界Knative ACK不是万能药。以下几个场景我建议慎重使用有状态服务。虽然Knative服务也能挂持久卷但它为无状态场景设计的缩容逻辑缩到0、Pod重建和数据库、Redis这类有状态组件天然存在冲突。如果业务强依赖长连接或者本地磁盘数据请让Knative让位。启动时间过长的应用。如果业务Pod从启动到Ready需要3分钟以上冷启动问题会把你折磨得痛不欲生。Knative的缩容到零会放大冷启动带来的延迟影响。此类服务建议保留最小副本数甚至不要用Knative。对成本极度敏感但又无法放宽SLA的场景。Knative的panic模式会在高峰期超量扩容如果业务对费用控制极其严格并且不能接受临时性超量成本需要预先通过maxScale硬性限制。6. 生产环境踩坑笔记最容易翻车的几个问题与解法6.1 缩容到零后首请求超时这个问题几乎每个刚落地Knative的团队都会遇到。现象很清晰服务缩到0之后某天突然来一个请求这个请求直接超时调用方显示504或者报connection timeout。根本原因通常有两层一是从0实例到新Pod Ready之间的冷启动耗时超过了调用方的超时阈值二是Activator在等待Pod Ready期间如果超过一定时间没有拿到就绪信号会直接断开连接。解决思路分三步。第一步给调用方设置合理的超时时间一般建议放到5秒以上如果调用方是外部系统且无法改超时那就只能靠minScale保底。第二步优化冷启动流程比如在ACK里开启镜像预热、使用本地缓存卷或提前把业务镜像推送到目标节点。第三步确认Knative控制器的revision超时配置在Servcie里增加s spec.template.spec.timeoutSeconds: 300给足Pod启动时间。6.2 日志、监控随Pod一起消失正常部署时Pod消失后日志就查不到了。这个问题在固定副本模式下不明显但在Knative缩容到零的机制下会被放大Pod都没了你到哪去看log到哪去关联当时的trace解决方案是让日志和监控数据在Pod之外落地。无论你用阿里云日志服务SLS还是自建Prometheus都必须保证你的组件在Pod退出前把采集到的数据异步发送到外部存储。具体做法是在Knative服务里配置日志采集Sidecar或者把业务日志打入标准输出交由集群的日志采集Agent接管。另一个关键点是为Pod设置terminationGracePeriodSeconds确保销毁前有足够时间完成最后的数据上报。6.3 与Ingress/网关配合时的流量灰度问题有一段时间我们为Knative服务配置了ALB Ingress做流量入口结果发现灰度策略不生效——流量总是打到老版本上。排查后发现问题出在Ingress转发目标的配置方式上。Knative Service本身通过DomainMapping暴露服务如果ALB直接把流量转发到Knative Service名它看到的只是Service的ClusterIP无法感知Knative内部的Revision路由策略。正确的方式是把流量转发到Knative的网关地址上让Knative自己的路由层负责把请求分配到具体Revision。实践中我建议统一走knative-serving命名空间下的Istio Gateway或者Kourier网关外部负载均衡只负责做公网接入和TLS终止不要把策略做死在ALB上。6.4 配额与节点池配置不当导致的扩容失败有一个容易忽略但杀伤力很大的坑Knative扩容到虚拟节点时可能因为配额不足导致Pod一直Pending。虽然ECI可以帮你弹资源但每个账号下的vCPU、内存配额都是有限制的。如果你的ECI配额接近上限扩容请求会反复失败而Knative的Autoscaler还在持续发出扩容指令最终表现为流量已经打进来了但Pod就是起不来。所以上线之前一定要去配额中心确认ECI相关的vCPU和内存余量。同时在Knative侧做好合理额maxScale限制避免一个服务把配额全部吃掉其他服务一扩容就失败。我还会在监控上增加一个专项看板重点盯Pending状态的Pod数量一旦发现持续Pending立刻排查是节点资源不足还是ECI配额超限。还有一个容易被忽略的细节普通节点池和虚拟节点的混合调度需要提前在Service里配置好nodeSelector。不然Pod可能因为调度失败一直Pending也会触发类似的扩容失败假象。最后我想说的是Knative ACK这套组合真正的门槛不在于安装配置而在于你能否从架构层面想清楚什么服务适合缩容到零、什么服务必须保留热备、流量峰值来了该让谁去扛。把这些边界划清楚它给你带来的不只是账单上的数字变化更是整个团队在容量管理和稳定性治理上的一次升级。如果你正在为K8s集群成本发愁或者被每次大促前的扩缩容演练搞得心力交瘁可以先用一个低频的异步任务服务做试点跑上一周看看曲线和账单再决定要不要把更多服务迁移过来。
返回列表