Trifecta, part 3: the app
Putting it all together with virtualenv (and foreman)
(This is the final (possibly) article in a series of four, and follows on from Docker services. Code from this series of articles is available on Github as yunojuno/trifecta, and the VM is available on Atlas, the Vagrant box repository hosted by Hashicorp.)
If you've been following this series to date, you will now have a vanilla Vagrant VM set up, with a set of docker containers running core services. It should look like this:
====================================================
Ubuntu 14.04 ........... localhost .......... Vagrant
|
+- nginx ............... localhost:80/443 ... Docker
|
+- postgres ............ localhost:5432 ..... Docker
|
+- redis ............... localhost:6379 ..... Docker
|
+- memcached ........... localhost:11211 .... Docker
|
+- elasticsearch ....... localhost:9200 ..... Docker
====================================================
The thing that is missing from this picture is your application. What we want to see is this:
========================================================
Ubuntu 14.04 ........... localhost .......... Vagrant
|
+- nginx ............... localhost:80/443 ... Docker
|
+- app ............... localhost:8000 ..... Virtualenv
|
+- postgres ........ localhost:5432 ..... Docker
|
+- redis ......... localhost:6379 ..... Docker
|
+- memcached ..... localhost:11211 .... Docker
|
+- elasticsearch . localhost:9200 ..... Docker
========================================================
(Before getting in to the details, the obvious question is "why Virtualenv, and why not Docker given that you use it everywhere else?" Whilst docker is great for running stable apps / services, it's not the right tool for live development. When your app is being worked on, it's inherently unstable, and in any given development session, a developer may need to run manage.py runserver
, migrate
, makemigrations
, shell
, and so on. Docker is simply the wrong tool for the job. So Virtualenv it is.)
Setting up your app in virtualenv is something that, if you are developing python web applications, you should already know about (and if not, start here). Suffice to say the trifecta VM contains Virtualenv and Virtualenvwrapper, so 'activating' your application is as easy as:
# make a new venv for the app
mkvirtualenv app
# cd into the app directory
cd /path/to/app
# install your app requirements
pip install -r requirement.txt
In your app configuration, you can link to all the external services using localhost and the default port numbers in the diagram above. The rest of this article is about the specifics of configuration, and the commands and aliases that you may find useful.
In order to demonstrate how this all fits together, the trifecta project on Github has a sample project, demo_app, which is a v. simple Django app that contains a test suite that 'pings' the various services that we have set up:
class TrifectaHealthTests(TestCase):
"""Confirm that trifecta services are running."""
def test_postgres_container(self):
"""Ping the postgres container."""
self.assertEqual(User.objects.count(), 0)
def test_memcached_container(self):
"""Ping the memcached container."""
cache.set('ping', 'pong')
self.assertEqual(cache.get('ping'), 'pong')
def test_redis_container(self):
"""Ping the redis container."""
r = redis.StrictRedis(host='localhost', port=6379, db=0)
r.set('ping', 'pong')
self.assertEqual(r.get('ping'), 'pong')
def test_elasticsearch_container(self):
"""Ping the elasticsearch container."""
url = settings.ELASTICSEARCH_URL
self.assertEqual(requests.get(url).status_code, 200)
In order to get this project up and running within the trifecta project, first you need to initialise the VM:
# this will create the trifecta VM
vagrant up
# now that it's running we can SSH into it
vagrant ssh
Once we are in the box, the following will set up the demo project:
# cd into the demo project directory
cd /vagrant/demo_project
# make a new virtualenv using virtualenvwrapper command
mkvirtualenv demo
# install the python dependencies
pip install -r requirements.txt
# try running the tests
python manage.py test
The tests will fail at this point, complaining about a missing setting DATABASE_URL
. This is because we are following Heroku's 12-Factor App philosophy, and expecting to find the setting in our local environment variables. Setting up local env vars can be a pain locally, where you are having to manage multiple projects, all with their own configuration. In order to make this simple, we follow the instructions and use Foreman - described by Heroku as "a command-line tool for running Procfile-backed apps." In this instance we are only using it to hoist local settings into the env vars from a .env file. If you look at the demo_project directory you will see the following files:
.env
manage.py
requirements.txt
settings.py
test_app
And inside the .env
file:
DATABASE_URL = postgres://postgres:postgres@localhost:5432/postgres
MEMCACHE_URL = memcache://localhost:11211
REDIS_URL = redis://localhost:6379
ELASTICSEARCH_URL = http://localhost:9200
If we now run the tests using foreman:
foreman run python manage.py test
We now have all the required settings. However, the tests will still all fail, for the very simple reason that none of the required services are actually running.
We know from the previous article (Docker services) that we have the service containers on the VM, but they are not currently running. We have to start them. They are configured to run using the /vagrant/docker/docker-compose.yml
file, and so we can spin them up using:
# the 'up' command will start the containers
# the '-d' flag will start them in the background
docker-compose -f /vagrant/docker/docker-compose.yml up -d
Now let's run the tests again:
foreman run python manage.py test
Et voila. We (should) have a working Django project, running inside a virtualenv, connecting to Postgres, ElasticSearch, Memcached and Redis, all of which are running as Docker containers.
The final piece of the puzzle is to get Nginx talking to the Django dev server to actually serve a web page. We know from the /vagrant/etc/nginx/sites/demo_app.conf
file that Nginx is expecting the Django app to run on 127.0.0.1:8000
, which is the default Django runserver
port, so it should be as simple as:
foreman run python manage.py runserver
Now lets fire up a browser and go to 192.168.100.100
from the host machine (the Vagrantfile specifies that as the local network IP). You should see this:
PS to prevent complaints of RSI there are a couple of aliases set up in the VM
alias forage="foreman run python manage.py"
# alias as 'fig' as the docker-compose came out of the fig project
alias fig="docker-compose -f /vagrant/docker/docker-compose.yml"
The trifecta VM is now available on the Vagrant box repo, Atlas, under yunojuno/trifecta, which means that you can create a trifecta project by building off this in your local Vagrantfile (using config.vm.box = "yunojuno/trifecta"
).
The box doesn't contain the demo project - in order to run your project on the VM simply follow the pattern above, replacing the demo_project directory with your project.
Feedback welcome.