Setting Up a Production Django Environment on Ubuntu Karmic

Posted on November 8th, 2009 in Django, Server Setup, Web Development | Comments

I love tutorials. It’s time I put a few of my own online.

This one will focus on setting up a complete Django production environment on Ubuntu Karmic. It will use nginx, Apache and mod_wsgi, MySQL, and virtualenv.

Install Everything

First, lets install everything we need:

apt-get install build-essential apache2 php5 php5-cgi libapache2-mod-php5 libapache2-mod-wsgi mysql-server mysql-client libmysqlclient15-dev php5-mysql php5-mcrypt nginx python-setuptools

This should bring up a window to set your MySQL root password, but if not:

mysqladmin -u root password [NEW PASSWORD]

Replacing [NEW PASSWORD] with whatever you want your root password to be.

So that was pretty easy. Let’s install virtualenv and virtualenvwrapper.

easy_install virtualenv

easy_install virtaulenvwrapper

Set up VirtualEnvWrapper

Let’s create two directories in our /var/www folder; one of them will be for PHP vhosts, and one for Python vhosts.

mkdir -p /var/www/php /var/www/python

We need to add some lines to our .bashrc file to get virtualenvwrapper to work correctly.

export WORKON_HOME=/var/www/python
source /usr/local/bin/virtualenvwrapper_bashrc

Configure PHP CGI

In order to serve php with nginx (as we are with phpMyAdmin), we need to setup php-cgi.

Put the following into /etc/init.d/php-fastcgi:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          php-fastcgi
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start and stop php-cgi in external FASTCGI mode
# Description:       Start and stop php-cgi in external FASTCGI mode
### END INIT INFO

# Author: Kurt Zankl <[EMAIL PROTECTED]>

# Do NOT "set -e"

PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="php-cgi in external FASTCGI mode"
NAME=php-fastcgi
DAEMON=/usr/bin/php-cgi
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
PHP_CONFIG_FILE=/etc/php5/cgi/php.ini

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

# If the daemon is not enabled, give the user a warning and then exit,
# unless we are stopping the daemon
if [ "$START" != "yes" -a "$1" != "stop" ]; then
        log_warning_msg "To enable $NAME, edit /etc/default/$NAME and set START=yes"
        exit 0
fi

# Process configuration
export PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS
DAEMON_ARGS="-q -b $FCGI_HOST:$FCGI_PORT -c $PHP_CONFIG_FILE"

do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
                || return 1
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
                --background --make-pidfile --chuid $EXEC_AS_USER --startas $DAEMON -- \
                $DAEMON_ARGS \
                || return 2
}

do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE > /dev/null # --name $DAEMON
        RETVAL="$?"
        [ "$RETVAL" = 2 ] && return 2
        # Wait for children to finish too if this is a daemon that forks
        # and if the daemon is only ever run from this initscript.
        # If the above conditions are not satisfied then add some other code
        # that waits for the process to drop all resources that could be
        # needed by services started subsequently.  A last resort is to
        # sleep for some time.
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
        [ "$?" = 2 ] && return 2
        # Many daemons don't delete their pidfiles when they exit.
        rm -f $PIDFILE
        return "$RETVAL"
}
case "$1" in
  start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  restart|force-reload)
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
        exit 3
        ;;
esac

And the following into /etc/default/php-fastcgi:

START=yes

# Which user runs PHP? (default: www-data)

EXEC_AS_USER=www-data

# Host and TCP port for FASTCGI-Listener (default: localhost:9000)

FCGI_HOST=localhost
FCGI_PORT=9000

# Environment variables, which are processed by PHP

PHP_FCGI_CHILDREN=4
PHP_FCGI_MAX_REQUESTS=1000

Make the init script executable:

chmod +x /etc/init.d/php-fastcgi

Make it permanent:

update-rc.d php-fastcgi defaults

Then turn it on:

/etc/init.d/php-fastcgi start

Install phpMyAdmin

Let’s get the current version of phpMyAdmin:

cd /tmp
wget http://downloads.sourceforge.net/project/phpmyadmin/phpMyAdmin/3.2.3/phpMyAdmin-3.2.3-english.tar.gz?use_mirror=voxel
tar xzvf phpMyAdmin-3.2.3-english.tar.gz -C /var/www/php/
mv /var/www/php/phpMyAdmin-3.2.3-english /var/www/php/phpmyadmin

Create a copy of config.sample.inc.php:

cd /var/www/php/phpmyadmin
cp config.sample.inc.php config.inc.php

And change the blowfish secret on line 18.

We’re going to use Nginx and FastCGI to serve phpmyadmin – since we want to dedicate Apache to our Python stuff. Now, create a new nginx site to serve phpMyAdmin (nano /etc/nginx/sites-available/phpmyadmin), replacing the IP and server_name below with your external IP and domain name:

server {
    listen 80;
    server_name phpmyadmin.sk3vy.com;
    access_log /var/log/nginx/php/phpmyadmin-access.log;
    error_log /var/log/nginx/php/phpmyadmin-error.log;

    location / {
        root /var/www/php/phpmyadmin;
        index index.php;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME /var/www/php/phpmyadmin$fastcgi_script_name;
        include /etc/nginx/fastcgi.conf;
    }
}

And in /etc/nginx/fastcgi.conf:

fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx;

fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;

fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;

fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;

Enable the site by creating a symlink:

ln -s /etc/nginx/sites-available/phpmyadmin /etc/nginx/sites-enabled/phpmyadmin

And create the log directory:

mkdir -p /var/log/nginx/php

Don’t restart Nginx yet, lets finish up the rest of this stuff first.

Install MySQLdb

I like to install MySQLdb python extension first so that it’s included in the virtualenv’s that we make.

apt-get install python-mysqldb

Make sure it worked by going into a python shell (type python at the command prompt) and then type import MySQLdb. If you don’t get any errors, the installation has worked successfully.

Setup a Virtual Environment and Django Project

Make a virtual environment and change into that directory:

mkvirtualenv testenviron
cdvirtualenv

Install django and pip (for better package management):

easy_install django
easy_install pip

Create a new django project:

django-admin.py startproject testproject
cd testproject

Setup Mod_WSGI in your Django project

To get mod_wsgi working, we have to create a wsgi file in our project directory:

mkdir apache
nano apache/django.wsgi

In this file, put the following:

import os
import sys

# put the Django project on sys.path

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

os.environ["DJANGO_SETTINGS_MODULE"] = "testproject.settings"

from django.core.handlers.wsgi import WSGIHandler

application = WSGIHandler()

Setup Apache to Serve the Project

Disable the default site:

a2dissite default

Change your /etc/apache2/ports.conf file to look like this:

NameVirtualHost *:8080
Listen 8080

<IfModule mod_ssl.c>
    # SSL name based virtual hosts are not yet supported, therefore no
    # NameVirtualHost statement here
    Listen 443
</IfModule>

This sets Apache to listen on port 8080, instead of port 80. Nginx will listen on port 80 and proxy requests to the Apache process.

Then, we need to create an Apache vhost to serve the project:

nano /etc/apache2/sites-available/testproject

Put in the following:

<VirtualHost *:8080>
    ServerName testproject.sk3vy.com
    WSGIDaemonProcess testproject-production processes=2 threads=15 display-name=%{GROUP} python-path=/var/www/python/testenviron/lib/python2.6/site-packages
    WSGIProcessGroup testproject-production
    WSGIScriptAlias / /var/www/python/testenviron/testproject/apache/django.wsgi
    <Directory /var/www/python/testenviron/testproject>
        Order deny,allow
        Allow from all
    </Directory>
    ErrorLog /var/log/apache2/python/testproject-error.log
    LogLevel warn
    CustomLog /var/log/apache2/python/testproject-access.log combined
</VirtualHost>

Make some directories for the logs:

mkdir /var/log/apache2/python

Enable the site:

a2ensite testproject

Setup Nginx to Proxy Through to the Project and Serve the Media Files

Remove the default site:

sudo rm /etc/nginx/sites-enabled/default 

Create a new nginx site for your testproject (nano /etc/nginx/sites-available/testproject), replacing the IP and server_name below with your external IP and domain name:

server {
    listen 80;
    server_name testproject.sk3vy.com;
    access_log /var/log/nginx/python/testproject-access.log;
    error_log /var/log/nginx/python/testproject-error.log;
    location / {
        proxy_pass http://127.0.0.1:8080;
        include /etc/nginx/proxy.conf;
    }
    location /media/ {
        root /var/www/python/testenviron/testproject/;
    }
}

Create a symbolic link to enable the project:

ln -s /etc/nginx/sites-available/testproject /etc/nginx/sites-enabled/testproject

Create a /etc/nginx/proxy.conf file:

proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k; 

Create the log directories:

mkdir -p /var/log/nginx/python

Restart Apache and Nginx:

sudo /etc/init.d/apache2 restart 
sudo /etc/init.d/nginx restart 

This finishes the tutorial. Everything should be set up properly, and to create a new virtual host, just follow the steps starting from “Setup a Virtual Environment and Django Project”.

Post to Twitter Post to Delicious Post to Facebook

Why you should think twice about dating an iPhone user

Posted on November 5th, 2009 in Funny | Comments

Sooooo funny. http://gizmodo.com/5397528/why-you-should-think-twice-before-dating-an-iphone-user

Post to Twitter Post to Delicious Post to Facebook

Sustainability at UMD

Posted on November 5th, 2009 in Sustainability | Comments Off

Good article on sustainability at University of Minnesota – Duluth http://media.www.umdstatesman.com/media/storage/paper1351/news/2009/11/04/Outdoors/Sustainability.Explained-3823556.shtml

Post to Twitter Post to Delicious Post to Facebook

Awesome Javascript Crypto Library

Posted on November 4th, 2009 in Javascript, Web Development | Comments Off

using this for my next project…https://www.pidder.com/pidcrypt/?start

Post to Twitter Post to Delicious Post to Facebook

A picture from a recent trip to the Packard Auto Plant in Hamtramck

Posted on November 4th, 2009 in Photography | Comments

20090816-IMG_1562

A picture from a recent trip to the Packard Auto Plant in Hamtramck.

Post to Twitter Post to Delicious Post to Facebook