Як ми створювали PHP Continuous Integration Workflow в нашій команді

26.09.2017

Як ми створювали 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 модуля знаходяться помилки, їх швидко виправляють силами команди, бо горять дедлайни, а наступне оновлення цього ж модуля вимагає повторного накладання латки, яка була зроблена раніше. І проблема не в тому, що цю латку можуть забути при наступному оновленні, а в тому, що вона ніколи не буде інтегрована в модуль, якщо її не відправити автору цього самого модуля.

Як ми вирішили проблему активності користувачів в спільноті? - ми створили правило, що якщо змінюєш contrib модуль - добийся того, щоб латку було прийнято і в наступній версії модуль знову повернувся з теки patched в теку contrib. А додатковим бонусом стало те, що профілі наших розробників на drupal.org і github.com стали рости (продавці тепер мають чим заманювати клієнтів).

Наступний крок розвитку - створення 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.

Ідея системи CIBox - для кожного проекту піднімається droplet DigitalOcean, на який інсталюється вся кухня CI і LAMP стек, для хостингу білдів.

Оновлення і обслуговування CI

Сама система Continuous Integration і весь набір складових - доволі складна річ. І рівень тих, хто її обслуговує, повинен бути відповідним. Ці люди повинні досконало знати як Development так і Operations, іноді навіть більше Operations.

Це призвело до ще одної проблеми - в кожній команді всіх наступних проектів потрібен був DevOps, який був би в курсі того, як живе окремий проект і володіє знаннями про те, як працює CIBox.

Як не дивно - проблема вирішилась зарахунок перших бонусів від минулих проектів, реалізованих із CI - деякі team lead і розробники зацікавились CI і захотіли всі свої наступні проекти робити лише на ньому.

Бонуси:

  • якість коду,
  • відсутність збоїв deployment
  • зручність manual code review
  • стандартизація і поява ситуацій, коли модулі з теки custom раптом почали підходити і для наступних проектів, бо вони стали більше відповідати бізнес логіці, а не конкретному завданню
  • зниження тривалості UAT (User Acceptance Testing) періоду

Останній пункт дуже зацікавив бізнес. В одному з проектів виявилось, що згідно з estimates - у нас за звичкою заплановано було 4 тижні на UAT. А в результаті на практиці виявилось, що впорались за тиждень - проблем як таких не було, клієнт все перевірив без глюків. Самі розумієте - скільки це економило коштів клієнту.

Всі пункти крім останнього дали мотивацію лідам і сеньйорам переходити на CI. Робити перевірку коду і займатись QA тестуванням стало в рази зручніше в купі із колосальним зменшенням проблем протягом усього періоду розробки.

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

Зміни в менеджменті проектів

Завдяки CI змінились такі важливі речі як деплой і менеджмент. CI виставив вимогу ділити завдання на короткі (1-8 годин), бо їх виявилось простіше контролювати і тестувати. Також, завдяки тому, що деплой на стейдж став в вигляді однієї кнопки в Jenkins - нові features на стейдж часом почали заходити руками менеджера проекту.

Складності в цьому немає. Збої відтестовані на створенні білдів (там всі помилки деплоя і виявляються, бо кожен білд - це і є деплой). Це значить, що напряг із деплоями на стейдж знявся повністю, - головне зробити Pull Request з master гілки в staging і дочекатись білда, щоб перевірити, чи не закралась раптом помилка, яка покладе стейдж. Якщо помилки немає - операція merge, і лише тоді запускається аналогічний до білда деплой, але вже в папку віртуального хоста staging сайту. Тобто, як ви могли зрозуміти, у нас з’явилось тестування самого деплою.

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

SQL CI flow

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

Ми зробили такий хід - спробували запустити розробку на production базі даних.

Кожного вечора, або по ручному запуску - база даних з production стягується завданням Jenkins і використовується як початкова база для reinstall скрипта на локальному середовищі і на білдах GitHub Pull Request.

Це ускладнило систему, додало деяких нюансів в організації процесу, але на 99% виключило проблеми релізу на production, адже розробники навіть локально по суті своїй постійно працюють з базою, в якій присутні останні зміни.

Звісно це ускладнило сам reinstall скрипт, бо з’явилось багато залежностей.

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

Якщо команді потрібно, щоб після імпорту бази даних в ній виконувались зміни, будь-хто може створити Pull Request, внести ці зміни і дочекавшись від CI сервера білда - перевірити, чи зміни відбулись. Таким чином, відпала необхідність в наявності команди Operations, яка займається підтримкою і модернізацією процесу - команди розробників краще знають, що їм потрібно.

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

CIBox Go opensource!!!

Через півтора роки ми вирішили поділитись своїми наробками із спільнотою.

Наша система пройшла випробовування на Drupal6/7/8, а також на Symfony2 проектах. Вона може з легкістю бути адаптована для Wordpress, Laravel і будь-яких інших фреймворків. І не тільки PHP.

Ми проводили codesprint, в ході якого тестували можливості системи для великих команд і отримали докази, що система працює без збоїв в командах до 100 чоловік.

В ній головне - ідея. Ідея того, що master - завжди стабільний.

Всі скрипти, які запускаються на CI сервері - під контролем команди, і можуть бути змінені командою в процесі розробки, для того, щоб відповідати вимогам проекту.

Запуск нового проекту  ~1 година (Розвертання DigitalOcean Droplet і запуск jenkinsbox.yml із github.yml із налаштуванням базових доступів).

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

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