Как реализовать принцип "одного бинарного" с помощью Docker
Единый двоичный принцип объясняется здесь: http://programmer.97things.oreilly.com/wiki/index.php/One_Binary утверждает, что нужно...
"Создайте один двоичный файл, который можно идентифицировать и продвигать на всех этапах конвейера выпуска. Храните в среде подробности, относящиеся к среде. Это может означать, например, хранение их в контейнере компонента, в известном файле или в путь."
Я вижу, что многие инженеры-разработчики, возможно, нарушают этот принцип, создавая один образ докера для каждой среды (то есть my-app-qa, my-app-prod и т. Д.). Я знаю, что Docker предпочитает неизменную инфраструктуру, которая подразумевает не менять образ после развертывания, поэтому не загружать и не загружать конфигурацию после развертывания. Есть ли компромисс между неизменной инфраструктурой и единым двоичным принципом или они могут дополнять друг друга? Когда дело доходит до отделения конфигурации от кода, что является лучшей практикой в мире Docker??? Какой из следующих подходов следует использовать...
1) Создание базового двоичного образа, а затем наличие файла конфигурации Dockerfile, который дополняет этот образ путем добавления конкретной конфигурации среды. (т.е. my-app -> my-app-prod)
2) Развертывание образа докера только в двоичном формате в контейнер и передача конфигурации через переменные среды и т. Д. Во время развертывания.
3) Загрузка конфигурации после развертывания файла Docker в контейнер
4) Загрузка конфигурации с сервера управления конфигурацией из образа работающего докера внутри контейнера.
5) Сохранение конфигурации в хост-среде и обеспечение ее доступности для работающего экземпляра Docker с помощью bind mount.
Есть ли другой лучший подход, не упомянутый выше?
Как можно реализовать один двоичный принцип, используя неизменную инфраструктуру? Это может быть сделано или есть компромисс? Какова лучшая практика?
2 ответа
У меня около 2 лет опыта развертывания контейнеров Docker, поэтому я собираюсь рассказать о том, что я сделал и / или знаю, чтобы работать.
Итак, позвольте мне сначала сказать, что контейнеры должны быть неизменными (я даже помечаю свои как доступные только для чтения).
Основные подходы:
- использовать файлы конфигурации, устанавливая статическую точку входа и переопределяя местоположение файла конфигурации, переопределяя команду запуска контейнера - это менее гибко, поскольку для его включения потребуется зафиксировать изменение и выполнить повторное развертывание; не подходит для паролей, безопасных токенов и т. д.
- использовать файлы конфигурации, переопределяя их местоположение переменной среды - опять же, зависит от предварительной подготовки файлов конфигурации;; не подходит для паролей, безопасных токенов и т. д.
- использовать переменные среды - это может потребовать изменения в коде развертывания, что приведет к сокращению времени, необходимого для изменения конфигурации, поскольку для этого не требуется проходить этап сборки приложения (в большинстве случаев), развертывание такого изменения может быть довольно легко. Вот пример - если при развертывании контейнерного приложения в Marathon изменение переменной среды может потенциально просто запустить новый контейнер из последнего использованного образа контейнера (возможно, даже на том же хосте), что означает, что это можно сделать за считанные секунды; не подходит для паролей, безопасных токенов и т. д., особенно в Docker
- сохраните конфигурацию в хранилище a k/v, например, Consul, сделайте так, чтобы приложение знало об этом, и пусть оно будет даже динамически реконфигурируемым. Отличный подход для одновременного запуска функций - возможно, даже для нескольких сервисов; если реализация с таким решением, как HashiCorp Vault, обеспечивает безопасное хранение конфиденциальной информации, у вас могут быть даже эфемерные секреты (например, секретный бэкэнд PostgreSQL для Vault - https://www.vaultproject.io/docs/secrets/postgresql/index.html)
- Попросите приложение или скрипт создать файлы конфигурации перед запуском основного приложения - сохраните конфигурацию в хранилище a k/v, например, Consul, используйте что-то вроде consul-template, чтобы заполнить конфигурацию приложения; немного более безопасно - так как вы не переносите все через весь конвейер как код
- сделать так, чтобы приложение или скрипт заполняли переменные среды перед запуском основного приложения - примером этого может быть envconsul; не подходит для конфиденциальной информации - кто-то с доступом к Docker API (через сокет TCP или UNIX) сможет прочитать эти
- У меня даже была ситуация, когда мы заполняли переменные в экземпляре user_data экземпляра AWS и вставляли их в контейнер при запуске (со скриптом, который изменяет конфигурацию json контейнеров при запуске)
Основные вещи, которые я бы принял во внимание:
- какие переменные я раскрываю и когда и откуда я получаю их значения (может быть программное обеспечение CD или что-то еще) - например, вы можете опубликовать конечную точку AWS RDS и учетные данные в user_data экземпляра, потенциально даже теги EC2 с некоторой магией профиля экземпляра IAM
- Сколько переменных мы должны управлять и как часто мы меняем некоторые из них - если у нас есть несколько, мы могли бы просто пойти с переменными среды, или использовать переменные среды для наиболее часто изменяемых и переменных, хранящихся в файле для те, которые мы меняем реже
- и как быстро мы хотим видеть их измененными - если это файл, обычно требуется больше времени для его развертывания в рабочей среде; если мы используем переменную среды s, мы обычно можем внедрить эти изменения намного быстрее
- как мы защищаем некоторые из них - где мы их вводим и как - например, Ansible Vault, HashiCorp Vault, хранение их в отдельном репо и т. д.
- как мы развертываем - это может быть файл конфигурации JSON, отправленный конечной точке инфраструктуры развертывания, Ansible и т. д.
- какая среда у нас есть - реально ли иметь что-то вроде Consul в качестве хранилища данных конфигурации (Consul имеет 2 разных типа агентов - клиент и сервер)
Я предпочитаю предпочитать самый сложный случай, когда они хранятся в центральном месте (хранилище k / v, база данных) и динамически изменяются, потому что я встречал следующие случаи:
- конвейеры медленного развертывания - что делает очень медленным изменение файла конфигурации и его развертывание
- слишком много переменных окружения - это действительно может выйти из-под контроля
- необходимость включать флаг функции во всем флоте (состоящем из десятков служб) одновременно
- среда, в которой реально стремятся повысить безопасность за счет лучшей обработки конфиденциальных данных конфигурации
Я, наверное, что-то пропустил, но я думаю, что этого должно быть достаточно, чтобы подумать о том, что будет лучше для вашей среды
То, как я делал это в прошлом, - это включение токенизации в процесс упаковки после выполнения сборки. Этими токенами можно управлять на уровне оркестровки, который находится сверху для управления инструментами платформы. Таким образом, для данного токена есть соответствующее выражение регулярного выражения или xpath. Этот токен связан с одним или несколькими конфигурационными файлами, в зависимости от выбранных отношений. Затем, когда эта сборка развернута в контейнере, служба платформы (то есть config mgmt) выдаст эти токены с правильным значением относительно среды. Эти значения poke, скорее всего, будут извлечены из хранилища.