Расскажите, кто и как решает проблему хранения конфигурационных файлов в 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
Комментариев нет:
Отправить комментарий