为了好玩,我正在使用微服务架构设计一些Web应用程序。我试图确定进行配置管理的最佳方式,并且我担心我的配置方法可能会有一些巨大的陷阱和/或更好的存在。
为了解决这个问题,我们假设我有一个用c ++编写的身份验证服务,用rust写的身份服务,用haskell编写的分析服务,用scala编写的一些middletier,以及用javascript编写的前端。还有相应的身份数据库,auth数据库,分析数据库,(可能是会话的redis缓存)等......我正在使用docker swarm部署所有这些应用程序。
每当部署其中一个应用程序时,它必须发现所有其他应用程序。由于我使用的是docker swarm,因此只要所有节点共享必需的覆盖网络,发现都不是问题。
但是,每个应用程序仍然需要上游服务host_addr,可能是端口,某些数据库或密封服务的凭据等...
我知道docker有secrets
,它允许应用程序从容器中读取配置,但我需要为每种服务用每种语言编写一些配置解析器。这看起来很乱。
我宁愿做的是configuration service
,它保留了有关如何配置所有其他服务的知识。因此,每个应用程序都会从一些RPC调用开始,这些调用旨在在运行时获取应用程序的配置。像
int main() {
AppConfig cfg = configClient.getConfiguration("APP_NAME");
// do application things... and pass around cfg
return 0;
}
AppConfig将在IDL中定义,因此该类将立即可用且与语言无关。
这似乎是一个很好的解决方案,但也许我真的错过了这一点。即使在规模上,一些配置服务也可以轻松地提供数万个节点,因此我不会预见任何扩展问题。同样,它只是一个爱好项目,但我喜欢考虑"假设"场景:))
如何在微服务架构中处理配置方案?这看起来像是一种合理的方法吗?像Facebook,谷歌,LinkedIn,AWS等主要参与者做什么?
答案 0 :(得分:2)
我将使用其中一个现有配置管理解决方案,而不是构建自定义配置管理解决方案:
Spring Cloud Config是一个用Java编写的配置服务器,提供HTTP API来检索应用程序的配置参数。显然,它附带了一个Java客户端和一个很好的Spring集成,但由于服务器只是一个HTTP API,您可以使用任何您喜欢的语言。配置服务器还具有配置值的对称/非对称加密。
配置源:外部化配置存储在GIT存储库中,该存储库必须可供Spring Cloud Config服务器访问。然后可以通过HTTP API访问该存储库中的属性,因此您甚至可以考虑实现配置属性的更新过程。
服务器位置:理想情况下,您可以通过域(例如config.myapp.io
)访问配置服务器,因此您可以根据需要实施负载平衡和故障转移方案。此外,您需要为所有服务提供的只是确切位置(以及一些身份验证/解密信息)。
使用入门:您可以在Spring文档中查看此getting started guide for centralized configuration或阅读此Quick Intro to Spring Cloud Config。
Netflix Archaius是Netflix OSS堆栈的一部分,"是一个Java库,提供API来访问和利用可在运行时动态更改的属性#34;。 虽然仅限于Java(与您提出的上下文不完全匹配),但该库能够使用数据库作为配置属性的源。
confd使用存储在外部源(etcd,consul,dynamodb,redis,vault,...)中的数据使本地配置文件保持最新。配置更改后,confd会重新启动应用程序,以便它可以获取更新的配置文件。
在您的问题的上下文中,这可能是值得尝试的,因为confd不对应用程序做出任何假设,并且不需要特殊的客户端代码。大多数语言和框架都支持基于文件的配置,因此在当前使用env变量并且没有预期分散式配置管理的现有微服务之上添加confd应该相当容易。
答案 1 :(得分:0)
我没有为您提供良好的解决方案,但我可以指出一些问题供您考虑。
首先,您的应用程序可能需要一些引导程序配置,使它们能够找到并连接到配置服务。例如,您提到使用IDL为支持远程过程调用的中间件系统定义配置服务API。我假设你的意思是像CORBA IDL。这意味着您的引导配置不仅仅是要连接的端点(可能指定为字符串化IOR或路径/ in / naming / service),而且还是您正在使用的CORBA产品的配置文件。您无法从配置服务下载该CORBA产品的配置文件,因为这将是鸡与蛋的情况。因此,您最终必须为每个应用程序实例手动维护CORBA产品配置文件的单独副本。
其次,您的伪代码示例建议您将使用单个RPC调用一次性检索所有应用程序的配置。这种粗略的粒度水平是好的。相反,如果应用程序使用单独的RPC调用来检索每个 name = value 对,那么您可能会遇到严重的可伸缩性问题。为了说明,我们假设一个应用程序在其配置中有100个 name = value 对,因此它需要进行100次RPC调用才能检索其配置数据。我可以预见到以下可伸缩性问题:
如果应用程序和配置服务器位于同一局域网上,则每个RPC可能需要1毫秒的往返时间,因此每个应用程序的启动时间为1毫秒100次RPC调用= 100毫秒= 0.1秒。这似乎可以接受。但是,如果您现在在另一个大陆上部署另一个应用程序实例,例如50毫秒的往返延迟,那么该新应用程序实例的启动时间将为100次RPC调用,每次调用50毫秒延迟= 5秒。哎哟!
需要进行仅 100次RPC调用才能检索配置数据,假定应用程序将检索每个 name = value 对,并将该信息缓存在其中,比如说,一个对象的实例变量,然后通过该本地缓存访问 name = value 对。但是,迟早有人会从x = cfg.lookup("variable-name")
循环中调用for
,这意味着应用程序每次循环都会生成一个RPC。显然,这会降低该应用程序实例的速度,但如果您最终有数十个或数百个应用程序实例,那么您的配置服务将每秒淹没数百或数千个请求,并且它将成为集中的性能瓶颈。
您可能会开始编写在启动时执行100个RPC的长期应用程序来检索配置数据,然后在终止之前运行数小时或数天。让我们假设这些应用程序是其他应用程序可以通过RPC与之通信的CORBA服务器。迟早你可能会决定编写一些命令行实用程序来执行以下操作:" ping"一个应用程序实例,看它是否正在运行; "查询"获取一些状态详细信息的应用程序实例;请求应用程序实例正常终止;等等。每个命令行实用程序都是短暂的;当他们启动时,他们使用RPC来获取他们的配置数据,然后执行" real"通过将单个RPC发送到服务器进程来ping /查询/终止它,然后它们终止。现在有人会编写一个UNIX shell脚本,每秒为您的数十个或数百个应用程序实例调用一次ping和query命令。这个看似无害的shell脚本将负责每秒创建数十个或数百个短期进程,而这些短期进程将对集中式配置服务器进行大量RPC调用以检索 name = value 一次配对一个。这种shell脚本可能会对集中配置服务器造成巨大负担。
我不试图阻止您设计集中配置服务器。以上几点只是警告您需要考虑的可伸缩性问题。您通过一个粗粒度RPC调用来检索所有其配置数据的应用程序的计划肯定会帮助您避免上面提到的各种可伸缩性问题。
为了提供一些思考的食物,您可能需要考虑不同的方法。您可以将每个应用程序的配置文件存储在Web服务器上。一个shell启动脚本"包装器"对于应用程序,可以执行以下操作:
使用wget
或curl
下载"模板"来自Web服务器的配置文件,并将文件存储在本地文件系统上。 A"模板"配置文件是一个普通的配置文件,但有一些值占位符。占位符可能看起来像${host_name}
。
还可以使用wget
或curl
下载包含搜索和替换对的文件,例如${host_name}=host42.pizza.com
。
在所有下载的模板配置文件上执行全局搜索和替换这些搜索和替换术语,以生成可供使用的配置文件。您可以使用诸如sed
之类的UNIX shell工具或脚本语言来执行此全局搜索和替换。或者,您可以使用Apache Velocity等模板引擎。
执行实际应用程序,使用命令行参数指定路径/到/ downloaded / config / files。