巨兽型可变服务器
本文讲的是微服务 to 变 or not to 变?,今天,在创建和部署应用的时候,最常用的方式就是可变服务器。我们会创建一个web服务器,该服务器上具有完整的应用,每次有新的版本发布时我们就会对服务器进行更新。服务器中具体的改动包括配置改动(属性文件、XML文件、数据库表等)、代码工件(JAR、WAR、DLL、静态文件等)、数据库模式和数据。因为每当有新版本时我们就会对服务器进行相应的改动,因此这种服务器叫做可变服务器。
对于可变服务器来说,我们不清楚开发、测试和生产环境是否相同,甚至是生产中的不同节点也可能会产生不利的差异问题。代码、配置文件或静态文件在某些实例中是否全部完成升级更新也未可知。
可变服务器是一种巨兽服务器,它包含了我们需要的所有东西,构成了一个简单的实例,后端、前端、API接口等等都包括在内。此外,这种服务器会不断成长。一段时间后可能没有人知道生产中某一部分的配置详情,而要想准确复制(新生产节点、测试环境等等)就只能复制整个虚拟机,然后开始进行配置(IP、主机文件、数据库连接等)。我们不断的在服务器中添加新东西,渐渐地就会失去对服务器的把控。过一段时间,当初设计完美惊艳的架构就会面目全非。
新添加的层次、新耦合的代码、还有源源不断的修补程序(补丁),最终你会迷失在迷宫一样的代码中。一开始还很美丽的小项目变成了一头丑陋的巨兽。你满怀希望的项目,到最后也成为了大家茶余饭后的笑话。对于这样的项目,人们可能会说最好的处理方式就是扔到一旁重新来过。但事已至此,巨兽一旦形成就难以重新开始了,已经投入了太多资源,重新开始将会耗费大量时间,而且会产生很多风险。所以巨石架构可能还会持续相当长的一段时间。
可变部署看起来简单,实则不然。它将所有东西都耦合在一起,试图把复杂性隐藏,但这也使各个实例之间更容易产生差异。
发布新版本应用时,何时重启这样的服务器很关键。因为在重启时,服务器通常都是不工作的,而这不仅会造成经济上的损失,还会丧失客户的信任。在今天的商业世界里,我们应当提供全天候运行的服务。此外,新版本发布时通常也意味着研发小组需要在夜间加班工作。面对这样的情况,持续部署看起来遥若星辰,可望而不可及。
测试同样也存在问题。不论我们在研发和测试环境中进行了多少次测试,只有我们将软件部署在生产环境中,让测试员和用户都能够使用,才开始真正意义上的第一次生产测试。
另外,这种服务器几乎无法实现快速回滚。因为服务器的可变性,所以不存在之前版本的“快照”。除非我们为整个虚拟机创建一个快照,但这又会产生很多新问题。
如果采用可变服务器,那么我们之前所描述到的需求是不可能全部满足的。由于服务器无法实现零宕机和快速回滚,我们也无法对其进行持续(频繁)部署。可变服务器的特性也决定了我们不可能实现全自动化(有风险),这样就延缓了我们的研发速度。
如果不能经常部署,那么我们只能不断的积累各种改变,而这些最终都会在发布的时候才暴露问题,很容易就会导致研发的失败。
为了解决这些问题,我们应该采用不可变部署方式,同时部署中应当包括小型、独立且自给自足性的应用。我们的目标很明确,零宕机时间、回滚能力、自动化以及快速功能。此外,我们还应当在用户接触软件前就在生产环境中对发布版本进行测试。
不可变服务器和反向代理
每一种“传统”的部署方式中,对系统的改动都会呈现在服务器上,从而增加了风险。而如果我们采用不可变部署方式,那么就可以立竿见影,获得成效。由于我们不需要考虑应用(应用是不可变的),因此环境的准备工作将变得极为简单。当我们在生产服务器上部署一个新的镜像文件或容器时,我们很清楚该文件或容器就是我们一直在开发和测试的东西。
不可变部署减少了未知的风险,我们知道每一个部署的实例和其他实例都是相同的。与可变部署不同,当程序包不可变而且包含了所有东西(应用服务器、配置文件和工件)的时候,我们就可以高枕无忧了。这些东西打包作为整体在部署流程中进行处理,我们只需要确保这些不可变的程序包顺利到达终端服务器就好。不可变部署消除了可变部署带来的不一致性,我们在其他环境中进行测试的程序包和最终到达服务器的程序包是完全相同的。
反向代理可用来实现零宕机。不可变服务器和反向代理可以通过下面这种简单的方式结合使用。
首先我们启动一个反向代理,指向已经完成的完全自给自足的不可变应用程序包。这个程序包可以是虚拟机,也可以是容器。这个不可变的镜像显而易见有别于可变应用。此外,还会有一个代理服务,服务器并不会直接暴露,代理服务会将所有访问(traffic)发送(route)到最终的目标位置。
一旦我们决定要部署一个新版本时,我们就会通过在另外一个独立的服务器上部署单独的镜像来完成。当然,有时候我们可以将这个镜像部署在相同的服务器上,但更多时候,由于巨石应用消耗大量资源,在不影响性能的前提下我们很难在同一节点进行多次部署。
此时,我们就会有两个实例(两台服务器)。一个是老版本,一个是新版本。所有的访问均通过代理服务由老的服务器处理,这样用户就不会察觉到任何改变。因为对于用户来说,我们仍旧在运行之前的服务器和软件。此时我们就可以放心的去做新软件应用的最后测试了,最好这些测试都是自动化的,而且属于部署过程的一部分,但仍需人工检测。
例如,如果我们在前端做了修改,那么就需要在最后做一次用户体验测试。不论我们进测试类型是什么,都需要绕过代理服务针对新发布软件进行测试。做这些测试的时候,我们很清楚自己在测试的软件将来会发布并应用于硬件上,而且我们是在没有影响用户体验(用户此时在使用旧版本软件)的情况下进行的测试。甚至我们可以通过A/B测试的形式选择对一部分用户开放新版本软件。
总的来说,这个时候我们就有了两个服务器实例,一个(旧版本)给用户使用,一个(新版本)用来进行测试。
第二个实例与第一个实例平行部署
不可变应用的新版本部署在独立的节点处
一旦我们完成测试,确保新版本万无一失,那么我们只需要修改代理服务,让访问指向新版本软件即可。旧版本可暂时保留一段时间,供可能的回滚使用。但对用户来说,旧版本已经不复存在了。用户的所有请求都会指向新发布的版本。而我们在此之前已经确保新
版本可以投入使用,因此请求指向的改变并不会影响服务体验(而如果在可变部署模式中,这样做就需要重启服务器,导致服务中断,影响用户体验)。当请求路径改变时,我们需要重新加载反向代理。例如,在所有连接转变到新路径之前,nginx会维持所有旧的连接路径。
最后,当所有转变完成后,我们可以移除旧版本,我们甚至可以让新版本去做这件事。这样的话,当到了合适的时间,新版本就会自动移除旧版本并取而代之。
上述方法已经在业内使用了很长时间,我们称之为蓝绿部署。之后我们在讲到Docker打包和部署示例的时候还会提到它。
不可变微服务
我们还能做的更好。不可变部署使得我们可以轻易实现流程的自动化,反向代理实现了零宕机,新旧版本的使用也简化了回滚工作。但由于我们面对的应用仍然过于庞大,因此部署和测试工作可能会花费大量的时间。这可能就会使我们的速度降低,而且无法频繁的进行部署工作。此外,体量庞大的应用在开发、测试和部署时的复杂度也很高。如果可以的话,我们可能会将其分割成易于管理的小部分,这样不仅易于管理操作,还能简化拓展。这些小服务可以部署在同一台机器上,如果其中某个服务到达瓶颈(需要扩展),那么我们就可以在网络中对其进行拓展或复制,而这正是微服务!
在研发“巨兽”型应用时,我们往往会产生解耦的层次。前端代码和后端代码分离,业务层和数据接入层分离等等。而在微服务中,我们应当开始换个角度来看问题。我们要分离的不再是业务层和数据接入层,而是各个服务。例如,用户管理服务可以从销售服务中分离出来。另外有一点不同的是在物理表现(physical)上,传统的架构分离是在程序包和类的级别进行,但所有东西还是共同部署的;而在微服务中,各项服务是物理隔离的,可能正在开发的两个服务都不在同一台机器上。
微服务的部署方式与之前描述的方式相同。
我们部署微服务不可变镜像的方式和部署其他软件的方式相同。
所有请求都通过代理服务选择路径
微服务应用是不可变的,部署时作为容器进行部署
当我们准备发布某个微服务的新版本时,会将其与旧版本部署在一起。
当新版本微服务完成测试后,我们改变代理的路径(route)
最终,我们就可以移除旧版本微服务,开始使用新服务。
唯一明显的不同就是,由于微服务体量较小,我们不需要额外的服务器存放新版本。现在,我们终于可以实现持续的(频繁的)自动部署,提高研发速度,零宕机时间,并且可以在出现错误时进行回滚工作。