在专栏的第13讲《容器技术真的是环境管理的救星吗?》中,我们初步结识了不可变基础设施(Immutable Infrastructure),这里我们再一起回顾一下:

在这种模式中,任何基础设施的实例(包括服务器、容器等各种软硬件)一旦创建之后便成为一种只读状态,不可对其进行任何更改。如果需要修改或升级某些实例,唯一的方式就是创建一批新的实例来替换它。

这种思想与不可变对象的概念完全相同。

为什么我会说,不可变基础设施的思想对持续交付的影响非常深远呢?因为不可变的思想正是解决了持续交付一直没有解决的一个难题,即环境、顺序、配置这些基础设施在测试环节和生产环节的不一致性所带来的问题。

那么,今天我就来详细分析一下不可变基础设施的由来、影响,以及如何实现的相关内容。

从持续交付中来

如果你是一个程序员,其实很容易理解不可变基础设施的概念,以及其实现的方式。因为它就和Java中的不可变类完全相同:类实例一旦创建,就无法变更,而可以变更的是指向实例的引用。

其实早在2011年出版的《持续交付:发布可靠软件的系统方法》一书中,就曾提到“蓝绿发布”的概念:你需要更新一组实例,但并不是直接在原有实例上进行变更,而是重新启动一批对等的实例,在新实例上更新,然后再用新实例替换老实例。此时老实例仍旧存在,以便回滚。

其实,这完全就是对不可变类的物理实现,也就是一个典型的不可变模型。

这里,我抛出了“不可变模型”的概念,那么我们再垂直一些,再来看看由不可变模型转化到不可变基础设施,又会有哪些具体的要求。

综合起来一句话,就是:

对任何的包、配置文件、软件应用和数据,都不做CRUD(创建、替换、更新、删除)操作。

也就是说,对于已经存在的基础设施,不再在其上创造任何新的事物。根据不可变模型,推导得出取而代之的方法则是:

  1. 构建一个新的基础设施;

  2. 测试新的基础设施是否符合需求;

  3. 将引用指向这个新的基础设施;

  4. 保留原有基础设施以备回滚。

虽然不可变模型的设想很好,但其中也会有一些特殊情况存在。比如,涉及数据的部分,特别是数据库,你不可能每次都重建一个数据库实例来达到“不可变”的目的。为什么呢?其根本原因是,数据库是有状态的。所以,从这里可以清楚地看到,不可变(Immutable)的前提是无状态。

不可变基础设施的神话

说到为什么会需要“不可变基础设施”这种方法论,还是挺有意思的。

首先是一个假设:如何保证两个实例的行为完全一致?最有效的方式是,在两个实例上以同样的顺序执行同样的变更。

然后,在2002年,Steve Traugott 写了一篇名为《为什么顺序很重要》(Why Order Matters)的论文,虽然论文本身涉及了很多数学推理,有些难懂,但它却很好地解释了为什么大规模基础设施是不可变的,并证明了不可变的价值所在。

第一,一致是最终的目标

在论文中,作者讨论了发散、收敛和一致三种模型。

  1. 发散, 是我们通常会碰到的基础设施的管理模型。在这个模型中,基础设施随着我们的想法而变化,也就是我们想更新什么就更新什么,最终就会形成一种发散的形态。

《Why Order Matters》论文中的发散模型
  1. 收敛, 是Puppet和Chef遵循的设计原则。随着时间推移,目标和实际需求汇聚,达到一致。通过这个模型,我们有了可扩展的基础设施的基础和实现。

《Why Order Matters》论文中的收敛模型
  1. 一致, 指的是整个基础设施始终把每一天当成是与第一天相同的模型。根据我们之前的假设,达到这一目的的关键点就在于,有序地正确执行从真正的第一天开始的所有变更。

《Why Order Matters》论文中的一致模型

那么你就会有疑问,为什么会有一致模型?通过发散和收敛这两个模型,没办法解决实例完全一致的问题吗?答案是,确实不行。

即使我们知道发散是一种不良状态,我们可以通过定期的收敛,将基础设施不断地趋向所期望的目标。但其中也会碰到很多问题。

  1. 顺序问题:你只有完全保证顺序的正确性,结果才会正确。但是,怎么保证顺序呢?特别是执行结果与你的预期不一致时,比如发生错误时,就会多出一些其他的处理步骤,直接影响原有的既定顺序。这也是为什么顺序那么重要的原因。

  2. 频率问题:假设你可以通过一些方法保证顺序,在面对大型基础设施时,应该如何制定收敛频率呢?最简单的回答,自然是越频繁越好。
    那么你就会陷入巨大的陷阱中,你会发现完全无法支撑并发的收敛工作。而且收敛工作与设施的规模成正比,直接否定了系统的可扩展性。

  3. 蝴蝶效应:你始终无法确定一个绝对的基准点,认为是系统的初始或者当前应该有的状态。因为你始终还在收敛中,只是无限趋近。因此任何小偏差,都会引起将来重大的、不可预知的问题。这就是蝴蝶效应。

但是,容器却通过分层镜像与镜像发布技术,解决了上面的顺序问题、频率问题和蝴蝶效应。所以说,容器是一个惊人的发明,它使得每一次变更都成为了一次发布,而每一次发布都成为了系统的重新构建, 从而使得“一致”模型的目标能够达成。

第二,Immutable的衍生

当然除了容器之外,Immutable 理念还有许多不同的衍生。比如,黄金映像、VDI(虚拟桌面)、Phoenix Server和基础设施即代码。

这些衍生技术都遵循Immutable的理念,曾在不同的场景下,比如快速灾备、快速恢复系统、增强系统健壮性等方面发挥了巨大的作用,从而收益。

但是,随着技术的发展,这些衍生技术不再能够适应我们对速度和扩展性的要求,加之容器技术的蓬勃发展,使得这一系列的难题都可以通过容器技术解决,因此我们已经完全可以把注意力放到容器上了。

回到持续交付中去

虽然我们一起从持续交付中走了出来,了解了不可变基础设施的种种,但最终我们还是要回持续交付中去,去解决构建持续交付平台的问题。那么,接下来我们就一起看看面对容器时代的新形式,持续交付要做哪些变化吧。

“不可变”模型的好处,已经显而易见。而对于容器时代的持续交付,也显然已经从原来单纯交付可运行软件的范畴,扩展为连带基础环境一起交付了,所以我们需要为此做好准备。

上文中,我已经总结了一句话,每一次变更都是一次发布,而每一次发布都是系统重新构建,更形象点说,每一次发布都是一个独立镜像的启动。所有持续交付的变化也都可以表现为这样一句话,那具体怎么理解呢。

首先,任何的变更,包括代码的、配置的、环境的,甚至是CPU、内存、磁盘的大小变化,都需要制作成独立版本的镜像。

其次,变更的镜像可以提前制作,但必须通过发布才能生效。 这有2个好处:

  1. 重新生成新的实例进行生效,完全遵循不可变模型的做法;

  2. 发布内容既包含代码也包含基础设施,更有利于DevOps的实施。

再次,一组运行中的同一个镜像的实例,因为“不可变”的原因,其表现和实质都是完全一样的,所以不再需要关心顺序的问题。因为任何一个都等价,所以也就没有发布或替换的先后问题了。

最后,根据“一致”模型的要求,我们需要记录系统从第一天发展到今天的所有有序变更。 对Docker而言,不仅要能向上追溯层层Base镜像的情况,更建议将系统和软件的配置以Dockerfile的方式进行处理,以明确整个过程的顺序。

这些理念,不仅传统的持续交付中没有涉及,甚至有些还与我们日常的理解和习惯有所不同。比如,你通常认为一个集群中的不同服务器的配置是可以不一样的,但在“不可变”模型中,它是不被允许的。

当然,我在之前的《容器技术真的是环境管理的救星吗?》一文中也提到过,Immutable对持续交付的环境管理来说确实有点违反人性。所以,容器对持续交付的影响,可以说是利弊都有吧。

也因此,持续交付中遇到“不可变”,更应该去理解它的概念和用意,合理发挥其优势。

总结

首先,我分享了“不可变”模型的概念,以及它的由来,介绍了三个非常有价值的模型:发散模型、收敛模型和一致模型。

其次,我解释了为什么“不可变”如此重要的原因,也就是重复发散到收敛过程无法解决的三个问题:顺序问题、频率问题和蝴蝶效应。

最后,我针对“不可变”及容器,提出了持续交付面对的新问题,即:每一次变更都是一次发布,每一次发布都是一个独立的镜像的启动。

思考题

你所在的公司有没有什么地方可以体现出“不可变”思想?如果没有,是什么原因呢?

感谢你的收听,欢迎你给我留言。

评论