不可变基础设施和声明式API听起来比较晦涩,其实它们是指一种云原生下的设计理念。明确哪些是不可变的,哪些是可变的,对于我们理解云原生有很大的帮助。
不可变基础设施(Immutable Infrastructure)由Chad Fowler在2013年提出,其核心思想是任何基础设施的实例一旦创建后即变为只读状态,若需修改和升级,则应使用新实例进行替换。不可变基础设施与程序设计中不可变变量(Immutable Variable)一样,赋值后不可变更,要想重新定义,只能通过创建新的变量来替换。
与不可变基础设施对应的是可变基础设施。在上容器之前,开发或运维人员的常见操作是根据应用名称,找到对应的服务器IP,之后进行代码文件的替换。一般来说,如果不进行扩容,服务器IP是常年不变的。比如我所在公司曾有一个用PHP语言写的搜索服务,对应的4台服务器IP是172.168.1.100-103,运维人员上线时通常是通过SSH(Secure SHell,安全外壳)协议找到这4台服务器,手动替换对应的代码文件,相当于这几台服务器虽然一直固定,但是代码在不断地更新迭代,久而久之补丁越大越多,可能同一段逻辑有多个不同的冗余版本,线上代码居然有1GB之大,无人敢去优化,逐步变成了一个极大的“代码肿瘤”。换言之,这些服务器创建之后,可以对它们进行更改、升级、调整,这就是之前的可变基础设施。
不可变基础架构是另一种基础架构范例,其中服务器在部署后永远不会被修改。如果需要以任何方式更新、修复或修改某些内容,则应根据具有相应更改的公共映像构建新服务器以替换旧服务器。经过验证后,新服务器投入使用,而旧的则会退役。比如Kubernetes中的Pod滚动更新,旧的4台Pod进行发布后,会有4台新的Pod被创建,它们具有新的Pod IP,旧的4台Pod会被停止,如图1-15所示。
图1-15 滚动更新示意图
1.1.1节介绍云计算时,我们用“牛与宠物”来类比可变与不可变基础设施在处理服务器(例如创建、维护、更新、销毁)方面的巨大差异。传统可变基础设施中的服务器是不可替代的,共独特的系统必须始终保持运行。在这种方式下,服务器就像宠物一样,独一无二,无法模仿,并且倾向于手工制作。不可变基础设施中的服务器是一次性的,易于复制或使用自动化工具进行扩展。在这种方式下,服务器就像牛一样,牛群中没有哪个是独一无二或不可或缺的。
可变与不可变基础设施之间最根本的区别在于它们的核心思想:前者旨在部署后进行更改;后者旨在通过完整替换来保持更新与迭代。在容器和云出现之前,实现不可变基础设施是很难的,因为一个服务的正常运行包括多个组成部分,我们通过虚拟机等技术可以实现多个环境底层操作系统、库、运行时的统一,但是配置项、代码合并很容易引发开发环境与测试环境不一致,以及生产环境间不一致的问题。处理这样的问题只能通过人工接入的方式在服务器上修修补补,进而导致了基础设施的可变。
容器镜像的出现,使不同的环境、标准化配置变成了可能,我们可以快速拉起成千上万个一模一样的服务,服务的版本升级、快速拉起变为常态,进而不可变基础设施也逐步变为可能。
在看不可变基础设施的优点前,我们先分析一下可变基础设施有什么问题。
·服务器维护日趋复杂,逐步变为“雪花型”服务器。可变基础设施中的服务器可能会受到配置偏差的影响,在未记录的情况下,即兴更改会导致服务器的配置变得越来越不同,这些不同会像雪花一样越积越多,不同服务间的差异可能越来越大,进而运维也会越来越困难。
·线上问题排查困难。在服务运行过程中,持续地修改服务器会引入中间状态,从而导致不可预知的问题。我之前帮别的团队定位线上问题时发现,堆转文件中提示的报错信息是56行,然而在代码仓库中56行对应的是一个空行,确认了好久才发现,生产上部署的代码是某一次为了修改Bug直接替换的class文件,导致与主干代码不一致。
·容灾后的快速恢复极其困难。老服务一旦发生故障,很难快速重新构建。由于之前持续做过太多的手工操作,而且缺乏记录,这很容易造成生产环境运行的服务与代码仓库中严重不一致,可能使得GitLab中的代码无法直接运行,只能依靠复制生产环境运行的War包来重新拉起。
与上述问题对应的就是不可变基础设施的优点。
·一致性。在不可变基础设施下,所有的配置都通过标准的描述文件(如YAML、Dockerfile)来统一定义,不同的Pod都是按照同样的定义来创建的,可以说是完整复制。不同服务器间配置不一致的情况基本不会出现。
·简单清晰。运维人员不会再去维护冗长繁多的服务器列表,查找对应服务的服务器IP,只需要维护好应用的描述,老服务有异常时直接停掉,快速使用新的服务取代即可。
·自动化快速容灾。当线上服务压力增大或使用率过低时,不可变基础设施可以快速进行弹性扩缩容、升级回滚,应对突发问题也更加快速和自动化,极大地提升了持续部署的效率。