CI/CD для Python: налаштування з pip, Poetry, Conda

Залежності в Python-проєктах розходяться між середовищами, і випуск перетворюється на ручну роботу з ризиком відкату. Ми налаштовуємо збірку та випуск під ключ: конвеєр сам збирає середовище, проганяє перевірки й готує реліз. Час від правки до робочої версії скорочується в кілька разів.
Часті запитання

Чому CI/CD Python розвалюється на залежностях

Класичний сценарій: розробник проганяє тести локально, усе зелене, гілка вирушає в загальний конвеєр — і збірка залежностей падає на встановленні пакетів. Причина не в коді, а в оточенні розробника: воно накопичувалося місяцями й не описане в жодному файлі проєкту.

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

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

Другий — змішування інструментів. pip, Poetry і Conda розв’язують залежності по-різному: Conda тягне бінарні збірки зі своїх каналів, pip — з індексів, Poetry фіксує власне дерево. Один пакет, поставлений в обхід основного менеджера, робить збірку невідтворюваною.

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

Ціна такої нестабільності — не одна збірка, що впала. Падіння збірки на етапі встановлення з’їдає час усієї команди, привчає ігнорувати червоні прогони й переносить пошук справжньої помилки на день релізу. Розбираємо, як це закривається.

Що змінюється в команді після налагодженої збірки й випуску

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

Нижче — чотири зрушення, які замовник помічає вже в перші тижні, без окремого проєкту з «упровадження метрик».

Збірка — хвилини замість десятків хвилин

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

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

Відтворюваність на всіх стендах

Poetry із зафіксованим lock-файлом, хеші пакетів і один образ для локальної машини, тестового стенда та бойового оточення прибирають розбіжності «у мене працює». Набір залежностей однаковий усюди, тому збій відтворюється локально, а не лише на сервері за п’ять хвилин до випуску.

Повернення до попереднього релізу — хвилини

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

Клас відмов, який зникає

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

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

Способи збирання оточень для Python-проєктів

У Python немає вбудованої фіксації оточення: інтерпретатор один, а набір бібліотек у системній гілці спільний для всіх проєктів. Варто підняти поруч другий сервіс із несумісною мажорною версією спільної залежності — і падає вже працюючий.

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

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

Варіант Коли підходить Обмеження
venv + pip + requirements.txt Невеликі сервіси, залежності перелічені вручну Немає фіксації транзитивних версій — повторне встановлення дає інший набір
pip-tools Уже склався requirements.txt, потрібна фіксація версій Прямі й транзитивні списки ведуться роздільно, конфлікти розв’язуються перезбиранням
Poetry / PDM Бібліотеки й сервіси з групами залежностей під тести та документацію Власний алгоритм розв’язання версій, розбіжності з pip під час збирання артефактів
Conda Науковий стек і бінарні залежності поза PyPI Важкі канали, оточення погано стискається в мінімальний образ
Образ контейнера як оточення Сервіси, де середовище збірки й рантайм мають збігатися Без шаруватого кешу час збірки зростає кратно на кожній правці коду

Практично вибір іде за двома осями: хто розв’язує конфлікти версій — pip чи власний механізм менеджера, і де проходить межа оточення — у віртуальному каталозі чи в образі.

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

Етапи впровадження: від розбору репозиторіїв до передачі

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

  1. Аудит репозиторіїв. Дивимося структуру проєктів і те, чим оголошені залежності: requirements.txt з точними версіями, pyproject.toml із Poetry або окреме середовище Conda. Відзначаємо, де оточення збирається руками і скільки це віднімає часу в команди.
  2. Схема конвеєра. Узгоджуємо стадії та їхній порядок: підготовка оточення, перевірки коду, тести, збірка артефакту, публікація, доставка на стенди. Для кожної стадії одразу визначаємо, що вважається блокуючою помилкою.
  3. Налаштування оточень. Приводимо версію інтерпретатора й склад залежностей до єдиного вигляду, вмикаємо кеш пакетів між запусками — повторна збірка більше не тягне все з нуля.
  4. Тестовий прогін. Запускаємо конвеєр на одному репозиторії або гілці, заміряємо час стадій і шукаємо падіння на порожньому місці: плаваючі залежності, відсутність ізоляції тестів.
  5. Приймальна перевірка. Проходимо сценарії, критичні для замовника: падіння тестів блокує доставку, відкат на попередній артефакт, паралельні збірки не заважають одна одній.
  6. Передача й навчання. Віддаємо схему конвеєра, інструкцію з правки стадій і розбір типових збоїв, проходимо їх із командою — далі підтримку ведуть власними силами.

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

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

Тому разом із робочою збіркою передаємо комплект артефактів у репозиторій проєкту та в базу знань команди — за ними процес відтворюється без автора.

  • Файли конфігурації конвеєра з коментарями: де стадії збірки й тестів, які умови запуску, що кешується між прогонами.
  • Файли керування залежностями для вибраного інструмента (pip, Poetry або Conda): зафіксовані версії, розділення наборів для розробки й для релізу.
  • Схема оточень: які контури є, що в кожному розгортається, чим відрізняються параметри, як оточення піднімається з нуля.
  • Політики доступу до секретів: де зберігаються ключі, хто їх видає, як проходить ротація і що гарантовано не потрапляє в логи.
  • Регламент випуску: шлях від гілки до продуктивного середовища, хто ухвалює рішення, у яких точках можливий відкат і що блокує випуск.
  • Інструкції для чергового інженера: типові збої збірки, порядок дій при падінні прогону й відкаті, точки ескалації.

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

Регламент і схема оточень залишаються робочими документами — їх правлять разом із кодом, а не відновлюють після збою.

Кейс: конвеєр випуску для розподіленої команди

У розподіленій команді збірка одного сервісу займала від 18 до 25 хвилин, і цей час майже не залежав від обсягу правок.

Причина була не в тестах: половину кроку з’їдало встановлення залежностей, яке кожен контур вирішував по-своєму — сервіси тягнули свіжі версії із загального індексу, частина проєктів збиралася Poetry, аналітичний контур жив в окремому Conda-оточенні. У результаті виходило головне: збирач і машина розробника давали різні набори пакетів.

Що змінили

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

Кеш шарів налаштований так, що перезбирання оточення запускається лише за зміни цього файлу, а сама збірка в GitLab CI іде в незмінному образі, прив’язаному до конкретної ревізії коду. Перевірки розділили на швидкий набір на кожну зміну й повний прогін перед випуском.

До: близько 20 хвилин на збірку, зривався кожен четвертий випуск — розбіжність оточень, падіння на кроці встановлення, ручні правки на сервері.

Після: збірка вкладається в 6–7 хвилин, зривається один випуск із двадцяти, а відкат повертає попередній образ за хвилини замість ручного розбору.

Чим збірка бібліотеки відрізняється від збірки сервісів?

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

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

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

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

Публікація артефактів для бібліотеки — операція незворотна за змістом: одна версія випускається один раз, перезапис заборонений, інакше у двох різних збірок буде однакове ім’я.

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

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

Версіонування прив’язують до стану репозиторію й мітки випуску, а не до номера пакета, — важлива трасування «яка ревізія коду зараз працює».

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

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

Технічні сумніви перед стартом робіт

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

У нас уже налаштований конвеєр збірки — доведеться переписувати все з нуля?

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

Що робити, якщо залежності двох сервісів конфліктують за версіями?

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

Чи можна обійтися без власного сховища пакетів?

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

Як забезпечується відтворюваність збірки?

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

Хто супроводжує конвеєр після передачі?

Передаємо конфігурації, схеми етапів і опис типових збоїв із порядком дій. Команда замовника може вести конвеєр сама — документація розрахована на це.

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

Обговоримо задачу й повернемося зі схемою та планом

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

Від вас потрібен мінімум вступних даних:

  • репозиторій або його структура: гілки, теги, порядок випуску версій;
  • файли залежностей — що використовуєте: pip, Poetry або Conda, чи є приватні дзеркала пакетів;
  • перелік середовищ і чим вони відрізняються за доступами та змінними оточення;
  • слабкі місця за останні місяці: збірки, що впали, ручні кроки, відкати;
  • обмеження: закриті мережі, вимоги до доступів і регламенти безпеки;
  • бажаний час збірки й випуску — цифри, які вважаєте прийнятними.

У відповідь надсилаємо схему конвеєра цілком: етапи, умови запуску, кеш залежностей, розділення перевірок і публікації артефактів.

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

Опишіть задачу у вільній формі — повернемося з оцінкою та планом.

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1369
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    816
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1141