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

    09 · Git і CI/CD

    Середовища, деплой і релізи

    Зміст

    Між «код замерджено» і «користувач бачить фічу» лежить ціла інженерна дисципліна: код треба зібрати, доставити на середовище, безпечно перемкнути на нього трафік, переконатися, що нічого не впало, — і мати план відкату, якщо впало. Для senior QA це не «не моя зона». Саме тут вирішується, чи ловиться регресія до того, як її побачать усі користувачі, чи smoke після деплою відрізнить «викотилось нормально» від «викотилось, але база не змігрувала», і чи можна вимкнути зламану фічу за секунди без нового релізу.

    На співбесіді цю тему питають, щоб перевірити не термінологію, а системне мислення: чому blue-green дає миттєвий відкат, а rolling — ні; чому feature flag і деплой — різні події; як не зіпсувати метрики A/B власними тестовими прогонами. Вага середня, але провал одразу опускає грейд: хто не бачить різниці між «задеплоїли» і «зарелізили», не тягне на senior.

    Середовища: dev, staging, prod

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

    СередовищеПризначенняДаніСтабільність
    devРозробник перевіряє свій кодСинтетичні, часто локальніНестабільне, ламається постійно
    staging (stage, pre-prod)Фінальний гейт перед продом, максимально схожий на продProd-like або анонімізований зліпокУмовно стабільне
    prod (production)Живий застосунок для користувачівРеальні дані користувачівМає бути завжди зелене

    Часто драбина довша: окремий QA/test-стенд, UAT (user acceptance testing) для замовника, sandbox для інтеграцій. Але суть однакова — код рухається знизу вгору, і кожна сходинка ловить свій клас проблем.

    Ключове поняття для QA — паритет середовищ (environment parity). В ідеалі staging відрізняється від prod лише даними й масштабом. На практиці розходяться версії залежностей, конфіги, фіче-флаги, версії БД, наявність кешу/CDN — і це джерело найпідступніших багів: «на staging працює, на prod падає». Конфігурація під кожне середовище (base URL, ключі інтеграцій, ліміти) живе не в коді, а в змінних середовища й секретах — глава Секрети та конфігурація в CI; prod-подібний стенд локально часто піднімають через Docker.

    Окрема дисципліна: на staging/prod з реальними інтеграціями працюють обережно. Тест, який справді списує гроші чи шле листи живим людям, — це інцидент, а не тест. Тому платежі, пошту й сторонні API навіть на prod-подібних середовищах мокають або переводять у sandbox.

    Smoke після деплою

    Розгортання (deploy) саме по собі нічого не гарантує: артефакт скопіювався, процес перезапустився — і все. Чи піднявся застосунок насправді, чи доступна база, чи не забули змінну середовища — це окрема перевірка. Post-deploy smoke (перевірка розгортання, deployment verification) — це короткий набір перевірок критичного шляху одразу після викату, який відповідає на одне питання: «релізу можна довіряти чи негайно відкочувати?».

    Це не та сама регресія, що ганяється на PR. Різниця в цілі й адресаті:

    • регресія на PR перевіряє код до злиття, ганяється проти тестового білда;
    • post-deploy smoke перевіряє конкретне розгортання на конкретному середовищі — уже задеплоєний застосунок з його реальною конфігурацією, БД і мережею.

    Мінімум для smoke: health-ендпоінт відповідає 200, головна сторінка рендериться, логін працює, один-два наскрізні критичні сценарії (для маркетплейсу — «пошук → картка товару → додати в кошик») проходять. Він має бути швидким (хвилини, не години) і стабільним, бо блокує промоушен релізу. Різницю між smoke, sanity й регресією як видами тестування розбирають у розділі «Основи тестування»; тут важливий саме кут «перевірка після деплою».

    // Post-deploy smoke: ганяється проти реального URL середовища, не проти білда
    test('критичний шлях доступний після деплою', async ({ page, request }) => {
      // 1. Інфраструктура жива
      const health = await request.get(`${process.env.BASE_URL}/health`);
      expect(health.status()).toBe(200);
    
      // 2. Критичний користувацький сценарій працює
      await page.goto('/');
      await page.getByRole('searchbox').fill('ноутбук');
      await page.getByRole('button', { name: 'Знайти' }).click();
      await expect(page.getByRole('article').first()).toBeVisible();
    });

    У зрілому пайплайні smoke — це окрема стадія після deploy-джоба, і її провал автоматично тригерить відкат. Механіка стадій і джобів — у главі Будова пайплайна; стабільність автотестів у CI — у Автотести в CI.

    зелено

    червоно

    зелено

    червоно

    Мерж у main

    Build + артефакт

    Deploy staging

    Smoke + регресія

    Deploy prod

    Стоп, не промоутимо

    Post-deploy smoke на prod

    Реліз завершено

    Rollback

    зелено

    червоно

    зелено

    червоно

    Мерж у main

    Build + артефакт

    Deploy staging

    Smoke + регресія

    Deploy prod

    Стоп, не промоутимо

    Post-deploy smoke на prod

    Реліз завершено

    Rollback

    Стратегії розгортання: rolling, blue-green, canary

    Коли на проді сотні тисяч користувачів, «зупинити застосунок, підмінити файли, запустити» — не варіант: це простій і ризик. Тому реліз викочують так, щоб мінімізувати ризик і мати шлях назад. Три базові стратегії, які senior QA має розрізняти.

    Rolling (поступова заміна). Інстанси застосунку оновлюють по черзі: вимкнули один, підняли на новій версії, перевели трафік, узялися за наступний. Плюс — не потрібна подвійна інфраструктура. Мінус, критичний для QA: під час викату дві версії обслуговують трафік одночасно. Користувач одним запитом може потрапити на стару версію, наступним — на нову. Це вимагає зворотної сумісності: новий код мусить розуміти старі дані/запити, а API — старих клієнтів. Відкат теж поступовий і повільний.

    Blue-green (синьо-зелений). Тримають два ідентичні середовища: «синє» (поточне, на бойовому трафіку) і «зелене» (нова версія). Розгортаємо на зелене, ганяємо smoke на ньому без користувачів, а тоді балансувальник перемикає весь трафік із синього на зелене однією операцією. Головна перевага — миттєвий відкат: якщо щось пішло не так, перемикаємо трафік назад на синє. Мінус — потрібна подвійна інфраструктура і акуратність зі спільною базою (обидва кольори ходять в одну БД).

    Canary (канарковий реліз). Нову версію отримує спершу маленька частка трафіку (умовно 1–5%), решта лишається на старій. Метрики нової версії — помилки, латентність, бізнес-показники — порівнюють зі старою. Якщо здорові — частку поступово нарощують до 100%; якщо деградація — відкочують, зачепивши мінімум користувачів. Назва — від канарки в шахті. Canary дає найкращий контроль ризику й природно поєднується з автоматичним відкотом за метриками.

    Так

    Ні

    Deploy canary
    нова версія

    Роутимо 5% трафіку

    Метрики здорові?
    помилки, латентність

    Нарощуємо: 25 → 50 → 100%

    Rollback канарки
    зачеплено 5%

    Реліз завершено

    Так

    Ні

    Deploy canary
    нова версія

    Роутимо 5% трафіку

    Метрики здорові?
    помилки, латентність

    Нарощуємо: 25 → 50 → 100%

    Rollback канарки
    зачеплено 5%

    Реліз завершено

    Роль QA у всіх трьох — не «пройти клац-тест після викату», а закласти механіку безпеки на етапі планування:

    • визначити критерії просування й відкату: які smoke-перевірки й пороги метрик (error rate, p95-латентність) вважаємо здоровими — інакше рішення «промоутити чи ні» ухвалюють на око;
    • перевірити зворотну сумісність для rolling/canary: змішані версії не мають ламати одна одну. Класична пастка — несумісна міграція БД; безпечний патерн — expand-contract (спершу додати нове, не ламаючи старе, прибрати старе лише після повного викату). Механіка міграцій — у розділі «Бази даних і SQL»;
    • прогнати smoke на «зеленому»/канарці до живого трафіку;
    • домовитися про спостережуваність: без метрик і логів canary — це просто повільніший спосіб зламати прод (глава Логи, моніторинг і спостережуваність).

    Feature flags: відокремити деплой від релізу

    Feature flag (фіче-флаг, feature toggle) — це умовний перемикач у коді, який вмикає чи вимикає гілку функціональності без нового деплою. Головна ідея, яку часто недооцінюють: флаг розриває зв'язок між деплоєм і релізом. Код нової фічі може вже бути на проді (задеплоєний), але вимкнений флагом (не зарелізений). Реліз стає зміною конфігурації, а не викатом коду.

    Навіщо це QA і команді:

    • Kill switch. Фіча зламалася на проді — вимикаємо флаг за секунди, без rollback і без нового білда. Це найшвидший «відкат», який існує.
    • Поступове відкриття. Флаг можна вмикати для когорти: спершу внутрішнім співробітникам, потім 10% користувачів, потім усім. По суті це canary на рівні фічі, а не інстансів.
    • Тестування на проді. Фічу можна ввімкнути лише для тестових акаунтів на живому середовищі.

    Що це означає для тестування:

    • Тест має знати стан флага. Той самий білд поводиться по-різному залежно від флага, тож у налаштуванні тесту стан флага треба задавати явно (через API конфігурації чи cookie/заголовок оверрайду), а не сподіватися на дефолт. Інакше тест «іноді зелений» — класичний флак, тільки причина не в синхронізації, а в конфігурації.
    • Комбінаторний вибух. N незалежних флагів — це 2^N конфігурацій. Усі не перекриєш; покривай релевантні комбінації, а не декартів добуток — та сама логіка, що й у попарному тестуванні з розділу «Тест-дизайн».
    • Обидві гілки живі. Поки флаг існує, стара й нова поведінка обидві в проді — тестувати треба і on, і off.
    • Flag debt. Фіча давно розкотилася на 100%, а мертвий флаг і його else-гілка живуть у коді роками. Прибирання відпрацьованих флагів — частина гігієни.
    // Стан флага задаємо явно в налаштуванні, а не покладаємось на дефолт середовища
    test.describe('новий чекаут', () => {
      test.use({ featureFlags: { 'new-checkout': true } });
    
      test('оплата проходить у новому флоу', async ({ page }) => {
        // ... перевіряємо гілку on
      });
    });

    A/B-експерименти: варіанти, когорти, чистота метрик

    A/B-тест — це не про якість коду, а про продуктову гіпотезу: «нова кнопка збільшить конверсію?». Користувачів випадково ділять на варіанти (variants): контрольний (стара версія) і один чи кілька експериментальних (treatment). Далі порівнюють цільову метрику й вирішують, чи різниця статистично значуща. Технічно варіант часто розводять тим самим фіче-флагом, але ціль інша — не безпека релізу, а вимір ефекту.

    Три речі, за які відповідає QA і на яких валяться експерименти:

    Розподіл і когорти. Одиниця рандомізації (найчастіше користувач або сесія) має розподілятися випадково й стабільно: один і той самий користувач у межах експерименту завжди бачить той самий варіант (sticky assignment). Якщо варіант «мерехтить» між візитами — досвід зламаний, а дані брудні. Перевір: чи призначення липке; чи фактичний спліт відповідає заявленому (50/50 має бути ~50/50). Різке відхилення від очікуваного співвідношення — sample ratio mismatch (SRM), сигнал, що з експериментом щось не так (баг у сплітері, фільтрація ботів зачепила один варіант), і його результатам довіряти не можна.

    Чистота метрик. Метрику дуже легко забруднити. Головні джерела бруду:

    • внутрішній трафік — співробітники, QA-прогони, автотести, боти. Якщо твої 500 smoke-прогонів на день падають у той самий бакет, що й реальні користувачі, метрика зсунеться. Внутрішній трафік має виключатися з аналітики або йти в окремий сегмент;
    • некоректний трекінг — подія конверсії не спрацювала, спрацювала двічі чи приписалася не тому варіанту. Перевірити, що аналітичні події летять правильно й рівно один раз, — прямий обов'язок QA;
    • cross-contamination — користувач побачив обидва варіанти (різні пристрої, скинутий cookie).

    Guardrail-метрики. Крім цільової метрики стежать за «захисними»: не зросли помилки, не впала швидкість, не просіла інша воронка. Позитивний ефект на одну метрику ціною регресії деінде — це не перемога.

    Головна теза для співбесіди: QA в A/B — це не «клацнув обидва варіанти», а гарант валідності експерименту: коректний і липкий розподіл, чистий трафік, правильний трекінг подій.

    Rollback: як відкотитися

    Rollback (відкат) — повернення системи до попередньої робочої версії після невдалого релізу. Альтернатива — roll-forward (швидкий фікс новим релізом уперед); вибір залежить від того, що швидше й безпечніше. Способи відкату прямо залежать від того, як ти деплоїш:

    • blue-green — перемкнути балансувальник назад на «синє»; майже миттєво;
    • canary/feature flag — вимкнути флаг або відвести трафік від канарки; теж секунди, без нового білда;
    • rolling — викотити попередню версію тим самим поступовим механізмом; повільно;
    • завжди можна redeploy попереднього артефакту — тому артефакти релізів і теги зберігають (див. нижче про S3 і теги).

    Найпідступніше в rollback — база даних. Код відкотити легко, дані — ні. Якщо реліз накотив несумісну міграцію (перейменував колонку, видалив поле), відкат коду впаде на новій схемі. Тому міграції проєктують зворотно сумісними: expand-contract дозволяє відкотити код, не чіпаючи БД. Питання «а як ви відкочуєте міграцію БД?» — улюблене на senior-співбесідах: на ньому видно, хто реально релізив.

    Роль QA: ще до релізу переконатися, що план відкату існує й перевірений — «відкотимося, якщо що» без відрепетируваної процедури не спрацьовує. Оракулом «чи вже відкотилось / чи та версія» служить маркер версії застосунку (той самий принцип, що й фінгерпринтинг бандлів у главі Кешування).

    Семантичне версіонування

    Семантичне версіонування (semantic versioning, SemVer) — угода, за якою номер версії несе зміст, а не просто зростає. Формат — MAJOR.MINOR.PATCH (наприклад, 2.4.1), і кожна частина має чітке правило (специфікація SemVer 2.0.0):

    • MAJOR — несумісні зміни (breaking changes): те, що зламає наявних споживачів. 1.x.x2.0.0;
    • MINOR — нова функціональність, зворотно сумісна. 2.4.x2.5.0;
    • PATCH — виправлення багів без зміни контракту. 2.4.12.4.2.

    Є ще pre-release та build-мітки: 2.5.0-rc.1, 2.5.0-beta+build.42. Версії 0.y.z — фаза початкової розробки, де ламати можна будь-коли.

    Чому це важливо QA:

    • Версія — це очікування. MAJOR-бамп у залежності — плануєш регрес на breaking changes; PATCH — очікуєш незмінний контракт і фокусуєшся на фіксі. Це прямо годує контрактне й API-тестування (розділ «API-тестування»).
    • Діапазони версій. Записи ^2.4.0 (дозволяє MINOR/PATCH) чи ~2.4.0 (лише PATCH) у package.json означають, що npm install може підтягнути іншу версію, ніж була вчора, — джерело «нічого не міняв, а зламалось» (механіка діапазонів і lock-файлів — розділ «JavaScript/TypeScript для AQA»).
    • Реліз-версії зазвичай фіксують git-тегом (Git для AQA: bisect, blame, хуки й теги).

    Застереження чесності: SemVer поважають не всі. Багато продуктів релізяться за датою (CalVer, 2026.07), за номером білда чи просто git-хешем. SemVer — конвенція для версійованих контрактів (бібліотеки, API), а не універсальний закон.

    Testing in production

    Довго вважалося, що тестувати на проді — соромно. Насправді частину проблем інакше не спіймати: обсяг і форму реальних даних, реальний патерн трафіку, поведінку сторонніх інтеграцій, справжню інфраструктуру. Staging завжди трохи бреше, тож зрілі команди свідомо роблять частину перевірок на проді — контрольовано.

    Техніки testing in production:

    • Canary + feature flags — нова поведінка в проді, але для мікрочастки чи лише тестових акаунтів.
    • Synthetic monitoring — автотести критичного шляху, що ганяються проти prod за розкладом і алертять, коли ключовий флоу відвалився. Це постійне продовження post-deploy smoke.
    • Shadow / dark traffic (дзеркалювання трафіку) — копію реального трафіку шлють на нову версію паралельно, а її відповіді відкидають: навантажуємо новий код бойовими запитами, не впливаючи на користувача.
    • A/B і RUM (real user monitoring) — вимір поведінки й продуктивності на живих користувачах.

    Залізні запобіжники (без них це не «testing in production», а «інцидент in production»):

    • не псувати реальні дані — тестові акаунти, ізольовані сутності, sandbox для платежів/пошти;
    • обмежувати радіус ураження — фіче-флаги й canary, щоб проблема зачепила мінімум людей;
    • спостережуваність і швидкий kill switch — бачити наслідки в реальному часі й уміти вимкнути за секунди.

    Головна теза: testing in production — це доповнення, а не заміна тестуванню до прода. Воно ловить те, що не відтворюється на staging, але не скасовує smoke, регресію й перевірки на нижчих середовищах.

    Хмари й де живуть середовища (оглядово)

    Раніше середовища були фізичними серверами в серверній. Сьогодні dev/staging/prod найчастіше живуть у хмарі (AWS, Google Cloud, Azure) або в приватному кластері. QA не мусить бути хмарним інженером, але корисно розуміти, де виконується застосунок і звідки брати артефакти й логи. Оглядово, застосунок виконується як одне з:

    • віртуальні машини (AWS EC2 тощо) — «сервер в оренду»;
    • контейнери й оркестрація (Kubernetes, ECS) — застосунок пакується в образ і масштабується репліками; звідси й rolling/blue-green/canary як штатні можливості оркестратора;
    • керовані сервіси й serverless — інфраструктуру тримає провайдер.

    Окремо для QA важливе об'єктне сховище, канонічний приклад — Amazon S3 (Simple Storage Service). Це «нескінченний диск» для файлів, і артефакти зберігають саме там: зібрані білди, а головне для QA — звіти прогонів, скриншоти, відео й трейси впалих тестів, JUnit-XML. Коли нічний прогін впав о третій ночі, ти йдеш не на агента (його вже нема), а за посиланням на артефакт у сховищі. Механіка артефактів — глава Будова пайплайна; ретенція артефактів зазвичай обмежена в часі, тож критичні докази варто зберігати окремо, а не покладатися на дефолт CI.

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

    • «Задеплоїли = зарелізили». Виглядає як одне, а насправді два: з фіче-флагами код може місяць лежати на проді вимкненим. Плутанина ламає планування релізу й тестування.
    • «Пройшло на staging — пройде на prod». Виглядає як гарантія, а насправді паритет середовищ ніколи не ідеальний: інші дані, конфіг, версії, CDN. Це окремий клас багів, а не випадковість.
    • «Деплой пройшов, отже все ок». Виглядає як успіх, а насправді deploy лише скопіював артефакт. Без post-deploy smoke ти не знаєш, чи піднявся застосунок і чи змігрувала база.
    • «Ми зробимо rollback, якщо що». Виглядає як план, а насправді це наміри. Невідрепетируваний відкат (особливо з міграцією БД) у момент інциденту не спрацьовує.
    • «A/B показав ріст конверсії». Виглядає як перемога, а насправді метрику могли забруднити внутрішній трафік, зламаний трекінг чи SRM. Без перевірки чистоти даних цифрам вірити не можна.

    Підсумок

    • Деплой і реліз — різні події: деплой ставить код на середовище, реліз відкриває його користувачам; фіче-флаги роблять цей розрив явним і керованим.
    • Post-deploy smoke перевіряє конкретне розгортання на конкретному середовищі — це не та сама регресія, що на PR, і його провал має тригерити відкат.
    • Стратегії розгортання торгують ризиком за інфраструктуру: rolling дешевий, але живе зі змішаними версіями; blue-green дає миттєвий відкат; canary — найкращий контроль ризику за метриками. Роль QA — задати критерії просування/відкату й зворотну сумісність.
    • У A/B-експерименті QA відповідає за валідність: липкий і коректний розподіл, чистий від внутрішнього трафіку сегмент, правильний трекінг подій.
    • SemVer (MAJOR.MINOR.PATCH, спец 2.0.0) кодує сумісність: MAJOR — breaking, MINOR — нова сумісна фіча, PATCH — фікс; але поважають його не всюди.

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

    • «Чим відрізняються blue-green, canary і rolling? Коли що обереш?» Інтерв'юер дивиться, чи розумієш ти механіку (одночасність версій, спосіб і швидкість відкату), а не завчені означення. Сильна відповідь прив'язує вибір до ризику й наявної інфраструктури.
    • «Яка різниця між деплоєм і релізом?» Перевіряють, чи бачиш ти роль фіче-флагів і decoupling. Слабкий кандидат вважає це синонімами.
    • «Як тестуєш feature flag? Скільки конфігурацій?» Дивляться, чи згадаєш ти обидві гілки, явне задання стану флага в тесті й комбінаторний вибух (2^N), а не «поклацаю з увімкненим».
    • «Як відкотити реліз, який зачепив схему БД?» Класичний фільтр на senior: очікують згадку про зворотно сумісні міграції (expand-contract) і про те, що код відкотити легко, а дані — ні.
    • «A/B-тест показав ефект — як переконаєшся, що йому можна вірити?» Шукають розуміння чистоти метрик: виключення внутрішнього трафіку, перевірку трекінгу, SRM, guardrail-метрики.
    • «Що таке smoke після деплою і чим він відрізняється від регресії?» Перевіряють, чи розділяєш ти «перевірити код» і «перевірити конкретне розгортання».
    • «Поясни ^1.2.0 у package.json» — точковий на розуміння SemVer-діапазонів і чому збірка може «поплисти» без змін у коді.

    Джерела