V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xiaket
V2EX  ›  Amazon Web Services

Serverless 爱好者请慎入 SAM 这个坑

  •  
  •   xiaket ·
    xiaket · 2019-03-23 09:17:09 +08:00 · 4165 次点击
    这是一个创建于 1832 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在做 Serverless 相关的工作. 我们前台用 Cloudfront 和 S3 来, 后台用了 API Gateway 和 lambda 来写逻辑. 在其他同事的建议下使用了SAM, 此时, 在 Cloudformation 模板里面需要使用一个名为 Serverless 的宏. 这样可以大量简化模板的编写, 提高模版的可维护性. 缺点是这个宏的代码不由我们控制, 逻辑对于我们而言也是黑箱子. 这个黑箱子不能正常工作时, 我们是完全没有办法的.

    首先是被 AWS 官方认证的 bug. 我们在模板里面定义了若干个 DynamoDB 表, 现有另外一个 stack 需要引用这些表里的内容. 正统的做法当然是在定义了表的模板中定义若干个 Output, 把这些 DynamoDB 的属性(比如 Arn)export 出去, 这样其他 Stack 就可以通过 ImportValue 来引用了. 我们加入这些 Output 后更新这个 stack 会报内部错误而不能继续. 我们各种尝试后交给 AWS 客服解决, AWS 工程师进一步将整个 stack 削减到只有一个 DynamoDB 的表, 加入 Output, 同时使用了前述的 Serverless 宏, 这样就能重现我看到的内部错误. 另外, 如果我们通过 Changeset 来更新, 则不会触发问题, 而当我们直接更新模板时, 就会出现问题. 我们目前解决方案是在其他需要使用 Dynamodb 表的模板中将表的 Arn 人肉拼出来, 虽然能工作, 但是毕竟只是 hack.

    第二个被 AWS 官方认为不是 bug, 但是我个人认为是 bug. 在使用 API Gateway 时, 一般修改了参数或属性时我们都需要做部署, 在 Cloudformation 中做 API Gateway 的部署时需要加入类型为 Deployment 资源. 在使用宏的情形下, 我们观察到宏有时会自动创建这个资源, 实现自动部署, 所以这个操作我们认为这个宏可以全部搞定, 没多去深究细节. 直到有一天我们通过 API Gateway 调用后台的 lambda 时发现 lambda 根本没被访问到, API Gateway 就直接返回了 502. 打开 API Gateway 的详细日志时发现日志的最后一条报权限错误, 即 API Gateway 没有权限访问这个 lambda. 再仔细一看, API Gateway 试图访问的 lambda 函数根本不是我们当前 stack 中有的函数, 于此, 我们意识到是 Deployment 没有被触发. 咨询 AWS 客服后被答复, 这是期待的行为, 因为模板里函数的 LogicalID 没有变, 所以不会触发 Deployment. 但是写过 Cloudformation 模板的同学都知道, 一个资源的 LogicalID 是很少会改动的. 这个宏自动创建 Deployment 资源的依据是 LogicalID, 未免太过儿戏.

    第三个算是无伤大雅的小问题, 不过仍然算是缺陷. Cloudformation 自带的模板校验命令只会校验 yaml/json 语法, 不会做更深一层的检查, 在开发同学屡次改错模板后, 我们希望在测试流程中添加一个靠谱的模板校验步骤, 我们看上了 AWS 的这个开源工具 cfn-python-lint, 结果用这货来跑我们的模板时就报错了, 即使这个模板在线上部署时运行如常, 原因是 AWS 对 SAM 的支持不够完善, GH 上有相关讨论.

    结论: 玩 Serverless 的同学们遇到 SAM 这货请绕路. 尤其是 Serverless 这个宏可能是大坑.

    12 条回复    2019-03-24 05:18:32 +08:00
    quietjosen
        1
    quietjosen  
       2019-03-23 10:00:36 +08:00 via iPhone
    正准备研究 Serverless 呢,谢踩坑
    whileFalse
        2
    whileFalse  
       2019-03-23 10:01:12 +08:00
    生产环境用 Serverless,非常酷啊。

    第一个问题,能否通过部署脚本,每次先创建 changeset 在应用该 changeset 解决?

    对于问题 2,这个很有意思。默认每次发布 lambda 时,是更新 lambda 的$LATEST 版本。api 默认引用 latest 版本,也就是说 restapi 引用的函数代码是动态的。而 deployment 是对 restapi 的快照,是静态的。
    虽然没有试过,但我估计使用 AWS::Lambda::Version 限定一个 lambda 的特定引用,再在 api 中引用这个 version 而不是 lambda 本身可能可以解决问题。

    对于问题 3,期待用纯离线方式验证 Cloudformation 模板可能要求太高了。不过 serverless 的好处是,部署本身不产生费用。如果你们能做每次部署一套临时环境并跑自动化测试,成功后再删除临时环境并合并到开发环境,是不是能够解决该问题?
    timle1029
        3
    timle1029  
       2019-03-23 10:13:58 +08:00
    以下仅代表个人看法。。


    有关于 CFN 的校验,从 CFN 的角度来说,他们只是代表你去 call AWS SDK,再 call 之前 CFN 也无法知道能否成功运行,因为 EC2/ENI/S3/SG-Group 的 validation 都是由各个 service team 单独去完成的,但又不能为了这个单独去 call 其他 service (要不然他们会被 throttle 到死)。


    最后,Cloudformation 是出了名的磨叽... 我们五个月之前发布的新功能他们至今还没有支持。。
    laxenade
        4
    laxenade  
       2019-03-23 10:17:37 +08:00 via Android
    SAM Lambda 内部支持就一般,遇到 IAM 和 S3 event 问题更多
    binux
        5
    binux  
       2019-03-23 10:29:35 +08:00
    一直用 https://serverless.com/ ,做了 40 多个组件了,没有遇到过问题
    而且我们也不倾向于在 app 的 CF 中同时创建 stateful 的服务,比如 DynamoDB。除非是和这个服务紧密相关的,没其他人用的,比如 SQS。

    cloudformation 的模板语法特别痛苦,现在打算加一层模板引擎渲染多一遍,或者干脆直接 terraform,还能 plan。
    另外你用 serverless 的话就不用写 CF 了
    xiaket
        6
    xiaket  
    OP
       2019-03-23 10:43:30 +08:00
    好多 AWS 同学冒泡了, 感谢各位捧场.

    @whileFalse 之前我想的是把这个大的 stack 拆掉, DynamoDB 的生命周期和 lambda 本身就不一样, 这样也能绕过这个问题, 而且架构会更合理. 也看到讨论说 changeset 是更被推荐的部署方式, 不过我更期望这个明显的 bug 能被修掉. 问题 2 我们目前准备去掉这个宏, 因为不知道后面还会不会再踩坑. 问题 3 我们现在的部署不是 blue-green 的, 不过后面有时间应该会做. 至于验证 cfn 模板, 真觉得是目前 cloudformation 自带的验证只检查语法, 实在不够用. 能增强一点儿就增强一点儿, 至少构建出来的模板会更可靠些.

    @timle1029 我看到文档里面有提到`cloudformation validate`仅检查语法, 我也认同你提到的, cfn 验证唯一靠谱的办法是在沙盒环境中部署. 但是这样未免测试时间也太长了. 我们的模板里还有 Cloudfront, 一个 distribution 部署下来半个小时是跑不了的. 所以我们这儿说增强的目的是能在 30 秒内尽最大努力测试模板里面是不是有问题, 不求一定能找到坑, 只求能够把开发同学的低价错误给抓出来. 至于 cfn 特性添加太慢的问题, 有兴趣也可以围观 reddit 上这个贴: https://www.reddit.com/r/aws/comments/as21r7/cloudformation_feature_support_lag_is_way_out_of/

    @laxenade 我们给 lambda 的 Role 里面也添加了不少的 policy, 还好没遇到坑. 我现在准备全部去掉这个宏, 希望能够好些.
    xiaket
        7
    xiaket  
    OP
       2019-03-23 10:46:39 +08:00
    @binux 我完全同意你的观点, DynamoDB 本身不应该出现在这个 stack 里面, 这样我们也不会踩这个坑, 说起来可笑, 虽然我是 infra 工程师, 但是一个资深但没怎么用过 Cloudformation 的前端程序员同学不顾我的多次抗议强烈要求所有的资源放到一个 stack 里面去, 我实在是拿这个人无语.
    whileFalse
        8
    whileFalse  
       2019-03-23 11:04:08 +08:00
    @xiaket 合理拆分是必要的。基础资源层(网络、安全组等)、数据层( DB、存储数据的 S3 )、app 层( lambda/api depoyment/存 web 资源的 s3 )、cloudformation 层拆开,并且使用不同的 iam user/role 部署。
    两个好处:
    1. 既然业务开发和基础架构是不同的人负责,那么业务就无权直接修改 DB 或基础架构层。
    2. 仅部署 app 层可以在数分钟甚至数秒内完成。那么你们就没有对静态检查的强烈依赖了。
    whileFalse
        9
    whileFalse  
       2019-03-23 11:06:43 +08:00
    “ cloudformation 层拆开” 改为“ cloudfront 层拆开”

    3. 可以在同一个数据层上部署多套 app 层实现,以便做非常轻量级的临时环境或属于每个开发者的个人环境
    laxenade
        10
    laxenade  
       2019-03-23 12:00:21 +08:00 via Android
    拆分有两种,一种是你把 json 或者 yml 拆开来但是部署的时候还是一个 stack,另一种是多个 stack。这两种有利有弊,但我偏向第一种。第二种的弊端太多,一旦写错了,发现错误的时候太晚了(因为你肯定是要一个一个 stack 部署的)。而且有些 service 不支持不是 cfn managed s3 bucket 虽然你在另一个 stack 里定义了 bucket,但在这个 stack 眼里就不是 cfn managed 的。
    xiaket
        11
    xiaket  
    OP
       2019-03-23 18:20:38 +08:00
    @whileFalse 感谢回复, 现在这个项目开发人员也就五六个, 所以还没到用不同 iam role 部署的程度, 不过有比较成熟的部署脚本了, 搭建个人 Stack 比较方便. 不过要做完整的部署, 即使不考虑创建 Cloudfront 层, 仍然快不起来, 因为 Cloudfront 的 cache invalidation 仍需要五分钟或更多.

    @laxenade 我们之前的做法是在 cfn 上加一层 python 逻辑, 部署的时候再是一层 Jenkinsfile 的逻辑. 不过由于人手不够, 有些新项目(比如用 SAM 的这个)就没有用这种做法, 而是直接拿 cfn 搞. 外面部署逻辑套一层脚本, Jenkinsfile 里的逻辑也简化了很多. 我周围的所有同事使用的方式都是你所说的第二种, 因为在 cfn 的 best practice 里面提到, 不同生命周期的资源应该尽量放到不同的 stack 里面去. 我能理解你说的多个 yml 拼成一个完整的 stack 这样的做法, 因为这样可以把相似资源的集合放到一个文件里面去, 方便重用. 不过我觉得这样仍可能有坑. 多个 stack 在部署时会有问题, 在于中间某个 stack 部署失败可能影响整个 app 的稳定性, 我们的解决方案是通过脚本来实现整体部署. 每次部署一个版本, 如果中间有一步出错, 全部回滚. 这套逻辑在线上也已经跑了一段时间, 稳定性还可以. 我不太明白你说的服务不支持不是 cfn managed S3 bucket 是什么意思, 方便给个例子了解下吗?
    laxenade
        12
    laxenade  
       2019-03-24 05:18:32 +08:00
    @xiaket #11 "我们的解决方案是通过脚本来实现整体部署. 每次部署一个版本, 如果中间有一步出错, 全部回滚" 不好意思没看懂,这和所有东西都放在一个 stack 里有什么区别。我说的在一个 stack 也不是说所有东西都在 main stack,你可以把 component 拆分放进 nested stack,如果 nested stack 报错了,main stack 也会回滚所有 nested stack。我说的第二种拆分指的是那种被拆成好几个老死不相往来的 stack。

    理论上如果你的部署会对生产环境造成影响,你们有可能需要想一下怎么解决这个问题。

    有些服务不支持非 CFN managed S3 bucket 指的是(记不太清了)如果你在 Lambda 是通过 S3 触发,那 CFN 规定你的 Lambda 和 S3 必须都是在同一个 stack 里。(可以通过 SNS 绕开限制)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1605 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 36ms · UTC 16:53 · PVG 00:53 · LAX 09:53 · JFK 12:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.