Страницы

Поиск по вопросам

понедельник, 25 ноября 2019 г.

git и конфигурационные файлы


Расскажите, кто и как решает проблему хранения конфигурационных файлов в git?

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

Сейчас я размещаю в git файлы с расширением .sample (например Web.config.sample ил
config.yml.sample) и пишу в документации, что перед тем как запустить проект, необходимо переименовать sample-файл и заполнить его правильными значениями. Сами конфигурационные файлы я добавляю в  .gitignore.

Недостатки такого подхода:


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


Возможно существуют и более удачные решения. Можно ли c git придумать что-то более удобное?
    


Ответы

Ответ 1



К сожалению, все так. В систему контроля версий не должны попадать конфигурационны файлы, которые не запустятся на других хостах, поэтому, как правило, ПО распространяется без реальных конфигов, и в консольные команды добавляется команда начальной конфигурации, которые могут сформировать этот файл. Добавление обязательных конфигурационных опций на ходу - плохая идея, по крайне мере между major-версиями. Они должны иметь свое значение по умолчанию, при которо приложение продолжает вести себя так же, как и раньше. Все эти -webkit-something-tralal в CSS появились ровно оттуда же - давайте добавим эту штуку, но не будем ее пихать как готовую опцию, когда будем готовы к внедрению - внедрим, чтобы она точно не переименовывалась и не менялась потом (конкретно в CSS имена задаются стандартом, но общая идея должна быть ясна). Впрочем, один хак я для себя нашел - весь dev-env засовывается в вагрант, где можн свободно писать любые конфиги и менять их на ходу, в результате в команде разработчиков можно свободно играться с тестовой конфигурацией. Еще одна штука, которая помогает - это "параллельные" файлы, в которых переопределяютс значения: configuration.yml содержит в себе некоторую конфигурацию, а configuration.local.yml - всего пару опций, который "берут верх" над аналогичными опциями из configuration.yml.

Ответ 2



На эту тему высказались авторы манифеста 12 factors. Вот что они пишут о конфигурации: Лакмусовой бумажкой того, правильно ли разделены конфигурация и код приложения является факт того, что кодовая база приложения может быть в любой момент открыта в свободный доступ без компрометации каких-либо приватных данных. Далее: Другим подходом к конфигурации является использование конфигурационных файлов которые не сохраняются в систему контроля версий, например, config/database.yml в Rails Это огромное улучшение перед использованием констант, которые сохраняются в коде, н по-прежнему и у этого метода есть недостатки: легко по ошибке сохранить конфигурационный файл в репозиторий; существует тенденция, когда конфигурационные файлы разбросаны в разных местах и в разных форматах, из за этого становится трудно просматривать и управлять всеми настройками в одном месте. Это как раз способ, который описан в вопросе. Авторы манифеста предлагают такое решение: Приложение двенадцати факторов хранит конфигурацию в переменных окружения (част сокращается до env vars или env). Переменные окружения легко изменить между развёртываниями не изменяя код; в отличие от файлов конфигурации, менее вероятно случайно сохранить их в репозиторий кода; и в отличие от пользовательских конфигурационных файлов или других механизмов конфигурации, таких как Java System Properties, они являются независимым от языка и операционной системы стандартом. Естественно, всё это применимо в основном к веб-приложениям. Для десктопных и мобильных приложений эти правила уже не подходят. От себя добавлю, что Azure-приложения ASP.NET и ASP.NET WebAPI сейчас настраиваютс именно так: в панели приложения на вкладке Application settings можно указать переменные окружения. Тоже самое в Heroku. Пример В нашем проекте требуется рассылать электронные письма и СМС. Естественно, на локальны контурах разработчиков и на общем контуре разработки ничего рассылать не надо, но пр этом нужен доступ к содержимому уведомлений. То есть разработчики должны видеть, что служба уведомлений сработала, и видеть, что именно будет отправлено клиенту на боевом контуре. Поскольку мы используем внедрение зависимостей, мы сделали несколько реализаций классов рассылающих уведомления. Продуктовая реализация осуществляет отправку писем, а реализаци разработчиков пишет уведомления в файл. В качестве библиотеки IoC мы используем Autofac, который позволяет регистрировать зависимости в конфигурационном файле, так что в нашем Web.config была зарегистрирована служба уведомлений для разработчиков. MSBuild умеет трансформировать конфигурационные файлы ASP.NET проектов во время развёртывания Если в вашей папке находятся файлы Web.config и Web.Release.config, при развёртывани проекта в конфигурации Release MSBuild применит трансформации из Web.Release.config к Web.config. Можно менять атрибуты разделов, добавлять и удалять подразделы. Мы у себя меняли регистрируемый класс, так что на контуре Release запускалась реальная служба уведомлений вместо отладочной. Тогда нас такое решение устроило. Некоторое время всё работало хорошо, но потом вышла 4-я версия Autofac, которая стал совместима с новой системой конфигурирования .NET. При этом разработчики Autofac выпилили поддержку старого способа, то есть старых добрых Web.config и App.config. При этом MSBuild не умеет автоматически трансформировать новые файлы конфигурации. Пришлось переделать схему. Теперь для каждого контура мы стали хранить свою верси конфигурации IoC в файлах IoC.Dev.json, IoC.Stage.json, IoC.Release.json. Загрузка нужного файла конфигурации стала осуществляться так: Startup.cs var configName = Environment.GetEnvironmentVariable("APPSETTING_CONFIG_NAME") ?? "Dev"; var config = new ConfigurationBuilder().AddJsonFile($"IoC.{configName}.json", optional: true, reloadOnChange: true); var module = new ConfigurationModule(config.Build()); builder.RegisterModule(module); Это было стихийное решение, которое мы обнаружили в Google и смогли применить к нашем Azure-проекту. Однако это ещё не идеал с точки зрения 12-тифактороного приложения. Часть настройки действительно вынесена в переменную окружения, но часть находится в файлах IoC.*.json. Что не так? Администратор системы может решить, что файлы IoC.Dev.json, IoC.Stage.json IoC.Release.json можно спокойно менять, хотя на самом деле мы довольно суровы относительно них. Нам не нужна здесь универсальность и гибкость, мы бы хотели ограничить настройку двумя вариантами: а) шлём уведомления; б) складываем уведомления в секретное место. Так что мы можем захардкодить эти две стратегии и при старте приложения выбират ту из них, которая указана в переменной окружения: var notifyStrategy = Environment.GetEnvironmentVariable("APPSETTING_NOTIFY_STRATEGY"); switch (notifyStrategy) { case "send": builder.RegisterType().As(); break; case "save": builder.RegisterType().As(); break; default: throw new ArgumentException("Ну всё теперь.", nameof(notifyStrategy)); } Теперь администратор приложения может конфигурировать его на своём уровне погружения. Он не сломает ничего важного в дебрях XML/JSON IoC. В результате нам удалось полностью избавиться от файлов конфигурации на этом уровне и стать ближе к идеалам 12-тифакторных приложений. Исходя из этого, я бы советовал: Все секретные настройки, включая строки подключения к базам данных, логины и парол для отправки писем и прочее, брать из переменных окружения. Развёртывание приложения свести к запуску одной команды (для .NET это MSBuild). Переменные окружения и процесс развёртывания описать в README.md. Внедрение зависимостей реализовывать непосредственно в коде, предоставляя нескольк стратегий, которыми будет управлять администратор. Он скажет вам спасибо, если ему не придётся изучать детали приложения, и он сможет одной настройкой внедрить совершенно другой набор из трёх-пяти-десяти типов, не разбираясь в их взаимосвязях. Некоторые конфигурационные файлы на самом деле представляют из себя декларативну часть кода и не требуют изменения после развёртывания. Детали зависит от языка, думаю, что чаще это встречается в интерпретируемых языках. Такие файлы мы не должны считать истинно конфигурационными и можем оставить их в проекте как есть. Конфигурацию, которую может менять администратор и которая может сохраняться межд развёртываниями, вынести в БД. Можно и в файл, но в этом случае он может быть уничтожен при неаккуратном развёртывании. Значения в базе или в файле, если их нет при первом запуске системы, прописываются стандартные из кода.

Ответ 3



Я стараюсь делать следующим образом. Сначала программа пытается использовать конфигурацию специализированную для текущего хоста, из файла config-hostname.xml. Вся чувствительна информация хранится в нём, и в git он не попадает благодаря игнорированию по маске config-*.xml. Если же специализированная конфигурация отсутствует, то используется дефолтная из файла config.xml, который сохраняется в репозитории. По истории изменений этого файла очень удобно отслеживать, когда появились те или иные фичи. Часто удобнее, чем искать по сhangelog'у. В тех случаях, когда некоему параметру невозможно придумать осмысленное значени по умолчанию, но в то же время и убирать его совсем из дефолтной конфигурации не хочетс (должно быть понятно, что он вообще есть), я прописываю ему какое-нибудь заведомо невалидное специальное значение (-1 или строку "must be customized"). Программа при работе с таким параметром понимает это значение, выдаёт соответствующую диагностику и выходит.

Ответ 4



Я именно храню sample-версии конфиг-файлов. Чисто для того, чтобы почитать пример, как это может выглядеть. Настоящие конфиги собираются при помощи ansible. Именно он и подставляет критичную информацию типа паролей. Выгоды налицо: централизованное описание и хранение конфигураций, все файлы промаркированы. В ряде случаев для упрощения жизни пользуюсь следующим трюком: пароли dev-конфигураций представляют собой md5 от паролей на production. Как-то так: [mysql] host = "{{ databases_mysql[0].dbhost }}" dbname = "{{ databases_mysql[0].dbname }}" user = "{{ db_users_mysql[0].name }}" password = "{{ mask_pwd | ternary(db_users_mysql[0].password|hash("md5"),db_users_mysql[0].password) }}" , где mask_pwd - логическая переменная, маскировать или нет пароль (устанавливается в зависимости от вида окружения).

Ответ 5



Все описанное ниже относится к spring приложению, первый пункт можно отнести и другим технологиям, если возможно подстановка данных из переменных среды. Есть множество способов задания конфигурации настроек. Но действительно есть определенны чувствительные данные(пароли, credentials и т.д.), которые нельзя хранить в открыто доступе. В этом случае можно использовать вариант хранения скелета настроек в property файлов в git`е, но с подстановками из enviroment переменных. В этом случае чувствительные данные будут прописываться в OC. У первого пункта есть один существенный недостаток - синхронизация таких данных межд различными серверами. Данная ситуация может возникнуть при микросервисной и SOA архитектурах(одно или несколько приложений используют одни credentials). На этот случай есть spring cloud vault, который позволяет ограничить доступ к таким данным через авторизацию приложений. Официальное описание проекта. Spring Cloud Vault Config provides client-side support for externalized configuratio in a distributed system. With HashiCorp’s Vault you have a central place to manage externa secret properties for applications across all environments. Vault can manage static and dynamic secrets such as username/password for remote applications/resources and provide credentials for external services such as MySQL, PostgreSQL, Apache Cassandra, MongoDB, Consul, AWS and more.

Ответ 6



Как способ, наверно, решить эту проблему использование систем непрерывной интеграции которые сами будут тебе создавать конфигурацию с переменной среды. Переменная среды устанавливается в самой системе непрерывной интеграции. TeamCity так может, в других CI думаю присутствует такая возможность. Так же та есть возможность добавлять кроме переменных сред еще и параметры, системные свойства что очень удобно. Ко всему этому удобностью является написание скриптов сборки. Пример генерации конфигурации на примере js файла file=$(cat <

Ответ 7



Я храню дефолтную конфигурацию в файле .env в корне проекта. ENV_database_host=127.0.0.1 Этот файл - один и тот же для всех разработчиков, он попадает в git-репозиторий Файл загружается при запуске docker-compose up -d и значения попадают в контейнер через блок environment в описании сервиса docker services: php: environment: ENV_database_host: "${ENV_database_host}" Затем это значение загружается в параметры приложения на Symfony в файле parameters.yml parameters: database_host: "%env(ENV_database_host)%" И уже параметр database_host используется в сервисах DI контейнера Symfony "как обычно" На удалённом сервере значения из файла .env должны быть перекрыты другими. Для этог в настройках Pipeline CI\CD в репозитории GitLab создаются переменные для каждого из окружений со своим суффиксом. Например ENV_database_host_MASTER для staging и ENV_database_host_PRODUCTION для production. Чтобы на удалённый сервер попала правильная конфигурация, значения переменных⁠-⁠с⁠-⁠суффиксо переносятся в переменные без суффикса, файл docker-compose.yml компилируется с уже перекрытыми значениями и результат копируется на сервер docker-compose -f docker-compose-deploy.yml config > build/docker-compose.yml

Комментариев нет:

Отправить комментарий