再谈十二要素方法论

发布于:2024-10-13 ⋅ 阅读:(8) ⋅ 点赞:(0)

本文回顾了 12 要素方法论的各个原则,探讨了这些原则在当前云原生技术环境下的应用情况、适用性和实践,并讨论了在实际操作中是否需要对其进行调整。原文: 12 Factor: 13 years later

导言

最近一次关于 CI/CD 的演讲中简要提到了 12 要素方法论,大致意思是"里面可能包含了一些好的做法",可将其总结为:

artifact
configuration +
---------------
deployment

从 12 要素方法论中可以获得很多好的做法,但是否所有原则都仍然有效?或者说,在某些情况下,完全照搬有没有可能会适得其反?

我曾将大量应用接入 Kubernetes,这些应用在构建时就已经考虑到了 12 要素。这个过程通常相当顺利,所以你开始认为一切都理所应当,直到遇到难以运维的应用。

如果仔细观察,通常会发现这些应用违反了 12 要素中的某些原则。

12 要素方法论是在13年前由Heroku提出[1]的,这是一家“云原生”公司,专注于开发人员体验和运维的便利性。所以毫不奇怪,这些原则仍然重要。

让我们回顾一下这 12 个原则,并将它们与现代云原生应用结合起来。

1. 代码库

在变更控制中跟踪代码库,进行多次部署

看一下,如今我们会在代码库和部署之间添加工件。工件可以是容器,也可以是压缩文件(无服务器)。

code         -> artifact       -> deploy
- versioned     - container       - prod
                - zip             - staging
                                  - local

值得注意的是,对于本地开发,根据不同设置,通常会以某种形式的实时重载来代替创建实际的工件。

2. 依赖关系

明确声明并隔离依赖关系

这在容器化应用中已变得越来越自然。

不过有一部分描述有点过时:"12 要素应用也不隐式依赖于任何系统工具。例如,使用 ImageMagick 或 curl"。

在容器化应用中,边界就是容器,其内容是明确定义的。因此,应用向 curl 发送命令就不成问题了,因为 curl 现在是随容器一起存在,而不是被假定存在的。

同样,在 AWS Lambda 等无服务器设置中,执行环境定义得非常明确,因此可以安全使用环境提供的任何依赖关系。

3. 配置

在环境中存储配置

这一点在特定解决方案上可能过于具体,主要点在于:

  • 配置不在应用程序代码中
  • 工件 + 配置 = 部署

令人困惑的是,特别是随着 GitOps 的兴起,配置被置于代码库中,但又与应用代码分离。

只要遵循上述概念,使用环境变量或配置文件都是实施细节。

使用 Kubernetes 时,根据安全要求,可能需要考虑使用文件而不是环境变量,并可选择加密。关于这个问题,推荐观看:KubeCon EU 2023: A Confidential Story of Well-Kept Secrets — Lukonde Mwila, AWS (video).

4. 后端服务

后端服务视为附加资源

这已成为常见做法。在 Kubernetes 中,通常很容易配置本地单节点(非开发)Redis 或 Postgres,或 RDS 或 Elasticache 等远程云管理资源。

使用本地文件系统或内存也有一定原因,例如性能或简单性。只要数据生命周期是短暂的,并且实施过程不会对其他因素产生负面影响,那么使用本地文件系统或内存就没有问题。

5. 构建、发布、运行

严格隔离[2]构建和运行阶段

从 Kubernetes 到 AWS Lambda:如今很难再违反这一原则了。将前面的总结完善一下:

Build   -> artifact
Release -> configuration +
--------------------------
Run     -> deployment
6. 进程

将应用作为一个或多个无状态进程执行

全文中有一句话更好地概括了这一点:

12 要素进程是无状态的,不共享任何东西

一些启示:

  • 单容器、单进程、单服务。
  • 无粘性会话。会话存储在外部,如 Redis。另请参见要素 4。
  • 考虑使用 init 容器Helm charts 钩子来简化流程。另请参见要素 12。

该要素与要素 4 有一定程度重叠,意味着尽可能使用外部服务。例如使用外部 Redis 而不是嵌入式 Infinispan。

7. 端口绑定

通过端口绑定输出服务

这对基于 TCP 的应用来说是可行的,但对基于 AWS Lambda 或 SpinKube (Kubernetes 上的 WASM 框架)等事件驱动型系统来说,就不再适用了。

8. 并发性

通过进程模型扩容

使应用具有水平扩展性,在一定程度上与要素 4 有关,因为要素 4 会保证应用进程无共享数据。

此外,应用应让操作系统或调度器负责进程管理。

9. 可替代性

通过快速启动和优雅关机最大限度提高稳健性

在某种程度上,这可以看作是对前一个要素的补充:正如要易于横向扩展一样,要易于删除或替换进程。

具体到 Kubernetes,可以归结为:

  • 遵守终止信号。应用程序应优雅关闭。要么在应用程序中处理 SIGTERM 信号,要么设置 PreStop 钩子(更多信息请参考:Kubernetes best practices terminating with grace)。

  • 设置探针。只有当应用程序真正准备好接收流量时,探针才会返回OK

  • 设置maxSurge滚动更新)和 PodDisruptionBudget调度)。

  • 节点是短暂的,因此总是可以重新调度 pod:无共享状态概念。

10. 开发、生产环境一致

尽可能保持开发、预发和生产环境的一致性[3]

这是一个宽泛的话题,而且一如既往具有现实意义。概括起来就是"左移":尽可能可靠、快速的验证变更。

解决方案有很多,可能包括 Docker ComposeVS Code 开发容器TelepresenceLocalstack 或设置临时 AWS 账户作为无服务器应用开发环境。

11. 日志

将日志视为事件流

不要在文件中存储日志,不要在应用程序中"发送"日志。

操作系统或调度器应捕获输出流,并将其路由到所选的日志存储。

12 要素方法略显陈旧的地方在于,没有提及度量和跟踪,二者和日志通常被称为"可观测性的三大支柱"。

将这种方法推广到日志记录,可以考虑"封装"应用程序,而不需要详细的实施。OpenTelemetry 零代码仪表可以作为一个很好的起点,也可以采用可观测性 SaaS 平台的 APM 代理(如 New Relic 或 Datadog)。

12. 管理进程

将管理任务作为一次性进程运行

完整描述中的这段话或许能更好的概括这一点:"管理代码应与应用程序代码一起提供"。

这与更改数据库模式或将资产捆绑上传至集中存储位置等任务有关。

目的是排除任何同步问题。关键词是:

  • 相同的环境
  • 相同的代码库
总结 12 要素

只要我们努力把握这些要素背后的理念,而不是关注每个细节,我认为大多数要素都是站得住脚的。

多年来,一些建议已或多或少成为了惯例。还有一些建议有些重叠,例如状态外部化(要素 4)使并发性(要素 8)和可替代性(要素 9)更容易实现。

要素 13:前向和后向兼容性

根据我的经验,有一点在 12 要素方法中没有涉及,但总是能使应用更易于运维:前后兼容性。

我们希望能够频繁部署应用,并且不停机。这意味着要么进行滚动更新,要么进行蓝/绿部署。在大型分布式平台中,即使是蓝/绿部署也很难做到真正的原子部署。而金丝雀部署等部署模式则意味着可以回滚。

因此,做好这一点就能为频繁顺滑的部署开辟道路。

这与数据库、缓存数据和 API 约定有关。我们需要考虑:

  • 当版本 NN+1 同时运行时,应用如何处理数据?
  • 如果需要从 N+1 回滚到 N,会发生什么情况?

一些建议:

  • 更改数据库模式时,首先 添加列。只有在迁移数据后,才能在后续版本中删除列。
  • 首先在 API 或事件模式定义中添加字段,然后再更新消费者,使其真正使用新字段。
  • 考虑缓存对象的兼容性。在缓存键前加上应用程序版本独有的前缀会有所帮助。

过渡时期的数据将如何处理?以新旧格式存储数据?是否需要与数据一起存储版本信息并支持多个版本?

对于提供给他人运维的应用来说,与开发团队自己运维并通过 CI/CD 发布的应用不同,可能会更复杂。外部用户通常不会关注所有次要版本,因此更有可能不具备向后兼容性。

结论

上述某些建议可能需要额外的努力。不过,根据我的经验,这些努力都是值得的,而且会因运维简便、省心和减少对发布协调的需求而得到连本带利的回报。


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

参考资料
[1]

The Twelve Factors: https://github.com/heroku/12factor/commit/2b06e7deabb64bb759f9fc6f4d9b6fcc546921bb

[2]

Build, Release, Run: https://12factor.net/build-release-run

[3]

Dev/Prod parity: https://12factor.net/dev-prod-parity

本文由 mdnice 多平台发布