Настраиваю сервер ubuntu для запуска на нём wsgi-приложений, написанных с использованием pylons.
Я не админ, я только учусь!
Мне нужно было настроить сервер, на котором бы крутилось pylons-приложение. Поскольку
я проверял разные конфигурации по памяти, процессору и версиям пакетов, мне нужно
было сделать всё возможное, чтобы упростить тестирование и повторное разворачивание
сервера на виртуальных машинах. К самому серверу были следующие требования:
- Статику должен отдавать шустрый сервер (nginx | lighttpd)
- Приложение должно работать не под рутом, чтобы было не очень страшно за безопасность.
- Процессы нужно мониторить и перезапускать в случае падения.
- Обо всех непрятностях сообщать админу (мне то есть).
- Для удобства тестирования весь процесс установки должен быть автоматизирован, чтобы
действия ограничивались запуском нескольких скриптов.
В результате решил, что:
- В качестве фронт-енд сервера будет nginx, потому что lighttpd + flup ложится при большой нагрузке из-за утечек памяти.
- В качестве wsgi-сервера взял uwsgi, потому что во время тестов этот сервер показал хорошую скорость.
- nginx и uwsgi ставим из исходников.
Что нужно сделать:
- Подготовить конфигурационные файлы для nginx, uwsgi, monit, pylons-приложения.
- Подготовить файл, который запускается под рутом, добавляет пользователя, устанавливает пакеты.
- Подготовить файл, который запускается под пользователем и компилирует uwsgi.
Что получаем в итоге:
- Набор конфигурационных файлов.
- Два скрипта, которые нужны для разворачивания сервера.
Далее идут файлы из расчёта, что приложение было создано командой:
paster create -t pylons tinycode
Настройка nginx
Файл настроек - nginx.conf:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 0;
server {
listen 80;
server_name tinycode.ru;
charset utf-8;
root /home/tinyuser/tinycode.ru/tinycode/public;
location ~*/(pdf|css|js|png|jpg|gif|jpeg|bmp|JPG)/ {
root /home/tinyuser/tinycode.ru/tinycode/public;
expires max;
add_header Cache-Control "public";
break;
}
location / {
uwsgi_pass 0.0.0.0:3993;
include uwsgi_params;
uwsgi_param SCRIPT_NAME "";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
Теперь нужен скрипт для запуска nginx, этот скрипт я честно упёр с одного сайта, о
чём сообщают копирайты в коментах. Исходный скрипт расчитан на дефолтную установку
nginx, для работы с собранным вручную сервером подправил пару путей, в остальном скрипт
тот же. Назовём его nginx:
#! /bin/sh
### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: nginx init.d dash script for Ubuntu <=9.10.
# Description: nginx init.d dash script for Ubuntu <=9.10.
### END INIT INFO
#------------------------------------------------------------------------------
# nginx - this Debian Almquist shell (dash) script, starts and stops the nginx
# daemon for ubuntu 9.10 and lesser version numbered releases.
#
# description: Nginx is an HTTP(S) server, HTTP(S) reverse \
# proxy and IMAP/POP3 proxy server. This \
# script will manage the initiation of the \
# server and it's process state.
#
# processname: nginx
# config: /usr/local/nginx/conf/nginx.conf
# pidfile: /acronymlabs/server/nginx.pid
# Provides: nginx
#
# Author: Jason Giedymin
# <jason.giedymin AT gmail.com>.
#
# Version: 2.0 02-NOV-2009 jason.giedymin AT gmail.com
# Notes: nginx init.d dash script for Ubuntu <=9.10.
#
# This script's project home is:
# http://code.google.com/p/nginx-init-ubuntu/
#
#------------------------------------------------------------------------------
# MIT X11 License
#------------------------------------------------------------------------------
#
# Copyright (c) 2009 Jason Giedymin, http://Amuxbit.com formerly
# http://AcronymLabs.com
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Functions
#------------------------------------------------------------------------------
. /lib/lsb/init-functions
#------------------------------------------------------------------------------
# Consts
#------------------------------------------------------------------------------
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/nginx/sbin/nginx
PS="nginx"
PIDNAME="nginx" #lets you do $PS-slave
PIDFILE=$PIDNAME.pid #pid file
PIDSPATH=/usr/local/nginx/logs
DESCRIPTION="Nginx Server..."
RUNAS=root #user to run as
SCRIPT_OK=0 #ala error codes
SCRIPT_ERROR=1 #ala error codes
TRUE=1 #boolean
FALSE=0 #boolean
lockfile=/var/lock/subsys/nginx
NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"
#------------------------------------------------------------------------------
# Simple Tests
#------------------------------------------------------------------------------
#test if nginx is a file and executable
test -x $DAEMON || exit 0
# Include nginx defaults if available
if [ -f /etc/default/nginx ] ; then
. /etc/default/nginx
fi
#set exit condition
#set -e
#------------------------------------------------------------------------------
# Functions
#------------------------------------------------------------------------------
setFilePerms(){
if [ -f $PIDSPATH/$PIDFILE ]; then
chmod 400 $PIDSPATH/$PIDFILE
fi
}
configtest() {
$DAEMON -t -c $NGINX_CONF_FILE
}
getPSCount() {
return `pgrep -f $PS | wc -l`
}
isRunning() {
if [ $1 ]; then
pidof_daemon $1
PID=$?
if [ $PID -gt 0 ]; then
return 1
else
return 0
fi
else
pidof_daemon
PID=$?
if [ $PID -gt 0 ]; then
return 1
else
return 0
fi
fi
}
#courtesy of php-fpm
wait_for_pid () {
try=0
while test $try -lt 35 ; do
case "$1" in
'created')
if [ -f "$2" ] ; then
try=''
break
fi
;;
'removed')
if [ ! -f "$2" ] ; then
try=''
break
fi
;;
esac
#echo -n .
try=`expr $try + 1`
sleep 1
done
}
status(){
isRunning
isAlive=$?
if [ "${isAlive}" -eq $TRUE ]; then
echo "$PIDNAME found running with processes: `pidof $PS`"
else
echo "$PIDNAME is NOT running."
fi
}
removePIDFile(){
if [ $1 ]; then
if [ -f $1 ]; then
rm -f $1
fi
else
#Do default removal
if [ -f $PIDSPATH/$PIDFILE ]; then
rm -f $PIDSPATH/$PIDFILE
fi
fi
}
start() {
log_daemon_msg "Starting $DESCRIPTION"
isRunning
isAlive=$?
if [ "${isAlive}" -eq $TRUE ]; then
log_end_msg $SCRIPT_ERROR
else
start-stop-daemon --start --quiet --chuid $RUNAS --pidfile $PIDSPATH/$PIDFILE --exec $DAEMON \
-- -c $NGINX_CONF_FILE
setFilePerms
log_end_msg $SCRIPT_OK
fi
}
stop() {
log_daemon_msg "Stopping $DESCRIPTION"
isRunning
isAlive=$?
if [ "${isAlive}" -eq $TRUE ]; then
start-stop-daemon --stop --quiet --pidfile $PIDSPATH/$PIDFILE
wait_for_pid 'removed' $PIDSPATH/$PIDFILE
if [ -n "$try" ] ; then
log_end_msg $SCRIPT_ERROR
else
removePIDFile
log_end_msg $SCRIPT_OK
fi
else
log_end_msg $SCRIPT_ERROR
fi
}
reload() {
configtest || return $?
log_daemon_msg "Reloading (via HUP) $DESCRIPTION"
isRunning
if [ $? -eq $TRUE ]; then
`killall -HUP $PS` #to be safe
log_end_msg $SCRIPT_OK
else
log_end_msg $SCRIPT_ERROR
fi
}
quietupgrade() {
log_daemon_msg "Peforming Quiet Upgrade $DESCRIPTION"
isRunning
isAlive=$?
if [ "${isAlive}" -eq $TRUE ]; then
kill -USR2 `cat $PIDSPATH/$PIDFILE`
kill -WINCH `cat $PIDSPATH/$PIDFILE.oldbin`
isRunning
isAlive=$?
if [ "${isAlive}" -eq $TRUE ]; then
kill -QUIT `cat $PIDSPATH/$PIDFILE.oldbin`
wait_for_pid 'removed' $PIDSPATH/$PIDFILE.oldbin
removePIDFile $PIDSPATH/$PIDFILE.oldbin
log_end_msg $SCRIPT_OK
else
log_end_msg $SCRIPT_ERROR
log_daemon_msg "ERROR! Reverting back to original $DESCRIPTION"
kill -HUP `cat $PIDSPATH/$PIDFILE`
kill -TERM `cat $PIDSPATH/$PIDFILE.oldbin`
kill -QUIT `cat $PIDSPATH/$PIDFILE.oldbin`
wait_for_pid 'removed' $PIDSPATH/$PIDFILE.oldbin
removePIDFile $PIDSPATH/$PIDFILE.oldbin
log_end_msg $SCRIPT_ok
fi
else
log_end_msg $SCRIPT_ERROR
fi
}
terminate() {
log_daemon_msg "Force terminating (via KILL) $DESCRIPTION"
PIDS=`pidof $PS` || true
[ -e $PIDSPATH/$PIDFILE ] && PIDS2=`cat $PIDSPATH/$PIDFILE`
for i in $PIDS; do
if [ "$i" = "$PIDS2" ]; then
kill $i
wait_for_pid 'removed' $PIDSPATH/$PIDFILE
removePIDFile
fi
done
log_end_msg $SCRIPT_OK
}
destroy() {
log_daemon_msg "Force terminating and may include self (via KILLALL) $DESCRIPTION"
killall $PS -q >> /dev/null 2>&1
log_end_msg $SCRIPT_OK
}
pidof_daemon() {
PIDS=`pidof $PS` || true
[ -e $PIDSPATH/$PIDFILE ] && PIDS2=`cat $PIDSPATH/$PIDFILE`
for i in $PIDS; do
if [ "$i" = "$PIDS2" ]; then
return 1
fi
done
return 0
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|force-reload)
stop
sleep 1
start
;;
reload)
$1
;;
status)
status
;;
configtest)
$1
;;
quietupgrade)
$1
;;
terminate)
$1
;;
destroy)
$1
;;
*)
FULLPATH=/etc/init.d/$PS
echo "Usage: $FULLPATH {start|stop|restart|force-reload|status|configtest|quietupgrade|terminate|destroy}"
echo " The 'destroy' command should only be used as a last resort."
exit 1
;;
esac
exit 0
Настройка uwsgi
Скрипт, запускающий непосредственно сервер run-uwsgi.sh (этот скрипт запускается из под пользователя, не из под рута):
#!/bin/sh
cd /home/tinyuser/tinycode.ru
# run uwsgi server
/home/tinyuser/uwsgi/uwsgi -p 4 --paste config:/home/tinyuser/tinycode.ru/tinyuser.ini --socket :3993 > /dev/null 2>&1 &</code></pre>
Скрипт для запуска и остановки uwsgi:
#!/bin/sh
case $1 in
start)
echo `exec 2>&1 su tinyuser -c /home/tinyuser/run-uwsgi.sh`;
ps -C uwsgi -o pid= | sed s/\ //g > /var/run/uwsgi.pid
;;
stop)
killall uwsgi ;;
*)
echo "usage: uwsgi {start|stop}" ;;
esac
exit 0
Настройка monit
Создадим конфиг для monit - monitrc:
## Start monit in the background (run as a daemon):
set daemon 300
with start delay 240
# admin settings
set mailserver localhost
set alert alert.mail@tinycode.ru
# check uwsgi
check process uwsgi with pidfile /var/run/uwsgi.pid
start program "/etc/init.d/uwsgi start"
stop program "/etc/init.d/uwsgi stop"
# check nginx
check process nginx with pidfile /var/run/nginx.pid
start program = "/etc/init.d/nginx start"
stop program = "/etc/init.d/nginx stop"
check system localhost
if loadavg (1min) > 4 then alert
if loadavg (5min) > 2 then alert
И ещё один файл - monit:
# Defaults for monit initscript
# sourced by /etc/init.d/monit
# installed at /etc/default/monit by maintainer scripts
# Stefan Alfredsson <alfs@debian.org>
# You must set this variable to for monit to start
startup=1
# To change the intervals which monit should run,
# edit the configuration file /etc/monit/monitrc
# It can no longer be configured here.
Настройка pylons-приложения
У каждого свой конфиг, я запишу свои настройки в tinyuser.ini
Компиляция uwsgi
Для того, чтобы подготовить uwsgi нужен будет файл install-uwsgi.sh:
.. code:
#!/bin/sh
#run this code under tinyuser
cd ~
wget http://projects.unbit.it/downloads/uwsgi-0.9.7.1.tar.gz
tar xzf uwsgi-0.9.7.1.tar.gz
mv uwsgi-0.9.7.1 uwsgi
cd uwsgi
make
Что получилось
В результате всех манипуляций должен получиться следующий набор файлов:
- nginx.conf
- nginx
- run-uwsgi.sh
- uwsgi
- monitrc
- monit
- tinyuser.ini
- install-uwsgi.sh
Осталось подготовить файл, который установит пакеты и пропишет демонов в автозагрузку.
install-server.sh
#!/bin/sh
# the default folder with pylons project = /home/tinyuser/tinycode.ru
RUN_USER=tinyuser
adduser $RUN_USER
apt-get update
#install soft
apt-get install -y vim python-setuptools
#install python libs
easy_install "Pylons == 0.9.7"
easy_install "SQLAlchemy >= 0.5"
easy_install "TurboMail == 3.0.3"
easy_install "AuthKit >= 0.4.3, <= 0.4.99"
easy_install uwsgi
apt-get install -y build-essential python-numpy python-scipy python-reportlab python-reportlab-accel python-renderpm libfreetype6 python-dev python-imaging libxml2-dev libpcre3-dev sqlite3 libpcre3-dev libssl-dev
# install nginx
mkdir soft
cd soft
wget http://sysoev.ru/nginx/nginx-0.9.6.tar.gz
tar xzf nginx-0.9.6.tar.gz
cd nginx-0.9.6
./configure && make && make install
cd ../../
cp nginx.conf /usr/local/nginx/conf/
cp nginx /etc/init.d/
/usr/sbin/update-rc.d -f nginx defaults
cp uwsgi /etc/init.d/
chmod 0755 /etc/init.d/uwsgi
/usr/sbin/update-rc.d -f uwsgi defaults
# install ftp
# apt-get install -y vsftpd
# install monit
apt-get install -y monit
cp monitrc /etc/monit/
chmod 0600 /etc/monit/monitrc
cp monit /etc/default/
chmod 0644 /etc/default/monit
/etc/init.d/monit restart
# copy files for uwsgi server to user dir
cp install-uwsgi.sh /home/$RUN_USER/
cp run-uwsgi.sh /home/$RUN_USER/
cp tinyuser.ini /home/$RUN_USER/
chown $RUN_USER /home/$RUN_USER/install-uwsgi.sh
chown $RUN_USER /home/$RUN_USER/run-uwsgi.sh
chown $RUN_USER /home/$RUN_USER/tinyuser.ini
echo "login under $RUN_SERVER and run install-uwsgi.sh"
Разворачиваем сервер:
- Под рутом запустить install-server.sh
- Под пользователем tinyuser запустить install-uwsgi.sh
- В папку /home/tinyuser/tinycode.ru скопировать файлы приложения. Это те файлы, которые лежат в папке tinycode после создания проекта командой
paster create -t pylons tinycode
- Скопировать файл tinyuser.ini в /home/tinyuser/tinycode.ru
- Перезагружаем сервер.
Ещё была идея запихивать архив с проектом вместе с этими файлами и автоматически его
распаковывать, но отказался от этой идеи, потому что проекты могут быть разными, могут
заливаться разными людьми, а моя цель была в настройке именно сервера, а не в деплое приложения.
На этом всё, до новых встреч :)