首页 文章

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

提问于
浏览
21

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

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

"Microservice A" Team Environment

  • 微服务A( SNAPSHOT

  • 微服务B(稳定版)

  • 微服务C(STABLE)

"Microservice B" Team Environment

  • 微服务A(STABLE)

  • 微服务B( SNAPSHOT

  • 微服务C(STABLE)

"Microservice C" Team Environment

  • 微服务A(STABLE)

  • 微服务B(稳定版)

  • 微服务C( SNAPSHOT

QA / Staging / Production

  • 微服务A(稳定,释放等)

  • 微服务B(稳定,释放等)

  • 微服务C(稳定,释放等)

这是很多部署,但这个问题可以通过持续集成服务器解决,也许像Chef / Puppet /等 . really 困难的部分是每个微服务都需要一些特定于其部署的每个地方的环境数据 .

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

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

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

  • Have the build server bake them into the application at build-time - 我想你可以创建一个每个环境属性文件或脚本的repo,并让每个微服务的构建过程伸出来并拉入相应的脚本(你也可以为 生产环境 提供一个单独的,有限访问的repo东西) . 但是,你需要大量的脚本 . 对于可以部署微服务的每个地方的每个微服务,基本上都是单独的 .

  • Bake them into base Docker images for each environment - 如果构建服务器将您的微服务应用程序放入Docker容器中作为构建过程的最后一步,那么您可以为每个环境创建自定义基本映像 . 基本映像将包含一个shell脚本,用于设置所需的所有环境变量 . 您的Dockerfile将设置为在启动应用程序之前调用此脚本 . 这与上一个要点有类似的挑战,因为现在你正在管理大量的Docker镜像 .

  • Pull in the environment info at runtime from some sort of registry - 最后,你可以将你的每个环境配置存储在像Apache ZooKeeper这样的东西中(或者甚至只是一个普通的' database), and have your application code pull it in at runtime when it starts up. Each microservice application would need a way of telling which environment it')(例如一个启动参数),这样它就知道要从注册表中获取哪一组变量 . 这种方法的优点是,现在你可以从团队环境到 生产环境 一直使用完全相同的构建工件(即应用程序或Docker容器) . 另一方面,你现在可以拥有另一个运行时依赖项,并且你可以使用仍然必须管理您的注册表中的所有数据 .

人们如何在微服务架构中解决这个问题?听起来这似乎是常见的事情 .

1 回答

  • 27

    概述

    长帖!

    • ENTRYPOINT 是你的朋友
      Sam Newman的

    • Building Microservices很棒

    • 服务间安全提示:双向TLS可能有效,但可能会出现延迟问题

    • 我将从我的团队中得到一个真实的例子 . 我们 could not 使用配置服务器,事情变得有趣了 . 现在可以管理 . 但随着公司提供更多服务,可能无法扩展 .

    • 配置服务器似乎是一个更好的主意

    更新:差不多两年后,我们可能会转到Kubernetes,并开始使用随附的etcd-powered ConfigMaps功能 . 我仍然在使用 ENTRYPOINT 和一些相同的概念,只是一些不同的工具 .

    ENTRYPOINT

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

    In short: create a script to bootstrap your service before starting, and use ENTRYPOINT to execute this script.

    我将详细介绍这一情况,并解释如何在没有配置服务器的情况下执行此操作 . 它有点深,但它并不难以管理 . 然后,我以配置服务器的详细信息结束,这是许多团队的更好解决方案 .

    构建微服务

    你're right that these are common concerns, but there just aren'吨一刀切的解决方案 . 最通用的解决方案是配置服务器 . (最常见,但仍然不是一刀切 . )但也许您不能使用其中之一:我们被安全团队禁止使用配置服务器 .

    如果您没有担心配置管理的完美解决方案,我强烈建议您阅读Sam Newman的Building Microservices . 从当前的微服务和环境集开始,使用"good enough"解决方案 . 您可以迭代和改进,因此您应该尝试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:"this configuration requires the client to provide their certificate to the server, in addition to the server providing their's to the client."生成和管理这些证书可能会变得复杂......但无论你选择这样做,你都会像其他任何配置/工件一样在配置/工件周围移动 .

    请注意,双向TLS可能会导致高容量的延迟问题 . 我们还没有 . 我们正在使用除了双向TLS之外的其他措施,并且随着时间的推移,我们可能会抛弃双向TLS .


    来自我的团队的真实案例

    我现在的团队正在做一些结合你提到的两种方法(转述):

    • 在构建时烘焙配置

    • 在运行时拉配置

    我的团队正在使用Spring Boot . Spring Boot与"profiles"系统非常复杂Externalized Configuration . Spring Boot 's configuration handling is complex and powerful, with all the pros/cons that go with that (won' t进入这里) .

    虽然这是Spring Boot的开箱即用,但这些想法很普遍 . 我更喜欢Dropwizard用于Java微服务,或者Flask用于Python;在这两种情况下,你可以做类似Spring Boot正在进行的事情......你'll just have to do more things yourself. Good and bad: These nimble little frameworks are more flexible than Spring, but when you'重新编写更多代码并进行更多集成,你需要更多的责任来测试你的复杂/灵活的配置支持 .

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

    对于Spring Boot,您可以一次激活多个配置文件 . 这意味着您可以拥有基本配置,然后使用更具体的配置覆盖 . 我们在 src/main/resources 中保留了基本配置 application.yml . 因此,此配置与可发送的JAR打包在一起,并且当执行JAR时,始终会选择此配置 . 因此,我们在此文件中包含所有默认设置(所有环境通用) . 示例:配置块,显示,"Embedded Tomcat, always use TLS with these cipher suites enabled."( server.ssl.ciphers

    如果某个环境只需要覆盖一个或两个变量,我们就会利用Spring Boot's support for getting configuration from environment variables . 示例:我们使用环境变量将URL设置为Service Discovery . 这将覆盖已发货/已提取配置文件中的任何默认值 . 另一个例子:我们使用环境变量 SPRING_PROFILES_ACTIVE 来指定哪些配置文件是活动的 .

    我们还想确保 master 包含用于开发环境的经过测试的工作配置 . src/main/resources/application.yml 具有合理的默认值 . 另外我们在 config/application-dev.yml 中放置了dev-only配置,然后检查它.The config directory is picked up easily, but not shipped in the JAR. Nice功能 . 开发人员(在README和其他文档中)知道,在开发环境中,我们所有的Spring Boot微服务都需要激活 dev 配置文件 .

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

    • 维护特定于环境的配置文件,如 application-stage.ymlapplication-prod.yml 等,覆盖默认设置偏差的设置(在非常严格锁定的git存储库中)

    • 维护模块化的,特定于供应商的配置文件,例如 application-aws.ymlapplication-mycloudvendor.yml (存储它的位置取决于它是否包含机密) . 这些可能包含跨越阶段,产品等的值 .

    • 在运行时使用环境变量覆盖任何相关设置;包括从1和2中挑选配置文件

    • 使用自动化在构建或部署时烘焙硬编码值(模板)(输出到某种严重锁定的存储库,可能与(1)的存储库不同)

    (1),(2)和(3)一起工作得很好 . 我们很乐意做这三件事,实际上很容易记录,推理和维护(在获得它的初始挂起之后) .

    你说 ...

    我想你可以创建一个每个环境属性文件或脚本的回购[...]但是你需要大量的脚本 .

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

    由于Spring Boot对(3)有如此好的支持,并支持在YML文件中使用默认值/模板,因此您可能不需要(4) . 但是,这里的事情对您的组织非常具体 . 我们团队的安全工程师希望我们使用(4)为 dev 之外的环境烘焙一些特定的值:密码 . 这位工程师不想在环境变量中使用密码"floating around",主要是因为那时 - 谁会设置它们? Docker来电? AWS ECS Task Definition (viewable through AWS web UI) ?在这些情况下,密码可能会暴露给自动化工程师,他们不一定能够访问包含 application-prod.yml 的"locked-down git repository" . 如果你做(1),则可能不需要(4);您可以将密码(硬编码)保存在严格控制的存储库中 . 但是,在部署自动化时可能会产生秘密,您不希望在同一个存储库中生成(1) . 这是我们的情况 .

    更多关于(2):我们使用 aws 配置文件和Spring Boot的"configuration as code"进行启动时调用以获取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 的一些问题 . 无论是从命令行还是从主管/调度程序(如AWS ECSApache Mesosphere/Marathon)传递 --env SPRING_PROFILES_ACTIVE=dev--env SPRING_PROFILES_ACTIVE=aws,prod 都很容易 . 我们的 entrypoint.sh 也有助于传递与Spring无关的JVM标志:我们使用常见的 JAVA_OPTS 约定 .

    (哦,我应该提一下......我们也使用Gradle作为我们的构建 . 目前......我们用Gradle任务包装 docker builddocker rundocker push . 我们的 Dockerfile 是模板化的,所以再次选择#4来自上面 . 我们有像 @agentJar@ 这样的变量在构建时被覆盖了. I really don't like this, and I think this could be better handled with plain old configuration (-Dagent.jar.property.whatever). This will probably go way. 但是我只是提到了它的完整性 . 我对此感到高兴:在构建, Dockerfileentrypoint.sh 脚本中没有做任何事情,它紧密地耦合到某个部署上下文(例如AWS) . 所有这些都可以在开发环境和部署环境中运行 . 所以我们不应该是可移植的 . )

    我们有一个文件夹 src/main/docker ,其中包含 Dockerfileentrypoint.sh (由 ENTRYPOINT 调用的脚本;它被烘焙到 Dockerfile 中) . 我们的 Dockerfileentrypoint.sh 在所有微服务中几乎完全一致 . 克隆我们的微服务模板时,这些都是重复的 . 不幸的是,有时你必须复制/粘贴更新 . 我们没有太大的痛苦 .

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

    • 从我们的"golden" base Dockerfile 派生用于Java应用程序

    • grab 我们用于拉动配置的工具 . (从内部服务器获取任何dev或Jenkins机器正在进行构建 . )(您也可以使用像 wget 这样的Linux工具以及基于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存储库而不是git存储库 . 这可以允许更好的访问控制 . 生成在同一个repo / bucket中的 application-prod.ymlapplication-stage.yml 可以使得 application-envspecific.yml 始终具有所需的配置,在S3存储桶中通过给定AWS账户中的某个常规名称 . 即“从 s3://ecs_config/$ENV_NAME/application-envspecific.yml 获取配置”(其中 $ENV_NAME 来自 entrypoint.sh 脚本或ECS任务定义) .

    我提到 Dockerfile 可以移植,并且没有耦合到某些部署上下文 . 这是因为 entrypoint.sh 被定义为以灵活的方式检查配置文件;它只是想要配置文件 . 因此,如果您使用Docker的 --volume 选项来安装带有config的文件夹,脚本将会很高兴,并且它不会尝试从外部服务器提取任何内容 .

    我不会太多地进入部署自动化......但是我们很快就会提到我们使用terraform,boto3和一些自定义Python包装代码 . jinja2用于模板化(烘焙那些需要烘焙的值) .

    Here's serious limitation of this approach: 必须杀死/重新启动微服务进程才能重新下载并重新加载配置 . 现在,对于无状态服务集群,这并不一定代表停机时间(给定一些事情,例如客户端负载 balancer ,为重试配置Ribbon,以及水平扩展,因此某些实例始终在池中运行) . 到目前为止,它正在解决,但微服务仍然具有相当低的负载 . 增长即将来临 . 我们会看到 .

    还有很多方法可以解决这些挑战 . 希望这个练习让你思考什么对你的团队有用 . 试着把事情搞定 . 快速原型,你随时都会动摇细节 .

    或许更好:配置服务器

    我认为这是一个更常见的解决方案: Configuration Servers. 你提到ZooKeeper . 还有Consul . ZooKeeper和Consul都提供配置管理和服务发现 . 还有etcd .

    在我们的案例中,安全团队对于服务发现不是't comfortable with a centralized Configuration Management server. We decided to use NetflixOSS',而是在配置服务器上推迟 . 如果我们不喜欢上述方法,我们可能会切换到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.
    

    配置服务器的比较

    如果您决定尝试配置服务器,则需要进行研究调整 . 以下是一些很好的资源:

    如果你尝试 Consul ,你应该 watch this talk, "Operating Consul as an Early Adopter" . 即使你在Consul之外尝试别的东西,这个谈话也会为你提供一些建议和见解 .

    16/05/11 EDIT: ThoughtWorks技术雷达现在moved Consul into the "Adopt" categoryhistory of their evaluation is here) .

    17/06/01 EDIT: 由于多种原因,我们正在考虑转向Kubernetes . 如果我们这样做,我们将利用K8S附带的etcd-powered ConfigMaps功能 . 这一切都是关于这个主题:-)

    更多资源

    • 我最喜欢的微服务一站式商店: Martin Fowler's "Microservices Resource Guide"

    • 如果您还不想购买/阅读所有的建筑微服务,那就是a PDF of a few chapters, via NGINX . 从第86页开始,它进入了"Dynamic Service Registries",涵盖了ZooKeeper,Consul,Eureka等...... Newman比我更好地介绍了这些主题 .

相关问题