Итак, задачи: создать приложение по вводу сайта в режим обслуживания, настроить сервер, автоматизировать процесс развертки на сервер с помощью 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 вообще. Надо поэкспериментировать на эту тему.