云原生不可变基础设施

2022/6/28 15:02:33 资讯频道 14363

01什么是不可变基础设施?如何理解?

熟悉云原生的小伙伴们都知道,云原生目前具有五大代表性的技术,它们分别是:容器、服务网格、微服务、不可变基础设施、声明式API。其中,不可变基础设施相比于其他四种概念难理解一些。

网上对于不可变基础设施的定义有很多,此处给大家展示一个比较有代表性的描述:

  • Immutable infrastructure refers to servers(or VMs) that are never modified after deployment.

生活中,不可变基础设施的例子比比皆是,我们以“水”为例来谈一谈不可变基础设施和其对应的可变基础设施:

  • 现实生活中,如果我们生活在农村或者比较落后的山区,水资源的获取对于我们来说是相对比较困难的,在使用水资源时会比较珍惜,会存在这种水资源使用方式:淘米水用好之后可能会用来洗菜、洗菜后的水会用来洗拖把、洗拖把后的水再用来冲马桶,这种水资源的利用被视为可变的基础设施;生活在城市的时候,水是作为一种不可变基础设施来使用的,我们打开水龙头后,用过的水直接进入了下水道,这种水资源的使用是没有进行复用的。

从现实回到代码,其实在代码中我们也是存在很多不可变基础设施和可变基础设施的思考:

图1

开发人员在编码时也会存在不可变基础设施的场景,java、c++等语言都提供一种能力让变量变成不可修改,包括传参的时候,如果进行限制后,对该变量进行修改会出现编译报错,如果要实现不可变数据的修改,需要通过再申明一个变量等方式去支持不可变数据的修改,有开发经验的开发人员知道不可变数据让代码逻辑更加清晰,减少错误,同时让并发变得更加简单。并发编程时如果让一个变量申明为只读类型的,对其进行并发修改时不需要加锁进行控制,这就是不可变性在并发中的思考。

其实“不可变基础设施”这个名词最早出现在2013年,随后,Docker带来的“容器时代”和k8s引领的“云原生时代”让不可变基础设施这个理念越来越流行。常见的服务器、虚拟机、容器都称为基础设施。

02不可变基础设施的优势特点有哪些?怎么改造?

大家熟知,云计算的出现是降低了环境标准化的成本,但业务的交付成本依然很高。

云原生技术架构展示如下:

图片

图2

不可变基础设施与之对应的是可变基础设施,在传统开发中,软件开发完成后需要部署到服务器上进行测试或者正式部署等,开发或者运维人员需要通过客户端连接到服务器端进行一些安装部署等工作,并且如果考虑多节点服务器部署的话,涉及到对应的配置项(比如环境变量等)需要对每个节点逐个进行配置参数修改,如果后续升级等还需要对每一个节点环境进行修改,比如电商那种更新迭代比较频繁的话,这些环境经历的一些操作很少能完全理清,后续的变更会经常遇到各种诡异的问题,基础设施变得很脆弱、敏感,一些比较小的变动就会引发不可预知的结果,这是一件非常头疼的事情,排查问题需要很丰富的技术积累,同时耗费的时间也会很长。

从开发者角度来看,不可变基础设施在时间和空间的一致性是非常棒的,特别是在排查业务侧问题的时候。对于时间的理解,如果应用部署在某一个服务器上面的时候,运行了一段时间(比如100天),服务器的状态还是一模一样的,这就能在很大程度上保证排查问题的效率;空间上,应用不管部署在研发区还是测试域、部署在linux还是windows,空间上也能做到一致。

可变基础设施常见问题:

  • 服务频繁持续的变更会给服务运行引入很多中间态,从而导致软件熵的增加,不可知风险增加;
  • 故障出现时,很难快速构建出新的服务副本,依赖于部署时的高可用节点;
  • 很难标准化,交付运维过程异常痛苦,虽然可以通过 Ansible、Puppet 等部署工具进行交付,但是也很难保证对底层各种异构的环境支持得很好,还有随时会出现的版本漂移问题。比如你可能经常遇到的,某个软件包几个月之前安装还能够正常运行,现在到一个新环境安装后,竟然无法正常工作了。

不可变基础设施是另外一个思路,部署之后即是只读状态,不可对其进行修改,如果需要更新或修改,则使用新的环境或服务器去替代旧的。不可变基础设施可以避免可变基础设施中遇到的各种常见问题。

不可变基础设施的特点

一致性

一致性是最明显的一个特征,不可变基础设施保持一致,同样的版本,同样的配置,和管理相同机器一样管理很大规模的集群;

简单

所有机器和实例都是一样,只有扩容和销毁两个状态,所有系统只要处理这两个状态就可以;

安全

所有实例扩容之后不会变,扩容之前可以对其进行充分的测试,安全人员可以对代码进行扫描,保证应用实例相关的数据都是经过测试安全的。

传统应用如何适配不可变基础设施,需要做哪些改造呢?

  • 将传统应用的运行环境打造成一个具体的服务器,比如虚拟机镜像、容器镜像,程序即可run起来;
  • 应用run起来之后会存在各种各样的输出,分析应用程序的输出类型,使其能够和服务器无关;

注:与服务器无关的含义

  • 将依赖于本地的缓存转移到分布式存储中;
  • 将依赖于本地存储的文件转移到分布式存储中,从而不会受到本地服务器重启丢失之类的影响;
  • 将依赖于本地存储的日志信息转移到标准输出中,由日志采集的side-car收集后统一汇总。

实际工作中,对于不可变设施的完全落地还是比较难的,可以做一些权衡:

  1. 如果日志不允许落盘对部分程序改造成本很高,可以使用ELK或EFK等技术做好实时的同步,保证日志可丢失;
  2. 如果完全依赖分布式缓存对性能压力过大,那么就建立一套分布式缓存与本地缓存的自动同步机制,保证重启后本地缓存丢失仍然可以修复;

综上所述,只要保证应用在基础设施上产生的数据可以在任意时刻丢失,就可以实现一定程度上应用无状态化,也能保证不可变基础设施落地。不可变基础设施是一种理念,具体落地还是比较依赖于容器或虚拟机的,以及还需要分布式存储等配套设施,不是按照一种技术标准去执行,应该综合分析现状,选择性地朝这个方向优化。不可变基础设施存在优势和劣势,在云原生场景下,优势是大于劣势的,分析如下:

  1. 云原生的不可变基础设施以容器镜像为标准,其中不但包含了二进制内容,还包含了程序运行需要的依赖环境、基础库、系统环境等,相对来说比较完整。
  2. 能提升应用交付效率,基于不可变基础设施的应用交付,可以由代码或编排模板来设定,这样就可以使用GIt等控制工具来管理应用和维护环境,基础设施环境一致性能保证应用在开发测试环境、预发布环境和线上生产环境运行表现一致,不会频繁出现开发测试时正常、发布后出现故障等情况。
  3. 能快速、可靠地水平扩展,基于不可变基础设施的配置模板,可以快速创建与已有基础设施环境一致性的新基础设施环境。
  4. 能保证基础设施的快速更新和回滚,基于同一套基础设施模板,若环境被修改,则可以快速进行回滚和恢复,如果需要对所有环境进行更新升级,则只需要更新基础设施模板并创建新环境,将旧环境进行替换。图片

03K8S是如何实现不可变基础设施的呢?

实现不可变基础设施需要满足一些条件,如下图:

图片

图3

首先最底层的条件是容器化,应用需要镜像化,依赖和配置都需要在Dockerfile里面即镜像描述里面能够体现,环境和依赖还需要额外的应用编排模板明确地编排出来。容器化是不可变设施的基础,一般只会在云原生情况下,才能实现不可变基础设施,是因为只有通过容器化才能保证整个扩缩容的高效和一致性。

第二个条件要让扩缩容变得足够简单,需要将扩缩容和替换的过程让其自动化,自动化也是需要让实例能够感知其可能会失败,节点会异常,实例的失败是一个常态,需要让扩缩容、替换应用的自愈过程变得非常简单。最后还需要有一套机制能够保证基础设施的一致性,禁止对应用实例本身文件的原地修改,这里的原地需要做相关的权衡,还需要控制实例的存活时间,任何一个实例只要运行,都会对其做一些修改(运行过程)包括手动修改,只要有修改时存在软件系统熵的变化,会存在不一致的问题。

k8S在不可变基础设施方面做的工作是如何体现的呢?首先,需要审视k8s中容器的状态,如下图:

图片

图4

图片

 图5

K8s中一个应用实例称为一个pod,一个pod中可以有多个容器,pod在k8s中被称为不可变的基本单位,一个应用实例是被应用负载控制器所管理,应用负载一般会提供一个应用实例的模板,模板里面可以定义一个应用实例的元数据metadata,也可以定义一个规格、镜像和镜像名。

图片

图6

K8s中落地不可变基础设施主要是通过滚动发布的方式,提供滚动发布的主要是deployment,这是一个控制器也称之为一个工作负载,deployment中还带了一个ReplicaSet这么一个工作负载,每一个ReplicaSet下面挂了同一个镜像名和同一个镜像配置的pod的集合。发布前,只有一个版本的ReplicaSet V1,发布过程中,会创建额外的ReplicaSet V2,同时会在新的V2的ReplicaSet进行扩容,扩容的是一份新的容器编排配置的pod。发布过程很简单,主要集中在新的ReplicaSet V2中进行扩容,在旧的ReplicaSet V1中进行缩容。发布之后,Deployment只会存在一个ReplicaSet,过程只会存在pod的扩容和缩容,这是k8s中保证不可变基础设施的实现过程,即发布及扩缩容。

K8s是云原生中最佳的应用实践,不过K8s这种滚动升级实现不可变基础实施的应用场景有一定的局限性,如果出现有些业务比如金融业或传统行业的需要保留应用IP,或者大厂存在大促的场景时,k8s的滚动发布是无法满足的,特别是应用实例pod里面有多个容器,有一些容器是不希望改动的。

不可变基础设施还有很多事情需要去做,比如:重建pod的原地升级能力、定期重建历史pod、迁移演练、应用实例保证熵不会太大(定期删除)等。为了保证一致性,不可变基础设施可以应用于更多的场景,我们在探索践行云原生不可变基础设施这个理念的时候,也需要探索除了k8s之外的相关内容,可以参考阿里的开源Open kruise项目。

04K8S中滚动升级实现不可变基础设施的实践演示

4.1 演示deployment和ReplicaSet

以nginx部署为例:

图片

图7

  • RS中DESIRED:用户期望的Pod副本个数(spec.replicas的值)。
  • RS中CURRENT:当前处于Running状态的Pod的个数。
  • DEPLOY中UP-TO-DATE:当前处于最新版本的Pod的个数,所谓最新版本指的是Pod的Spec部分与Deployment里Pod模板里定义的完全一致。
  • DEPLOY中AVAILABLE:当前已经可用的Pod的个数,即:既是Running状态,又是最新版本,并且已经处于Ready(健康检查正确)状态的Pod的个数。

执行扩缩容操作:

图片

图8

4.2 滚动更新

图片

图9

上图中可以看到本地容器镜像是有两个版本的,将已经部署的1.20.2版本的nginx进行版本更新为1.14-alpine,这里处理的是版本的回退更新;

通过kubectl edit操作进行编辑,将images的信息内容进行替换:

图片

图10

图片

图11

图11可以看到nginx版本更新时的内容,更新策略一般有两种:

  • ReCreate:在创建新pod之前,所有的实例相关的pods会被杀死;
  • RollingUpdate:滚动升级,逐步替换的策略,同时滚动升级时,支持更多的附加参数。

综上,K8S滚动升级操作其实很简单,我们需要结合不同的场景、不同的需求去使用,滚动升级是k8s实现不可变基础设施最经典的应用。