vyvchy
    Теми розділу

    09 · Git і CI/CD

    Інструменти CI: GitHub Actions, GitLab CI, Jenkins

    Зміст

    У попередній главі ми розібрали пайплайн абстрактно: стадії, джоби, тригери, артефакти, runners (детально — у главі «Будова пайплайна»). Але жоден проєкт не запускає «пайплайн взагалі» — він запускає його в конкретному інструменті. І кожен інструмент говорить власним діалектом: те, що GitHub називає workflow, GitLab називає pipeline, а Jenkins — job. Той самий «крок» в одному місці — run, в іншому — script, у третьому — sh. Через це конфіг чужого проєкту читається як шифр, поки ти не побачиш за назвами ту саму знайому анатомію.

    Для AQA це щоденна реальність, а не факультатив. Ти приходиш на проєкт — а там уже лежить теку .github/workflows/, або файл .gitlab-ci.yml, або Jenkinsfile. Треба вміти його прочитати, додати туди запуск своїх автотестів, зрозуміти, чому пайплайн червоний і що саме впало. На співбесіді класика: «З яким CI працювала? Як налаштуєш прогін автотестів?» Мета глави — навчитися бачити одну модель крізь три інструменти, щоб будь-який конфіг перестав бути магією.

    Pipeline as code: конфігурація живе в репозиторії

    Головний принцип, який об'єднує всі сучасні CI-інструменти, — пайплайн як код (pipeline as code). Замість того щоб клікати налаштування збірки в вебінтерфейсі, ти описуєш весь пайплайн текстовим файлом і кладеш його поруч із кодом у той самий репозиторій.

    Чому це важливо, стає видно, коли порівняти з тим, як робили раніше. Стара школа Jenkins — freestyle job: адміністратор заходить у вебморду, руками проставляє галочки, вписує команди в текстові поля, зберігає. Проблем із цим підходом чотири, і всі болючі:

    • Немає історії. Хто і коли змінив крок збірки? Невідомо — конфіг живе в базі сервера, не в git.
    • Немає рев'ю. Зміну в пайплайні ніхто не переглядає перед застосуванням, як переглядають код.
    • Немає відтворюваності. Впав сервер — конфіг треба відновлювати руками по пам'яті.
    • Конфіг не подорожує з гілкою. У feature-гілці не можна легко мати інший набір кроків.

    Pipeline as code лікує все це одним рухом. Конфіг — це файл у репозиторії, тож він версіонований (видно кожну зміну через git blame), рев'ю́ється у звичайному pull request (див. главу «Pull Request»), відтворюваний (клон репо = повний пайплайн) і подорожує з гілкою (кожна гілка несе свою версію кроків). Формат — майже завжди декларативний YAML: ти описуєш що має статися, а не пишеш імперативний скрипт як це зробити крок за кроком. Jenkins тут виняток — його мова Groovy ближча до справжнього коду, але й він переїхав із кліків у файл Jenkinsfile.

    GitHub Actions: workflow, event, job, action

    GitHub Actions — вбудований у GitHub CI. Його модель має чотири рівні, і плутанина між ними — найчастіша помилка новачка.

    Workflow — це один YAML-файл у теці .github/workflows/ репозиторію. Проєкт може мати кілька workflow-файлів (окремо тести, окремо деплой, окремо нічний прогін). Кожен запускається у відповідь на event (подію) — це те, що описано в секції on:: push, pull_request, schedule (cron), workflow_dispatch (ручний запуск кнопкою). Ключове: подія — це подія на сервері GitHub, а не на твоєму диску. on: push спрацьовує, коли ти зробив git push, а не git commit (межу «Git ≠ GitHub» розібрано в главі «Системи контролю версій»).

    Усередині workflow — jobs (джоби). За замовчуванням усі джоби виконуються паралельно, кожен на своєму свіжому runner (машині-виконавці; hosted на кшталт ubuntu-latest або self-hosted). Якщо один джоб має чекати на інший — це задають явно через needs:. Усередині джоба — steps (кроки), і от вони вже йдуть строго послідовно. Кожен крок — це одне з двох:

    • run: — виконати shell-команду (npm ci, npx playwright test);
    • uses: — підключити action (екшен): готовий переиспользуваний блок, свій чи зі спільного каталогу GitHub Marketplace. Наприклад, actions/checkout викачує код репозиторію, actions/setup-node ставить Node.js. Це і є та «цеглинка», що дала назву всьому інструменту.

    Event: push / pull_request / schedule

    Workflow
    .github/workflows/e2e.yml

    Job: playwright
    свій runner

    Job: lint
    паралельно, свій runner

    Step (uses): actions/checkout

    Step (uses): actions/setup-node

    Step (run): npx playwright test

    Event: push / pull_request / schedule

    Workflow
    .github/workflows/e2e.yml

    Job: playwright
    свій runner

    Job: lint
    паралельно, свій runner

    Step (uses): actions/checkout

    Step (uses): actions/setup-node

    Step (run): npx playwright test

    Матриця (strategy.matrix) множить один джоб на набір комбінацій — наприклад, прогнати ті самі тести на трьох браузерах чи двох версіях Node одразу. Секрети підставляються через вираз ${{ secrets.NAME }} і ніколи не пишуться у файл відкритим текстом (гігієна секретів — окрема тема, глава «Секрети та конфігурація в CI»).

    GitLab CI: один файл .gitlab-ci.yml

    GitLab пішов іншим шляхом: замість теки з багатьма файлами — один файл .gitlab-ci.yml у корені репозиторію. Він описує весь пайплайн проєкту.

    Модель виконання простіша і крутиться навколо двох понять — stages і jobs. Спочатку ти оголошуєш список стадій у порядку виконання:

    stages:
      - build
      - test
      - deploy

    Далі описуєш джоби, і кожен прив'язуєш до стадії через ключ stage:. Правило виконання, яке треба засвоїти твердо: стадії йдуть послідовно, а джоби всередині однієї стадії — паралельно. Тобто всі джоби стадії test стартують разом, і лише коли всі вони зелені, починається стадія deploy.

    stage: deploy

    job: deploy-staging

    stage: test

    job: unit

    job: e2e

    stage: build

    job: compile

    stage: deploy

    job: deploy-staging

    stage: test

    job: unit

    job: e2e

    stage: build

    job: compile

    Кожен джоб обов'язково має секцію script: — список команд. Ключ image: задає Docker-образ, у якому джоб виконається (механіка контейнерів — глава «Docker і тестові середовища»). Умовний запуск задають через rules: (сучасний спосіб) або старіші only/except. Артефакти й кеш — секції artifacts: і cache:. Виконавці називаються GitLab Runner і вибираються за тегами. Якщо треба порушити суворий порядок «стадія за стадією» й побудувати граф залежностей, є ключ needs: — так само, як needs: у GitHub Actions. Готового «маркетплейсу екшенів» тут немає, натомість повторне використання роблять через include: і шаблони.

    Jenkins: Jenkinsfile і спадок freestyle

    Jenkins — найстаріший із трьох і роками був найпоширенішим (за останніми опитуваннями розробників GitHub Actions уже випередив його за загальним поширенням, хоча Jenkins досі широко зустрічається у великих компаніях), і в ньому фактично живуть два світи.

    Legacy-світ — freestyle jobs. Це та сама «конфігурація кліками» через вебінтерфейс, з якої ми почали розмову про pipeline as code. Досі величезна кількість проєктів так і працює, тож на реальній роботі ти це зустрінеш. Але це антипатерн: не версіонується, не рев'ю́ється, крихке.

    Сучасний світ — Pipeline. Пайплайн описується файлом Jenkinsfile у репозиторії, мовою Groovy. Є два стилі: declarative (структурований, з фіксованими секціями — рекомендований за замовчуванням) і scripted (повноцінний імперативний Groovy — гнучкіший, але складніший). Структура declarative-пайплайна:

    pipeline {
        agent { docker { image 'mcr.microsoft.com/playwright:vX.Y.Z-noble' } }
        stages {
            stage('Install') {
                steps { sh 'npm ci' }
            }
            stage('Test') {
                steps { sh 'npx playwright test' }
            }
        }
        post {
            always {
                archiveArtifacts artifacts: 'playwright-report/**', allowEmptyArchive: true
                junit 'results.xml'
            }
        }
    }

    Тут agent каже, де виконувати (машина або Docker-образ), stages містить stage-и, а кожен stage — блок steps із командами (sh для shell). Секція post виконується після пайплайна — блоки always/success/failure зручні саме для AQA: зберегти звіт і скриншоти незалежно від того, впали тести чи ні.

    Архітектурно Jenkins — це controller (координатор; історична назва «master») і agents (виконавці; історична назва «slave», машину-виконавця також називають node). Уся сила й уся біль Jenkins — у плагінах: майже кожна інтеграція (git, Docker, Allure, JUnit, Slack) — це окремий плагін, який хтось має встановити й підтримувати. Чому Jenkins досі всюди, попри вік: він self-hosted і безкоштовний, працює в закритому контурі (air-gapped, де хмарний CI заборонений політиками безпеки), а екосистема плагінів закриває майже будь-яку екзотику. Ціна — його треба самому ставити, оновлювати й доглядати.

    Мапінг термінології

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

    ПоняттяGitHub ActionsGitLab CIJenkins (declarative)
    Де живе конфіг.github/workflows/*.yml.gitlab-ci.ymlJenkinsfile
    Верхній контейнерworkflowpipelinepipeline
    Що запускаєevent (on:)тригер / rulestrigger / SCM-хук
    Групування джобів(немає стадій; needs)stagestage
    Одиниця роботиjobjobstage
    Крокstep (run / uses)рядок у scriptstep (sh)
    ВиконавецьrunnerGitLab Runneragent / node
    Переиспользуваний блокactioninclude / шаблонplugin / shared library
    Матрицяstrategy.matrixparallel:matrixmatrix
    Секретиsecrets.*CI/CD variables (masked)credentials

    Дві пастки в цій таблиці варто підкреслити окремо. По-перше, «job» у GitHub і GitLab — це одиниця, що виконується на окремій машині, а в Jenkins цю роль грає радше stage (сам «job» у Jenkins — це весь пайплайн). По-друге, у GitHub Actions немає рівня «стадія»: джоби або паралельні, або зв'язані через needs, а візуальні групи в UI — це вже похідне від needs. У GitLab стадія — явна й обов'язкова.

    Мінімальний workflow автотестів

    Зберемо все докупи на найчастішому для AQA завданні: прогнати Playwright-тести на кожен push і pull request і зберегти звіт. Ось повний робочий workflow для GitHub Actions:

    name: e2e-tests
    on:
      push:
        branches: [main]
      pull_request:
    
    jobs:
      playwright:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: 20
          - run: npm ci
          - run: npx playwright install --with-deps
          - run: npx playwright test
          - uses: actions/upload-artifact@v4
            if: always()
            with:
              name: playwright-report
              path: playwright-report/

    Читаємо крок за кроком, і кожен крок — універсальний, незалежно від інструмента:

    1. checkout — викачати код репозиторію на runner. Без цього немає що тестувати.
    2. setup-node — поставити потрібну версію Node.js.
    3. npm ci — детермінована установка залежностей із package-lock.json (саме ci, не install — вона відтворювана й для CI призначена).
    4. npx playwright install --with-deps — докачати браузери та їхні системні бібліотеки.
    5. npx playwright test — власне прогін. Ненульовий код виходу цієї команди робить джоб (і весь пайплайн) червоним — це і є сигнал CI.
    6. upload-artifact з if: always() — зберегти HTML-звіт навіть якщо тести впали (без always() крок пропустився б після червоного прогону, і ти лишився б без доказів).

    Той самий флоу в GitLab CI — це джоб зі script, image з браузерами й секцією artifacts: { when: always }; у Jenkins — stage('Test') і збереження звіту в блоці post { always { ... } }. Кроки ті самі, змінюються лише ключові слова.

    Окремо про формат обміну результатами. Усі три інструменти вміють читати JUnit XML — універсальний формат звіту про тести. Playwright уміє його генерувати, GitLab показує впалі тести прямо в merge request через artifacts:reports:junit, Jenkins — через junit-плагін, GitHub — через сторонні екшени-репортери. Це той місток, що дає бачити впалі тести в інтерфейсі CI, а не рити текстовий лог. Артефакти фейлу (трейси, скриншоти, відео) і специфічні для CI причини падінь — тема глави «Автотести в CI: стабільність і дебаг».

    Як обирають інструмент

    Чесна відповідь, яка часто дивує кандидатів: найчастіше інструмент не обирають — його диктує хостинг коду. Код на GitHub → природний вибір GitHub Actions (нічого не треба ставити, воно вже там). Код у GitLab → GitLab CI з тієї ж причини. Ці два «вбудовані» варіанти виграють за замовчуванням завдяки нульовому порогу входу.

    Jenkins обирають свідомо й зазвичай тоді, коли є одна з причин:

    • Self-hosted / закритий контур. Дані не можна віддавати в хмару — потрібен CI на своєму залізі.
    • Готова інфраструктура. У компанії вже роками крутиться Jenkins із десятками плагінів і скриптів — переписувати все дорожче, ніж підтримувати.
    • Екзотичні інтеграції. Потрібне щось, чого немає в хмарних CI, але є плагін під Jenkins.
    • Незалежність від git-хостингу. Jenkins працює однаково з будь-яким сервером — і GitHub, і GitLab, і Bitbucket, і власним.

    Решта критеріїв, які згадують на співбесіді: вартість (хмарні CI тарифікуються за хвилини — деталі економіки в главі «Паралелізація і швидкість пайплайна»), багатство екосистеми готових блоків, підтримка self-hosted runners, зручність секретів і оточень, крива навчання. Для AQA практичний висновок один: інструмент — це деталь, анатомія — спільна. Хто розуміє модель «тригер → джоби → кроки → артефакти», той за годину освоює будь-який із трьох.

    Типові помилки

    • «Джоби виконаються в тому порядку, як я їх написав». Виглядає логічно, а насправді за замовчуванням джоби паралельні (і в GitHub, і всередині стадії GitLab). Порядок задають явно: needs: у GitHub, стадії або needs: у GitLab. Тест, що залежить від збірки, без цього стартує на порожньому місці.
    • «Пайплайн запуститься на мій commit». Ні: тригер push — це подія на сервері, вона спрацьовує на git push, а не на локальний commit. Поки не запушив — CI про твої коміти не знає.
    • «Jenkins — це конфіг кліками». Так виглядає legacy-freestyle, але сучасний Jenkins — це Jenkinsfile у репозиторії, той самий pipeline as code. Плутати ці два світи — типовий провал на співбесіді.
    • «Action, job і step — синоніми». Ні. У GitHub: job — окрема машина, step — крок усередині неї, action — готова цеглинка, яку крок підключає через uses. Три різні рівні.
    • «Тести впали — звіту не буде». Так і станеться, якщо забути if: always() (GitHub) чи when: always (GitLab) чи post { always } (Jenkins): крок збереження артефакту пропуститься після червоного прогону — саме тоді, коли звіт найпотрібніший.
    • «Той самий YAML перенесу з GitHub у GitLab». Синтаксис несумісний: різні ключі, різна модель стадій, різні вирази. Спільна лише анатомія, не текст.
    • «Помилка відступу — то дрібниця». У YAML відступ — це синтаксис. Зайвий пробіл ламає весь пайплайн з малозрозумілою помилкою парсера; більшість «зелений код, червоний CI на порожньому місці» — це саме биті відступи.

    Підсумок

    • Pipeline as code — конфіг пайплайна живе у репозиторії як версіонований файл, який рев'ю́ється в PR; це витіснило «конфігурацію кліками» (freestyle) з усіх трьох інструментів.
    • Одна анатомія — три діалекти. GitHub Actions (workflow/event/job/step/action), GitLab CI (.gitlab-ci.yml, stages/jobs), Jenkins (Jenkinsfile, agent/stages/steps) описують ту саму модель різними словами.
    • Порядок виконання за замовчуванням — паралельний для джобів; послідовність задають явно (needs: або стадії). Кроки всередині джоба — навпаки, строго послідовні.
    • Мінімальний прогін автотестів усюди однаковий: checkout → залежності → браузери → тест → артефакт-звіт із always. Змінюються лише ключові слова.
    • Інструмент диктує хостинг коду: GitHub → Actions, GitLab → GitLab CI; Jenkins — свідомий вибір під self-hosted, закритий контур чи наявну інфраструктуру.

    Що питають на співбесіді

    • «З якими CI-інструментами працювала?» — інтерв'юер перевіряє не список брендів, а чи розумієш ти спільну модель. Сильна відповідь: назвати інструмент, але одразу показати анатомію («у GitHub Actions workflow реагує на event, містить паралельні джоби, кожен — з кроків, крок або run, або uses екшен»).
    • «Опиши, як налаштуєш прогін автотестів у CI». — чекають послідовність checkout → setup → install → test → артефакт і згадку, що звіт зберігається навіть при падінні. Плюс — згадати запуск на PR і на push.
    • «Чим GitHub Actions відрізняється від Jenkins?» — дивляться, чи розумієш вісь hosted-vs-self-hosted і pipeline-as-code-vs-freestyle, а не завчені гасла.
    • «Джоби в пайплайні йдуть послідовно чи паралельно?» — пастка на розуміння моделі виконання. Правильно: паралельно за замовчуванням, послідовність — через залежності/стадії.
    • «Що таке pipeline as code і навіщо він?» — чекають чотири вигоди: версіонування, рев'ю, відтворюваність, конфіг подорожує з гілкою.

    Джерела