Deploying us some Django

Create a directory, cd into it and slip into your favourite editor to create a file called ‘Yaybufile’. Type in the following:

# The details of the application we're going to deploy
repo: https://github.com/yaybu/chaser.git
branch: part-1

# The Operating System we're deploying to
server_image: http://yaybu.com/library/ubuntu-12.04.3-server-amd64.zip

# The development domain servers will appear under
devdomain: local.dev

# Where we are deploying to: http://chaser.local.dev:8000
app_user: ubuntu
app_name: chaser
app_dir: /home/ubuntu/chaser
pidfile: {{app_dir}}/chaser.pid
port: 8000
listen: 0.0.0.0:{{port}}

start_command: >
    ./bin/python manage.py
    run_gunicorn {{listen}}
    --pid={{pidfile}} -D

stop_command: >
    test -e {{pidfile}} &&
    kill `cat {{pidfile}}` &&
    rm {{pidfile}} ||
    /bin/true

# A zone
new Zone as local:
    driver: MINIDNS
    domain: {{devdomain}}
    records:
        - name: {{app_name}}
          data: {{app.server.public_ip}}

# The instance we're going to put our things on
new Compute as app_server:
    name: {{app_name}}
    driver: VMWARE
    image:
        id: {{server_image}}
    user: ubuntu
    password: password

resources:

    - Package:
        - name: git-core
        - name: python-virtualenv

    - Checkout:
        name: {{app_dir}}
        scm: git
        repository: {{repo}}
        branch: {{branch}}
        user: {{app_user}}

    - Execute:
        name: virtualenv
        command: virtualenv .
        cwd: {{app_dir}}
        user: {{app_user}}
        creates: {{app_dir}}/bin/activate

    - Execute:
        name: rebuild-restart
        commands:
            - ./bin/pip install -r requirements.txt
            - {{stop_command}}
            - {{start_command}}
        cwd: {{app_dir}}
        user: {{app_user}}

new Provisioner as app:
    server: {{app_server}}
    resources: {{resources}}

Save it, and get your minidns server running if it isn’t already:

$ minidns start

Then:

$ yaybu up

While it runs, lets walk through this Yaybufile. First we define a bunch of things that will come in useful later:

# The details of the application we're going to deploy
repo: https://github.com/winjer/chaser.git
branch: part-1

# The Operating System we're deploying to
server_image: http://yaybu.com/library/ubuntu-12.04.3-server-amd64.zip

# The development domain servers will appear under
devdomain: local.dev

# Where we are deploying to
app_user: ubuntu
app_name: chaser
app_dir: /home/ubuntu/chaser
pidfile: {{app_dir}}/chaser.pid
listen: 0.0.0.0:8000

Then we need some way of starting and stopping Django. We are using Green Unicorn, which is well integrated with Django.

This runs a webserver on port 8000 of all interfaces, and writes it’s PID out to the pidfile:

start_command: >
    ./bin/python manage.py
    run_gunicorn {{listen}}
    --pid={{pidfile}} -D

And this will kill a running process if there is one:

stop_command: >
    test -e {{pidfile}} &&
    kill `cat {{pidfile}}` &&
    rm {{pidfile}} ||
    /bin/true

Now we need to find our web application on our virtual network. For that we create a zone in MiniDNS:

new Zone as local:
    driver: MINIDNS
    domain: {{devdomain}}
    type: master
    ttl: 60
    records:
        - name: {{app_name}}
          type: A
          data: {{app.server.public_ip}}

The URL is going to be http://{{app_name}}.{{devdomain}}:8000, e.g. http://chaser.local.dev:8000.

The next bit is to define a server on which we’re going to install our components:

new Compute as app_server:
    name: {{app_name}}
    driver: VMWARE
    image:
        id: {{server_image}}
    user: ubuntu
    password: password

Then we define the resources we’re going to deploy to this server:

resources:

    - Package:
        - name: git-core
        - name: python-virtualenv

    - Checkout:
        name: {{app_dir}}
        scm: git
        repository: {{repo}}
        branch: {{branch}}
        user: {{app_user}}

    - Execute:
        name: virtualenv
        command: virtualenv .
        cwd: {{app_dir}}
        user: {{app_user}}
        creates: {{app_dir}}/bin/activate

    - Execute:
        name: rebuild-restart
        commands:
            - ./bin/pip install -r requirements.txt
            - {{stop_command}}
            - {{start_command}}
        cwd: {{app_dir}}
        user: {{app_user}}

And finally we need a Provisioner, to provision our components onto the server. All the provisioner needs is to know where to provision the things, and which things to provision:

new Provisioner as app:
    server: {{app_server}}
    resources: {{resources}}

The first time you run this, yaybu will download the specified packed vm, clone it to a brand new VM, start it and then run all of the appropriate commands. The VM will be left running so the next time you deploy it will be much faster.

Yaybu examines the current state of the system and only applies the changes necessary to bring your system up to the state you have requested. This means that often running Yaybu will be idempotent: that nothing will be touched if it doesn’t need to be.

Yaybu should have finished running by now, and you should have a running virtual machine with a Django application running on it.

You can go to http://chaser.local.dev:8000 to see it.

When you run Yaybu a second time, you will get much less output because it has already done most of the work. It doesn’t preserve it’s own state to work this out, it introspects the state of the machine:

$ yaybu up
[*] Testing DNS credentials/connectivity
[*] Testing compute credentials/connectivity
[*] Updating 'chaser'
[*] Connecting to '192.168.213.148'
/---------------------------- Execute[pip-install] -----------------------------
| # ./bin/pip install -r requirements.txt
| Requirement already satisfied (use --upgrade to upgrade): Django in ./lib/python2.7/site-packages (from -r requirements.txt (line 1))
| Requirement already satisfied (use --upgrade to upgrade): gunicorn in ./lib/python2.7/site-packages (from -r requirements.txt (line 2))
| Cleaning up...
\-------------------------------------------------------------------------------
/---------------------------- Execute[kill-django] -----------------------------
| # test -e /home/ubuntu/chaser/chaser.pid && kill `cat /home/ubuntu/chaser/chaser.pid` && rm /home/ubuntu/chaser/chaser.pid || /bin/true
\-------------------------------------------------------------------------------
/---------------------------- Execute[start-django] ----------------------------
| # ./bin/python manage.py run_gunicorn 0.0.0.0:8000 --pid=/home/ubuntu/chaser/chaser.pid -D
\-------------------------------------------------------------------------------
[*] Applying configuration... (7/7)

It’s kind of annoying that it does anything at all though really.

Events and policies

Lets tidy it up a bit. First, we only want to stop and start Django if we’ve changed anything. This means, only run the stop and start if our Checkout actually synced. Change the final Execute step to:

- Execute:
     name: rebuild-restart
     commands:
         - ./bin/pip install -r requirements.txt
         - {{stop_command}}
         - {{start_command}}
     cwd: {{app_dir}}
     user: {{app_user}}
     policy:
         execute:
             when: sync
             on: Checkout[{{app_dir}}]

And Yaybu will only re-run pip and restart Django if there have been any code changes, because the Checkout resource will only emit a sync event if there have been changes.

Try this yourself - run yaybu up and it shouldn’t make any changes at all.