Pensieve: 1905
2019-05-26 18:50
所观所读
最近事情比较多, 上公车后往往没心情读大部头了, 于是看了看剧, 顺手看了一些休闲级的书. 比如, 读完了马亲王的古董局中局, 这本实在有点不能看了, 整个创作水平下降到鬼吹灯的档次了. 我还是喜欢他写的埋梗卖梗的书, 这种近猎奇类的RPG攻略没什么兴趣. 另外在读的一本是当音乐停止之后, 我喜欢这种断代史.
接下来是最近看的影视剧, 权力游戏第八季看完了前三集就弃了, 理由众所周知. 倒是这次大众对结局一致不满后, 原作者跑出来说拍成这样他也不愿意什么的, 让我对这个作者有了更多的厌恶. 是, 作者没必要对读者负责, 作者想怎么写就什么写, 想什么时候写完就什么时候写完, 这是作者的自由, 天赋神权. 现在的这个结局当然也不是作者独裁, 而是和电视编剧一起创作的结果. 但是你自己驾驭不住这么多人, 写了这么多冲突, 摊子铺得那么大, 想要一个好的结局又没有好的笔力, 剧情烂尾还往编剧那儿甩锅, 合适吗? 你早点努力些, 把这部写完了, 哪来这么多幺蛾子?
除此之外, 还看完了流浪地球和两杆大烟枪, 都没什么特别的感觉. 最近生活大爆炸全剧终, 找来最终季开始看. 看了一半了, 预计月底前能看完, 至少编剧是更有诚意的.
所玩
饥荒遇到了一个大bug. 我用机器人, 转生了若干次后在夏天进了地穴探险, 没待一两天出来了, 发现外面正值严冬, 而且一群野狗在追我, 原因大概是出洞穴的时候重新计算季节, 没考虑到转生只拿了当前总计生存天数. 我相信这个档我能熬过去, 不过我对这个移植的诚意丧失了一些信心. 有心情的时候回头再玩吧. 另, 我全价买之后不久它就半价打折了…
月中的时候尝试了下新的Asphalt版本, Asphalt Legend, 结果发现里面开始有了收集碎片的元素, 于是果断弃了. 这种为了延长游戏寿命而降低游戏体验的事情, 真让人不齿. 倒是很希望Switch上能有个好一点的赛车游戏啊… 叹气.
暗黑三新赛季终于又开始了, 到现在刷了700多级, 大秘境刷到了95, 这个赛季眼看又要结束了: 这次身上还有若干件装备没远古, 完全没用宝石堆属性, 更是完全没有用赛季的梦魇套特效. 罢了罢了, 快点玩完这个赛季玩死亡细胞吧.
Ennio
最近在和Michael Diender一起搞一个用于调度Cloudformation里的Stack的框架. 里面包含了一些我们对现有的技术架构的一些反思. 在谈这个项目前, 我想先简单介绍一下供职单位目前对Cloudformation的使用方式.
数年前, 所在的供职单位开始把自有的业务往AWS上迁移, 迁移过程中自然要用到Cloudformation. 当时在组里的一些人对此有了一个讨论, 一些人建议用Ansible来生成模版, 并通过Ansible的Role来管理; 另外一些人建议应该有一层比较可靠的DSL来优化整个过程(原话是, 应该有一个企业级的解决方案). 当时, 同意第二种方案的人占了多数, 供职单位开始使用stacker, 并在它的基础上做了二次开发, 加入了很多特性. 我大概也是这个时候开始在这个供职单位做事, 自己也给这个过程添加了一些特性.
但是, 使用这个框架并不是无痛的, 事实上, 这个决定给我们带来的痛苦甚至大余收益:
-
Python版本依赖关系: 作为一个使用Cloudformation的python工具, stacker自然要依赖boto3, 而且, 在测试代码中, 它还依赖了moto. 作为一个框架, 它做得挺不错, 安装依赖和测试依赖也区分开了. 但是在二次开发的时候, 当时的设计者对Python不太熟悉, 把所有的依赖全部放进了安装依赖中, 而且额外加入了不少包, 这么多的包放到一起后, 整个依赖关系树错综复杂. 而且, 由于pip本身不能提供有效的冲突协商, 所以这个大杂烩每隔一段时间就需要维护一次, 而且每次只有两三个人能够理解中间发生了什么事情并给出解决方案, 而此时很可能生成环境的发布由此受到了影响. 这实在不是一个好的体验.
-
安全隐患: 不久前, 我们的安全人员发现S3里面有部分文件是只要有AWS登陆(不仅限于当前账号)就可以读取的, 这实际上是将这部分资源置于可公开下载的危险下. 这个问题的源头是stacker, 并在这个commit中得到了处理. 但是, 我不太满意的是stacker开发组对于这个问题的存在漠视和被修补后的不作为. 作为一个负责任的开发组, 应该适当通知用户, 让大家知道我们之前的版本可能会导致部分文件存在泄漏风险. 另外一个例子是我今天去这个项目的时候看到的一个commit, 这个IAM权限理应在扫描模板后判断是否包含Macro再自动添加, 这样直接添加, 不是合适的.
-
代码库设计不当: 大概一年前, 我给stacker提过一个PR, 帮助它能够做Python3的兼容. 期间, 我有一半以上的时间不是花在Python3的兼容, 而是升级到Python3的过程中暴露出来之前代码中的问题, 包括不恰当的测试配合不恰当的代码得到了不正确的通过结果.
-
stacker的特性: stacker本身的特性能够让我开心的只有那些钩子. 它要求blueprint用troposphere来定义, 不算是一个好选择. 它内部的变量替换使用的是Python自带的
string.Template
, 则是一个让人心碎的选择. 至于更新stack的时候仍不是通过创建changeset来进行, 则只能说是固步自封了. -
没有稳定的API: 前一段时间, stacker改善了模版渲染流程, 期间, 修改了内置的lookup接口, 这本是一个很有帮助的接口, 不过在这个过程中, 整个lookup的API都有了很大变化, 没有给应用留出适应的过渡期, 而是直接升级
上面的缺点都还只是stacker本身的缺点, 而我们自己在二次开发的时候, 设计有些不当, 也是造成使用上各种困难的重要原因. 我们二次开发时加入的特性包括:
- 调用stacker命令会找git提交信息并放入Cloudformation的description中去, 标记版本.
- 提供了一整套单元测试方案.
- 添加了很多lookup, 很多能映射成一个Cloudformation函数(例如Ref/ImportValue等).
- 添加了一些类型策略, 来做一些公司层面的权限控制.
于是, 也给自己挖了这么些坑:
- 当依赖关系有问题时(例如由第三方包升级导致), 集成测试可能会失败.
- 当二次开发的包本身有升级时, 集成测试可能会失败, 而应用本身会觉得我本身没动过代码, 几天前运行都还没问题, 但升级后却出现了问题, 从而对这种使用方式有了很大的抵触情绪.
- venv的管理很麻烦. venv是手工创建的, 但是当venv里面的包有升级时, Make脚本却没有去升级所有的包(此时也不应该去升级所有的包), 所以导致开发时本地测试没问题, 而到Jenkins上运行测试时有了问题(因为两个环境不完全一致).
- 对于lookup的滥用导致本来已经很难用的Cloudformation函数更难用.
这样的种种问题, 我们有些有解决方案, 有些没有. 所以当这个问题出现了一段时间后, 组内开始反思, 并觉得stacker太难用, 没必要. 中肯地说, 这种看法是有道理的, 但是如果自己从头来写Cloudformation模板, 则我们会很想念stacker里的这些特性:
- 模板系统, 能够替换变量(虽然实在很弱, 但至少存在)
- hook系统, 能够在创建/删除Stack的前/后执行任意逻辑.
现在我们该说一下新框架了. 这部分代码大部分源自我在一个新项目的实践, 在这个项目中, 我们的策略是:
- 每次push的时候, 无论是哪个分支, 都会在一个S3里创建一个目录, 里面包含这次build出来的结果, 包括Cloudformation模板和lambda的zip包等.
- 由多个Stack组成一个Application
- 每个Stack可以定义自己的
deploy
方法, 用来自定义部署步骤, 可以任意插入逻辑. - 每个Stack可以定义自己的部署参数, 即Cloudformation中的参数.
- 每个Application里按顺序部署每个Stack, 每次部署时接受一个S3的路径为参数, 如果部署过程中出错, 则依次回滚操作.
- 每次部署完成后写一个ssm parameter store来记录这次build的各种信息, 例如分支/构建号等等, 回滚时会参考这个ssm值.
在这个新项目中, 我们没有用任何的模板引擎, 而这不算是一个良好的行为. 为此, 我们正准备给这套框架增加jinja2模板引擎, 这样不用写难以维护的Cloudformation函数了. 理想状况下, 项目的目录结构应该类似:
. # 项目根目录
├── infra # Infra代码
│ ├─── apache # apache stack的相关文件
│ │ ├── apache.j2 # apache stack的cloudformation模板, 使用jinja2模板
│ │ └── apache.yml # 上面jinja模板中使用到的变量
│ ├─── mysql # mysql stack的相关文件
│ │ ├── mysql.j2
│ │ └── mysql.yml
│ ├─── cdn # cdn stack的相关文件
│ │ ├── cdn.j2
│ │ └── cdn.yml
│ ├─── environments # 各个环境下的变量
│ │ ├── dev.yml # dev账号中运行的
│ │ ├── dev-kx.yml # dev账号中运行的我的个人测试Stack所使用的变量
│ │ ├── stg.yml # stg账号中运行的
│ │ └── prd.yml # stg账号中运行的
│ ├── manage.py # 项目的Python代码, 包括各个stack的参数获取, stack部署前后的钩子等
│ └── README.md # 文档
└── src # 项目源码等其他内容
目前, 整个项目的结构已经基本清楚了, 大部分代码也已经存在了. 就等着我最后把这套框架写完了…