Подтвердить что ты не робот

Как вы управляете данными об окружающей среде в микросервисах на базе Docker?

В архитектуре микросервиса я с трудом понимаю, как можно управлять конфигурацией, специфичной для среды (например, IP-адрес и учетные данные для базы данных или брокера сообщений).

Скажем, у вас есть три микросервиса ( "A", "B" и "C" ), каждая из которых принадлежит и поддерживается другой командой. Каждая команда нуждается в командной среде интеграции... где они работают с последним снимком своих микросервисов, а также стабильными версиями всех зависимых микросервисов. Конечно, вам также понадобятся QA/промежуточная/производственная среда. Упрощенный вид большой картины будет выглядеть следующим образом:

Командная среда Microservice A

  • Microservice A (SNAPSHOT)
  • Microservice B (STABLE)
  • Microservice C (STABLE)

Командная среда Microservice B

  • Microservice A (STABLE)
  • Microservice B (SNAPSHOT)
  • Microservice C (STABLE)

Командная среда Microservice C

  • Microservice A (STABLE)
  • Microservice B (STABLE)
  • Microservice C (SNAPSHOT)

QA/Staging/Production

  • Microservice A (STABLE, RELEASE и т.д.)
  • Microservice B (STABLE, RELEASE и т.д.)
  • Microservice C (STABLE, RELEASE и т.д.)

Это много развертываний, но эта проблема может быть решена сервером непрерывной интеграции и, возможно, чем-то вроде Chef/Puppet/etc. Тяжелая часть действительно заключается в том, что каждому микросервису потребуются некоторые данные среды, относящиеся к каждому месту развертывания.

Например, в командной среде "A" для "A" требуется один адрес и набор учетных данных для взаимодействия с "B". Однако в командной среде "B" для развертывания "A" требуется другой адрес и учетные данные для взаимодействия с этим развертыванием "B".

Кроме того, по мере того, как вы приближаетесь к производству, такая информация об экологических конфигурациях, возможно, нуждается в ограничениях безопасности (т.е. только некоторые люди могут изменять или даже просматривать ее).

Итак, с архитектурой микросервиса, как вам поддерживать конфигурационную информацию, зависящую от конкретной среды, и сделать ее доступной для приложений? На ум приходит несколько подходов, хотя все они кажутся проблематичными:

  • Попросите сервер сборки испечь их в приложении во время сборки. Я полагаю, вы могли бы создать репо для файлов свойств или сценариев для каждого объекта и создать процесс сборки для каждого микросервиса и вытащите соответствующий script (у вас также может быть отдельный репозиторий с ограниченным доступом для производственного материала). Однако вам понадобится тонна скриптов. В основном отдельный для каждого микросервиса в каждом месте, где может быть развернута микросервис.
  • Выпекать их в базовых изображениях Docker для каждой среды. Если сервер сборки помещает ваши приложения для микросервиса в контейнеры Docker в качестве последнего шага процесса сборки, вы можете создавать собственные базовые изображения для каждого Окружающая среда. Базовое изображение будет содержать оболочку script, которая устанавливает все необходимые переменные среды. Ваш Dockerfile будет настроен на вызов этого script до запуска вашего приложения. У этого есть схожие проблемы с предыдущим пунктом, поскольку теперь вы управляете тонной копией Docker.
  • Извлеките информацию об окружающей среде во время выполнения из какого-то реестра. Наконец, вы можете сохранить конфигурацию для каждой среды внутри чего-то вроде Apache ZooKeeper (или даже простой базы данных), и если ваш код приложения вытащит его во время выполнения, когда он запустится. Каждому приложению микросервиса нужен способ сообщить, в какой среде он находится (например, параметр запуска), чтобы он знал, какой набор переменных должен захватывать из реестра. Преимущество такого подхода заключается в том, что теперь вы можете использовать тот же артефакт сборки (например, приложение или контейнер Docker) на всем пути от командной среды до производства. С другой стороны, теперь у вас будет другая зависимость от выполнения, и вам все равно придется все эти данные записывать в реестр.

Как люди обычно решают эту проблему в архитектуре микросервиса? Кажется, это было бы обычным делом, о котором можно было бы услышать.

4b9b3361

Ответ 1

Обзор

Длинная почта!

  • ENTRYPOINT - ваш друг
  • Создание Microservices Сэмом Ньюманом замечательно
  • Межсервисный совет безопасности: 2-сторонняя TLS может работать, но может возникать проблемы с задержкой.
  • Я получу реальный пример из своей команды. Мы не могли использовать сервер конфигурации, и все получилось... интересным. Теперь можно управлять. Но может не масштабироваться, поскольку у компании больше услуг.
  • Серверы конфигурации выглядят как лучшая идея.

Обновление: почти через два года мы можем перейти на Kubernetes и начать использовать etcd-powered ConfigMaps, которая поставляется с ней. Я расскажу об этом еще раз в разделе серверов конфигурации. Сообщение могло бы быть полезным, если вы заинтересованы в этих предметах. Мы по-прежнему будем использовать ENTRYPOINT и некоторые из тех же понятий, а также некоторые другие инструменты.

ENTRYPOINT

Я предлагаю, чтобы ENTRYPOINT был ключом к управлению конфигурацией, специфичной для среды для ваших контейнеров Docker.

Вкратце: создайте script для начальной загрузки вашей службы перед запуском и используйте ENTRYPOINT для выполнения этого script.

Я подробно расскажу об этом, а также объясню, как мы это делаем без сервера конфигурации. Он становится немного глубоким, но он не неуправляем. Затем я заканчиваю детали на серверах конфигурации, лучшее решение для многих команд.

Строительство микросервисов

Вы правы, что это общие проблемы, но просто нет решений одного размера. Наиболее общим решением является сервер конфигурации. (Самый общий, но все же не один-размер-подходит.) Но, возможно, вы не можете использовать один из них: нам было запрещено использовать сервер конфигурации командой безопасности.

Я настоятельно рекомендую прочитать Building Microservices Сэмом Ньюманом, если вы еще этого не сделали. Он рассматривает все общие проблемы и обсуждает множество возможных решений, а также дает полезную перспективу от опытного архитектора. (Обратите внимание: не беспокойтесь о идеальном решении для вашего управления конфигурацией, начните с "достаточно хорошего" решения для вашего текущего набора микросервисов и сред. Вы можете выполнять итерацию и улучшать, поэтому вы должны попробовать получить полезное программное обеспечение для ваших клиентов как можно скорее, а затем улучшить последующие выпуски.)

Предупреждающая история?

Перечитывая это снова... Я немного сжимаю, сколько нужно, чтобы полностью объяснить это. Из Zen of Python:

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

Я не в восторге от решения, которое у нас есть. Тем не менее это приемлемое решение, поскольку мы не могли использовать сервер конфигурации. Это также реальный пример.

Если вы прочтете это и подумайте: "О боже нет, зачем мне все это!" то вы знаете, вам нужно пристально смотреть на серверы конфигурации.

Межсервисная безопасность

Похоже, вы также обеспокоены тем, как разные микросервисы аутентифицируют друг друга.

Для артефактов и конфигурации, связанных с этой аутентификацией... рассматривайте их как любые другие артефакты конфигурации.

Каковы ваши требования в отношении безопасности между службами? В вашем сообщении это звучит так, будто вы описываете аутентификацию уровня приложения, имени пользователя и пароля. Возможно, это имеет смысл для служб, которые вы имеете в виду. Но вы также должны рассмотреть Two-Way TLS: "для этой конфигурации клиент должен предоставить свой сертификат серверу, в дополнение к серверу, предоставляющему свои для клиента". Создание и управление этими сертификатами может усложниться... но, тем не менее, вы решили это сделать, вы будете перемешать вокруг config/артефактов, как и любые другие конфиги/артефакты.

Обратите внимание, что двухсторонняя TLS может вводить проблемы с задержкой при больших объемах. Мы еще не там. Мы используем другие меры, помимо 2-стороннего TLS, и мы можем вырезать 2-way TLS, как только они будут доказаны, со временем.


Реальный пример из моей команды

Моя нынешняя команда делает что-то, что сочетает в себе два подхода, которые вы упомянули (перефразированный):

  • Конфигурация выпечки при времени сборки
  • Конфигурация Pull во время выполнения

Моя команда использует Spring Загрузка. Spring Загрузка действительно сложная Внешняя настройкас системой "профилей". Spring Обработка конфигурации загрузки сложна и мощна, и все плюсы/минусы, которые идут с этим (не попадут сюда).

Пока это готово с помощью Spring Boot, идеи являются общими. Я предпочитаю Dropwizard для микросервисов Java или Flask в Python; в обоих случаях вы могли бы сделать что-то похожее на то, что Spring Загрузка продолжается... Вам просто нужно делать больше вещей самостоятельно. Хорошие и плохие: эти проворные небольшие рамки более гибкие, чем Spring, но когда вы пишете больше кода и делаете больше интеграций, больше ответственности за ВАС за QA и тестируйте свою сложную/гибкую конфигурационную поддержку.

Я продолжу пример Spring Boot из-за из первых рук, но не потому, что рекомендую его! Используйте то, что подходит вашей команде.

В случае Spring Boot вы можете одновременно активировать несколько профилей. Это означает, что вы можете иметь базовую конфигурацию, а затем переопределить ее с более конкретной конфигурацией. Мы сохраняем базовую конфигурацию, application.yml в src/main/resources. Таким образом, эта конфигурация упакована с JP с возможностью доставки, и когда JAR выполняется, эта конфигурация всегда подбирается. Поэтому мы включаем все настройки по умолчанию (общие для всех сред) в этом файле. Пример: блок конфигурации, в котором говорится: "Embedded Tomcat, всегда использует TLS с включенными наборами шифров". (server.ssl.ciphers)

Если для определенной среды требуется перезаписать только одну или две переменные, мы используем Spring Поддержка загрузки для получения конфигурации из переменных окружения, Пример: мы установили URL-адрес нашего Обслуживания, используя переменную среды. Это отменяет любые значения по умолчанию в файлах с отгруженными/вытащенными конфигурациями. Другой пример: мы используем переменную среды SPRING_PROFILES_ACTIVE, чтобы указать, какие профили конфигурации активны.

Мы также хотим убедиться, что master содержит проверенную рабочую конфигурацию для среды разработки. src/main/resources/application.yml имеет нормальные значения по умолчанию. Кроме того, мы устанавливаем конфигурацию dev-only в config/application-dev.yml и проверяем это. Каталог config легко загружается, но не отправляется в JAR. Хорошая функция. Разработчики знают (из README и другой документации), что в среде dev все наши Spring загрузочные микросервисы требуют активации профиля dev.

Для сред, помимо dev, вы, вероятно, уже можете увидеть некоторые параметры... Любой из этих параметров мог бы (почти) сделать все, что вам нужно. Вы можете смешивать и сопоставлять по мере необходимости. Эти варианты совпадают с некоторыми идеями, которые вы упомянули в своем оригинальном посте.

  • Поддерживать профили, специфичные для конкретной среды, такие как application-stage.yml, application-prod.yml и т.д. которые переопределяют настройки с отклонениями от значений по умолчанию (в очень сильно заблокированном репозитории git)
  • Поддерживать модульные профили, специфичные для поставщика, такие как application-aws.yml, application-mycloudvendor.yml (где вы храните это будет зависеть от того, содержит ли он секреты). Они могут содержать значения, которые пересекаются этап, prod и т.д.
  • Использовать переменные среды для переопределения любых соответствующих параметров во время выполнения; включая выбор профиля (ов) от 1 и 2
  • Использовать автоматизацию для испечения в жестко заданных значениях (шаблонах) в момент сборки или развертывания (вывод в сильно запертый репозиторий какого-то рода, возможно отличающийся от (1) репозитория)

(1), (2) и (3) хорошо работают вместе. Мы счастливо делаем все три, и на самом деле довольно легко документировать, разум и поддерживать (после получения первоначального зависания).

Ты сказал...

Я полагаю, вы могли бы создать репо для файлов свойств для каждого объекта или script [...] Вам понадобилось бы тонну скриптов.

Это может быть управляемо. Сценарии, которые вытягивают или выпекают конфигурацию: они могут быть одинаковыми во всех сервисах. Возможно, script копируется, когда кто-то клонирует ваш шаблон микросервиса (кстати, у вас должен быть официальный шаблон микросервиса!). Или, возможно, это Python script на внутреннем сервере PyPI. Подробнее об этом после того, как мы поговорим о Докере.

Так как Spring Boot имеет такую ​​хорошую поддержку (3) и поддерживает использование шаблонов по умолчанию/шаблонов в файлах YML, вам может не понадобиться (4). Но здесь, где все становится очень специфичным для вашей организации. Инженер безопасности в нашей команде хотел, чтобы мы использовали (4), чтобы испечь некоторые конкретные значения для сред за пределами dev: пароли. Этот Инженер не хотел, чтобы пароли "плавали вокруг" в переменных среды, главным образом потому, что тогда - кто их установил? Кто-нибудь звонит? Определение задачи AWS ECS (просмотр через веб-интерфейс AWS)? В таких случаях пароли могут быть предоставлены инженерам по автоматизации, которые не обязательно будут иметь доступ к "заблокированному хранилищу git", содержащему application-prod.yml. (4) может не понадобиться, если вы это сделаете (1); вы можете просто сохранить пароли, жестко закодированные, в жестко контролируемом репозитории. Но, возможно, есть секреты для генерации при времени автоматизации развертывания, которые вы не хотите в том же хранилище, что и (1). Это наш случай.

Подробнее о (2): мы используем конфигурацию aws и Spring Boot "как код", чтобы сделать запуск во время вызова для получения метаданных AWS,  и переопределить некоторую конфигурацию, основанную на этом. Определения задач AWS ECS активируют профиль aws. Документация Spring Cloud Netflix дает пример следующим образом:

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

Далее, Докер. Переменные среды - очень хороший способ передать аргументы конфигурации в Docker. Мы не используем никаких аргументов в командной строке или позициях из-за некоторых ошибок, с которыми мы столкнулись с ENTRYPOINT. Легко передать --env SPRING_PROFILES_ACTIVE=dev или --env SPRING_PROFILES_ACTIVE=aws,prod... из командной строки или из диспетчера/планировщика, такого как AWS ECS или Меша/марафон Apache. Наш entrypoint.sh также облегчает передачу флагов JVM, которые не имеют ничего общего с Spring: для этого мы используем общее соглашение JAVA_OPTS.

(О, я должен упомянуть... мы также используем Gradle для наших сборников. На данный момент... Мы завершаем docker build, docker run и docker push с задачами Gradle. Наш шаблон Dockerfile является шаблоном, так что опять опция # 4 сверху. У нас есть переменные типа @[email protected], которые перегружаются во время сборки. Мне это действительно не нравится, и я думаю, что это может быть лучше обработано с простой старой конфигурацией (-Dagent.jar.property.whatever). Это, вероятно, будет идти. Но я просто упомянул об этом для полноты. > am радует: ничего не сделано в сборке, Dockerfile или entrypoint.sh script, которая тесно связана с определенным контекстом развертывания (например, AWS). Все это работает в dev, а также развернутые среды. Поэтому нам не нужно разворачивать образ Docker для его проверки: он переносится, как и должно быть.)

У нас есть папка src/main/docker, содержащая Dockerfile и entrypoint.sh (script, вызываемые ENTRYPOINT, это запекается в Dockerfile). Наши Dockerfile и entrypoint.sh почти полностью однородны во всех микросервисах. Они дублируются, когда вы клонируете наш шаблон микросервиса. К сожалению, иногда вам приходится копировать/вставлять обновления. Мы пока не нашли хорошего пути, но это не ужасно больно.

Dockerfile выполняет следующее (время сборки):

  • Выходит из нашей "золотой" базы Dockerfile для приложений Java
  • Захватывает наш инструмент для вытягивания конфигурации. (Захватывает с внутреннего сервера, доступного для любого разработчика или машины 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 (в вашей логике конфигурации-puller) и вытащить только то, что нужно.

Если вы используете AWS, вы можете использовать репозиторий S3 вместо репозитория git. Это может обеспечить лучший контроль доступа. Вместо application-prod.yml и application-stage.yml, живущих в одном и том же репо/ведро, вы можете сделать так, чтобы application-envspecific.yml всегда имела требуемую конфигурацию в ведре S3 с помощью некоторого обычного имени в данной учетной записи AWS. "Получить конфигурацию из s3://ecs_config/$ENV_NAME/application-envspecific.yml" (где $ENV_NAME происходит от entrypoint.sh script или определения задачи ECS).

Я упомянул, что Dockerfile работает портативно и не связан с определенными контекстами развертывания. Это связано с тем, что entrypoint.sh определен для проверки файлов конфигурации гибким способом; он просто хочет файлы конфигурации. Поэтому, если вы используете опцию Docker --volume для установки папки с конфигурацией, script будет счастлив, и она не попытается извлечь что-либо с внешнего сервера.

Я не буду вдаваться в автоматизацию развертывания... но просто упомянем быстро, что мы используем terraform, boto3 и некоторый пользовательский код упаковки Python. jinja2 для шаблонов (выпекание в тех значениях пары, которые нужно испечь).

Здесь серьезное ограничение этого подхода: процесс микросервиса должен быть убит/перезапущен для повторной загрузки и перезагрузки конфигурации. Теперь с кластером служб без состояния это не обязательно означает время простоя (учитывая некоторые вещи, такие как балансировка нагрузки на стороне клиента, Ribbon настроен для повторных попыток и горизонтальной шкалы, поэтому в пуле всегда выполняются некоторые экземпляры). Пока это работает, но микросервисы все еще имеют довольно низкую нагрузку. Рост идет. Мы увидим.

Есть еще много способов решить эти проблемы. Надеюсь, это упражнение заставит вас задуматься о том, что будет работать для вашей команды. Просто попробуй кое-что сделать. Прототип быстро, и вы будете вытряхивать детали, когда идете.

Возможно, лучше: серверы конфигурации

Я думаю, что это более распространенное решение: Серверы конфигурации. Вы упомянули ZooKeeper. Там также Consul. Оба ZooKeeper и Consul предлагают как управление конфигурацией, так и сервисное обнаружение. Там также etcd.

В нашем случае группе безопасности не было комфортно с централизованным сервером управления конфигурацией. Мы решили использовать NetflixOSS Eureka для Обслуживания Обслуживания, но удерживать его на сервере конфигурации. Если мы не рассмотрим вышеописанные методы, мы можем переключиться на Archaius для управления конфигурацией. Spring Cloud Netflix стремится облегчить эту интеграцию для Spring пользователей загрузки. Хотя я думаю, что он хочет использовать Spring Cloud Config (Сервер/Клиент) вместо Archaius. Еще не пробовал.

Серверы конфигурации кажутся намного проще объяснять и думать. Если вы можете, вы должны начать с сервера конфигурации.

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

Сравнение серверов конфигурации

Если вы решите попробовать конфигурационный сервер, вам понадобится исследовательский шип. Вот несколько хороших ресурсов, чтобы начать:

Если вы попробуете Консула, вы должны посмотреть этот разговор "Операционный консул как ранний приемник" . Даже если вы попробуете что-то еще, кроме Консула, в разговоре есть сводные советы и проницательность для вас.

16/05/11 EDIT: Радар Google ThoughtWorks теперь переместил Консула в категорию "Принять" (история их оценки здесь).

17/06/01 EDIT: Мы рассматриваем возможность перехода на Kubernetes по нескольким причинам. Если мы это сделаем, мы будем использовать etcd-powered ConfigMaps который поставляется с K8S. Это все на данный момент на эту тему: -)

Дополнительные ресурсы