Самоконфигурирующиеся програми

Всім привіт.
Впровадження методики безперервної інтеграції впевнено крокує по нашій багатостраждальній батьківщині і все більше людей переймаються її ідеями і концепціями, що дуже добре. У даній статті я б хотів розповісти про прийом, який використовую на одній із стадій безперервної інтеграції – конфігурування програм.


Фотку взяв з Yaplakal


Проблема
Почнемо з опису проблеми. Всі ми в своїй роботі використовуємо кілька середовищ. Як правило це середовище розробки (у статті ми будемо називати її Dev), середовище тестування (Stage) і продуктивна середовище (Prod). Їх, звичайно, може бути більше. Наприклад локальні машини разработчкиов і тестувальників теж є середовищами, якщо на них розгорнуті ваші програми. Питання конфігурування середовища – це окрема велика тема, яку ми не будемо розглядати в цій статті, а розглянемо питання конфігурування безпосередньо самого додатка. У чому складність? В тому, що всі ваші додатки спілкуються з зовнішніми ресурсами, такими як бази даних, FTP, інші програми і сервіси, і для кожної середовища набір таких ресурсів унікальний. Тобто якщо ми, умовно, візьмемо і повністю скопіюємо додаток з одного середовища в іншу, воно не буде працювати коректно або зовсім не буде працювати. Для того що б його запустити, нам потрібно вручну правити конфігураційні файли, а це суперечить принципам безперервної інтеграції, яка говорить нам, що не повинно бути ніяких ручних робіт, і що всі процеси повинні бути автоматизованими. Автоматизувати цю роботу можна різними способами. Наприклад, зберігати конфігураційні файли від різних стендів в репозиторії і підставляти їх при деплое. Тут є кілька проблем. По-перше, потрібно вирішити зберігати конфіги від прода, можна використовувати окремий закритий репозиторій, але як тоді розробники(або хто взагалі) будуть туди вносити зміни? По-друге, в конфігах, як правило, зберігаються не тільки параметри доступу до зовнішніх ресурсів, але і інші параметри, які не змінюються від стенду до стенду. До того ж у вас напевно не один додаток, а багато, і що якщо вам знадобиться на якомусь стенді поміняти рядок підключення до бази, то доведеться правити безліч файлів і спробуй згадай які саме. У результаті підтримка всіх конфігураційних файлів в актуальному стані стає дуже трудомістким.
Звичайно існують різні додатки платні і не дуже, що дозволяють вирішити проблему конфігурацій. Ви вільні вибрати будь-який зручний для вас спосіб. Але я б хотів розповісти про найбільш витонченому, на мій погляд, і простому способі конфігурування програми. У плані мов програмування я використовую PowerShell і Python, тому що в моєму зоопарку використовуються .NET-додатки, але думаю створити подібну систему для вашого стека не складе праці. Тут важливіше ідея, а не технологія.

натхнення
Звичайно, ідея не нова і не моя, вона чудово описана у фундаментальній праці з безперервної інтеграції. Ось кілька принципів управління конфігураціями додатків звідти:
• Проаналізуйте, в якій точці життєвого циклу програми краще ввести в нього порцію конфігураційної інформації — в момент складання, коли упаковується реліз-кандидат, під час розгортання чи установки, в момент запуску або під час виконання. Поговоріть з адміністраторами та командою техпідтримки, щоб з'ясувати їх потреби.
• Зберігайте доступні конфігураційні параметри програми в тому ж місці, в якому знаходиться вихідний код, проте значення зберігайте в іншому місці. Життєві цикли конфігураційних параметрів і коду абсолютно різні, а паролі та інша секретна інформація взагалі не повинні реєструватися в системі управління версіями.
• Конфігурування завжди повинно бути автоматичним процесом, що використовує значення, отримані з сховища конфігурацій, щоб у будь-який момент можна було ідентифікувати конфігурацію кожного додатка в будь-якому середовищі.
• Система конфігурування повинна надавати додатком (а також сценаріями упаковки, установки і розгортання) різні значення залежно від версії програми і середовища, в якому воно розгорнуто. Кожна людина повинна мати можливість легко побачити, які конфігураційні параметри доступні для даної версії програми у всіх середовищах розгортання.
• Застосовуйте угоди про іменування конфігураційних параметрів. Уникайте незрозумілих, неінформативних імен. Уявіть собі людину, яка читає конфігураційний файл без документації. Дивлячись на ім'я конфігураційного властивості, він повинен зрозуміти, для чого воно призначене.
• Инкапсулируйте конфігураційну інформацію і створіть для неї модульну структуру, щоб зміни в одному місці не вплинули на інші частини конфігурації.
• Не повторяйтесь. Визначайте елементи конфігурації таким чином, щоб кожна концепція була представлена у наборі конфігураційних властивостей тільки один раз.
• Будьте мінімалістом. Конфігураційна інформація повинна бути як можна більш простий і зосередженої на сутності розв'язуваної задачі. Не створюйте непотрібних конфігураційних властивостей.
• Не ускладнюйте систему конфігурування. Як і конфігураційна інформація, вона повинна бути як можна більш простий.
• Створіть тести конфігурацій, що виконуються під час розгортання або установки. Перевірте доступність служб, від яких залежить додаток. Застосовуйте димові тести, щоб переконатися, що кожна функція програми, залежить від конфігураційних параметрів, правильно сприймає їх.
Зазначу, що у своєму рішенні я не враховую версію програми, по крайней мере в явну, на практиці мені це не знадобилося, хоча додати таку можливість не важко. Так само я не розглядаю питання тестування конфігурацій, так як це окрема тема.

Реалізація
Отже, основні кроки:
  • Збираємо додаток на сервері інтеграції, проганяємо тести, на виході отримуємо робоче додаток налаштоване «за замовчуванням», наприклад на середу Dev;
  • Доставляємо файли цього додатка на сервер (або сервера);
  • Запускаємо процес конфігурації програми;
  • Запускаємо додаток.
Тут важливо відзначити, що на всі стенди треба поставляти однаковий набір файлів, тобто спочатку всі додатки однакові, а їх налаштування відбувається вже безпосередньо на цільовому сервері.

Тепер докладніше про процес конфігурування:



  1. Сервер безперервної інтеграції після доставки цільової програми на стенд викликає PowerShell-скрипт Configurator.ps1, який поставляється разом з додатком. В якості параметрів в нього передається шлях до файлу змін Webconfig.json і шлях до джерела параметрів стенду (детальніше трохи нижче).
  2. Файл змін розташовується в тій же директорії, що і файл конфігурації додатка Web.config (і в тому ж репозиторії). Вміст файлу – це опис тих тегів і їх параметрів у конфігураційному файлі, які підлягають зміні.
    {
    "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 – якийсь псевдонім, за яким ми будемо визначати підставляється значення з файлу налаштувань стенду.

    Приклад файлу Web.config
    <?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>
    


  3. Тепер про джерело параметрів стенду. У своєму рішенні я можу використовувати в якості другого параметра скрипта Configuration.ps1 три різних типи значень. Перший – це шлях до json-файлу параметрів стенду, другий – це URL до web-сервісу зберігання конфігурацій (ConfigStorage), який повертає json-об'єкт, і третій — шлях до гілки реєстру на локальній машині в якій міститься URL до ConfigStorage. Фокусом з реєстром я вирішив дві задачі: перша – не хотілося зберігати URL сервісу на CI-сервері, так як від параметрів GET-запиту залежить налаштування якогось стенду поверне сервіс. А друга – хотілося однаковості команд на CI-сервері, тобто зробити так, що б «як сконфигурироваться» визначав сам сервер, а не сервер безперервної інтеграції.
  4. Сервіс зберігання конфігурацій (ConfigStorage) я написав на Python. Суть його дуже проста: є json-файли параметрів стендів, які поміщені в певну директорію:
    /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",
    "джерела":[
    {
    "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",
    "джерела":[
    {
    "name":"LinkToOtherApp",
    "value":"http://OtherApp_stage2.com"
    }
    ]
    }
    ]
    }
    

    stand — назва стенду;
    settings – масив параметрів;
    appName — назва додатка. Тут сенс ось у чому: за замовчуванням у всі конфігураційні файли додатків отримує значення з розділу де appName дорівнює «default», але якщо нам для якогось конкретного додатка потрібно інше значення, то ми створюємо додатковий розділ з appName, де перевизначаємо це значення. В моєму прикладі програми MegaApp в тег з ім'ям OtherApp підставиться значення OtherApp_stage.com, а для MegaApp2 – значення OtherApp_stage2.com.sources — масив імен, псевдонімів (name) та їх значень (value)
  5. І останнім кроком в конфігураційний файл програми Web.config підставляються значення згідно файлу змін Webconfig.json з файлу параметрів стенду stand_stage.json.
  6. Profit.
Навіщо все це потрібно?
Ось що це дає:
  • У вас з'являється повне і актуальне опис того, які параметри програми змінюються в залежності від стенду;
  • У вас з'являється повне і актуальне опис всіх параметрів всіх стендів;
  • Оскільки ці описи містяться в системі контролю версій, ви отримуєте повну історію змін та причин цих змін;
  • Ви отримуєте єдину точку всіх змін, вносити правки потрібно тільки в одному місці;
  • Ви отримуєте гарантію, що всі конфігураційні файли на всіх стендах однакові. Улюблена відмовка розробників «у мене на компі працює, значить, це ви щось не так законфигурили» перестає працювати;
  • Плюшка нетехнічного характеру, в тому, що проводиться чітка лінія поділу відповідальності сторін. Розробники відповідають за коректність конфігураційного файлу та файлу змін, інженери за коректність файлів параметрів стенду. Вододіл проходить на рівні репозиторіїв.
  • Тестувальники для своїх дослідів можуть створити самі збочені файли параметрів стендів і миттєво перемикати всі додатки на ту чи іншу конфігурацію;
  • Звичайно, найбільше задоволення ви отримаєте, коли у вашому зоопарку багато різних додатків, які так чи інакше пов'язані між собою. Я до сих пір розтягуюсь в задоволеній усмішці, коли вся ця ватага дружним строєм приймає потрібне мені становище в один клік.


ЗИ: Исходники, на жаль, викласти не можу, так як вони є власністю компанії, але на написання статті я витратив набагато більше часу, ніж на написання самих скриптів.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.