Доброе время суток, уважаемые читатели!
Ниже приведена увлекательная(?) история о том как наша организация решала проблему т.н. «деплоймента как у людей». Наш основной язык разработки Python, с примесями разных интересных (и не очень) пакетов (Django, Bottle, Flask, PIL, ZMQ, и т.д.).
Начнём с краткого описания одного из наших приложений:
- Django 1.4
- MySQL
- Celery для крон-имитации и поддержки вспомогательных функций в фоновом режиме
- Daemon-процесс, основанный на Django management command
Всё это дело работает под связкой gUnicorn и nginx, на ОС CentOS 5.8.
Детали, как принято, ниже.
Суть Проблемы
В одной из заключительных фаз проекта, нас посетила мысль о том, что в принципе, «svn up && python manage.py syncdb && python manage.py migrate» это криво; начались поиски «более оптимального» подхода.
Вариант Первый — «Змея в Космосе»
Так-как мы используем Spacewalk для менеджмента серверов, возникла идея паковать наше приложение в RPM-пакет; возможность установки «одним кликом» манила и вариант был принят в разработку.
Часов через 8, когда показатель WTF/hr застрял в красной зоне, мы решили искать что-нибудь попроще. Основные причины:
- Все зависимости надо паковать в RPM
- Не всем пакетам/дистрибутивам это «нравится».
- Процесс упаковки ведёт к fork-у пакета, т.к. часто надо редактировать setup.py для правильной конвертации в .spec-файл.
- Сама среда упаковки требует относительно много опыта.
Вариант Второй — «Змея в Магазине»
Второй вариант возник после слов, «а как же мы ставим чужие пакеты?» — и поиски локальной копии PyPi начались. Из множества заброшенных и безперспективных пакетов, был выбран localshop, который порадовал простотой установки и даже не просил странных пакетов именно ВОТ ТОЙ версии.
Подгон нашего приложения занял относительно немного времени — всё что нам требовалось это добавить setup.py и указать «левые» пакеты прямо там, хотя на грабли мы все-таки наступили:
Надо было явно указать zip_safe=False и include_package_data=True, т.к. некоторые файлы не распаковывались при установке.
Apache (который скоро будет заменен) надо было указать KeepAliveTimeout 300, SetEnv proxy-sendcl и ProxyTimeout 1800 для загрузки крупных пакетов.
Кроме этого, процесс конфигурации localshop-а прошел нормально, достаточно было запустить (под «чистой» учеткой):
cd && virtualenv venv && . venv/bin/activate && pip install localshop
~/venv/bin/localshop init && ~/venv/bin/localshop upgrade
После чего нам осталось лишь подогнать ~/.pipyrc под наш «магазин»:
[distutils] index-servers = local [local] username: developer password: parolcheg123 repository: http://cheese.example.com/simple/
После чего, процесс релиза сводится к python setup.py upload -r local
, после изменения номера версии в setup.py.
Финальный Подход
При первой попытке установить наше приложение на «боевом» сервере, нас постигла грустная участь — нам требовался пакет PIL, а GCC и разные штуки типа libpng-devel отсуствовали как класс. Пришлось все-таки «собрать ручками» RPM-пакет Питона и разных интересных штук (MySQL-python, setuptools, PIL, ZMQ) и залить на Spacewalk.
После этой познавательной операции (которой, вообще-то, полагается собственный пост), установка самого приложения закончилась и мы начали «добивать» процесс, дорабатывая мелкие проблемы:
- Авто-запуск gUnicorn (для localshop вообще и наше приложения конкретно): хватило мелкой обработки напильником откопанного скрипта.
- «Правильная» настройка pip на «боевых» серверах: в раздел [global] в (новом) файле ~produser/.pip/pip.conf добавить index-url = cheese.example.com/simple/
- Добавка всего этого добра в систему мониторинга (OpsView): добавка нового service check-а на процесс с аргументами «run_gunicorn» или «gunicorn» и привязка к серверу.
Кроме того, нам понадобилось запустить «долгоиграющий» процесс, а также Celery. Для нашего приложения решено было использовать ZDaemon, для которого были написаны init-script и файл настройки:
#!/bin/bash ### BEGIN INIT INFO # Provides: our_app # Required-Start: $all # Required-Stop: $all # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: controls our_app via zdaemon # Description: controls our_app via zdaemon ### END INIT INFO . /etc/rc.d/init.d/functions . /etc/sysconfig/network . ~produser/venv/bin/activate # Check that networking is up. [ "$NETWORKING" = "no" ] && exit 0 RETVAL=0 APP_PATH=~/produser/app/ PYTHON=~produser/venv/bin/python USER=produser start() { cd $APP_PATH zdaemon -C our_app.zdconf start zdaemon -C our_app_celery.zdconf start } stop() { cd $APP_PATH zdaemon -C our_app.zdconf stop zdaemon -C our_app_celery.zdconf stop } check_status() { cd $APP_PATH zdaemon -C our_app.zdconf status zdaemon -C our_app_celery.zdconf status } case "$1" in start) start ;; stop) stop ;; status) check_status ;; restart) stop start ;; *) esac exit 0
# our_app[_celery].zdconf <runner> daemon true directory /opt/produser/app/ forever false backoff-limit 10 user produser # run_command -> actual command or celeryd program /opt/produser/venv/bin/python /opt/produser/app/manage.py run_command --settings=prod_settings socket-name /tmp/our_app.zdsock </runner>
Конечный результат
Запуск ~/venv/bin/activate && pip install -U our_app под учеткой produser почти без проблем устанавливает свежую версию нашего приложения + все «левые» пакеты указанные в setup.py.
Процесс syncdb и migrate все-таки производится «ручками», но:
- «Боевая» версия всегда известна
- Нет надобности ставить GCC и т.д. на «боевые» сервера
- Rollback прост
Надеюсь что сие описание процесса просветило кого-то из читателей.
Автор: marklarius