Выкладка python-проектов с помощью pip и wheel

Привет, Хабрахабр!

В этом посте я буду говорить о выкладке Python-проектов: о том как положить на сервер код и все требуемые сторонние модули. Многие из нас сталкивались с проблемой развертки проекта на боевой машине, но на хабре об этом мало пишут; я хочу поделиться своим опытом.

image



Проблема


Каждый запускает проект по разному; для полноты картины коротко опишу что использую я.

  1. pip и virtualenv для локальной установки пакетов
  2. uwsgi прослойка между nginx и web приложением
  3. supervisor для запуска нужных процессов: uwsgi, celery, tornado и т.д.
  4. nginx запускается под root и смотрит в даль


В наших проектах мы частенько используем модули, которые в какой то мере написаны на C и требуют компиляции. Тут и начинаются неудобства: на сервере нужно установить компилятор и еще кучу dev пакетов, чтобы скомпилировать зависимости вашего проекта (таких как simplejson или lxml). Первое, что приходит в голову — это взять и скомпилировать локально, а потом просто скопировать на сервер. А что тут такого? И там и там x86_64. Но как оказалось бинарная совместимость это шаткое понятие в Linux системах. Я конечно догадываюсь почему это так, но слабоват чтобы об этом рассуждать. Короче, то что вы скомпилируете у себя на Ubuntu не обязательно будет работать на Debian. Если у вас куча серверов, можно выделить один для сборки пакетов или поднять идентичную виртуальную машину локально и компилировать там.

При выкладке проекта на боевой сервер есть два варианта: компилировать код на сервере или делать сборку пакетов самому. Лично меня этот скудный выбор расстраивает. Напрашивается вопрос, а почему авторы модулей или PyPi не делает сборку пакетов для разных платформ, есть же бинарные яйца(binary eggs)? Они это делали, и вы даже можете устанавливать их с помощью easy_install, но эта попытка провалилась, так как нет гарантий, то что эти бинарники будут работать у вас на сервере даже с той же версией Python и архитектурой. И наверное на PyPi не у всех пакетов, требующих компиляции, есть бинарные сборки. Кстати, pip не поддерживает бинарные яйца именно по этой причине. А компилировать код на сервере это какой-то больной подход, особенно если серверов для выкладки у вас много, да даже работая за ноутбуком этот процесс просто выбешывает. Все должно быть как то проще: просто скачал и установил.

Попытка №1


Когда я занялся этим вопросом в первый раз, я пошел во все тяжкие. Я поднимал свой Debian репозиторий и собирал проект в нативные deb-пакеты. Все было вполне автоматизировано: я написал скрипты для автоматического запуска проекта; на fabric написал скрипт для сборки deb-пакетов и выкладки проекта на сервер. Проект разбивался на три разных deb-пакета: код проекта, virtualenv со всеми модулями, конфигурации (dev, prod). Установка и запуск на сервере заключалась в том, чтобы выполнить одну команду:

sudo apt-get install my-project-venv my-project-dev-conf my-project

Это все конечно здорово, но очень надоедало то, что при обновлении одного модуля в virtualenv приходилось компилировать все модули, чтобы собрать deb-пакет. А еще там было немало танцев с бубном, чтобы сделать непереносимую virtualenv переносимой: переписывать путь в строке !#python во всех файлах в папке bin, удалять все pyc-файлы, удалять все ссылки и устанавливать все пакеты в папке src в папку site-packages. Чтобы проработать эту махину по выкладке, мне пришлось потратить кучу времени и теперь как-то не очевидно, что я сделал свою жизнь проще.

Попытка №2


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

Wheel — это альтернатива бинарным яйцам; автор старается делать все по последним веяниям, и не так давно его PEP был принят. Из отличий примечательно, что wheel это формат установки, а не импортируемое. Еще wheel хороший помощник при работе на локальной машине: можно скачать и скомпилировать все часто используемые пакеты в одну папку, потом при создании новой виртуальной среды устанавливать пакеты оттуда за O(1).

Теперь для выкладки проекта я делаю следующее:
  1. Поднял свой индекс пакетов: на github есть немало проектов, которые позволяют поднять свой приватный pypi, я использую localshop, потому что в нем есть возможность ограничить доступ на скачивание.
  2. Все зависимости (из requires.txt) собираю в формате wheel и заливаю в свой индекс: для этого мне пришлось немного дописать localshop, так как он не поддерживал этот формат.
  3. Для того чтобы не устанавливать git на сервере, проект так же пакую и кладу в свой индекс пакетов.


На стороне сервера остается только установить и запустить. Это выглядит примерно так:
virtualenv myproject
. myproject/bin/activate
# проект установится и подтянет все зависимости
pip install --use-wheel myproject==0.1.1
myproject init
myproject runserver 0.0.0.0:8000


Запуск под uwsgi:
pip install --use-wheel uwsgi
uwsgi --module=myproject.wsgi --home=myproject ....


Запуск supervisor:
pip install --use-wheel supervisor
supervisor -c supervisor.conf -j supervisor.pid


Я пишу на Python 2.7.3, который стоит по умолчанию на моей Ubuntu, а на серверах стоит Debian и Python версии 2.6. Конечно же между ними есть разница, например: форматирование строк c помощью format нет на Python 2.6. Пытаться поставить 2.7.3 из дистрибутива не лучшая идея, легче скомпилировать Python и в этом нам хороший помощник — проект pythonbrew.

Лень — двигатель прогресса! Все мы программисты лентяи(особенно те, кто пишут на Python и Ruby) и, сталкиваясь с неудобствами, мы хотим сделать себе жизнь проще. А как делаете выкладку вы?

Ссылки


lucumr.pocoo.org/2012/6/22/hate-hate-hate-everywhere
github.com/pypa/pip/issues/492
hynek.me/articles/python-app-deployment-with-native-packages
crate.io/packages/wheel
www.python.org/dev/peps/pep-0427
bitbucket.org/dholth/wheel
wheel.readthedocs.org/en/latest/story.html
github.com/mvantellingen/localshop
github.com/utahta/pythonbrew
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 20

    0
    Напрашивается вопрос, а почему авторы модулей или PyPi не делает сборку пакетов для разных платформ


    Делают: code.activestate.com/pypm/

    есть же бинарные яйца(binary eggs)?


    Термин «бинарные яйца» воодит в заблуждение. В понимании автора, «бинарный» — это отличный от текстового. Конкретно «binary eggs» — это те же исходники, но запакованные zip'ом.

      0
      Возможно я напутал с терминологией; я имел ввиду:
      Конкретно «binary eggs» — это те же исходники, но запакованные zip'ом.

      + архив может содержать бинарные файлы для определенной платформы.

      wiki.python.org/moin/PythonPackagingTerminology
        0
        Я таких яиц не встречал. В качестве «ресурса» можно в любую систему дистрибуции положить любой бинарник — как он по платформе выбираться будет?
          +1
          Вот пример — https://pypi.python.org/packages/2.6/s/simplejson/

          В имени файла есть информация о версии питона и платформе.
            0
            Да, действительно — там внутри настоящий линуксовский .so файл :). Спасибо за наводку.
        0
        Спасибо за pypm, раньше не слышал.

        Кстати они тоже говорят, что не факт что будет работать:

        We recommend ActivePython users to install packages using PyPM, and only if that fails (usually it doesn't), attempt the same using pip/easy_install.


        Можно пытаться комбинировать: сначала ставить из pypm, а нерабочие пакеты ставить из своего индекса. Но тут есть опасность, что нерабочий модуль обнаружится не сразу.
        0
        Интересно. Пара вопросов:

        1. В pip что-ли уже поддержку wheel влили? Или нужно ветку использовать, или wheel сам поддержку добавляет?

        2. А без localshop нельзя? pip умеет ставить пакеты просто из папки («pip install --no-index --find-links:file:///local/dir/SomePackage»); можно еще просто путь до архива указывать, хоть локальный, хоть нет («pip install ./downloads/SomePackage-1.0.4.tar.gz»), да и листинг файлов вроде можно хоть nginx'ом, хоть «python -m http.server» отдавать (и ключиком каким-нибудь сказть pip-у там пакеты искать, pip же умеет ссылки в свободной форме скрейпить). Я сильно в вопросе не разбирался, но всегда было непонятно, почему люди хотят поднять локальный pypi сервер — может, подскажите, какие именно преимущества дает свой pypi сервер?
          0
          У pip есть ветка, в которой добавлена поддержка wheel.

          Можно и без localshop — зависит от потребностей. По началу у нас была общая папка под NFS; сборочная машина кладет, а сервер оттуда ставит. Можно использовать и для разработки в офисе, чтобы не тянуть с интернета. С nginx тоже получится, хотя часть API pip'а не будет работать (с поиском кажись). Если требуется аутентификация, то можно настроить basic_auth на nginx.

          Плюсы использования localshop:
          1. Самое главное — возможность загрузки пакетов: своего проекта, сторонние дописанные модули. Например, надо как-то допилить чей-то модуль, и не хочется ждать пока автор примет pull request. Добавим, например, свои инициалы в версию в файле setup.py и зальем в свой индекс. В requirements.txt своего проекта указываем нашу версию, так во время развертки избежим вытягивание из git'a и мороки с доступом к приватным проектам.
          2. Аутентификация: каждому сотруднику выдать по пользователю, при нужде можно отключить без возни с htaccess файлами.
          3. В фоновом режиме подгружает используемые модули — выступает как зеркало.

          Вот так загружаем свой проект в индекс(загрузит .tar.gz и .whl пакеты):
          python setup.py sdist bdist_wheel upload -r mypypi

          Если ваш проект написан только на питоне, то bdist_wheel можно исключить.

          А эта команда скачает все зависимости, построит из них wheel пакеты, и загрузит их в индекс:
          pip wheel -v --build-option upload --build-option -r --build-option mypypi -r requirements.txt

          Могу в деталях описать в отдельном посте, будет ли полезно?
            0
            Ага, т.е. все-таки ветка. С ней, кстати, нужно иметь в виду, что там (в отличие от pip 1.3.1) не проверяются ssl-сертификаты при скачивании, ее обновить бы.

            При установке с pypi-сервера еще, вроде бы, есть одна особенность — pip лезет еще по разным ссылкам (не разбирался каким, но вроде из long_description (часто это README) и по всем со странички Home Page, которая как url в setup.py указывается) и ищет новые версии еще и там (что замедляет установку — иногда очень сильно, а при отсутствии проверки ssl — это еще и небезопасно). Т.е., вроде бы, без pypi сервера может все работать быстрее и безопаснее. Ну так, предостережение — если используйте ветку, то лучше форкнуть и до мастера обновить ее.

            С загрузкой пакетов — сделать «python setup.py sdist bdist_wheel», без «upload» — .tar.gz-шки (и, видимо, .whl-ки) появятся в папке dist рядом с setup.py (а дальше уже эти файлы куда угодно) — точно так же можно избежать выкачивания из git и прочей мороки.

            Зеркало для архивов делается установкой переменной окружения PIP_DOWNLOAD_CACHE (хотя оно обычно не очень спасает, т.к. основное время все равно чаще тратится на скрейпинг ссылок c домашней странички проекта).

            Но вцелом ясно, спасибо — с pypi-сервером, видимо, и правда удобнее права раздавать, + pip search заработает.

            Пост про wheel — думаю да, полезно.
          0
          Для установки неродного питона есть ppa deadsnakes от Felix Krull, которое теперь не только dead, но и наоборот.

            0
            Блин.
            Мой мозг съеден ubuntu-lts-сервером.
              0
              А нет проблемы, что какие-то системные утилиты начинают использовать установленный python и не запускаются, так как не могут найти модули установлены в старый python?
                0
                Пакеты из deadsnakes не переписывают то, что уже есть в системе.
                Новый и старый питоны живут параллельно.
                Репозитории модулей для каждого питона свои, в /usr/lib/python-2.6 /usr/lib/python-2.7
                равно как и свои утилиты pip-2.6 pip-2.7
                Системные утилиты юзают родной питон, который каки прежде засимлинчен /usr/bin/python — > /usr/bin/python-2.6
                Система полностью уверена, что у неё родной, старый питон (кстати, я так и не нашёл, где именно прописана эта «уверенность»)

                Нужные утилиты нацеливаются на новый питон через #!python2.7

                Проблема возникла только с mod_wsgi для апача, котрый пришлось пересобрать, поменяв какую-то переменную в мэйкфайле.
            • UFO just landed and posted this here
              • UFO just landed and posted this here
                  0
                  Тема интересна. Просто у всех свои велосипеды, зависящие от сложности проекта.
              • UFO just landed and posted this here
                  0
                  Да вроде нет, а как запустить под своим пользователем и получить доступ к 80 порту?
                  • UFO just landed and posted this here
                    • UFO just landed and posted this here

                  Only users with full accounts can post comments. Log in, please.