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