什么是不可变基础设施(Immutable Infrastructure)

原文


HashiCorp 之道的一个关键方面是不可变的概念,即一旦我们创造了一个东西,我们就不会在创建之后改变它。我们经常被问到的一件事是,“可变风格和不可变风格之间有什么区别?它们之间的优势或权衡是什么?”

可变风格的基础设施

让我们举个例子来描述两种风格的不同。假设我正在创建一个服务器,可能是一台虚拟机,也可能是裸金属(Bare Metal)机,这并不重要。我们称它为网络服务器。

我将部署例如 Apache 2.4 作为 Web 服务器程序,然后再部署我的应用程序。我们称它为网络服务器版本 1。现在我们看到有网络流量传入,用户正在向这个服务发出请求。

但随着时间的推移,我想做出改变,也许是想升级 Web 应用程序的版本,也许是想部署最新版本的 Apache ,又或者我想切换到不同的 Web 服务器,比如 NGINX。要考虑的事情是我们已经定义了我们的网络服务器版本 1。我们要做的是定义我们想要的版本 2 的样子。我们假设将要升级 Web 应用程序的版本到版本 2,并使用 NGINX 取代 Apache。

我们需要在版本 2 中使用 NGINX,如果使用可变风格的基础设施管理,我们要做的是尝试将现有的服务器升级到这个新的版本 2 配置。我们直接进入服务器,修改数据与状态,使之含有版本 2 的新配置。

这个过程通常是通过使用 Chef、Puppet、Ansible 等配置管理之类的工具来完成。我们可以在一开始运行配置管理工具将服务器配置成版本 1,然后当我们决定从版本 1 升级到版本 2 时再运行一次配置管理工具。

这么做的优点是,我们在一台正在运行的服务器上执行的变更。也许我们已经在本地写入了一些数据,并且我们的 Web 服务器正在使用这些数据。当我们就地更新时,我们不必操心将数据拷贝到其他机器、创建新机器,所有的基础设施已经在那里了,我们要做的就是执行升级。

可变风格的挑战,以及与之相关需要权衡的是:如果不能完美升级会怎样?在现实世界中,事情是会出错的。当我们开始升级时,我们要做的第一件事就是:“我们需要安装这个新版本的 NGINX,因为我们以前不用 NGINX。”

我们将尝试运行 apt-get 安装 NGINX ,但安装可能会失败。比如,在我们运行该工具的那一刻,我们的网络不稳定,也许 DNS 服务不可用,也许我们的 APT 存储库没有响应。谁知道,有一百万种可能导致失败的原因。

安装失败导致我们此时处于这样一种奇怪的状态:没有成功安装 NGNIX,但成功红部署了我们的 Web 应用程序的版本 2。我们测试过版本 2,我们知道该版本可以搭配 NGINX 一起工作,我们已经测试并验证过这个组合了。我们同样理解、验证和测试过基于 Apache 和我们的 Web 服务器的版本 1 的组合。

但现在我们两个版本都不是。我们陷入了一个奇怪的版本: 1.56。我们的 Apache 仍在运行,我们没有安装 NGINX,与此同时我们有一个新版本的 Web 服务(它只能与 NGINX 搭配工作)。

我们可以称之为部分升级,如果我们从数据库领域的角度考虑,它就是一个部分提交的事务。一部分变化发生了,一部分变化没有发生。

可变性的权衡

这对我们有何启示?这种升级过程引入了风险:我们可能处于半失败的情况。另一方面,它增加了复杂性。如果我们运行过测试,我们就会了解版本 1 的情况。我们测试并验证了该版本。版本 2 也一样,它是被测试并验证过的。

我们从未测试或验证过 1.56。我们不了解 1.56,因为我们从来没有预料到会是这个状态。现在对我们来说,搞清楚是否有从版本 1.56 版到版本 2 版的可能升级路径,以及当流量现在达到版本 1.56时用户的体验如何,是很复杂的。

网站是否正常运行,是否出现错误,发生了什么?这些问题对于即便是单台服务器来说都很复杂,现在想象一下我有一个由数百或数千台机器组成的集群,它们各自都以略有不同的方式发生过故障。也许其中一个安装 NGINX 失败,而另一个安装了 NGINX 但没有安装 web 服务程序。也许第三台机器无法卸载 Apache,所以您最终会看到“好吧,我们有版本 1.56 的这台服务器,那台则是版本 1.54,那台是版本 1.78”。

我们这时必须重新审视我们的版本控制,将其视为一个连续的范围,而不是将其视为离散的版本 1版本 2,所有的内容都有可能处于中间状态,这将成为一个非常复杂的场景。

你可能会说这种假设不切实际。在实践中,99% 的情况下可变风格都能正常工作。确实如此,但问题是,某样东西有 99% 的概率可以正常工作,乘以一千台机器,这意味着在相当多的时间里,它是行不通的。您最终会遇到这些复杂的调试问题:十分之一的请求出现错误,或者十分之一的请求比应有的速度稍慢。

这些系统变得异常难以调试,因为系统正处于一种难以理解的状态。

这让我们想到了另一种思考方式,即,相对于这种可变风格的管理方式,不可变风格基础设施又是怎么样的?

不可变风格的基础设施

不可变风格的不同之处在于,我们不会就地更新状态。服务器被创建后,我们永远不会尝试将其升级到 版本 2。我们将创建我们的服务器,将其称为版本 1,我们将安装 Apache,我们将安装我们的 Web 服务程序,然后我们将制作该虚拟机的快照,我们将创建的快照称为服务器的版本 1

我们将使用该快照对用的镜像创建虚拟机,然后我们将引导用户流量流入其中。太好了,我们已经部署了服务的版本 1,就像我们在可变配置中所做的那样。但是当我们要升级到版本 2 时,我们要做的是创建一个全新的服务器,该服务器将安装 Web 服务程序的 版本 2,另外安装 NGINX,这就是 Web 服务器的版本 2。这一切都将发生在一台新的虚拟机上,我们不会去变更版本 1 服务器上的文件和状态。

如果安装过程中发生任何错误,我们可以中止创建,删除所有中间制品然后再试一次。但是如果我们成功创建了版本 2,过程中没有发生错误,一切都被正确安装配置好了,那么我们要做的就是切换流量。我们的用户现在开始访问版本 2 而不是版本 1。然后我们将停用版本 1。我们将把该版本的虚拟机停机或是删除,我们的目标是,不尝试修改这个系统。

这让我们拥有了一个离散的版本控制。流量要么通往版本 1,要么通往版本 2。没有中间区域,两者之间没有版本 1.5。这样做的好处是,当我们考虑风险和复杂性时,风险要低得多,因为我们没有这些未经验证的未定义状态,同时我们也降低了基础设施的复杂性。

我们可以说我们在版本 1 中有 50 台机器,在版本 2 中有 20 台机器,而不是有一些机器和不同版本的分布。这个样子的基础设施管理起来复杂度就要低得多。

不可变性的权衡

不可变风格并不是在任何情况下都可以直接采纳的。如果这个应用程序有状态怎么办?如果这个应用程序正在往本地磁盘写入对应用程序很重要的数据怎么办。这种情况下,创建一个新机器然后删除老机器,包括它的数据,包括它的磁盘,显然是行不通的。

为了在这种场景下使用不可变风格,我们通常需要做的是将数据外部化。与其将数据和应用放在一起,不如使用共享的外部数据库。两个版本的服务器可以同时使用一个数据库。

当我用这种方法切换到不可变风格时,我们不用担心会破坏机器上的数据,因为我们已经将数据保存在机器外部了。数据的外部化是我们在这种场景下应用不可变模式的关键所在。一般来说,诸如数据库这样的系统更新频率往往远小于我们的应用程序,所以我们可以说,“你知道吗,我们将使用可变方法来管理数据库,因为它不太更新,而且我们不会因为数据迁移而烦恼。”

也可能我们的应用部署在云上,并且使用了 Elastic Block Store 或外部的软件定义存储之类的技术,也许底层磁盘是可变风格的,但是运行我们数据库的机器仍然可以是不可变风格的。

我们可以关闭正在运行的虚拟机,比如说我们有一台服务器安装了MySQL 7,我们将关闭它,我们将启动一个运行 MySQL 8 的新虚拟机,并将它重新连接到同一个磁盘。通过这种方式,数据本身并没有丢失,只是机器、计算从一个版本迁移到了另一个版本。

有多种不同的方法可以实践不可变基础设施。它与可变风格基础设施相比根本的区别是:我们是在现有基础设施上原地变更,还是创建新的基础设施,取代并销毁旧的版本?

这是可变基础设施和不可变基础设施之间的核心区别。

results matching ""

    No results matching ""