- PVSM.RU - https://www.pvsm.ru -
Всем привет.
Внедрение методики непрерывной интеграции [1] уверено шагает по нашей многострадальной родине и всё больше людей проникаются её идеями и концепциями, что очень хорошо. В данной статье я бы хотел рассказать про прием, который использую на одной из стадий непрерывной интеграции – конфигурирования приложений.

Фотку взял с Yaplakal [2]
Начнем с описания проблемы. Все мы в своей работе используем несколько сред. Как правило это среда разработки (в статье мы будем называть её Dev), среда тестирования (Stage) и продуктивная среда (Prod). Их, кончено, может быть больше. Например локальные машины разработчкиов и тестировщиков тоже являются средами, если на них развернуты ваши приложения. Вопрос конфигурирования среды – это отдельная большая тема, которую мы не будем рассматривать в рамках этой статьи, а рассмотрим вопрос конфигурирования непосредственно самого приложения. В чем сложность? В том, что все ваши приложения общаются с внешними ресурсами, такими как базы данных, FTP, другие приложения и сервисы, и для каждой среды набор таких ресурсов уникальный. То есть если мы, условно, возьмем и полностью скопируем приложение из одной среды в другую, оно не будет работать корректно или вовсе не будет работать. Для того что бы его запустить, нам нужно вручную править конфигурационные файлы, а это противоречит принципам непрерывной интеграции, которая говорит нам, что не должно быть никаких ручных работ, и что все процессы должны быть автоматизированными. Автоматизировать эту работу можно различными способами. Например, хранить конфигурационные файлы от различных стендов в репозитории и подставлять их при деплое. Тут есть несколько проблем. Во-первых, нужно решить как хранить конфиги от прода, можно использовать отдельный закрытый репозиторий, но как тогда разработчики(или кто вообще) будут туда вносить изменения? Во-вторых, в конфигах, как правило, хранятся не только параметры доступа к внешним ресурсам, но и другие параметры, которые не меняются от стенда к стенду. К тому же у вас наверняка не одно приложение, а много, и что если вам понадобится на каком-то стенде поменять строку подключения к базе, то придется править множество файлов и попробуй вспомни какие именно. В итоге поддержка всех конфигурационных файлов в актуальном состоянии становится очень трудоемкой.
Конечно, существуют различные приложения платные и не очень, позволяющие решить проблему конфигураций. Вы вольны выбрать любой удобный для вас способ. Но я бы хотел рассказать о наиболее изящном, на мой взгляд, и простом способе конфигурирования приложения. В плане языков программирования я использую PowerShell и Python, потому что в моем зоопарке используются .NET-приложения, но думаю создать подобную систему для вашего стека не составит труда. Тут важнее идея, а не технология.
Конечно идея не нова и не моя, она прекрасно описана в фундаментальном труде по непрерывной интеграции [3]. Вот несколько принципов управления конфигурациями приложений оттуда:
• Проанализируйте, в какой точке жизненного цикла приложения лучше ввести в него порцию конфигурационной информации — в момент сборки, когда упаковывается релиз-кандидат, во время развертывания или установки, в момент запуска или во время выполнения. Поговорите с администраторами и командой техподдержки, чтобы выяснить их потребности.
• Храните доступные конфигурационные параметры приложения в том же месте, в котором находится исходный код, однако значения храните в другом месте. Жизненные циклы конфигурационных параметров и кода совершенно разные, а пароли и другая секретная информация вообще не должны регистрироваться в системе управления версиями.
• Конфигурирование всегда должно быть автоматическим процессом, использующим значения, извлеченные из хранилища конфигураций, чтобы в любой момент можно было идентифицировать конфигурацию каждого приложения в любой среде.
• Система конфигурирования должна предоставлять приложению (а также сценариям упаковки, установки и развертывания) разные значения в зависимости от версии приложения и среды, в котором оно развернуто. Каждый человек должен иметь возможность легко увидеть, какие конфигурационные параметры доступны для данной версии приложения во всех средах развертывания.
• Применяйте соглашения об именовании конфигурационных параметров. Избегайте непонятных, неинформативных имен. Представьте себе человека, читающего конфигурационный файл без документации. Глядя на имя конфигурационного свойства, он должен понять, для чего оно предназначено.
• Инкапсулируйте конфигурационную информацию и создайте для нее модульную структуру, чтобы изменения в одном месте не повлияли на другие части конфигурации.
• Не повторяйтесь. Определяйте элементы конфигурации таким образом, чтобы каждая концепция была представлена в наборе конфигурационных свойств только один раз.
• Будьте минималистом. Конфигурационная информация должна быть как можно более простой и сосредоточенной на сущности решаемой задачи. Не создавайте ненужных конфигурационных свойств.
• Не усложняйте систему конфигурирования. Как и конфигурационная информация, она должна быть как можно более простой.
• Создайте тесты конфигураций, выполняемые во время развертывания или установки. Проверьте доступность служб, от которых зависит приложение. Применяйте дымовые тесты, дабы убедиться, что каждая функция приложения, зависящая от конфигурационных параметров, правильно воспринимает их.
Отмечу, что в своем решении я не учитываю версию приложения, по крайней мере в явную, на практике мне это не понадобилось, хотя добавить такую возможность не трудно. Так же я не рассматриваю вопрос тестирования конфигураций, так как это отдельная тема.
Итак, основные шаги:
Тут важно отметить, что на все стенды надо поставлять одинаковый набор файлов, т.е. изначально все приложения одинаковые, а их настройка происходит уже непосредственно на целевом сервере.
Теперь подробнее про процесс конфигурирования:

{
"appName":"MegaApp",
"fileName":"Web.config",
"changes":[
{
"path":"configurations/navigation/sections/add",
"filter":"name=OtherApp",
"target":"link",
"sourceName":"LinkToOtherApp"
},
{
"path":"configurations/connections/add",
"filter":"name=MainDB",
"target":"ConnectionString",
"sourceName":"MainDBConnectionString"
}
]
}
appName – название приложения;
fileName – название файла конфигурации приложения, который мы будем изменять;
changes – массив изменений, который необходимо вносить в файл конфигурации при перемещении приложения с одного стенда на другой;
path — путь к тэгу в конфигурационном файле. (В моем случае конфигурационный файл имеет формат xml);
filter – используется когда по указанному пути path имеется несколько одинаковых тэгов, а нам нужно взять конкретный. Мы можем отфильтровать его по значению какого-либо параметра;
target — параметр тега, который мы будем менять;
sourceName – некий псевдоним по которому мы будем определять подставляемое значение из файла настроек стенда.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<navigation>
<sections>
<add name="OtherApp" text="Приложение1" link="http://OtherApp_dev.com" />
<add name="NotChangeApp" text="Хабр" link="http://habrahabr.ru" />
</sections>
</navigation>
<connectionStrings>
<add name="MainDb" connectionString="data source=maindb-server;Initial Catalog=MAINDB;User ID=user;Password=pass" providerName="System.Data.SqlClient" />
<add name="NotChangeDb" connectionString="data source=localhost;Initial Catalog=DB;User ID=user;Password=pass;" providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
/ConfigStorage
/jsons
/stand_dev.json
/stand_stage.json
/stand_prod.json
Есть конфигурационный файл сервиса:
{
"keys_storage":[
{
"key":"secret_dev",
"pathToFile":"/jsons/stand_dev.json "
},
{
"key":"secret_stage",
"pathToFile":"/jsons/stand_stage.json "
},
{
"key":"secret_prod",
"pathToFile":"/jsons/stand_prod.json "
}
]
}
Соответственно, что бы получить данные того или иного файла нужно отправить GET-запрос с нужным ключом:
http://ConfigStorage/ConfigStorage.py?key=secret_stage
Безопасность в моем случае достигается за счет закрытого сегмента сети и доменной политики, поэтому с шифрованием я не стал заморачиваться, хотя если такая необходимость есть это тоже можно реализовать.
Файл параметров стенда выглядит примерно так:
{
"stand":"Stage",
"settings":[
{
"appName":"default",
"sources":[
{
"name":"LinkToOtherApp",
"value":"http://OtherApp_stage.com"
},
{
"name":"MainDBConnectionString",
"value":"data source=maindb-stage-server;Initial Catalog=MAINDB;User ID=user;Password=pass"
}
]
},
{
"appName":"MegaApp2",
"sources":[
{
"name":"LinkToOtherApp",
"value":"http://OtherApp_stage2.com"
}
]
}
]
}
stand — название стенда;
settings – массив настроек;
appName — название приложения. Здесь смысл вот в чем: по умолчанию во все конфигурационные файлы приложений подставляются значения из раздела где appName равен “default”, но если нам для какого-то конкретного приложения нужно иное значение, то мы создаем дополнительный раздел с appName, где переопределяем это значение. В моем примере для приложения MegaApp в тэг с именем OtherApp подставится значение
http://OtherApp_stage.com
, а для MegaApp2 – значение
http://OtherApp_stage2.com.
sources — массив имен псевдонимов (name) и их значений (value)
Вот что это дает:
ЗЫ: Исходники, к сожалению, выложить не могу, так как они являются собственностью компании, но на написание статьи я потратил намного больше времени, чем на написание самих скриптов.
Автор: lepism
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/129134
Ссылки в тексте:
[1] непрерывной интеграции: https://ru.wikipedia.org/wiki/Непрерывная_интеграция
[2] Yaplakal: http://www.yaplakal.com/forum2/topic281720.html?sa=X&ved=0CCkQ9QEoADAPOChqFQoTCJuXpe-9-sYCFYjRFAodYdEBbg
[3] непрерывной интеграции: http://www.ozon.ru/context/detail/id/7243884/
[4] Источник: https://habrahabr.ru/post/302726/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.