В статье мы рассмотрим организацию частичного зеркала PyPi при помощи devpi, запуск сервера будет автоматизирован при помощи docker-compose.

С учётом текущей обстановки, имеет смысл позаботиться о том, чтобы привычные инструменты оставались доступны и в дальнейшем, даже в случае тех или иных блокировок. В частности это касается менеджера пакетов pip (проблема с ним из-за блокировок ранее уже возникала). Делать полную копию всего архива пакетов вряд ли рационально, но довольно легко можно настроить своё частичное зеркало, которое будет сохранять для повторного использования пакеты, которые вы через него загружаете.

Ранее на Хабре уже появлялась статья на эту тему, но всё же решился опубликовать свой вариант, он основан на том же devpi-server, но устанавливается проще, в минимальном варианте нужно только настроить пару конфигурационных файлов в несколько строк и выполнить пару типовых команд:

git clone https://github.com/askh/pypimirror.git
docker-compose up -d

Для работы вам понадобятся Docker, Docker Compose, Python, pip и Git.

Общая информация о настройке сервера

Сначала рассмотрим собственно процедуру подготовки и запуска программного обеспечения, используемого для создания зеркала (если теория неинтересна, то можно сразу перейти к следующему разделу, там инструкция по установке). Для создания зеркала используем devpi-server, вместе с ним лучше сразу установить пакеты devpi-web и devpi-client (это если вы хотите поэкспериментировать; на клиентской стороне в процессе использования зеркала они вам вероятно не понадобятся, или понадобится только третий из них):

pip install -U devpi-server devpi-web devpi-client

Для запуска сервера нам по сути нужно выполнить всего две команды (в примере предполагается, что путь к данным находится в переменной SERVER_DIR). Сначала нужно инициализировать рабочий каталог:

devpi-init --serverdir "$SERVER_DIR"

И, далее, запустить сервер (в примере предполагается, что сервер будет работать на всех сетевых интерфейсах, на порту по умолчанию, равном 3141):

devpi-server --serverdir "$SERVER_DIR" --host 0.0.0.0

Если вы будете запускать зеркало на системе с ограниченными ресурсами (например, в VPS), убедитесь, что в системе достаточно памяти (по опыту работы, в системе с менее 1 ГБ памяти devpi-server аварийно останавливался).

Однако devpi предназначен не только для создания зеркала пакетов PyPi, основной его функционал направлен на создание своих коллекций пакетов, что находит применение при разработке. И по умолчанию сервер открыт для анонимной регистрации, то есть, любой имеющий к нему доступ может зарегистрировать там аккаунт, чтобы использовать свою коллекцию пакетов.

Возможно это не представляет угрозы (только в плане использования места на диске), но скорее всего вы предпочтёте ограничить такую возможность. Это делается при помощи опции командной строки вида --restrict-modify root (или соответствующей опции конфигурационного файла). В данном случае root — это имя пользователя, который будет иметь возможность создавать новых пользователей (в частности). Это не пользователь операционной системы с тем же именем. Пользователь root создаётся автоматически при инициализации сервера, и получает пустой пароль. Как показывает практика, пустые пароли недостаточно хорошо противостоят атакам злоумышленников, поэтому наверняка вы захотите поменять его на более надёжный. Это можно сделать на этапе инициализации сервера (при запуске devpi-init при помощи опции командной строки или в конфигурационном файле) или потом, при помощи скрипта devpi. Таким образом, команды для подготовки и запуска сервера могут быть следующими:

Первичная инициализация каталога сервера

devpi-init --serverdir "$SERVER_DIR" --root-passwd YOUR_SECRET_PASSWORD

Запуск devpi-server

devpi-server --serverdir "$SERVER_DIR" --host 0.0.0.0 --restrict-modify root

Автоматическая загрузка сервера

Автоматическую загрузку сервера можно осуществлять разными способами, самым простым мне показалось настроить его используя docker-compose. Вероятно нет смысла копировать сюда полный текст конфигурационных файлов и скрипта для запуска сервера, вы можете скачать их с github.com. Для настройки локального зеркала нужно будет выполнить четыре простых шага:

  1. Скопировать каталог с настройками для docker-compose (git clone ...).

  2. Создать файл .env с переменными окружения по образцу.

  3. Собрать образ и запустить контейнер (выполняется одной командой).

  4. Скопировать конфигурационный файл для pip (указав там адрес своего сервера).

Теперь более подробно по этим шагам.

Получение кода настроек

Для получения настроек для запуска зеркала через docker-compose клонируйте репозиторий с ними следующим образом:

git clone https://github.com/askh/pypimirror.git

Там более-менее стандартный набор файлов для запуска приложения на Python при помощи docker-compose: Dockerfile, docker-compose.yml, requirements.txt а также скрипт для запуска зеркала devpisrv.py и образцы конфигурационных файлов.

Создание файла с переменными окружения

Скопируйте файл .env.example в каталоге с настройками под именем .env и измените там пароль пользователя root (он находится в переменной ROOT_PASSWORD). Также там можно поменять IP-адрес и порт, на которых работает сервер зеркала. По умолчанию он будет слушать на все сетевые интерфейсы на порту номер 3141, но, если вы, например, планируете настроить защищённое соединение, используя Apache или nginx в качестве обратного прокси, вероятно вы захотите, чтобы devpi-server был доступен только на локальном интерфейсе (чтобы к нему не было прямого доступа по HTTP извне), в этом случае в .env вы можете определить следующую переменную:

SERVER_IP=127.0.0.1

Сборка образа и запуск контейнера

Для тех, кто не работал с docker-compose, заголовок может звучать пугающе, но на самом деле это всё делается одной командой (запускайте в каталоге pypimirror):

docker-compose up -d

Запустить контейнер возможно от пользователя, принадлежащего к группе docker или от пользователя root.

Проконтролировать работу сервера можно командой (выполненной в том же каталоге):

docker-compose logs

Настройка клиента

Указать pip, откуда нужно брать пакеты, можно через конфигурационный файл или опции командной строки. Создайте файл настроек для pip (если рассматривать настройки для конкретного пользователя, то для Linux это $HOME/.config/pip/pip.conf, для Windows, согласно документации, это файл %APPDATA%\pip\pip.ini, а для Mac OS X — $HOME/Library/Application Support/pip/pip.conf) cо следующим содержимым (для примера предположим, что адрес вашего сервера — mirror.example.com):

[global]
index-url=http://mirror.example.com:3141/root/pypi/+simple/
trusted-host=mirror.example.com
timeout=120

Те же самые опции можно передать в командной строке, например:

pip install --index-url http://mirror.example.com:3141/root/pypi/+simple/ --trusted-host mirror.example.com --timeout 120 virtualenv

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

Теперь при установке пакетов командой pip install файлы будут скачиваться через зеркало, которое сохранит их для последующего использования, и эти пакеты можно будет повторно загружать даже если сервера pypi.org и files.pythonhosted.org (или Интернет в целом) окажутся недоступны.

Загрузка уже установленных пакетов

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

 pip freeze > requirements.txt

Но в этом случае в списке будут не только пакеты, установленные через менеджер пакетов, но и, например, те из них, которые вы устанавливали другими способами, (например, через apt install, в виде deb пакетов), и они даже могут быть недоступны для установки через pip. Вероятно лучше будет собрать список пакетов, установленных от имени пользователя (если вы устанавливали их таким образом):

pip freeze --user > requirements.txt

Также возможно вам пригодятся файлы requirements.txt из ваших проектов. Теперь эти пакеты нужно пропустить через сервер. Можно просто загрузить их без установки, для этого создайте временный каталог, перейдите в него и выполните команду (предполагается, что файл requirements.txt находится в том же каталоге):

pip download --no-cache-dir -r requirements.txt

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

Проверяем результат

Итак, мы запустили свой сервер для хранения пакетов, загрузили в него те пакеты, которые нам потребуются, и осталось проверить, действительно ли всё работает как предполагалось. Отключаем Интернет, перезагружаем сервер (проверяем этим, что наш сервер пакетов запускается после перезагрузки, и что у нас нет ошибок в настройке volume — что информация сохраняется после перезапуска контейнера), и запускаем загрузку ранее скачанных пакетов. Всё должно пройти успешно. На клиенте мы увидим примерно следующее:

Если посмотрим логи на сервере, мы увидим там ошибки при обращении к сети и успешно выполняемые запросы на скачивание пакетов:

Далее проверим доступ к серверу со стороны клиента. Предполагается, что вы уже установили пакет devpi-client, в состав которого входит скрипт devpi. Во-первых, необходимо указать, какой сервер devpi вы планируете использовать (укажите адрес своего сервера):

devpi use http://mirror.example.com:3141/

Далее попытайтесь создать там пользователя (поменяйте пароль на свой):

devpi user -c testuser password=YOUR_SECRET_PASSWORD

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

Далее аутентифицируйтесь как пользователь root (с паролем, который вы указали в .env, при его отсутствии пароль по умолчанию будет пустой):

devpi login root

И теперь можете повторить команду для создания пользователя, в этот раз она должна сработать.

Заключение

В данной статье devpi рассмотрен только поверхностно, и не показаны все его возможности. Основная цепь, которую хотелось достигнуть: представить быстрое решение для создания частичного зеркала PyPi, чтобы обезопасить себя от ряда случайностей в нашем динамично меняющемся мире.

Более подробно про devpi вы можете прочитать в документации.

Комментарии (1)


  1. TyVik
    07.04.2022 15:42
    +2

    Есть ещё утилита bandersnatch, которая весь pypi выкачивает и позволяет использовать копию вместо оригинального. Я остановился, когда она скачала 1.5Tb, и начал думать над фильтрами (например, python 2.x мне совсем не пригодится). Весь pypi по моим подсчётам весит ~10Tb.