像Docker这样的应用程序容器技术,为底层应用组件提供了基于标准的打包和运行时管理机制。
可以利用容器来快速部署并有效利用系统资源。使用容器,开发人员可以提升应用程序可移植性,并实现可编程的镜像管理,运营团队也可以进行标准化的部署和管理。
但是,尽管已经知道容器技术有许多优点,人们普遍认为容器是短生命周期的,因此仅适用于无状态的微服务应用,不可能对有状态的应用程序实施容器化。让我们深入看看是不是真的如此。
应用程序的状态(state)就是应用程序组件完成工作(比如执行一个任务)所需的数据。所有的应用都有状态。架构模式、范例和语言从本质上描述了如何管理应用程序的行为(任务,操作等)和状态(数据)。
即使是微服务式应用程序也有状态!在微服务体系结构中,每个服务可以有多个实例,每个服务实例被设计为无状态。这意味着服务实例不会跨越两个或多个操作存储数据。因此,无状态就意味着任何服务实例都可以从某处获取执行一个行为所需的所有应用程序状态。这是微服务式应用程序的一个重要架构约束,因为它可以提升弹性、可扩展性,并允许任何可用的服务实例执行任何任务。
通常,应用程序状态存储在数据库、缓存、文件或其他形式的存储中。另外,任何需要在操作中记录的状态更改都必须写回存储。
所以,所有的程序都有状态,但是一个程序组件可以是无状态的——如果它可以干净地将行为从数据中分离出来并且可以获取行为所需的数据。但是,这似乎只是简单地将问题传递给了其他组件。另一个组件如何管理状态?这取决于我们下面要讨论的状态的类型。
为了回答这个问题,我们考虑应用程序可能具有的五种状态,以及我们如何处理其中每一种状态来容器化程序:
持久的应用程序状态需要在应用程序重新启动和中断之后可继续。这种状态通常存储在冗余数据库层中,并对其执行定期备份。
虽然可以将应用程序和数据库放在同一个容器中,但最好将它们分开,因为应用组件的更改频率会更高。分离数据库还允许在多个应用程序实例之间共享。
如果你的应用已经使用外部数据库(不论是作为服务提供的数据库,还是安装在其他物理或虚拟服务器上的),你可以直接保留这个架构,并简单地通过容器化应用程序层来启动。大多数容器管理系统将允许将数据库访问信息作为配置状态传递给应用层容器(参见下面的“配置状态”)。
或者,你也可以选择容器化数据库。这样做具有从容器到数据层的快速恢复和部署等好处。在这种情况下,需要考虑关于数据库的这几点:
想要在容器终止时允许数据存在,需要使用容器外的存储机制来进行管理。使用主机卷(host volume)并将其映射到容器可以轻松完成。
同样,为了在主机终止时允许数据存在,您将需要使用一个存储机制来管理主机之外的数据。大多数云平台支持共享(联网)文件系统或块存储(卷),可以独立管理和连接/分离到任何主机。因此,如果你的容器编排服务提供生命周期事件来管理存储组件,这也相当简单。
但是,如果你的数据需要保持连接在特定的容器上呢?这可能是必要的,例如,我们的客户想要管理大量的无法复制的视频内容。如果他们的容器停止并在另一台主机上重新启动,他们希望相同的数据可用于该容器。
如果你有很多这样的应用程序,卷插件可以简化数据的编排。卷插件位于容器引擎之下,协助存储编排。很多卷插件只是IaaS / CMP调用的简单包装,但除此之外也有很多卷插件提供丰富的功能,如QoS和分层存储以及对企业存储的支持,或许值得一看。
让我们总结一下可选的解决方案:
应用程序通常需要非域(non-domain)数据才能正确配置,比如其他外部服务的IP地址,或用于连接数据库的证书。
由Heroku推广的大多数PaaS解决方案所采用的12要素应用宣言规定将配置数据存储在环境中。在容器化的世界里,大部分配置数据都可以作为可注入容器的环境变量进行管理。
但是,机密信息(如凭证,密码,密钥和其他秘密数据)最好通过其他安全机制处理,这些机制可以更好地控制主机、网络或存储上的秘密数据可见和可访问。对于这种类型的配置状态,像KeyWhiz和Vault这样的凭证管理工具可以在具有一次性访问令牌的容器中使用。其他的选项还有将卷插件和密钥存储相结合以安全地向容器化应用提供秘密数据。
当用户登录时,应用程序可能生成会话数据。这可能是用户的身份验证密钥或其他临时状态。在大多数现代应用程序中,会话状态存储在分布式缓存或一个任何服务实例都能访问的数据库中。
但是,在传统的多页面Web应用中,每个Web页面都需要访问由服务器管理的会话状态。因此,该会话的所有用户请求必须定向到相同的后端服务器,否则用户将被强制重新登录。这样的应用要求会话状态存储在特定服务器,即“粘性会话”(sticky session),并且所有对客户机会话的请求总是被路由到相同的服务。
这不是一个容器化化问题,因为在虚拟或物理机器中部署的负载均衡应用服务器之间存在相同的问题。而且大多数负载均衡器都可以选择支持粘性会话。
在容器化的世界里,你的容器的IP地址可能和你主机的IP地址不一样。如果您将第4-7层负载平衡解决方案用于具有有状态会话数据的前端应用程序容器,那么负载平衡器也将需要处理粘性会话。
容器原生(container-native)解决方案Nirmata的服务网关提供对粘性会话的支持,并且可以在容器重新部署到主机之间时动态更新路由信息。
某些应用程序可能使用协议进行通信,如Websockets,因为通信实体可以通过连接交换消息序列,所以这些应用程序被认为是有状态的。相比之下,像HTTP这样的协议被认为是无状态的,因为服务器不记得任何请求状态,允许其他任何服务器回答下一个请求。
如果您的应用程序使用有状态协议,则容器负载平衡解决方案还需要支持将客户端请求路由到有状态协议的容器。例如,如果您使用Websockets,负载平衡解决方案将需要支持持续跨请求的TCP连接。这个特性在传统的负载均衡器中很常见,可以在大多数容器原生负载均衡器中找到。
一些应用作为集群中的多个实例运行,以适应可用性和规模要求,需要共享集群成员和状态信息。此状态不是持久性的,但是如果集群成员更改,就需要更新状态。
在集群应用中,每个集群成员都需要了解其他成员及其角色。大多数现代集群应用都需要使用初始成员集(通常是其IP地址和端口)进行引导,然后才能动态管理成员及其更改。但是,某些集群服务可能需要手动更新,并在需要传播成员信息的更改时重新启动。
容器原生(container-native)编排系统有能力够处理这两种情况。例如,Kubernetes 最近引入了一个名为 PetSet 的功能来管理一个有状态的集群。Nirmata 支持对预先计算容器布局的集群系统进行预订编排,并为所有集群成员注入唯一标识和集群状态。
为客户的应用实行容器化时,我们遇到了各种各样有趣的情况。例如,一个应用读取本地MAC地址,并将其用作唯一标识自身的方法!很显然,如果容器重启并获得不同的MAC地址,这个方案就会崩溃。
幸运的是,Docker现在允许指定容器的MAC地址。对于这样的异常,需要确保你的编排系统在运行容器时能够灵活地指定自定义设置。
在这篇文章中,我们讨论了什么应用程序状态,您可能遇到的不同类型的应用状态。我们还介绍了如何在容器环境中管理每种类型的状态。在大多数情况下,都有几种策略可供选择。所以,尽管容器是短生命周期的,但是应用的状态未必如此。
我发布文章的目标是说明有状态的应用程序可以被容器化。我做的如何呢?很乐意听取您的反馈和经验,另外如果您有任何问题,我可以帮助解答。
免费获取所有的Nirmata资料:https://try.nirmata.io/
获得我们的免费电子书:容器化传统应用程序