如何稳定开发和发布的思考与总结

2020.10.10

如何稳定开发和发布的思考与总结

[TOC]

如何在周末睡个好觉 以及 避免每天早上的surprise motherfxxker! 的 方法论 XD

笔者当下的一点浅薄的见解,这篇文章阐述的观点在未来或许还会有所发展

关于这篇文章的意义

这篇文章尝试在 假设 资源 完全充足的情况下,如何做到 0 错误的发布和开发, 虽然 在实际的开发过程中, 不一定可以全部做到, 但是我们会有 标准去衡量 当前的状态 以及 改进和发展的 方向。

事因

发布的程序中有 bug 从而影响功能,是每个开发者都会经历的 事情,笔者从毕业开始到现在常常受其困扰,甚至有时候会恐惧发布 XD。对于这种情况,笔者通常下意识的会将原因归结到 时间不够 上面 (当然有的时候确实是笔者 懒得测试 ……觉得这么简单的功能不会有问题的啦~之类的……)

那么我们接着反问自己一个问题, 假设在所有资源都充足的情况下, 如何完成 零出错发布

观点

这里直接先开门见山给出笔者的 观点: 及时感知和回滚减少错误发生的概率

从 概率 和 概率 上讲,是人都会犯错,开发者是人,所以 开发者 会犯错。那么既然犯错无法避免,那么我们要避免 用户 比 开发者 先察觉到问题,在那之前 修复 或者 回滚。

仅仅 只做到 快速感知和回滚 是不够的, 另一方面也需要降低发布后出问题的概率,这就需要做好 开发 和 发布的流程。 另外,低错误率 会一定程度上延长项目工期,但这是值得的。

How?

笔者认为 可以分为以下三个 阶段来阐述 ,设计阶段编码阶段发布阶段

  • 设计阶段
    • 避免引入 不够稳定 和 过于复杂 以及 不成熟的特性
  • 编码阶段
    • 克制修改的诱惑
    • 做好灰度发布的准备
      • 灰度开关
      • 重要位置的错误告警
    • 测试
      • 集成测试
      • 单元测试
  • 发布阶段
    • 发布策略
      • 灰度/金丝雀/蓝绿 发布
    • 持续关注错误率 和 日志 与快速回滚切换

设计阶段

避免引入 不够稳定 和 过于复杂 以及 不成熟的特性

开发的乐趣在于 实现功能,或者更近一步说 在于细节。我们想要用更 power 或者 更加 fushion 的形式去实现一个细节,在当前的项目里去实践那些知名项目中的具体实现。这种过程可能是 不够稳定的, 或者 过于复杂的 以及极易 引入 一些不成熟的特性。 而在项目层面, 则需要保障稳定,需要的时完成的结果,以及 可维护性和写协作。任何 过度复杂 和 不成熟的特性, 都很容易和上面要关注的点相冲突。 所以有一个方法论就是 保持项目的简单,才更容易保持 项目的 稳定以及可维护。 那么从这样看来, 上述二者似乎处于一种互相制衡的关系上。一方面我们你需要抵制诱惑,避免引入 不稳定的特性。另一方面,我们也需要不断求知,以提高项目的水平和 以更高的 视野来简化项目的实现。 如何让二者在 某种程度 或者 维度上 达成均衡,是需要持续考虑的问题。 把稳定、合适、且能解决实际问题的元素 引入项目,而克制 将 感兴趣 却解决不了问题的点 带入项目 而 影响项目的 形状 和 稳定。

便于测试的结构

在设计的时候,结构上就需要为测试的便利性作出考虑,例如将外部依赖的逻辑做成 单独的 util 或者 pkg,以便 mock 和测试。 不过在这一块,笔者目前实践的还比较少。

编码阶段

克制修改的诱惑

往往在开发新功能的时候,我们很想去做以下的事情,

  • 修改之前写的觉得 结构不够优雅的代码段,即便它只是看上去舒服了😌
  • 升级部分依赖,即便什么新特性都没有,新版本也只是修了几个无关痛痒的 bug,但是看到它是新版本就莫名舒服😌
  • ……

「顺便」 对吧~but HOLD ON!这其实很容易给项目带来意料之外的不确定性。这种不是计划中的修改又不会专门

做好灰度发布的准备

一个影响范围很大或者关键路径上的更新上线时, 通常需要考虑代码上做好 灰度发布的准备, 以实现 即使感知快速回滚

重要位置的错误告警 和 监控

在上线初期,需要在大部分的关键路径和关键位置添加告警,这样我们可以在程序异常时, 比 任何人都更快知道 问题的产生,并开始修复。

大部分公司的基础设施里, 基本都会有 错误上报系统的存在,除了 各个公司自研的 错误上报系统, 还有开源的 如 Sentry 或者基于日志的错误上报。

所以笔者觉得这个方案算是一个 挺通用的方法, 例如 笔者今天要上线一个基础组件, 虽然做了很多测试,但是心里还是没有底, 那么就可以在一些错误产生的地方添加上错误上报,还有 try catch 或者 recovery 的地方, 加上错误日志。

有的同学可能会觉得这样太粗暴了, 会加很多垃圾代码在程序中, 毕竟等到 这个代码 稳定下来之后, 大部分的错误告警都是 冗余代码。 确实没错。 但对于这点, 笔者觉得可以在稳定下来之后,逐步删除多余的告警, 毕竟大多数告警代码没有太多依赖的上下文, 最多就是依赖一下前面传入的 错误信息, 这种程度的修改,过一下编译,或者 简单的回归测试一下 跑一下测试用例就能基本确定没有问题。

另外可能有的同学会觉得 多余代码影响性能, 对于这点, 笔者觉得, 如果程序完全运转正常,根本不会进入上报流程。如果进入了上报流程,那么这个告警所产生的价值, 绝对比 多付出的这么点性能多很多。

另外就是告警风暴的问题,过多的告警会刷掉别的 告警信息。对于这点,告警系统需要有抑制或沉默机制,将一段时间内的告警进行聚合,并在一段时间内不再对相同内容进行通知。sentry 中已经默认会将相同的 content 和 代码行数 的告警进行聚合,如果读者是依赖 Prometheus + AlertManager 进行告警,AlertManager 中也提供了抑制和沉默的配置。其他系统中应该也有提供类似特性, 在自研系统中实现这个特性也特别简单。

如果理想的话,大部分通过上报发现的异常都可以在 极短的时间内 发布 patch 修复。但理想终究只是理想, 如果出现短时间无法修复的问题, 那么需要立刻回滚,但有时候代码上的回滚其实并没有那么方便,那么可以考虑在灰度上线的时候加上灰度开关。

灰度上线 和 灰度开关

对于版本的更新已经经过测试, 但是由于比较关键,心里仍然没有信心的话, 可以考虑添加若干灰度测试,引入少量真实用户流量,来进行验证。但灰度上线也是上线,如果出现了 bug 我们依旧需要回滚,但是对于一些不方便回滚 或者 回滚时间过长的场景,我们考虑 灰度开关, 藉由灰度开关,我们可以达成对 灰度特性维度 的快速回滚, 特别适合 不方便代码回滚, 或者回滚时间过长的场景。

对于灰度开关, 很多公司的基础设施都有 配置中心,我们可以在配置中心中添加 一个 灰度开关, 来完成 针对功能的快速回滚。另外如果经过 灰度开关的 并发量 很高,可以加上 5-10s 的缓存时间, 来对灰度开关的配置进行缓存。对于缓存开关生效的实时性要求不高的场景, 也可以考虑调高 灰度开关的缓存时间,或者 借由 Kubernetes 的 Config Map 之类的特性来实现。

另外, 很多公司 都会有 AB 发布 或者 金丝雀发布的机制, 但千万不要拿这个 机制来 做测试……AB/金丝雀 发布也是发布,如果不慎也会吃邮件 ;) , 所以尽量 测试完备之后再 发布

测试

测试部分就是常规的 单元测试 和集成测试, 这个会放在别的文章里再介绍 ;)

发布阶段

发布策略

根据不同的 场景, 可以为发布特性选择不同的选择不同的发布策略, 常见的 有 灰度/蓝绿/金丝雀

持续关注错误率/日志 以及快速回滚切换

在 编码阶段做好了 错误上报和监控 或者 回滚机制(例如 灰度开关)后, 当收到告警后, 采取对应的措施进行处理。如果上述编码阶段的准备都不奏效, 尽快使用 pod 级别的回滚或者代码回滚重编上线修复问题。如果有时间保存现场当然好, 但是如果没有办法的话,先以回滚为主。

So,What’s Next?

所以做到了以上这些,就能够保证 稳定发布了么?

笔者觉得 做到稳定发布这件事情是 Endless 的,需要根据当下环境 做出 合适的 行动。

Last modified 2020.10.10