Як ми створювали PHP Continuous Integration Workflow в нашій команді
Почалось усе з того, що в компанії піднялось питання knowledge exchange для команд, які рознесені по всьому світу. Виникла проблема: команди кожного разу придумують велосипеди і пишуть по суті один і той же код.
На той час в компанії вже була спільна поштова конференція по Drupal, але користі від неї було мало. Також була в наявності деяка база минулих проектів, але жодної інвентаризації того, що в них там було створено, не існувало. Щоб було більш зрозуміло: компанія, по суті, створювала проекти, написані на PHP/Drupal, часом на Symfony2.
Якось одразу стало зрозуміло, що людина, яка не приймала участь в розробці коду, не може зробити його інвентаризацію. Особливо після того, як я спробував глянути на той код... В той час я зрозумів, що не важливо, якого розміру компанія і якого рівня проекти вона реалізовує — гавнокод пишеться всюди.
Як тільки ми обломались із інвентаризацією минулих проектів, я приступив до реалізації Continuous Integration, яка б дозволила привнести в компанію певний рівень стандартизації робочого процесу.
Ідея — додати автоматичну перевірку коду на стандарти, якими, як відомо, володіє Drupal спільнота.
Перша реалізація — додати перевірку коду PHP CodeSniffer кожного разу, як відбувається здача окремого завдання розробником.
Для цього ми використали вже існуючий на той час функціонал GitHub Pull Request. Для тих, хто не знайомий з даною технологією — перш ніж додати зміну коду в головну гілку з допомогою PR є можливість переглянути внесені зміни (manual code review).
Насправді додання цієї ручної перевірки, хоч воно було реалізовано першим, виявилось найскладнішим завданням, над втіленням якого ми працюємо досі. Мотивувати людей переглядати код інших розробників було досить складно. Але благо — в нас у команді виявилось два маніяки, для яких цей процес важливий.
Ми поставили на потік перевірку одне одного і почали вводити дану культуру в інші команди.
Було боляче. На цьому етапі ми чітко почали виявляти справжній рівень знань наших розробників. Звісно, наявність злих сеньйорів, які постійно дістають коментарями до написаного, сильно не змінить підхід до розробки. Все, чого можна досягти у такий спосіб — це змусити всіх розробників вас ненавидіти.
Але є і плюс від процесу ручної перевірки коду: на цьому етапі ті, хто робить цю перевірку, починають розуміти масштаб проблеми в командах і те, які складності приходиться долати в процесі розробки.
В той період головним надбанням стало створення домовленостей:
— які технології ми повинні використовувати для переносу конфігурацій
— як ми повинні уніфікувати середовища розробки (тут ми всі пересіли на Vagrant)
— як ми повинні діяти із базою даних, коли в ній вже присутні матеріали, додані клієнтом
— як розбивати завдання і чому вони мають бути короткими, а не довгими.
Вибір технологій — це те, що постійно змінюється. Думаю, що це не тільки в Drupal світі, але і в будь-якому середовищі програмування. Клієнти постійно хочуть чогось нового. Технології не стоять на місці. Але певні речі вдалось уніфікувати. Ну, принаймні для тих команд, які були в нашому полі зору.
Для тих, хто знайомий Drupal, скажу, що ми повністю перейшли в цей час на розробку з допомогою інсталяційного профілю.
Для тих, хто не так близько: інсталяційний профіль — це набір команд, який виконується в момент розвертання збірки Друпал на хостінг. За його допомогою можна вказати, які модулі, які теми і конфігурації потрібно увімкнути на етапі інсталяції, щоб отримати інтернет-проект, готовий до роботи.
Що дає прив’язка до профілю?
Прив'язавшись до інсталяційного профілю, ми повністю перейшли на Code Driven Development — дана технологія майже повністю виключає можливість робити зміни в базі даних не через код.
Приклад початку роботи над завданням:
Для розгортання локального середовища розробки потрібно було
— завантажити конфігурацію віртуальної машини,
— розгорнути цю віртуальну машину локально,
— завантажити репозиторій проекту в віртуальний хост всередині віртуальної машини,
— встановити Друпал, використовуючи браузер і інсталяційний профіль, над яким працює вся команда,
— почати працювати над завданням,
— створити Pull Request зі змінами, і відправити його на перевірку іншому учаснику команди.
Дивлячись сьогодні на той процес, розумію, наскільки далеко ми пішли зараз із нашим CI. Але все по черзі.
На той час у нас була ручна перевірка коду на GitHub. І виникла ідея додати деякої автоматизації, бо більшість коментарів на той час були такі:
— додай docblock для функції
— додай docblock для файлу
— виправ стиль коду згідно зі стандартами
— виправ реалізацію згідно з ^^^
В основному ми стопорились саме на стандартах, бо багато розробників не дотримувались їх, і тут майже не було залежності від рівня — лажали всі. І це було зрозуміло: щоб бути мотивованим на дотримання стандартів, потрібно бути активним учасником спільноти Друпал і публікувати свої модулі на drupal.org. Лише тоді приходить розуміння, для чого це, лише тоді зустрічаєшся із тим, що твій модуль не приймають модератори, бо ніхто не хоче дивитись на код, який читати неможливо.
А нам саме це і було потрібно — створити мотивацію в наших співробітників, щоб вони публікувати свій код в спільноту — це і є code sharing, якого ми прагнули досягти.
Перші кроки автоматизації
Почати ставити команду на CI — нетривіальне завдання. Перший крок буквально міняє всі правила гри. В нашому випадку це було написання мною скрипта reinstall.sh
Спочатку все було на банальному shell
— вбити базу даних
— скинути налаштування до базових
— запустити інсталяцію Друпал
— оновити файли із Stage локально.
Цей скрипт було залито разом із проектом, і кожен почав його використовувати. Як бачите, про сканування коду на предмет стандартів тоді ще не йшлося. Все відбувалось малими кроками.
Віртуалізація + Vagrant
В наших командах були, є і будуть розробники, які працюють на MacOS, Windows OS && Linux OS. І потрібно було розробити таку систему, щоб уникнути проблему конфліктів і різниці середовищ. Ми прийняли дуже нелегке рішення — перейшли на віртуалізацію.
Стартанули із puphpet.com, по якому навіть провели кодспринт, щоб привести проект в вигляд, необхідний для наших потреб. В результаті ми отримали уніфікацію середовищ розробки за однієї проблеми — скрипти puppet, де були зібрані віртуальні конфігурації puphpet.com, не відрізнялись стабільність в тривалій перспективі — їх необхідно було постійно доробляти, щоб встигати в ногу за новими версіями програм. З часом ми перейшли на іншу систему, базовану на ansible provisioner.
Go Jenkins!
Для автоматичної перевірки коду ми використали Jenkins. Вибір сам напрошувався: були навички написання коду на Java і деяка практика використання цієї системи для автоматизації обслуговування серверів. Був використаний, а потім і дописаний плагін GitHub Pull Request Builder, який дозволяє запускати Jenkins job по події створення нового Pull Request в GitHub проекті.
Наступні кроки були справою техніки — створити скрипт, який буде запускати PHP CodeSniffer із стандартами Друпал для коду, який знаходиться в новому Pull Request.
І тут ми вперлись в проблему: як ядро, так і 100% контріб модулів, які ми використовуємо в кожному проекті, не проходять стандарти Друпал.
Це був перший шок, який дав нам зрозуміти, що доведеться змінювати всю суть розробки. В цей час ми вирішили відділити наш код від коду, який ми перевикористовуємо. І тільки наш код перевірявся на стандарти — ядро і contrib модулі ми пропускали. Це призвело до створення структури проекту, якою ми користуємося і досі.
Як для модулів, так і для шаблонів тем ми перейшли на структуру
— sites/all/modules/contrib
— sites/all/modules/custom
— sites/all/modules/features
— sites/all/modules/patched
— sites/all/modules/patches
— sites/all/themes/contrib
— sites/all/themes/custom
Відповідно в цій структурі сканування на стандарти коду відбувається лише для тек custom, бо тільки цей код пише команда.
Це вже кінцева структура. Бо теки patched && patches з’явились пізніше. Дану конвенцію ми розробляли кілька місяців, тим самим змусили всіх розробників публікувати патчі до contrib модулів на drupal.org.
Для чого це?
Думаю, багато хто зустрічався із проблемою, коли в коді contrib модуля знаходяться помилки, їх швидко виправляють, бо горять терміни, а наступне оновлення цього самого модуля вимагає повторне накладання латки. І проблема не в тому, що цю латку можуть забути при наступному оновленні, а в тому, що вона ніколи не буде інтегрована в модуль без участі його автора.
Таким чином ми вирішили проблему активності користувачів у спільноті — ми створили правило: якщо змінюєш модуль, добийся того, щоб латку було прийнято, і в наступній версії модуль знову повернувся з теки patched в теку contrib. Ну, а додатковим бонусом тут було те, що профілі наших розробників стали рости (продавці тепер мають, чим заманювати клієнтів), і досвід розробників разом з цим.
Project build
Наступний крок розвитку — створення project build для кожного Pull Request. До цього ми змушені були прийти, рано чи пізно.
Проблема, яка мотивує на це, — стара, як світ. Розробник на своєму локальному середовищі все реалізував, все у нього працює, код зливається в головну гілку репозиторія, робиться deploy на Stage — і, вуаля, щось не працює.
Причина: той самий розробник забув про конфігурацю. І проблема не в тому, що потрібно виправляти, а в тому, що час виконання завдання збільшується — deploy на Stage відбувається не кожного дня, і проблему можна навіть пропустити, бо минає якийсь час.
Тут нам стало цікаво отримати збірки проектів, таким самим чином як це роблять розробники десктопного програмного забезпечення.
В нагоді став скрипт reinstall.sh, який на той час вже мав кроків з 10.
Все, що залишилось — це реалізувати Jenkins job, яка буде запускати цей скрипт на окремому сервері із кодом, який є в наданому розробником Pull Request, і інсталювати проект, надаючи унікальне посилання для доступу до тестування цього проекту.
Це змінило наш процес глобально. Як тільки ми релізували перший build, виявилось, що тепер немає необхідності робити QA на Stage — ми можемо відправляти їх на тестування білдів ще до того, як код заходить в master.
Перша реалізація системи, яка створювала білди для проекту, зайняла у нас майже тиждень. Інсталяція Jenkins + LAMP + PHP CodeSniffers + JShint + налаштування всього цього, щоб працювало разом із проектом GitHub.
Knowledge Exchange via QA
Першочергова проблема, яка стояла перед нами — обмін досвідом — так і не була вирішена аж до моменту, коли ми заборонили PR merge без проходження всіх тестів на стандарти коду. І тепер, при наявності готових build, у нас з’явилось нове правило — віддавати код на ручну перевірку кожен раз іншому учаснику команди. Це викликало деякі баталії щодо стилів, але з часом вони припинились, бо люди просто домовились, як ми пишемо.
Але головне — тепер в команді не було випадків, коли той чи інший функціонал був відомий лише одному учаснику, який його реалізовував — тепер є людина, яка переглядала код реалізації і шукала в ньому помилки, і був QA, котрий тестував функціональність реалізації одразу — тут же, в PR, який ще не зайшов в master гілку репозиторія.
Це призвело до того, що команди автоматично стали писати Steps for review для кожного Pull Request. І ці кроки змушений був писати той розробник, який реалізовував завдання.
Думаю, багато хто здогадався, наскільки швидко ми прийшли до behat тестування з цими кроками. І процес розробки тепер включав в себе дуже ясний набір кроків для тих же менеджерів проекту, щоб демонструвати клієнту вже зроблені завдання.
На цьому етапі прості розробники стали розуміти, що реалізація тієї чи іншої задачі включає в себе логіку тестування, і з часом ми помітили, що реалізації ставали чим далі, тим більш елегантними — розробники стали більш розуміти бізнес процеси і UX.
Народження CIBox
Прийшов наступний проект, на якому ми теж хотіли використовувати CI. Налаштовувати новий сервер для нового проекту руками вже дуже не хотілось. Так ми прийшли до ansible.
Всі кроки інсталяції і всі конфігурації з першого проекту були за кілька днів зашиті у скрипт для ansible, який давав змогу налаштувати сервер не за тиждень, а за 12 хвилин, а потім за кілька годин зміни ключів доступу в налаштуваннях Jenkins ми мали змогу отримувати перші білди для нового проекту.
Даний ansible пакет було названо CIBox (Continuous Integration toolBox). Лише через півтора роки ми вирішили зробити його opensource.
Оновлення і обслуговування CI
Сама система Continuous Integration і весь набір складових — доволі складна річ. І рівень тих, хто її обслуговує, повинен бути відповідним. Ці люди повинні добре знати як Development, так і Operations, іноді навіть більше Operations.
Це призвело до ще однієї проблеми — в кожній команді всіх наступних проектів потрібен був DevOps, який в курсі того, як живе окремий проект, і володіє знаннями про те, як працює CIBox.
Як не дивно, проблема вирішилась тим, що ми отримали перші бонуси від проектів, реалізовані із CI — деякі team lead і розробники зацікавились CI і захотіли всі свої наступні проекти робити лише на ньому.
Бонуси
— якість коду,
— відсутність збоїв deployment,
— зручність manual code review,
— стандартизація і поява ситуацій, коли модулі з теки custom раптом почали підходити і для наступних проектів, бо вони стали більше відповідати бізнес логіці, а не конкретному завданню,
— зниження тривалості UAT (User Acceptance Testing) періоду.
Останній пункт вже зацікавив бізнес. В одному з проектів згідно estimate у нас за звичкою було заплановано 4 тижні на UAT. В результаті ми завершили роботу за тиждень — проблем як таких не було, клієнт все перевірив без глюків. Самі розумієте, скільки коштів це зекономило клієнту.
Всі інші пункти дали мотивацію лідам і сеньйорам переходити на CI — робити перевірку коду, займатись тестуванням стало в рази зручніше із колосальним зменшенням проблем протягом розробки проекту.
Ідея, що перевірку коду повинен робити кожного разу інший учасник команди, привела до того, що навантаження на ліда зменшилось — люди самі контролюють якість коду, головне, щоб була людина, яка контролює наявність цієї ручної перевірки в команді.
Зміни в менеджменті проектів
Завдяки CI змінились такі важливі речі, як деплой і менеджмент. CI виставив вимогу ділити завдання на короткі, бо їх простіше контролювати і тестувати, а також — завдяки тому, що деплой на стейдж став однією кнопкою в Jenkins, — нові features на стейдж почали заходити руками менеджера проекту. Складності в цьому немає. Збої відтестовані на створенні білдів (там всі помилки деплоя і виявляються, бо кожен білд — це і є по суті своїй деплой), а це значить, що напряг із деплоями на стейдж знявся повністю, головне — зробити Pull Request з master гілки в staging і дочекатись білда, щоб перевірити, чи не закралась раптом помилка, яка покладе стейдж. Якщо помилки немає — merge, і лише тоді запускається аналогічний до білда деплой, але вже в папку віртуального хоста staging сайту.
Це дозволило менеджерам проектів робити демонстрацію прогресу хоч кожен день, як загалом і клієнту реагувати на прогрес швидко, не допускаючи помилок.
SQL CI flow
Гадаю, багато хто зустрічався з проблемою, коли на dev базі даних все працює, а от на базі з production, де є матеріали, додані контент менеджерами, — глюки. Побороти це не так і просто, бо доводиться робити подвійне тестування.
Ми зробили хід — спробували запустити розробку на production базі даних. Кожного вечора, або по ручному запуску база даних з production стягується і використовується як початкова база для reinstall скрипта на локальному середовищі і на білдах GitHub Pull Request.
Це ускладнило систему, додало деяких нюансів в організації процесу, але на 99% виключило проблеми деплоя не production — розробники навіть локально по суті своїй постійно працюють з базою, в якій присутні останні зміни.
Звісно, це ускладнило сам reinstall скрипт, бо з’явилось багато залежностей.
І тут ми використали магію — віддали відповідальність за reinstall скрипт команді, яка працює над проектом.
Всі скрипти запуску тестів, сканерів стандартів коду від самого початку розробки проекту знаходяться тепер разом із файлами проекту, і команда може змінювати їх у процесі розробки.
Потрібно, щоб після імпорту бази даних в базі робились деякі зміни — будь-хто може створити Pull Request, внести ці зміни і, дочекавшись від CI сервера білда, перевірити, чи вони відбулись. Таким чином, відпала необхідність у наявності команди Operations, яка займається підтримкою і модернізацією процесу — команди краще знають, що їм потрібно.
Єдина відповідальність, з якою складно, — це інтеграція внесених змін назад в CIBox, щоб кожен наступний проект використовував всі нововведення. Ця відповідальність впала на лідів, які, в свою чергу, зацікавлені, щоб на наступному проекті все відбувалося швидше. Благо, вдалось достукатись до керівництва і виділити час на розробку самого CIBox, знову ж таки, сформувавши для них список переваг, які отримуються в результаті.
CIBox Go opensource!
Через півтора роки ми вирішили поділитись своїми наробками із спільнотою.
Наша система пройшла випробовування на Drupal6/7/8, а також на Symfony2 проектах. Може з легкістю бути адаптована для Wordpress, Laravel і будь-яких інших фреймворків. І не тільки PHP.
Ми проводили codesprint, в ході якого виявили можливості системи для великих команд і отримали докази, що система працює без збоїв в командах до 100 учасників.
В ній головне — ідея.
Ідея того, що master — завжди стабільний. Всі скрипти, які запускаються на CI сервері, — під контролем команди і можуть бути змінені в процесі розробки, щоб відповідати вимогам проекту.
Запуск нового проекту — ~1 година (Розвертання DigitalOcean Droplet і запуск jenkinsbox.yml із github.yml із налаштуванням базових доступів).
Основна мета відкриття проекту — ми шукаємо таланти. А також будемо щасливі отримати наробки спільноти в проект для розвитку даного продукту, адже він дозволяє вести розробку з великим задоволенням і зменшеними ризиками.
47 коментарів
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.