保姆级|腾讯云助手编写 SCF 自定义灰度发布脚本(一) 保姆级腾讯云助手编写 SCF 自定义灰度发布脚本一别名与版本权重流量切分的自研实现系列导航第一篇本篇为什么不重度依赖 Serverless Framework、SCF 流量切分的底层机制、自研灰度控制脚本与 AI 初版的坑第二篇自动指标判断与回滚保护——AI 原始输出漏洞与修正版本逐项对比一、为什么不用 Serverless Framework又要灰度团队的选择背景很典型SCF 函数十几个、更新频率每周多次用控制台手动发太原始引入 Serverless Framework 又嫌重——组件版本管理、本地依赖、团队学习成本为了发个函数引入一整套工具链不划算。我们想要的是一个 200 行以内、能进 Git、谁都能读懂的轻量脚本。而灰度发布在 SCF 场景比传统部署更重要原因有两个SCF 的发布是版本切换——新版本一旦成为流量入口瞬间 100% 生效没有传统 LB 摘节点的缓冲过程出问题就是全量事故事件驱动的流量形态消息洪峰、批量任务让问题暴露有延迟——上线 5 分钟正常不代表 40 分钟后的洪峰正常必须有持续观察窗口。先讲清楚底层机制自研的前提是懂原理再上脚本最后看 AI 初版踩的坑。二、SCF 流量切分的底层机制别名 版本 权重自研灰度脚本依赖三个原语函数版本$LATEST / $1 / $2 / ... │ ▼ 别名alias如 prod──── 指向某个版本 │ ▼ 触发器 / API 网关 ──────── 绑定的是别名不是具体版本灰度的本质就是操作别名把别名prod的流量按权重拆到两个版本发布前prod → $2100% 灰度中prod → $290% $310% ← 10% 流量进新版本 放量中prod → $250% $350% 完成时prod → $3100%三个关键认知AI 初版在这三处都出过问题**认知 1触发器必须绑别名而非LATEST。∗∗绑‘LATEST。** 绑 LATEST。∗∗绑‘LATEST 的函数每次代码更新直接生效没有任何灰度可能。这是最常见的配置错误也是很多团队从来没有灰度的根本原因。认知 2流量切分是按请求粒度加权的不是按时间片。10% 权重意味着每个请求独立 10% 概率进新版本不是前 10% 时间用新版。这意味着灰度期的指标对比必须按版本维度分组看函数日志里带版本号混在一起看不清。认知 3回滚 把别名权重拨回旧版本秒级生效。这也是第二篇回滚保护的基础——回滚动作本身很轻重的是什么时候该拨回去的判断。三、自研灰度脚本核心实现依赖腾讯云 SDK核心约 150 行。主流程# scf_canary.py —— 轻量自研灰度控制importtime,sysfromtencentcloud.scf.v20180416importscf_client,modelsclassCanaryDeployer:def__init__(self,namespace,func_name,aliasprod):self.clientscf_client.ScfClient(cred,ap-shanghai)self.ns,self.fn,self.aliasnamespace,func_name,aliasdefpublish_new_version(self)-str:发布 $LATEST 代码为新版本号reqmodels.PublishVersionRequest()req.Namespace,req.FunctionNameself.ns,self.fn respself.client.PublishVersion(req)returnresp.FunctionVersion# 如 $7defget_alias_routing(self)-dict:读取别名当前路由 {版本: 权重}reqmodels.GetAliasRequest()req.Namespace,req.FunctionName,req.Nameself.ns,self.fn,self.alias respself.client.GetAlias(req)returnparse_routing(resp.RoutingConfig)# {$6: 0.9, $7: 0.1}defset_weight(self,old_ver:str,new_ver:str,new_weight:float):核心调整别名权重切分流量reqmodels.UpdateAliasRequest()req.RoutingConfigmodels.RoutingConfig(Versionstr(old_ver)ifold_ver!$LATESTelse$LATEST,Weightround((1-new_weight)*100),AdditionalVersionWeights[models.VersionWeight(Versionnew_ver,Weightround(new_weight*100))])self.client.UpdateAlias(req)defget_version_metrics(self,version:str,window_s:int)-dict:按版本拉监控错误率、P95 延迟走云监控 APIreturnmonitor.query(self.ns,self.fn,version,metrics[ErrorRate,P95Duration],windowwindow_s)灰度编排逻辑阶段推进STAGES[(0.10,900),(0.50,900),(1.00,0)]# (权重, 观察秒数)defdeploy(self,thresholdNone):old_vercurrent_main_version(self.get_alias_routing())new_verself.publish_new_version()audit.log(version_published,new_ver)forweight,wait_sinself.STAGES:self.set_weight(old_ver,new_ver,weight)audit.log(weight_set,old_ver,new_ver,weight)ifwait_s0:breaktime.sleep(wait_s)# 观察窗口metricsself.get_version_metrics(new_ver,window_swait_s)ifnotself.check_health(metrics,threshold):audit.log(unhealthy_detected,metrics)returnself.rollback(old_ver,new_ver)# 第二篇展开audit.log(deploy_complete,new_ver)几个实现细节阶段配置数据化STAGES列表进配置文件不同函数可以有不同节奏核心链路 10%→50%→100% 三段各 15 分钟边缘函数直接 100%每个动作进审计日志发版本、调权重、检测异常、回滚全部留痕——灰度过程可回溯出了争议有据可查get_version_metrics按版本维度拉取云监控的函数监控支持按版本过滤这是认知 2的落地——新旧版本指标必须分开看。四、AI 初版脚本的三个坑把需求“写一个 SCF 灰度发布脚本按权重切流量”丢给腾讯云助手初版能跑通但有三个坑每个都值得展开——这也是本系列的保留节目坑 1权重参数的单位错误。AI 生成的set_weight里权重直接传了0.1而 API 的Weight字段要的是整数百分比10。调用不报错部分 SDK 版本会静默取整实际效果是 0% 灰度——看起来在灰度实际上新版本一点流量都没拿到灰度变成了纯摆设。修正所有权重换算处显式round(x * 100)并加断言0 w 100。坑 2旧版本号假设为$LATEST。AI 初版写死了old_ver $LATEST——但我们生产别名早就指向数字版本。第一次执行时脚本把$LATEST含未测试代码设成了 90% 权重的主流版本差点变成一次反向发布。修正old_ver必须从别名当前路由动态读取如上面get_alias_routing所示禁止任何假设。这个坑的本质是 AI 对生产别名指向什么没有先验知识凡是从环境读取的事实都不能硬编码。坑 3观察窗口用time.sleep阻塞且无指标拉取。初版的观察就是sleep(900)后直接进下一阶段——只等待不观察。900 秒里新版本错误率飙到 40% 脚本也照常放量。这正是用户痛点AI 输出经常缺失回滚保护在灰度场景的具体形态AI 把灰度理解为分批发布把观察窗口理解为等待时间。修正观察 拉指标 判断健康度 异常即回滚即第二篇的全部内容。五、本篇小结灰度的本质是操作别名的版本权重前提是触发器绑别名而非$LATEST流量切分是请求粒度加权指标必须按版本分组对比不能混看自研脚本约 150 行publish → set_weight分阶段→ 观察指标 → 异常回滚每个动作进审计日志AI 初版三坑权重单位静默错误灰度变摆设、硬编码旧版本差点反向发布、sleep 当观察只等不看——修正原则分别是显式换算断言、环境事实禁硬编码、观察必须有指标。下一篇展开观察和回滚健康判断的多指标策略错误率 延迟 业务指标、回滚的完整动作链权重回拨 触发器检查 通知以及 AI 原始输出 vs 修正版本的逐项对比总表。不重度依赖 Serverless Framework 又要安全发布的团队可以直接抄。点赞收藏评论区聊聊你们的 SCF 发布方式。