Итак, задачи: создать приложение по вводу сайта в режим обслуживания, настроить сервер, автоматизировать процесс развертки на сервер с помощью fabric.
Вспомним о том, что у нас есть redmine и mylyn, создадим данные задачи (не забываем создать категории задач в настройках проекта в redmine).
django-maintenancemode
Для ввода сайта в режим обслуживания есть целое приложение.
Устанавливаем:
- C:\>c:\Python26\Scripts\pip.exe install django-maintenancemode
- Downloading/unpacking django-maintenancemode
- Downloading django-maintenancemode-0.9.2.tar.gz
- Running setup.py egg_info for package django-maintenancemode
- Installing collected packages: django-maintenancemode
- Running setup.py install for django-maintenancemode
- Successfully installed django-maintenancemode
- Cleaning up...
Прописываем в requirements.txt:
django-maintenancemode==0.9.2
Настраиваем. В MIDDLEWARE_CLASSES добавляем "maintenancemode.middleware.MaintenanceModeMiddleware".

В templates создаем файл 503.html со статическим содержимым того, что будет выводиться в период обслуживания сайта.
Функции приложения:
* MAINTENANCE_MODE - включает\выключает режим обслуживания, по умолчанию: False.
* Страница 503 не отображается залогиненым админам и клиентам с ip адресами, входящими в INTERNAL_IPS.
Итак, пропишем MAINTENANCE_MODE = True, в development.py и в production.py (в development.py закомментируем вскоре).
Запускаем pydev сервер, отладку, переходим на страницу и видим следующее:

Немного поправим 503.html по своему желанию.
Настройка сервера
Устанавливаем и настраиваем фаерволл:
- $ sudo aptitude install ufw
- $ sudo ufw enable
- $ sudo ufw logging on
- $ sudo ufw allow 80/tcp
- $ sudo ufw allow SSH_port
- $ sudo ufw default deny
Настройку веб сервера выбрал такую (nginx + uwsgi). Тем более, nginx, начиная с версии 0.8.40 поддерживает uwsgi из коробки.
- # apt-get install gcc libssl-dev libpcre++-dev make
- # wget http://sysoev.ru/nginx/nginx-0.8.44.tar.gz
- # tar -xzvf nginx-0.8.44.tar.gz
- # cd nginx-0.8.44/
- # ./configure --conf-path=/etc/nginx/nginx.conf \
- --prefix=/usr \
- --error-log-path=/var/log/nginx/error.log \
- --pid-path=/var/run/nginx.pid \
- --lock-path=/var/lock/nginx.lock \
- --http-log-path=/var/log/nginx/access.log \
- --with-http_dav_module \
- --http-client-body-temp-path=/var/lib/nginx/body \
- --with-http_ssl_module \
- --http-proxy-temp-path=/var/lib/nginx/proxy \
- --with-http_stub_status_module \
- --http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
- --http-uwsgi-temp-path=/var/lib/nginx/uwsgi \
- --http-scgi-temp-path=/var/lib/nginx/scgi \
- --with-debug \
- --with-http_flv_module
- # make
- # make install
Скрипт запуска /etc/init.d/nginx я взял из стандартного пакета debian (устанавливать его не нужно, ибо можно перетереть новые конфиги старыми. В принципе, не страшно, так как мы будем их писать заново, но например mime.types могут отличаться).
Создаем рабочую директорию для сайта, например /srv/musicmans
Структура:
- /srv/musicmans
- | backups
- --| src
- --| db
- | logs
- | root
- --| src
- --| www
На машине разработчика создаем файл в src wsgi.py (основой файл запуска проекта для веб-сервера):
- import os
- import sys
- import locale
- import django.core.handlers.wsgi
- DIR=(os.path.abspath(__file__))
- sys.path.append(DIR)
- os.environ['DJANGO_SETTINGS_MODULE'] = 'settings.production'
- def force_utf8_hack():
- reload(sys)
- sys.setdefaultencoding('utf-8')
- for attr in dir(locale):
- if attr[0:3] != 'LC_':
- continue
- aref = getattr(locale, attr)
- locale.setlocale(aref, '')
- (lang, enc) = locale.getlocale(aref)
- if lang != None:
- try:
- locale.setlocale(aref, (lang, 'UTF-8'))
- except:
- os.environ[attr] = lang + '.UTF-8'
- force_utf8_hack()
- application = django.core.handlers.wsgi.WSGIHandler()
Перед тем, как настраивать сервер, запустим тестирование проекта.

Введем команду test и получим ошибку.
Добавим в development.py:
- INSTALLED_APPS += (
- 'django.contrib.admin',
- )
А также в manage.py:
- if settings.DEBUG and command == "test":
- settings.MAINTENANCE_MODE = False
- execute_manager(settings)
Ибо нам тесты в режиме обслуживания не нужны, да и не отрабатывают они, у меня вышла ошибка отсутствия темплейта 503.html и куча других.
Сделаем коммит.
Вернемся к серверу с сайтом. Сделаем предварительную настройку:
1. Перейдем в директорию /srv/musicmans/ и заберем транк в root:
$export SVN_SSH="ssh -l loginname"
или сделаем пару ключа (она нам все рано пригодиться при использовании fabric). На сервере с сайтом:
- $ ssh-keygen -t dsa
- $ cat ~/.ssh/id_dsa.pub
копируем вывод, добавляем на сервер с кодом в ~/.ssh/authorized_keys2 на сервер (если файла нет, то touch ~/.ssh/authorized_keys2 && chmod 600 ~/.ssh/authorized_keys2 ). Пробуем логиниться без пароля.
- $svn checkout --depth=empty svn+ssh://codesrv/repos/musicmans/trunk/backend root
- $cd root/
- $svn update --set-depth=infinity www
- $svn update --set-depth=infinity src
Так как нам нужны только две директории src и www, то делаем пустой checkout, после чего обновляем две директории с бесконечной вложенностью. После этого svn update будет нам обновлять только директории www и src.
Устанавливаем необходимые приложения для сайта:
- vermus@musicmans:~$ cd /srv/musicmans/root/src
- vermus@musicmans:~$ sudo pip install -r requirements.txt --download-cache /usr/src/pipcache/
Установка postgresql:
- # apt-get install postgresql python-psycopg2
- # su postgres
- $ createuser musicmans --no-superuser --no-createdb --no-createrole --login --pwprompt --encrypted
- $ createdb --owner=musicmans --encoding=utf-8 musicmans
База создана, пробуем синхронизировать django с базой данных (мы это делали уже на машине разработчика, но так как у нас база на сайте будет жить своей жизнью, а девелоперская своей, то сделаем это еще раз, т.е. миграцию данных выполнять не будем):
- vermus@musicmans:~$ cd /srv/musicmans/root/src/
- vermus@musicmans:/srv/musicmans/root/src$ python manage.py syncdb
Итак, все в порядке. Осталось настроить веб-сервер. Конфигурацию мы уже выбрали.
Установим uwsgi сервер:
- $ cd /usr/src/
- $ sudo pip install http://projects.unbit.it/downloads/uwsgi-latest.tar.gz
Настроим скрипт init.d для запуска через файловый сокет сервера uwsgi с проектом (/etc/init.d/uwsgi):
- # cat uwsgi
- ### BEGIN INIT INFO
- # Provides: uwsgi
- # Required-Start: $all
- # Required-Stop: $all
- # Default-Start: 2 3 4 5
- # Default-Stop: 0 1 6
- # Short-Description: starts the uwsgi app server
- # Description: starts uwsgi app server using start-stop-daemon
- ### END INIT INFO
- PATH=/sbin:/bin:/usr/sbin:/usr/bin
- DAEMON=/usr/bin/uwsgi
- OWNER=uwsgirun
- NAME=uwsgi
- DESC=uwsgi
- test -x $DAEMON || exit 0
- # Include uwsgi defaults if available
- if [ -f /etc/uwsgi ] ; then
- . /etc/uwsgi
- fi
- set -e
- DAEMON_OPTS="--socket /var/lib/nginx/uwsgi/musicmans.sock --chmod-socket -d /srv/musicmans/logs/uwsgi.log --pythonpath $PYTHONPATH --module $MODULE"
- case "$1" in
- start)
- echo -n "Starting $DESC: "
- start-stop-daemon --start --chuid $OWNER:$OWNER --user $OWNER \
- --exec $DAEMON -- $DAEMON_OPTS
- echo "$NAME."
- ;;
- stop)
- echo -n "Stopping $DESC: "
- start-stop-daemon --signal 3 --user $OWNER --quiet --retry 2 --stop \
- --exec $DAEMON
- echo "$NAME."
- ;;
- reload)
- killall -1 $DAEMON
- ;;
- force-reload)
- killall -15 $DAEMON
- ;;
- restart)
- echo -n "Restarting $DESC: "
- start-stop-daemon --signal 3 --user $OWNER --quiet --retry 2 --stop \
- --exec $DAEMON
- sleep 1
- start-stop-daemon --user $OWNER --start --quiet --chuid $OWNER:$OWNER \
- --exec $DAEMON -- $DAEMON_OPTS
- echo "$NAME."
- ;;
- status)
- killall -10 $DAEMON
- ;;
- *)
- N=/etc/init.d/$NAME
- echo "Usage: $N {start|stop|restart|reload|force-reload|status}" >&2
- exit 1
- ;;
- esac
- exit 0
Не забываем создать пользователя uwsgirun, под которым будет запускаться uwsgi. Параметр chmod-socket устанавливает права 666 на сокет, если Вас это не устраивает смотрите документацию. Если uwsgi после запуска ругается на права, проверьте права на директорию с сокетом, на директорию с логами.
Создадим файл конфигурации /etc/uwsgi :
- PYTHONPATH=/srv/musicmans/root/src
- MODULE=wsgi
Обратите внимание, что мы указываем имя модуля python, а не имя файла.
Устанавливаем chmod 755 для скрипта /etc/init.d/uwsgi , загружаем при старте системы:
- root@musicmans:/var/lib/nginx# chown -R uwsgirun uwsgi
- root@musicmans:/etc/init.d# chmod 755 uwsgi
- root@musicmans:/etc/init.d# update-rc.d -f uwsgi defaults
- root@musicmans:/etc/init.d# /etc/init.d/uwsgi start
Конфиги nginx: nginx.conf, стандартный из пакета debian. Конфиг сайта:
- root@musicmans:/etc/nginx/sites-available# cat musicmans
- #serving Django.
- upstream django {
- ip_hash;
- server unix:/var/lib/nginx/uwsgi/musicmans.sock;
- }
- server {
- listen 80;
- server_name musicmans.ru;
- charset utf-8;
- error_log /srv/musicmans/logs/nginx_error.log info;
- access_log /srv/musicmans/logs/nginx_access.log;
- # Django admin media.
- #location /media/admin/ {
- # alias lib/python2.6/site-packages/django/contrib/admin/media/;
- # }
- # Your project's static media.
- location /media/ {
- alias /srv/musicmans/root/www/media/;
- }
- # Finally, send all non-media requests to the Django server.
- location / {
- uwsgi_pass django;
- include uwsgi_params;
- }
- location ~ /.svn/ {
- deny all;
- }
- }
Включаем сайт
- # ln -s /etc/nginx/sites-available/musicmans /etc/nginx/sites-enabled/musicmans
Перезапускаем /etc/init.d/uwsgi restart и /etc/init.d/nginx restart.
Заходим http://musicmans.ru/:

Процесс развертывания кода и структуры базы данных на сервер с помощью fabric
Установим на машину разработчика pip и fabric.
- #pip install fabric
Создаем fab файл с командами fabric в корне проекта для установки необходимых приложений из requirements.txt, обновления кода, миграции базы данных и перезапуска Nginx:
* Включить режим обслуживания сайта (см. выше).
* Сделать резервную копию базы данных.
* Сделать резервную копию кода (src) сайта.
* Обновить код с репозитория subversion.
* Запустить миграцию базы данных (South).
* Выключить режим обслуживания сайта.
У нас fabric 0.9.1, а в 1.0 обещают поддержку django. Ну а пока ее нет создаем fabfile.py в корне проекта следующего содержания (перевод windows консоли для понимания удаленного UTF8 в случае ошибок - шрифт cmd окна Lucida Console (или любой другой true type), далее команда chcp 65001).
- # -*- mode: python; coding: utf-8; -*-
- import sys
- from fabric.api import env, run, prompt, local, get, cd, sudo, require
- from fabric.state import output
- from fabric.contrib.files import uncomment
- import datetime
- now = datetime.datetime.now()
- def production():
- #здесь данные об удаленном сервере с сайтом
- env.environment = "production"
- env.hosts = ['codesrv']
- env.user = 'vermus'
- env.path = '/srv/musicmans/root'
- env.root_path = '/srv/musicmans'
- env.db_name = 'musicmans'
- env.db_user = 'musicmans'
- def deploy():
- """
- In the current version fabfile no initial database creation and configure the virtual server host.
- """
- require('environment', provided_by=[production])#дописать по желанию dev и stage
- if env.environment == 'production':
- if "y" != prompt('Are you sure you want to update the production site (test & check in trunk release code!)? (y/[n])?', default="n"):
- return
- if "y" == prompt('Set MAINTENANCE_MODE (y/n)?', default="y"):
- maintenance_mode()
- if "y" == prompt('Create database backup? (y/n)?', default="y"):
- backup_db()
- if "y" == prompt('Create source code backup? (y/n)?', default="y"):
- backup_src()
- update_from_svn()
- if "y" == prompt('Install the necessary applications (y/n)?', default="n"):
- install_requirements();
- migrate_database()
- maintenance_mode(set=False)
- restart_webserver()
- def install_requirements():
- require('environment', provided_by=[production])#дописать по желанию dev и stage
- print(" * install the necessary applications...")
- requirements_file = env.path+'/src/requirements.txt'
- args = ['install',
- '-r', requirements_file,
- '--download-cache', '/usr/src/pipcache/'
- ]
- sudo('pip %s' % ' '.join(args))
- def maintenance_mode(set=True):
- require('environment', provided_by=[production])#дописать по желанию dev и stage
- print(" * change production.py and restart nginx...")
- if set:
- uncomment(env.path+'/src/settings/production.py', 'MAINTENANCE_MODE = True')
- else:
- comment(env.path+'/src/settings/production.py', 'MAINTENANCE_MODE = True')
- restart_webserver()
- def backup_db():
- require('environment', provided_by=[production])#дописать по желанию dev и stage
- print(" * create database dump...")
- db_name = env.db_name
- db_user = env.db_user
- backup_file = "backup_%d_%d_%d_%d_%d.sqlgzip" % (now.day, now.month, now.year, now.hour, now.minute)
- backup_dir = env.root_path+'/backups/db'
- with cd(backup_dir):
- run("echo dbpassword | pg_dump -W -U %s -F c %s > %s" % (db_user, db_name, backup_file))
- def backup_src():
- require('environment', provided_by=[production])#дописать по желанию dev и stage
- print(" * create source code backup...")
- backup_dir = env.root_path+'/backups/src'
- backup_file = "backup_%d_%d_%d_%d_%d.tar.gz" % (now.day, now.month, now.year, now.hour, now.minute)
- src_dir = env.path+'/src'
- run("mkdir -p %s" % backup_dir+'/all')
- run("cp -f -R %s %s" % (src_dir, backup_dir+'/all'))
- run("cp -f -R %s %s" % (env.path+'/www/static', backup_dir+'/all'))
- with cd(backup_dir):
- run ('tar -zcf %s %s' % (backup_file, backup_dir+'/all'))
- run ('rm -f -R %s' % (backup_dir+'/all'))
- def update_from_svn():
- require('environment', provided_by=[production])#дописать по желанию dev и stage
- with cd(env.path):
- run('svn update') #svn checkout сделаем вручную первый раз
- def migrate_database():
- require('environment', provided_by=[production])#дописать по желанию dev и stage
- with cd(env.path+'/src'):
- run('python manage.py migrate --no-initial-data')
- run('python manage.py syncdb')
- def restart_webserver():
- require('environment', provided_by=[production])#дописать по желанию dev и stage
- print(" * restart nginx")
- sudo('/etc/init.d/uwsgi restart', pty=True)
- sudo('/etc/init.d/nginx force-reload', pty=True)
Схема такая:
Запуск $fab production deploy с машины разработчика - логин по ssh на сервер с сайтом (fabric выполняет автоматически при выполнении run, sudo и др., используя данные из env), далее выполняются необходимые действия, в том числе svn+ssh с сервера с кодом с транка.
Добавим в /etc/postgresql/8.4/main/pg_hba.conf следующую строчку:
- # "local" is for Unix domain socket connections only
- local musicmans musicmans md5
- local all all ident
Обратите внимание, что строчку добавляем перед ident. Она позволит нам соединяться пользователю без логина с указанием пароля при беакпе базы.
Статья получилась объемной, старался быстрее закончить с технической стороной и перейти наконец к созиданию. :)
Не забываем про задачи (перспектива planning), указываем время, потраченное на задачи и закрываем их.
ps. Если вы хотите сразу обеспечить полноценную защиту сайта от различных атак, которая требует более тонкой настройки системы, то следует обратиться к профессионалам или почитать соответствующую литературу. Нам пока некому завидовать, поэтому оставляем все как есть на данном этапе.
Сделайте, пожалуйста, кат. Очень неудобно такие портянки в ридере проматывать.
ОтветитьУдалить2Arts надо нажать J в ридере и не мотать, зато так можно спокойно читатать напрямую в ридере
ОтветитьУдалитьОшибка в конфиге сайта - в секции, где вырубается svn, нужно передвинуть кавычку
ОтветитьУдалитьDoctor013, спасибо, исправил.
ОтветитьУдалитьНепонятно, зачем в fabfile.py есть import sys, обращений я не увидел. Может заменить print на кошерный sys.stdout?
ОтветитьУдалитьИ кстати, когда продолжение ? :-)
Doctor013, да, лишний, остался от экспериментов. А насчет print, можно поподробнее, чем он плох, я что-то не в курсе насчет этого...
ОтветитьУдалитьПродолжение пишется, к середине недели наверное будет, спасибо за интерес :)
C print ничего страшного :-), я употребил слово "кошерный". Подробнее - http://diveintopython.org/scripts_and_streams/stdin_stdout_stderr.html
ОтветитьУдалить"When you print something, it goes to the stdout pipe;"
ОтветитьУдалить"stdout is a file-like object; calling its write function will print out whatever string you give it. In fact, this is what the print function really does; it adds a carriage return to the end of the string you're printing, and calls sys.stdout.write. "
Кошерный - идиологически верный, как я понимаю.
Не понимаю, чем лучше sys.stderr.write(), а не print(), если только отсутствием возврата каретки? %)
Vermus, проконсультируйте с правами. Вот эта строчка:
ОтветитьУдалитьroot@musicmans:/var/lib/nginx# chown -R uwsgirun uwsgi делает владельцем uwsgirun, но при старте nginx владелец переопределяется на www-data. Это правильно?
>Doctor013 комментирует...
ОтветитьУдалитьДа, действительно, похоже это лишняя строчка. Тем более лишнее -R, ибо сам сокет у нас 666.
Предлагаю выполнить,
1. Создать группу uwsgirun (в него пользователя uwsgirun).
2. root@musicmans:/var/lib/nginx# chmod +t uwsgi
это стики на всякий случай.
3. root@musicmans:/var/lib/nginx# chown www-data:uwsgirun uwsgi
4. root@musicmans:/var/lib/nginx# chmod 570 uwsgi
грубо, чтение для владельца (www-data), запись для группы (uwsgirun)
Ваши предложения?
Кстати, я тут подумал, а надо ли рестартовать nginx вообще. Надо поэкспериментировать на эту тему.