如何在基于 Docker 的微服务中管理每个环境的数据?

Posted

技术标签:

【中文标题】如何在基于 Docker 的微服务中管理每个环境的数据?【英文标题】:How do you manage per-environment data in Docker-based microservices? 【发布时间】:2015-09-10 23:15:19 【问题描述】:

在微服务架构中,我很难理解如何管理特定于环境的配置(例如,IP 地址和数据库或消息代理的凭据)。

假设您有三个微服务(“A”、“B”和“C”),每个微服务都由不同的团队拥有和维护。每个团队都需要一个团队集成环境……他们可以在其中使用其微服务的最新快照,以及所有依赖微服务的稳定版本。当然,您还需要 QA/staging/生产环境。大图的简化视图如下所示:

“微服务 A”团队环境

微服务 A(SNAPSHOT) 微服务 B(稳定) 微服务 C(稳定)

“微服务 B”团队环境

微服务 A(稳定) 微服务 B (SNAPSHOT) 微服务 C(稳定)

“微服务 C”团队环境

微服务 A(稳定) 微服务 B(稳定) 微服务 C (SNAPSHOT)

质量检查/分期/制作

微服务 A(STABLE、RELEASE 等) 微服务 B(STABLE、RELEASE 等) 微服务 C(STABLE、RELEASE 等)

这是很多部署,但这个问题可以通过持续集成服务器来解决,也许像 Chef/Puppet/etc 这样的东西。 真正困难的部分是每个微服务都需要一些特定于其部署位置的环境数据。

例如,在“A”团队环境中,“A”需要一个地址和一组凭据才能与“B”交互。但是,在“B”团队环境中,那个“A”的部署需要不同的地址和凭据才能与“B”的那个部署进行交互。

此外,随着您越来越接近生产,像这样的环境配置信息可能需要安全限制(即只有某些人能够修改甚至查看它)。

那么,对于微服务架构,您如何维护特定于环境的配置信息并使其可用于应用程序?我想到了一些方法,尽管它们似乎都有问题:

让构建服务器在构建时将它们烘焙到应用程序中 - 我想您可以为每个环境的属性文件或脚本创建一个存储库,并让每个微服务的构建过程伸出并拉入适当的脚本(你也可以有一个单独的、有限访问的 repo 用于生产的东西)。不过,您需要 ton 的脚本。基本上,在可以部署微服务的每个地方,每个微服务都有一个单独的服务。 将它们烘焙到每个环境的基础 Docker 映像中 - 如果构建服务器将您的微服务应用程序放入 Docker 容器作为构建过程的最后一步,那么您可以为每个环境创建自定义基础映像。基本映像将包含一个 shell 脚本,用于设置您需要的所有环境变量。您的 Dockerfile 将设置为在启动应用程序之前调用此脚本。这与之前的要点有类似的挑战,因为现在您要管理大量 Docker 映像。 在运行时从某种注册表中提取环境信息 - 最后,您可以将每个环境的配置存储在 Apache ZooKeeper 之类的东西(甚至只是一个普通的 ol' 数据库)中,并让您的应用程序代码在启动时在运行时将其拉入。每个微服务应用程序都需要一种方法来告诉它所处的环境(例如启动参数),以便它知道要从注册表中获取哪组变量。这种方法的优势在于,现在您可以在从团队环境到生产的整个过程中使用exact 相同的构建工件(即应用程序或 Docker 容器)。另一方面,您现在将拥有另一个运行时依赖项,而且您仍然必须管理注册表中的所有数据。

人们通常如何在微服务架构中解决这个问题?这似乎是一件很常见的事情。

【问题讨论】:

我认为您的第三个选项等同于使用 etcd/confd 或 hashcorp 的 terraform 之类的服务发现。但是,我认为您还需要考虑小规模(本地)部署的环境变量 (docker run -e) - docker compose 对此很有用 【参考方案1】:

概述

长篇大论!

ENTRYPOINT是你的朋友 Building Microservices Sam Newman 很棒 服务间安全提示:2-way TLS 可能有效,但可能会出现延迟问题 我将从我的团队中获得一个真实的例子。我们无法使用配置服务器,事情变得……有趣。暂时可以管理。但随着公司提供更多服务,可能无法扩大规模。 配置服务器似乎是一个更好的主意

更新:大约两年后,我们可能会迁移到 Kubernetes,并开始使用它附带的 etcd-powered ConfigMaps 功能。我将在配置服务器部分再次提到这一点。如果您对这些主题感兴趣,这篇文章仍然值得一读。我们仍将使用ENTRYPOINT 和一些相同的概念,只是一些不同的工具。

ENTRYPOINT

我建议ENTRYPOINT 是管理 Docker 容器特定环境配置的关键。

简而言之:在启动之前创建一个脚本来引导您的服务,并使用ENTRYPOINT 执行此脚本。

我将详细介绍这一点,并解释我们如何在没有配置服务器的情况下做到这一点。它有点深,但并非无法控制。然后,我将详细介绍配置服务器,这对于许多团队来说是一个更好的解决方案。

构建微服务

您说得对,这些都是常见问题,但没有一种万能的解决方案。最通用的解决方案是配置服务器。 (通用但仍然不是万能的。)但也许你不能使用其中之一:安全团队禁止我们使用配置服务器。

如果您还没有阅读,我强烈建议您阅读 Sam Newman 的 Building Microservices。它检查了所有常见的挑战并讨论了许多可能的解决方案,同时还从经验丰富的架构师那里提供了有用的观点。 (旁注:不要担心配置管理的完美解决方案;从针对您当前的微服务和环境集的“足够好”的解决方案开始。您可以迭代和改进,所以您应该尝试get useful software to your customers ASAP,然后在后续版本中改进。)

警示故事?

再读一遍……我有点畏缩要完全解释这一点。来自Zen of Python:

If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.

我对我们的解决方案并不满意。然而,鉴于我们无法使用配置服务器,这是一个可行的解决方案。这也是一个真实的例子。

如果您阅读它并想,“天哪,不,我为什么要这么多!”那么你知道,你需要仔细研究配置服务器。

服务间安全

您似乎还关心不同的微服务如何相互验证。

对于与此身份验证相关的工件和配置......像对待任何其他配置工件一样对待它们。

您对跨服务安全性有哪些要求?在您的帖子中,听起来您在描述应用层、用户名/密码身份验证。也许这对您心目中的服务有意义。但是您还应该考虑Two-Way TLS:“除了服务器向客户端提供他们的证书之外,此配置还要求客户端向服务器提供他们的证书。”生成和管理这些证书可能会变得很复杂......但是无论您选择做什么,您都会像任何其他配置/工件一样在配置/工件周围进行洗牌。

请注意,2-way TLS 可能会在大容量下引入延迟问题。我们还没有。我们正在使用除 2-way TLS 之外的其他措施,一旦这些措施得到证实,随着时间的推移,我们可能会放弃 2-way TLS。


我团队的真实示例

我目前的团队正在做一些结合你提到的两种方法的事情(释义):

在构建时烘焙配置 在运行时拉取配置

我的团队正在使用Spring Boot。 Spring Boot 具有非常复杂的Externalized Configuration 和“配置文件”系统。 Spring Boot 的配置处理是复杂而强大的,具有所有优点/缺点(这里不再赘述)。

虽然这是 Spring Boot 开箱即用的,但这些想法是通用的。对于 Java 微服务,我更喜欢 Dropwizard,在 Python 中我更喜欢 Flask;在这两种情况下,您都可以做类似于 Spring Boot 正在发生的事情......您只需要自己做更多的事情。好与坏:这些灵活的小框架比 Spring 更灵活,但是当您编写更多代码并进行更多集成时,您对 QA 和测试您的复杂/灵活配置支持有更多责任。

由于第一手经验,我将继续使用 Spring Boot 示例,但 不是,因为我推荐它!使用适合您团队的方法。

在 Spring Boot 的情况下,您可以一次激活多个配置文件。这意味着您可以拥有一个基本配置,然后用更具体的配置覆盖。我们在src/main/resources 中保留一个基本配置application.yml。因此,此配置与可交付的 JAR 打包在一起,并且在执行 JAR 时,此配置总是被拾取。因此,我们在此文件中包含所有默认设置(所有环境通用)。示例:显示“嵌入式 Tomcat,始终在启用这些密码套件的情况下使用 TLS”的配置块。 (server.ssl.ciphers)

当某个环境只需要覆盖一个或两个变量时,我们会利用Spring Boot's support for getting configuration from environment variables。示例:我们使用环境变量将 URL 设置为我们的服务发现。这会覆盖已发布/拉取的配置文件中的任何默认值。另一个例子:我们使用环境变量SPRING_PROFILES_ACTIVE 来指定哪些配置文件处于活动状态。

我们还想确保master 包含经过测试的、可工作的开发环境配置。 src/main/resources/application.yml 具有合理的默认值。此外,我们将 dev-only 配置放入 config/application-dev.yml,并将其签入。The config directory is picked up easily, but not shipped in the JAR. 不错的功能。开发人员知道(从 README 和其他文档中)在开发环境中,我们所有的 Spring Boot 微服务都需要激活 dev 配置文件。

对于dev 以外的环境,您可能已经看到了一些选项......这些选项中的任何一个都可以(几乎)完成您需要的一切。您可以根据需要混合搭配。这些选项与您在原始帖子中提到的一些想法重叠。

    维护特定于环境的配置文件,例如 application-stage.ymlapplication-prod.yml 等, 覆盖偏离默认值的设置(在一个非常严格锁定的 git 存储库中) 维护模块化、特定于供应商的配置文件,例如 application-aws.ymlapplication-mycloudvendor.yml (您将其存储在哪里取决于它是否包含秘密)。这些可能包含跨越的值 舞台、产品等。 在运行时使用环境变量覆盖任何相关设置;包括从 1 和 2 中挑选配置文件 使用自动化在构建或部署时烘焙硬编码值(模板)(输出到 某种高度锁定的存储库,可能与 (1) 的存储库不同)

(1)、(2) 和 (3) 可以很好地协同工作。我们很高兴这三个都做,而且实际上很容易记录, 推理和维护(在初步掌握它之后)。

你说...

我想你可以为每个环境的属性文件或脚本创建一个 repo [...] 不过你需要大量的脚本。

它是可以管理的。提取或烘焙配置的脚本:这些可以在所有服务中统一。当有人克隆你的微服务模板时,可能会复制脚本(顺便说一句:你应该有一个官方的微服务模板!)。或者它可能是内部 PyPI 服务器上的 Python 脚本。在我们讨论 Docker 之后再详细讨论。

由于 Spring Boot 对 (3) 有很好的支持,并且支持在 YML 文件中使用默认值/模板,因此您可能不需要 (4)。但这里的事情对您的组织来说非常具体。我们团队的安全工程师希望我们使用 (4) 为环境烘焙一些特定值 超越dev:密码。这位工程师不希望密码在环境变量中“浮动”,主要是因为——谁来设置它们? Docker 调用者? AWS ECS 任务定义(可通过 AWS Web UI 查看)?在这些情况下,密码可能会暴露给自动化工程师,他们不一定有权访问包含application-prod.yml 的“锁定的 git 存储库”。如果您执行 (1),则可能不需要 (4);您可以将硬编码的密码保存在严格控制的存储库中。但也许在部署自动化时会生成一些秘密,您不希望在与 (1) 相同的存储库中。这是我们的情况。

关于 (2) 的更多信息:我们使用 aws 配置文件和 Spring Boot 的“配置即代码”进行启动时调用以获取 AWS 元数据, 并基于此覆盖一些配置。我们的 AWS ECS 任务定义激活 aws 配置文件。 The Spring Cloud Netflix documentation gives an example like this:

@Bean
@Profile("aws")
public EurekaInstanceConfigBean eurekaInstanceConfig() 
  EurekaInstanceConfigBean b = new EurekaInstanceConfigBean();
  AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka");
  b.setDataCenterInfo(info);
  return b;

接下来,Docker。环境变量是在 Docker 中传递配置参数的好方法。由于ENTRYPOINT 遇到的一些问题,我们不使用任何命令行或位置参数。很容易通过--env SPRING_PROFILES_ACTIVE=dev--env SPRING_PROFILES_ACTIVE=aws,prod ...无论是从命令行,还是从诸如AWS ECS 或Apache Mesosphere/Marathon 的主管/调度程序。我们的 entrypoint.sh 还有助于传递与 Spring 无关的 JVM 标志:为此我们使用通用的 JAVA_OPTS 约定。

(哦,我应该提一下...我们还使用Gradle 进行构建。目前...我们用Gradle 任务包装docker builddocker rundocker push。我们的Dockerfile是模板化的,所以再次选择上面的选项#4。我们有像@agentJar@ 这样的变量,它们在构建时会被覆盖。我真的不喜欢这样,我认为这可以用普通的旧配置更好地处理( -Dagent.jar.property.whatever)。这可能会成功。但我只是为了完整性而提及它。我am对此感到满意:构建中没有做任何事情,Dockerfile ,或entrypoint.sh 脚本,它与某个部署上下文(例如AWS)紧密耦合。所有这些都可以在开发环境和已部署环境中运行。因此我们不必部署Docker 映像来对其进行测试:它应该是可移植的。)

我们有一个文件夹src/main/docker,其中包含Dockerfileentrypoint.sh(由ENTRYPOINT 调用的脚本;它包含在Dockerfile 中)。我们的Dockerfileentrypoint.sh 在所有微服务中几乎完全一致。当您克隆我们的微服务模板时,这些是重复的。不幸的是,有时您必须复制/粘贴更新。我们还没有找到解决这个问题的好方法,但这并不是很痛苦。

Dockerfile 执行以下操作(构建时):

    派生自我们用于 Java 应用程序的“黄金”基础 Dockerfile 获取我们用于拉取配置的工具。 (从可供任何开发人员或 Jenkins 机器进行构建的内部服务器获取。)(您也可以使用 Linux 工具,如 wget 以及基于 DNS/约定的命名来获取它。您还可以使用 AWS S3 和基于约定的命名。) 将一些内容复制到 Dockerfile 中,例如 JAR、entrypoint.sh... ENTRYPOINT exec /app/entrypoint.sh

entrypoint.sh 执行以下操作(运行时):

    使用我们的工具来拉取配置。 (一些逻辑可以理解,如果 aws 配置文件未激活,则不应使用 aws 配置文件。)如果有任何问题,会立即响亮地死掉。 exec java $JAVA_OPTS -jar /app/app.jar(获取所有属性文件、环境变量等)

所以我们已经讨论过,在应用程序启动时,配置是从某个地方提取的……但是在哪里呢?从前面的观点来看,它们可能位于 git 存储库中。您可以下拉所有配置文件,然后使用SPRING_PROFILES_ACTIVE 说明哪些是活动的;但随后您可能会将application-prod.yml 拉到舞台机器上(不好)。因此,您可以查看SPRING_PROFILES_ACTIVE(在您的配置拉取逻辑中),然后只拉取需要的内容。

如果您使用 AWS,则可以使用 S3 存储库/ies 而不是 git 存储库。这可以允许更好的访问控制。而不是 application-prod.ymlapplication-stage.yml 生活在同一个 repo/bucket 中,您可以使 application-envspecific.yml 始终具有所需的配置,在 S3 存储桶中使用给定 AWS 账户中的某个常规名称。即“从s3://ecs_config/$ENV_NAME/application-envspecific.yml 获取配置”(其中$ENV_NAME 来自entrypoint.sh 脚本或ECS 任务定义)。

我提到Dockerfile 可移植,并且不耦合到某些部署上下文。那是因为entrypoint.sh 被定义为以灵活的方式检查配置文件;它只想要配置文件。所以如果你使用 Docker 的 --volume 选项来挂载一个带有配置的文件夹,脚本会很高兴,它不会尝试从外部服务器拉取任何东西。

我不会过多地讨论部署自动化......但只是快速提及我们使用 terraform、boto3 和一些自定义 Python 包装代码。 jinja2 用于模板(烘焙需要烘焙的几个值)。

这是这种方法的严重限制:必须杀死/重新启动微服务进程才能重新下载和重新加载配置。现在,对于无状态服务集群,这并不一定代表停机(考虑到某些情况,例如客户端负载平衡、Ribbon 配置为重试,以及水平扩展,因此某些实例始终在池中运行)。到目前为止,它正在解决,但微服务的负载仍然很低。增长即将来临。我们拭目以待。

还有很多方法可以解决这些挑战。希望这个练习能让你思考什么对你的团队有用。试着让一些事情顺利进行。快速制作原型,您将在进行过程中摆脱细节。

或许更好:配置服务器

我认为这是一个更常见的解决方案:配置服务器。您提到了ZooKeeper。还有Consul。 ZooKeeper 和 Consul 都提供两者配置管理服务发现。还有etcd。

在我们的案例中,安全团队不喜欢集中式配置管理服务器。我们决定使用 NetflixOSS 的 Eureka 进行服务发现,但暂不使用配置服务器。如果我们最终不喜欢上述方法,我们可以切换到Archaius 进行配置管理。 Spring Cloud Netflix 旨在让 Spring Boot 用户轻松进行这些集成。虽然我认为它希望你使用Spring Cloud Config (Server/Client) 而不是 Archaius。还没试过。

配置服务器似乎更容易解释和思考。如果可以,您应该从配置服务器开始。

If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.

配置服务器的比较

如果您决定尝试配置服务器,则需要进行一次研究。这里有一些很好的资源可以帮助您开始:

Service Discovery: Zookeeper vs etcd vs Consul Aphyr's write-up on consistency in etcd and Consul(已确定的特定问题现已修复;但值得阅读和思考)

如果你尝试 Consul,你应该watch this talk, "Operating Consul as an Early Adopter"。即使您尝试除了 Consul 之外的其他内容,该演讲也会为您提供建议和见解。

16/05/11 编辑:ThoughtWorks 技术雷达现在有moved Consul into the "Adopt" category (history of their evaluation is here)。

17/06/01 编辑:出于多种原因,我们正在考虑迁移到 Kubernetes。如果我们这样做,我们将利用 K8S 附带的 etcd-powered ConfigMaps 功能。这就是这个主题的全部内容:-)

更多资源

我最喜欢的微服务一站式商店:Martin Fowler's "Microservices Resource Guide" 如果您还不想购买/阅读所有构建微服务,这里有a PDF of a few chapters, via nginx。从第 86 页开始,它进入“动态服务注册中心”,涵盖 ZooKeeper、Consul、Eureka 等……Newman 比我更好地涵盖了这些主题。

【讨论】:

A relevant comment from a news.ycombinator.com discussion:“总有那么一点,你所做的事情对你的环境来说是独一无二的,你必须能够以某种方式管理/复制/升级它。”有多种方法可以管理这种复杂性,上面介绍了一些不太理想的方法和可能更好的方法。所以评估要求和限制,选择一些不错的开始,然后迭代:) Sam 的 PDF,实际上并没有提到 Archaius。 @MarkHu 我相信Building Microservices这本书确实提到了它的名字。无论如何,它只是多种服务发现技术中的一种,而服务发现和配置服务是我这里的两个关键主题。 PDF 和书中都提到过。 感谢您为您的回答付出了这么多努力

以上是关于如何在基于 Docker 的微服务中管理每个环境的数据?的主要内容,如果未能解决你的问题,请参考以下文章

AWS 上基于 docker 的微服务架构的部署方法

基于 Docker 的微服务架构实践

百万在线直播互动平台基于Docker的微服务架构实践

基于docker部署的微服务架构(二): 服务提供者和调用者

docker mesos在生产环境的实践

基于ServiceStage的微服务开发与部署