When I first started working with django I deployed my apps using apache and mod_python. Then after a little while I started playing with nginx and switched my setup so that nginx was serving the static content and reverse proxied requests back to apache and mod_python. Not too long after that, I switched out mod_python with mod_wsgi and ran mod_wsgi in daemon mode.

This setup worked well for a while, but one thing I never really liked was the fact that I needed to run apache which is pretty heavy even when you strip out all the unused modules. Apache is great, but all I was really using it for was a router between nginx and mod_wsgi, I wasn't using any of the other features in apache.

I looked at fastcgi and uswgi and they looked good, but for one reason or another I never made the jump. Recently I have been hearing a lot of good things about green unicorn, so I decided to check it out. When I first looked at it, it was fairly new and because of this a little concerned with stability, so I kept and eye on it and watched it mature.

While I was waiting for green unicorn to mature I ended up doing a lot of research on the new python hosting services that recently hit the market. Three out of the five services that I looked were using green unicorn, the other two were using uWSGI.

The fact that these three services are basing there new businesses on green unicorn gave it a lot of credibility. Not too long after that I started playing with green unicorn to see what it would take to get my sites up and running.

The first thing that I noticed was that I didn't need to create a wsgi file if I used their gunicorn_django command, which was pretty sweet. The fact that they built it into the service shows you that django is a first class citizen.

The second thing that I noticed was that I needed a way to start up green unicorn and keep it running, something that apache does for you with mod_wsgi. I did a little bit of research and found out that supervisord would work perfectly for what I needed to do with green unicorn.

Because seeing is better then reading, I'll guide you throw the steps that you will need to do in order to get your system setup in a way that will make using green unicorn very easy, especially if you want to run more then one website on your server. I'm going to use a 256MB rackspace cloud instance running centos 5.5.

Create a rackspace cloud server

Go into the rackspace cloud server management website and allocate yourself a new 256MB CentOS 5.5 server or if you prefer do the same thing using their API. Now that you have a server and the root password, follow along step by step to get you system all setup.

Software and versions

  • RackSpace Cloud Server 256MB
  • CentOS 5.5
  • Python 2.6.6
  • nginx 1.0.4
  • supervisord 3.0a10
  • virtualenv 1.6.1
  • pip 1.0.1
  • gunicorn 0.12.2
  • fabric 1.1.0

Bitbucket project

To make things easier I have created a django bootstrap project directory with all of the file used in the blog post. It is located here, so feel free to clone and fork.

https://bitbucket.org/kencochrane/django-gunicorn-nginx-supervisord-bootstrap/

Login to server

ssh root@<RackSpaceIP>

Update packages

yum -y update

Install packages

You might not need all of these right now, but I normally need these down the line, so doing them all now.

yum -y install emacs readline-devel ncurses-devel libevent-devel glib2-devel libjpeg-devel freetype-devel bzip2 bzip2-devel bzip2-libs openssl-devel pcre pcre-devel gpg make gcc yum-utils unzip

Add a django user as a system user

useradd -d /opt/django -m -r django

Set password for django to what ever you want

passwd django

Setup directories

mkdir -p /opt/django
mkdir -p /opt/django/apps
mkdir -p /opt/django/logs
mkdir -p /opt/django/logs/nginx
mkdir -p /opt/django/logs/apps
mkdir -p /opt/django/configs
mkdir -p /opt/django/scripts
mkdir -p /opt/django/htdocs
mkdir -p /opt/django/tmp
mkdir -p /opt/django/configs/nginx
mkdir -p /opt/django/configs/supervisord
mkdir -p /opt/django/apps/my_app

Add blank html page

echo "<html><body>nothing here</body></html> " > /opt/django/htdocs/index.html

Install Zlib

# download from zlib.net
mkdir -p /tmp/downloads
cd /tmp/downloads
wget http://www.zlib.net/zlib-1.2.5.tar.gz
tar -xvzf zlib-1.2.5.tar.gz
cd zlib-1.2.5
./configure -s
make install

Install python 2.6.6

CentOS 5.5 doesn't come with python2.6 pre installed so we need to do that on our own.

mkdir -p /tmp/downloads
cd /tmp/downloads
wget http://www.python.org/ftp/python/2.6.6/Python-2.6.6.tgz
tar -xvzf Python-2.6.6.tgz
cd Python-2.6.6
./configure --enable-shared
make
make altinstall

Add the following to /etc/profile

We need to add the lib path to the LD_LIBRARY_PATH or else you will get an error saying it can't find libpython2.6.so.1.0

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/:/usr/local/lib64/

Source the new profile

source /etc/profile

Install distribute

mkdir -p /tmp/downloads
cd /tmp/downloads
curl -O http://python-distribute.org/distribute_setup.py
python2.6 distribute_setup.py

Install Pip & virtualenv

mkdir -p /tmp/downloads
cd /tmp/downloads
curl -O -k https://raw.github.com/pypa/pip/master/contrib/get-pip.py
python2.6 get-pip.py
pip install virtualenv

Install supervisor

pip install supervisor

Install mercurial

pip install mercurial

Install NGINX

mkdir -p /tmp/downloads
cd /tmp/downloads
wget http://nginx.org/download/nginx-1.0.4.tar.gz
tar -xzvf nginx-1.0.4.tar.gz
cd nginx-1.0.4
./configure --sbin-path=/usr/local/sbin --with-http_ssl_module --with-http_stub_status_module
make
/etc/init.d/nginx stop
sleep 2
sudo make install
sudo chmod +x /usr/local/sbin/nginx

Install my app

Add first virtualenv

cd /opt/django/apps/my_app/
virtualenv --distribute --no-site-packages v0.1

# make this a post_create hook?
touch /opt/django/apps/my_app/v0.1/.venv

cd /opt/django/apps/my_app/v0.1/
hg clone https://bitbucket.org/kencochrane/django-gunicorn-nginx-supervisord-bootstrap my_app

ln -s /opt/django/apps/my_app/v0.1 /opt/django/apps/my_app/current

ln -s /opt/django/apps/my_app/current/my_app/conf/nginx.conf /opt/django/configs/nginx/myapp.conf

ln -s /opt/django/apps/my_app/current/my_app/conf/supervisord.conf /opt/django/configs/supervisord/myapp.conf

# activate the ve
source /opt/django/apps/my_app/current/bin/activate
cd /opt/django/apps/my_app/current/my_app/
./bootstrap.py

Configure nginx

# as root
mkdir -p /etc/nginx
ln -s /opt/django/apps/my_app/current/my_app/server/etc/nginx.conf /etc/nginx/nginx.conf
ln -s /usr/local/nginx/conf/mime.types /etc/nginx/mime.types
ln -s /opt/django/apps/my_app/current/my_app/server/init.d/nginx /etc/init.d/nginx
chmod 755 /etc/init.d/nginx
chkconfig --add nginx
chkconfig nginx on

Configure Supervisord

# as root
ln -s /opt/django/apps/my_app/current/my_app/server/etc/supervisord.conf  /etc/supervisord.conf
ln -s /opt/django/apps/my_app/current/my_app/server/init.d/supervisord /etc/init.d/supervisord
chmod 755 /etc/init.d/supervisord
chkconfig --add supervisord
chkconfig supervisord on

Firewall

We need to open up the firewall so that we are allowed connection, if you don't know anything about this, check out these links.

http://cloudservers.rackspacecloud.com/index.php/Firewalls http://cloudservers.rackspacecloud.com/index.php/Introduction_to_iptables http://cloudservers.rackspacecloud.com/index.php/Sample_iptables_ruleset

# Open http port 80
iptables -I RH-Firewall-1-INPUT -p tcp --dport 80 -j ACCEPT

.bashrc file changes

I can't remember where I saw this little trick, if you know please let me know so that I can give them credit. If you put a file in your mercurial directory called .venv, when you cd into the directory this little bash hack will automatically activate your virtual environment for you. This allows you to have something similar to virtualenvwrapper in this custom setup.

Add this code to the .bashrc file

emacs /opt/django/.bashrc
#
# User specific aliases and functions
has_virtualenv() {
    if [ -e .venv ]; then
        deactivate >/dev/null 2>&1
        source bin/activate
    fi
}

venv_cd () {
    cd "$@" && has_virtualenv
}

alias cd="venv_cd"

#end of changes

# source the file to get new changes in active shell
source /opt/django/.bashrc

Change permissions of the django home directory to django

This cleans up and left over root ownership

chown -R django:django /opt/django/*

Switch to django user

su - django

Start up nginx

service nginx start

Startup supervisord

service supervisord start

Test Nginx and supervisord

Check supervisord status

supervisorctl status
my_app                           RUNNING    pid 13594, uptime 0:00:05

To check nginx go to the IP or domain name for your rackspace server in your browser and make sure it worked.

Updating the application using fabric

Inside of the bitbucket project directory there is a file called fabfile.py. This file will allow you to update the application from your machine whenever you want just by calling one command.

It will prompt you for your hostname and password for the django user. Then it will go out to the rackspace server and pull and update the app and restart the application in supervisord. It is very basic for right now, but should get you started if you want to do more advanced stuff.

fab update_server

Conclusion

Now that we have everything setup, if you want to add a new application to our setup all we need to do is.

  • create a new directory under apps
  • create the virtualenv
  • run the bootstrap to install the software
  • make sure that the application has a supervisord and nginx configuration file
  • symlink those files to the correct locations in the config directory
  • run any python management commands you might need to run (syncdb, migrate, etc)
  • reload supervisord and nginx
  • you should be good to go.

I hope this was helpful to someone besides myself, if it was helpful for you please let me know in the comments.

Comments