<audio id="audio" title="20 | Immutable！任何变更都需要发布" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f2/bf/f2b26181a6e4c93ffc301e8ba13b9bbf.mp3"></audio>

在专栏的第13讲[《容器技术真的是环境管理的救星吗？》](https://time.geekbang.org/column/article/12404)中，我们初步结识了不可变基础设施（Immutable Infrastructure），这里我们再一起回顾一下：

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


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

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

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

## 从持续交付中来

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

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

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

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

综合起来一句话，就是：

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


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

<li>
构建一个新的基础设施；
</li>
<li>
测试新的基础设施是否符合需求；
</li>
<li>
将引用指向这个新的基础设施；
</li>
<li>
保留原有基础设施以备回滚。
</li>

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

## 不可变基础设施的神话

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

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

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

**第一，一致是最终的目标**

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

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

<img src="https://static001.geekbang.org/resource/image/cc/c3/cc386bb76b29d7a802d5daad5afbd3c3.png" alt="" />

1. **收敛，** 是Puppet和Chef遵循的设计原则。随着时间推移，目标和实际需求汇聚，达到一致。通过这个模型，我们有了可扩展的基础设施的基础和实现。

<img src="https://static001.geekbang.org/resource/image/81/7f/811b23f808a992bbee073b042672887f.png" alt="" />

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

<img src="https://static001.geekbang.org/resource/image/c3/42/c39eb9619decb5018eb33663b6fa7542.png" alt="" />

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

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

<li>
**顺序问题**：你只有完全保证顺序的正确性，结果才会正确。但是，怎么保证顺序呢？特别是执行结果与你的预期不一致时，比如发生错误时，就会多出一些其他的处理步骤，直接影响原有的既定顺序。这也是为什么顺序那么重要的原因。
</li>
<li>
<p>**频率问题**：假设你可以通过一些方法保证顺序，在面对大型基础设施时，应该如何制定收敛频率呢？最简单的回答，自然是越频繁越好。<br />
那么你就会陷入巨大的陷阱中，你会发现完全无法支撑并发的收敛工作。而且收敛工作与设施的规模成正比，直接否定了系统的可扩展性。</p>
</li>
<li>
**蝴蝶效应**：你始终无法确定一个绝对的基准点，认为是系统的初始或者当前应该有的状态。因为你始终还在收敛中，只是无限趋近。因此任何小偏差，都会引起将来重大的、不可预知的问题。这就是蝴蝶效应。
</li>

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

**第二，Immutable的衍生**

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

- 黄金映像，指的是将绝大部分不变的基础设施（包括操作系统、大多数软件、基本配置等），包含在映像内，只留很少一部分变更通过脚本执行解决；
- VDI（虚拟桌面），指的是操作系统运行在后端的服务器上，用户只使用属于他自己的虚拟桌面，无法改变后端的系统内容；
- Phoenix Server，指的是完全被破坏的服务器，能够从灰烬中自动进行恢复；
- 基础设施即代码，指的是把基础设施的构建以代码的方式组织起来，从而通过运行代码可以完全构建出你想要的全部基础设施。

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

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

## 回到持续交付中去

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

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

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

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

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

<li>
重新生成新的实例进行生效，完全遵循不可变模型的做法；
</li>
<li>
发布内容既包含代码也包含基础设施，更有利于DevOps的实施。
</li>

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

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

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

当然，我在之前的[《容器技术真的是环境管理的救星吗？》](https://time.geekbang.org/column/article/12404)一文中也提到过，Immutable对持续交付的环境管理来说确实有点违反人性。所以，容器对持续交付的影响，可以说是利弊都有吧。

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

## 总结

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

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

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

## 思考题

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

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


