Процес розробки та тестування з Docker та Gitlab CI

Пропоную ознайомитись із розшифровкою доповіді Олександра Сігачова з Inventos «Процес розробки та тестування з Docker + Gitlab CI»

Ті, хто тільки починає впроваджувати процес розробки та тестування на базі Docker+Gitlab CI, часто запитують базові питання. З чого почати? Як організувати? Як тестувати?

Ця доповідь хороша тим, що структуровано розповідає про процес розробки та тестування за допомогою Docker та Gitlab CI. Сама доповідь 2017 року. Думаю, що з цієї доповіді можна почерпнути основи, методологію, ідею, досвід використання.

Кому цікаво, прошу під кат.

Мене звуть Олександр Сігачов. Працюю в компанії Inventos. Розповім про свій досвід використання Docker і про те, як його потроху впроваджуємо на проектах у компанії.

Тема доповіді: Процес розробки за допомогою Docker та Gitlab CI.

Процес розробки та тестування з Docker та Gitlab CI

Це моя друга доповідь про Docker. На момент першої доповіді ми використовували Docker лише у Development на машинах розробників. Кількість співробітників, які використовували Docker, було близько 2-3 осіб. Поступово був накопичений досвід, і ми трохи просунулися далі. Посилання на наш перша доповідь.

Що буде у цій доповіді? Ми поділимося досвідом про те, які граблі зібрали, які проблеми як вирішили. Не скрізь це було гарно, але дозволило рушити далі.

Наш девіз: докеруй все, до чого доходять наші руки.

Процес розробки та тестування з Docker та Gitlab CI

Які проблеми вирішуємо?

Коли компанії кілька команд, то програміст є шареным ресурсом. Бувають етапи, коли програміста висмикують з одного проекту і дають на якийсь час в інший проект.

Щоб програміст швидко вникнув йому необхідно завантажити вихідний код проекту і якнайшвидше запустити середовище, яке дозволить йому далі просуватися вирішуючи завдання даного проекту.

Зазвичай якщо починати з нуля, то документації в проекті ведеться мало. Інформація про те, як налаштувати, є лише у старожилів. Самостійно співробітники настроюють своє робоче місце за один-два дні. Щоб прискорити це, ми застосували Docker.

Наступна причина – це стандартизація налаштувань у Development. На мій досвід, розробники завжди виявляють ініціативу. У кожному п'ятому випадку вводиться кастомний домен, наприклад vasya.dev. Поруч сидить сусід Петя, який має домен petya.dev. Вони розробляють сайт або компонент системи, використовуючи це доменне ім'я.

Коли система розростається і ці доменні імена починають потрапляти в конфігурації, виникає конфлікт Development оточень і переписується шлях сайту.

Те саме відбувається з налаштуваннями бази даних. Хтось не морочиться безпекою і працює з порожнім паролем root. У когось на етапі установки MySQL вимагав пароль і пароль виявився один 123. Часто трапляється, що конфіг бази даних постійно змінювався залежно від commit розробника. Хтось виправив, хтось не поправив конфіг. Були хитрощі, коли ми виносили якийсь тестовий конфіг у .gitignore і кожен розробник мав установлювати базу даних. Це ускладнювало процес старту. Потрібно також пам'ятати про базу даних. Базу даних треба ініцілізувати, треба прописати пароль, треба прописати користувача, створити табличку і таке інше.

Ще одна з проблем – це різні версії бібліотек. Часто буває, що розробник працює з різними проектами. Є Legacy проект, який розпочинали п'ять років тому (від 2017 року – прим. ред.). На момент старту стартували з MySQL 5.5. Є й сучасні проекти, де ми намагаємося впроваджувати вже сучасніші версії MySQL, наприклад 5.7 або старше (у 2017 році — прим. ред.)

Той, хто працює з MySQL знає, що ці бібліотеки тягнуть за собою залежності. Досить проблематично запустити дві бази разом. Принаймні старі клієнти проблематично підключити до нової бази даних. Це породжує своєю чергою кілька проблем.

Наступна проблема — коли розробник працює на локальній машині, він використовує локальні ресурси, локальні файли, локальне ОЗУ. Вся взаємодія на момент розробки розв'язання задач виконується в рамках того, що це працює на одній машині. Прикладом може бути, коли у нас в Production 3 backend-сервера, а розробник зберігає файли в кореневий каталог і звідти nginx бере файли відповіді запит. Коли такий код потрапляє в Production, виходить, що файл присутній на одному з 3 серверів.

Зараз розвивається напрямок мікросервісів. Коли ми ділимо наші великі програми на якісь невеликі компоненти, які взаємодіють між собою. Це дозволяє під конкретний стек завдань вибрати технології. Також це дозволяє розділити роботу і зону відповідальності між розробниками.

Frondend-розробник, розробляючи на JS, мало впливає на Backend. Backend-розробник у свою чергу розробляє, у нашому випадку, Ruby on Rails і не заважає Frondend. Взаємодія виконується за допомогою API.

Як бонус за допомогою Docker нам вдалося утилізувати ресурси на Staging. Кожен проект через свою специфіку вимагав певні налаштування. Фізично потрібно було виділяти або за віртуальним сервером і окремо їх налаштовувати, або ж ділити якесь змінне оточення та проекти могли залежно від версії бібліотек впливати один на одного.

Процес розробки та тестування з Docker та Gitlab CI

Інструменти. Що ми використовуємо?

  • Саме сам Docker. У Dockerfile описуються залежності однієї програми.
  • Docker-compose - це зв'язка, яка об'єднує кілька наших Docker додатків.
  • GitLab ми використовуємо для збереження вихідного коду.
  • GitLab-CI ми використовуємо для системної інтеграції.

Процес розробки та тестування з Docker та Gitlab CI

Доповідь складається із двох частин.

Перша частина розповість, як запускали Docker на машинах розробників.

Друга частина розповість про те, як взаємодіяти з GitLab, як ми запускаємо тести та як ми викочуємо на Staging.

Процес розробки та тестування з Docker та Gitlab CI

Docker – це технологія, яка дозволяє (використовуючи декларативний підхід) описати необхідні компоненти. Це приклад Dockerfile. Тут ми оголошуємо, що ми успадковуємося від офіційного Docker-образу Ruby:2.3.0. Він містить встановлений Ruby версії 2.3. Ми встановлюємо необхідні бібліотеки складання та NodeJS. Описуємо, що створюємо каталог /app. Призначаємо каталог app робочою директорією. У цей каталог розміщуємо необхідний мінімальний Gemfile і Gemfile.lock. Потім виконуємо збирання проектів, які встановлюють цей образ залежності. Вказуємо, що контейнер готовий слухати на зовнішньому порту 3000. Остання команда — це команда, яка безпосередньо запускає наш додаток. Якщо ми виконаємо команду запуску проекту, програма спробує виконатися і запустить зазначену команду.

Процес розробки та тестування з Docker та Gitlab CI

Це мінімальний приклад файлу docker-compose. У разі ми показуємо, що відбувається зв'язок двох контейнерів. Це безпосередньо обслуговування бази даних і сервіс web. Наші веб-програми в більшості випадків вимагають як backend для зберігання даних якусь базу даних. Так як ми використовуємо MySQL, то приклад з MySQL – але ніщо не заважає використовувати якусь другу базу даних (PostgreSQL, Redis).

Ми беремо з офіційного джерела з Docker hub образ MySQL 5.7.14 без змін. Образ, який відповідає за наш web-додаток, ми збираємо з поточної директорії. Він під час першого запуску збирає образ. Після чого запускає команду, яку ми тут виконуємо. Якщо ми повернемося назад, то побачимо, що була визначена команда запуску через Puma. Puma – сервіс, написаний на Ruby. У другому випадку ми перевизначаємо. Ця команда може бути довільною залежно від наших потреб чи завдань.

Також ми описуємо, що потрібно прокинути порт на нашій хост-машині розробника з 3000 на 3000 порт контейнера. Це виконується автоматично за допомогою iptables та свого механізму, який безпосередньо закладений у Docker.

Розробник може також як і раніше звернутися на будь-яку доступну IP-адресу, наприклад, 127.0.0.1 локальну або зовнішню IP-адресу машини.

Останній рядок каже, що контейнер web залежить від контейнера db. Коли ми викликаємо запуск контейнера web попередньо docker-compose запустить нам базу даних. Вже по старту бази даних (насправді після запуску контейнера! Готовності БД це не гарантує) запустить нам додаток, наш backend.

Це дозволяє уникнути помилок, коли база даних не піднята і дозволяє економити ресурси, коли ми зупиняємо контейнер баз даних, звільняючи цим ресурси під інші проекти.

Процес розробки та тестування з Docker та Gitlab CI

Що дає використання докеризації бази даних на проекті. Ми у всіх розробників фіксуємо версію MySQL. Це дозволяє уникнути деяких помилок, які можуть виникнути під час розходження версій, коли змінюється синтаксис, конфігурація, дефолтні настройки. Це дозволяє вказати загальні hostname для бази даних, login, password. Відходимо від того зоопарку імен та конфліктів до config-файлів, які були раніше.

Ми маємо можливість використовувати більш оптимальний config для Development середовища, яке відрізнятиметься від дефолтного. MySQL за замовчуванням налаштований на слабкі машини та його продуктивність із коробки дуже низька.

Процес розробки та тестування з Docker та Gitlab CI

Docker дозволяє використовувати інтерпретатор Python, Ruby, NodeJS, PHP потрібної версії. Ми позбавляємось необхідності використовувати якийсь менеджер версій. Раніше для Ruby використали rpm-пакет, який дозволяв змінювати версію залежно від проекту. Також це дозволяє завдяки Docker-контейнеру плавно мігрувати код і версіонувати разом залежностями. У нас немає проблеми зрозуміти версію як інтерпретатора, так і коду. Для оновлення версії необхідно опустити старий контейнер та підняти новий контейнер. Якщо щось пішло не так, ми можемо опустити новий контейнер, підняти старий контейнер.

Після складання образу контейнери як у Development так і в Production будуть однаковими. Це особливо актуально для великих інсталяцій.

Процес розробки та тестування з Docker та Gitlab CI На Frontend ми використовуємо JavaScipt та NodeJS.

Нині останній проект у нас на ReacJS. Розробник запускав усі контейнери та розробляв використовуючи hot-reload.

Далі запускається завдання зі складання JavaScipt і код, зібраний у статику, віддається через nginx заощаджуючи ресурси.

Процес розробки та тестування з Docker та Gitlab CI

Тут я навів схему нашого останнього проекту.

Які завдання вирішували? У нас виникла потреба побудувати систему, з якою взаємодіє мобільні пристрої. Вони одержують дані. Одна з можливостей надіслати push-повідомлення в цей пристрій.

Що ми зробили для цього?

Ми розділили на додаток такі компоненти, як: адмінська частина на JS, backend, який працює через REST-інтерфейс під Ruby on Rails. Backend взаємодіє із базою даних. Результат, який генерується, віддаються клієнту. Адмінка з backend та базою даних взаємодіє за REST-інтерфейсом.

Також у нас була необхідність відправляти Push повідомлення. До цього ми мали проект, в якому було реалізовано механізм, який відповідає за доставку повідомлень на мобільні платформи.

Ми розробили таку схему: оператор з браузера взаємодіє з адмінкою, адмінка взаємодіє з backend, ставиться завдання, що треба надіслати Push повідомлення.

Push повідомлення взаємодіють з іншим компонентом, реалізованим на NodeJS.

Будуються черги і далі йде за своїм механізмом відправлення повідомлень.

Тут намальовано дві бази даних. На даний момент у нас за допомогою Docker використовуються дві незалежні бази даних, які ніяк не пов'язані з собою. Крім того, що вони мають спільну віртуальну мережу, а фізичні дані зберігаються в різних каталогах на машині розробника.

Процес розробки та тестування з Docker та Gitlab CI

Те ж саме в цифрах. Тут важливе перевикористання коду.

Якщо раніше ми говорили про перевикористання коду у вигляді бібліотек, то в даному прикладі наш сервіс, який відповідає Push-повідомленню, перевикористовується як повністю сервер. Він надає API. А з ним взаємодіє вже наша нова технологія.

На той момент ми використали 4 версію NodeJS. Зараз (у 2017 році — прим. ред.) у нових розробках ми використовуємо 7 версію NodeJS. Немає проблем у нових компонентах залучати нові версії бібліотек.

При необхідності можна провести рефакторинг та підняти версію NodeJS у сервісу Push повідомлень.

А якщо ми зможемо зберегти сумісність API, то можна буде його замінити на інших проектах, які використовувалися раніше.

Процес розробки та тестування з Docker та Gitlab CI

Що потрібно, щоб додати Docker? Додаємо до нашого репозиторію Dockerfile, який описує необхідні залежності. У цьому прикладі компоненти розбиті за логікою. Це мінімальний набір розробника backend.

Під час створення нового проекту створюємо Dockerfile, описуємо потрібну екосистему (Python, Ruby, NodeJS). У docker-compose описує необхідну залежність – базу даних. Описуємо що потрібна база такої версії, зберігати дані там.

Ми використовуємо для віддачі статики окремий третій контейнер із nginx. Передбачена можливість завантаження зображень. Backend кладе їх у заздалегідь підготовлений том, який також змонтований у контейнер з nginx, який віддає статику.

Щоб зберегти конфігурацію nginx, mysql ми додали папку Docker, де зберігаємо необхідні конфіги. Коли розробник робить git clone репозиторію собі на машину, у нього виходить вже готовий проект для локальної розробки. Не виникає питання, який порт або які налаштування застосувати.

Процес розробки та тестування з Docker та Gitlab CI

Далі у нас є кілька компонентів: адмін, інформ-API, push-сповіщення.

Для того, щоб це все запустити, ми створили ще один репозиторій, які назвали dockerized-app. На даний момент ми використовуємо кілька репозиторіїв кожного компонента. Вони просто логічно відрізняються – у GitLab це виглядає як папка, а на машині розробника папка під конкретний проект. На рівень нижче лежать компоненти, які об'єднуватимуться.

Процес розробки та тестування з Docker та Gitlab CI

Це приклад саме вмісту dockerized-app. Ми також виносимо сюди Docker каталог, в якому наповнюємо конфігурації, потрібні для взаємодії всіх компонентів. Є README.md, в якому коротко описано, як запускати проект.

Тут ми застосували два файли docker-compose. Це зроблено для того, щоб мати можливість запускати східчасто. Коли розробник працювати з ядром, йому не потрібні Push-сповіщення, то він запускає просто файл docker-compose і відповідно ресурс економиться.

Якщо є необхідність інтеграції з Push-повідомленнями, запускається docker-compose.yaml і docker-compose-push.yaml.

Оскільки docker-compose.yaml та docker-compose-push.yaml лежать у папці, то автоматично створюється єдина віртуальна мережа.

Процес розробки та тестування з Docker та Gitlab CI

Опис компонентів. Це більш розширений файл, який відповідає за збір компонентів. Що тут чудово? Тут ми вводимо компонент балансера.

Це готовий Docker-образ, в якому запускається nginx та програма, яка слухає Docker socket. Динамічні, у міру включення та вимкнення контейнерів, перегеніює конфіг nginx. Поводження з компонентами ми розносимо за доменними іменами третього рівня.

Для Development середовища ми використовуємо домен .dev - api.informer.dev. Програми з доменом .dev доступні на локальній машині розробника.

Далі передатися конфіги до кожного проекту та запускається всі проекти разом одночасно.

Процес розробки та тестування з Docker та Gitlab CI

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

Балансер по доменному імені визначає, до якого контейнера потрібно звернутися.

Це може бути nginx, який дає JS адмінки. Це може nginx, який віддає API або статичні файли, які віддаються nginx у вигляді завантаження картинок.

На схемі видно, що контейнери об'єднані віртуальну мережу і приховані за проксі.

На машині розробника можна звернутися до контейнера, знаючи IP, але ми в принципі це не застосовуємо. Необхідності прямого звернення практично немає.

Процес розробки та тестування з Docker та Gitlab CI

Який приклад подивитися, щоб докеризувати свою програму? На мою думку хорошим прикладом є офіційний docker образ для MySQL.

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

У hub.docker.com зазвичай є посилання на github.com, де наведені безпосередньо сирі дані, з яких можна самостійно зібрати образ.

Далі в цьому репозиторії є скрипт docker-endpoint.sh, який відповідає за первинну ініціалізацію і за подальшу обробку запуску програми.

Також у цьому прикладі є можливість конфігурування за допомогою змінних оточення. Визначаючи змінне оточення при запуску одиночного контейнера або через docker-compose можна сказати, що нам потрібно задати порожній пароль для docker на root на MySQL або ж якийсь, який ми хочемо.

Є варіант створити рандомний пароль. Ми говоримо, що нам потрібен користувач, потрібно встановити користувачеві пароль і потрібно створити базу даних.

У своїх проектах ми трохи уніфікували Dockerfile, який відповідає за ініціалізацію. Там ми поправили під свої потреби зробити просто розширення прав користувача, яку використовує програму. Це дозволило надалі просто створити базу даних із консолі програми. У програмах Ruby є команда створення, зміни та видалення баз даних.

Процес розробки та тестування з Docker та Gitlab CI

Цей приклад того, як виглядає конкретна версія MySQL на github.com. Dockerfile можна відкрити і подивитися, як там відбувається установка.

docker-endpoint.sh скрипт, який відповідає за точку входу. При первинній ініціалізації потрібні деякі дії з підготовки та всі ці дії винесені якраз у скрипт ініціалізації.

Процес розробки та тестування з Docker та Gitlab CI

Переходимо до другої частини.

Для зберігання вихідних кодів ми перейшли на gitlab. Це досить сильна система, яка має візуальний інтерфейс.

Один із компонентів Gitlab це Gitlab CI. Він дозволяє описувати наслідування команд, які згодом будуть використовуватися для того, щоб організувати систему доставки коду або запуску автоматичного тестування.

Доповідь по Gitlab CI 2 https://goo.gl/uohKjI - Доповідь з Ruby Russia club - досить докладний і можливо він вас зацікавить.

Процес розробки та тестування з Docker та Gitlab CI

Зараз ми з вами розглянемо, що потрібно для того, щоб активувати Gitlab CI. Для того, щоб запустити Gitlab CI, нам достатньо в корінь проекту покласти файлик .gitlab-ci.yml.

Тут ми описуємо що хочемо виконувати послідовність станів типу тесту, деплою.

Виконуємо скрипти, які викликають безпосередньо docker-compose складання нашої програми. Це приклад саме backend.

Далі ми говоримо, що необхідно прогнати міграції зі зміни бази даних та виконати тести.

Якщо скрипти виконуються коректно і не повертає код помилки, то система переходить до другої стадії деплою.

Стадія деплою зараз реалізована для staging. Ми не організовували безпростий перезапуск.

Ми примусово гасимо всі контейнери, а потім усі контейнери піднімаємо заново, зібрані на першому етапі під час тестування.

Проганяємо вже для поточного змінного оточення міграції баз даних, написаних розробниками.

Є позначка, що застосовувати це тільки для гілки майстер.

При зміні інших гілок не виконується.

Є можливість організувати викочування по гілках.

Процес розробки та тестування з Docker та Gitlab CI

Щоб надалі це організувати, нам потрібно встановити Gitlab Runner.

Це утиліта написана на Golang. Вона є одиничним файлом, як це прийнято в світі Golang, які не потрібно ніяких залежностей.

Під час запуску ми реєструємо Gitlab Runner.

Отримуємо у web-інтерфейсі Gitlab ключ.

Потім викликаємо команду ініціалізації у командному рядку.

Налаштовуємо Gitlab Runner у діалоговому режимі (Shell, Docker, VirtualBox, SSH)

Код Gitlab Runner буде виконувати при кожному коміті в залежності від налаштування .gitlab-ci.yml.

Процес розробки та тестування з Docker та Gitlab CI

Як це візуально виглядає у Gitlab у web-інтерфейсі. Після того, як підключили GItlab CI, у нас з'являється прапор, який показує в якому стані знаходиться білд на поточний момент.

Ми бачимо, що ось 4 хвилин тому був зроблений коміт, який пройшов усі тести і проблем не викликав.

Процес розробки та тестування з Docker та Gitlab CI

Ми можемо детальніше подивитися по білдах. Тут ми бачимо, що вже пройшли два стани. Стан тестування та стан деплою на staging.

Якщо ми клацнемо на конкретний білд, то там буде консольний висновок команд, які були запущені в процесі згідно .gitlab-ci.yml.

Процес розробки та тестування з Docker та Gitlab CI

Так виглядає історія нашого продукту. Ми бачимо, щоб були вдалі спроби. Коли тести подають, то наступного кроку не переходить і код на staging не оновлюється.

Процес розробки та тестування з Docker та Gitlab CI

Які завдання ми вирішували на staging коли впровадили docker? Наша система складається з компонентів і у нас виникла потреба перезапускати лише частину компонентів, які були оновлені в репозиторії, а не всю систему повністю.

Для цього нам довелося рознести все за окремими татками.

Після того, як ми це зробили, у нас виникла проблема з тим, що Docker-compose створює для кожної папочки свій простір мережі не бачить компоненти сусіда.

Для того, щоб обійти, ми створили мережу в Docker вручну. У Docker-compose прописали, що використовуй для цього проекту таку мережу.

Таким чином, кожен компонент, який стартує з цією сіткою бачить компоненти в інших частинах системи.

Наступна проблема – це поділ staging між кількома проектами.

Тому що все це виглядало красиво і максимально наближено до production добре використовувати 80 або 443 порт, який використовує повсюдно в WEB.

Процес розробки та тестування з Docker та Gitlab CI

Як ми це вирішили? Ми призначили один Gitlab Runner всім великим проектам.

Gitlab дозволяє запустити кілька розподілених Gitlab Runner, які будуть просто по черзі хаотично брати всі завдання, проганяти їх.

Щоб у нас не виникло хауса, ми обмежили групу наших проектів одним Gitlab Runner, який при наших обсягах справляється без проблем.

Ми винесли nginx-proxy до окремого скрипту запуску і в ньому прописали сітки всіх проектів.

У нас проект має одну сітку, а балансер має кілька сіток на ім'я проектів. Він може за доменними іменами проксирувати далі.

У нас запити надходять по домену на 80 портів і розрулюються в групу контейнерів, яка обслуговує цей домен.

Процес розробки та тестування з Docker та Gitlab CI

Які ще були проблеми? Це те, що за замовчуванням всі контейнери виконують від користувача root. Це root є нерівний root хост системи.

Однак якщо увійти в контейнер, то це буде root і файл, який ми створюємо в цьому контейнері, отримує права root.

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

Як це можна вирішити? Можна додати користувачів, які будуть у контейнері.

Які проблеми виникли, коли ми додали користувача?

Створюючи користувача, у нас часто не збігаються ID групи (UID) і ID користувача (GID).

Щоб вирішити цю проблему в контейнері, ми використовуємо користувачів з ID 1000.

У нашому випадку це збіглося з тим, що практично у всіх розробників використовується ОС Ubuntu. А в ОС Ubuntu перший юзер має ID 1000.

Процес розробки та тестування з Docker та Gitlab CI

Які ми плани?

Перечитати документацію по Docker. Проект активно розвивається, документація змінюється. Дані, які були отримані два-три місяці тому, вже потихеньку старіють.

Частина проблем, які ми вирішували цілком можливо, вже вирішені стандартними засобами.

Так хочеться пройти далі перейти безпосередньо до оркестрації.

Один із прикладів це вбудований у Docker механізм під назвою Docker Swarm, який представляється із коробки. Хочеться запустити щось продакшене на базі технології Docker Swarm.

Породження контейнерів робить незручність роботи з логами. Наразі логи ізольовані. Вони розкидані контейнерами. Одне із завдань зробити зручний доступ до логів через web-інтерфейс.

Процес розробки та тестування з Docker та Gitlab CI

Джерело: habr.com

Додати коментар або відгук