Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Lock-файлы и воспроизводимая установка зависимостей

Lock-файлы: воспроизводимая установка зависимостей в Python

Lock-файлы Python: отличие от requirements.txt, фиксация полного дерева с контрольными суммами, проверка хешей при установке и обновление блокировки.

Прямой ответ

Воспроизводимая установка означает, что одинаковый перечень зависимостей даёт одинаковый набор установленных пакетов на любой машине и в любой момент. Без файла блокировки это не выполняется: установка разрешает версии заново, и результат зависит от состояния реестра на момент выполнения.

Различие двух подходов:

Требования с диапазонами Файл блокировки
Содержимое Прямые зависимости Полное дерево зависимостей
Версии Диапазон Точная версия
Транзитивные зависимости Разрешаются при установке Зафиксированы
Контрольные суммы Обычно нет Есть
Результат установки Может различаться Одинаков всегда

Перечень с диапазонами описывает намерение:

httpx>=0.27
pydantic>=2.6

Файл блокировки фиксирует результат разрешения, включая транзитивные зависимости и контрольные суммы:

httpx==0.27.0 \
    --hash=sha256:7b8f...
certifi==2024.2.2 \
    --hash=sha256:0f0d...

Установка с проверкой контрольных сумм отклоняет пакет, содержимое которого не соответствует ожидаемому:

pip install --require-hashes -r requirements.lock

Это защищает от подмены содержимого в реестре и от установки версии, выпущенной после формирования блокировки; о рисках цепочки поставки — в статье про supply chain.

Разделение ролей принципиально: файл с диапазонами редактируется человеком и описывает, что нужно проекту. Файл блокировки формируется инструментом и описывает, что именно устанавливается. Правка блокировки вручную недопустима, поскольку нарушает согласованность дерева.

Обновление блокировки — отдельная операция:

# пересоздать блокировку с учётом актуальных версий
pip-compile pyproject.toml --generate-hashes -o requirements.lock

Почему блокировка не решает все задачи

Файл блокировки фиксирует набор версий, но не устраняет четыре источника расхождения.

Платформенные зависимости. Дерево зависимостей может различаться по операционным системам: пакет, нужный только в Linux, отсутствует в разрешении для другой платформы. Блокировка, созданная на одной платформе, может не покрывать другую.

Версия интерпретатора. Набор зависимостей зависит от версии Python, поскольку часть пакетов объявляет ограничения по версии. Блокировка, созданная под одну версию, может быть неприменима к другой.

Состояние реестра при пересоздании. Пересоздание блокировки даёт результат, зависящий от актуальных версий в реестре. Две блокировки, созданные в разное время из одних требований, различаются.

Дополнительные наборы зависимостей. Необязательные зависимости включаются в блокировку только если указаны при её создании. Блокировка без них не покрывает установку с дополнительным набором.

Поэтому блокировка воспроизводит установку при совпадении платформы, версии интерпретатора и набора дополнительных зависимостей. Изменение любого из этих параметров требует пересоздания файла.

Практическое следствие: блокировка создаётся в том же окружении, в котором выполняется сборка, либо создаётся несколько файлов для разных целевых платформ.

Практика: работа с файлами блокировки

1. Разделяйте файл требований и файл блокировки. Первый описывает намерение и редактируется человеком, второй фиксирует результат и формируется инструментом.

2. Фиксируйте контрольные суммы. Установка с проверкой хешей исключает подмену содержимого пакета в реестре.

3. Храните блокировку в репозитории. Без неё воспроизведение установки на другой машине невозможно; о настройке пакета — в статье про структуру.

4. Обновляйте блокировку осознанно. Пересоздание меняет все транзитивные версии сразу, поэтому обновление выполняется отдельным коммитом с полным прогоном тестов.

5. Не правьте блокировку вручную. Изменение одной версии без пересчёта дерева приводит к несогласованному состоянию.

6. Используйте отдельные блокировки для разных целей. Зависимости приложения фиксируются полностью, а зависимости библиотеки задаются диапазонами.

7. Проверяйте установку из блокировки в конвейере. Шаг установки с проверкой хешей выявляет расхождение до развёртывания.

8. Обновляйте зависимости регулярно. Блокировка годичной давности означает, что обновление затронет сотни пакетов одновременно; о процессе — в статье про обновление зависимостей.

Рекомендации:

  1. Фиксируйте версии и в образе контейнера. Установка без блокировки при сборке образа делает образ невоспроизводимым.
  2. Обновляйте блокировку одним типом изменения. Смешение обновления зависимостей и правок кода затрудняет откат.
  3. Проверяйте, что блокировка покрывает все платформы. Различия в зависимостях по платформам требуют учёта в файле.
  4. Ведите отдельную блокировку для инструментов разработки. Линтер и инструменты тестов не должны влиять на зависимости приложения.
  5. Сохраняйте историю изменений блокировки. Коммит с обновлением показывает, какие версии изменились и почему.

Практический сценарий: разное поведение продакшена и разработки из-за отсутствия блокировки

Контекст: сервис разворачивался установкой зависимостей из перечня с диапазонами. Через месяц после релиза поведение в продуктивной среде изменилось без изменения кода.

  1. Диагностика. Одна из транзитивных зависимостей выпустила новую минорную версию с изменённым поведением при разборе входных данных. Перечень допускал диапазон, поэтому при сборке нового образа установилась новая версия.
  2. Причина. Транзитивные зависимости не были зафиксированы: перечислялись только прямые. Разрешение версий выполнялось при каждой сборке образа, и результат зависел от состояния реестра.
  3. Действия. (а) Ввели файл блокировки с полным деревом и контрольными суммами; (б) установку в конвейере перевели на режим с проверкой хешей; (в) проблемную зависимость зафиксировали на проверенной версии; (г) добавили шаг обновления блокировки отдельным запросом с прогоном тестов; (д) проверили остальные сервисы и перевели их на блокировки.
  4. Результат. Сборка образа стала воспроизводимой: одинаковый коммит даёт одинаковый набор зависимостей независимо от даты сборки.

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

FAQ

Чем файл блокировки отличается от requirements.txt? Блокировка содержит полное дерево зависимостей с точными версиями и контрольными суммами, а requirements.txt обычно перечисляет прямые зависимости с диапазонами.

Нужны ли контрольные суммы? Да, если требуется защита от подмены содержимого. Установка с проверкой хешей отклоняет пакет, не соответствующий ожидаемому.

Можно ли править блокировку вручную? Нет. Изменение версии без пересчёта дерева нарушает согласованность. Блокировка пересоздаётся инструментом.

Нужна ли блокировка для библиотеки? Нет. Библиотека указывает диапазоны, а фиксацию выполняет приложение, которое её использует.

Источники: pip docs (Secure Installs), pip-tools docs, PEP 508 — ссылки в шапке.