Use pip to install django_docker_engine
, either adding a line to your requirements.txt
or on the commandline:
$ pip install django_docker_engine
If the parent Django application has user sessions:
- Containers need to make sure that any AJAX requests back to the host
preserve session cookies. Any
fetch
orRequest
needs to includecredentials: "same-origin"
- If the container is itself a Django application, either the container application
or the parent application needs to change
SESSION_COOKIE_NAME
to a non-default value to avoid a collision.
There are essentially two ways of passing input to a container on startup:
By mounting a file from the host filesystem and by passing environment variables.
The method used depend on the parameters you use for DockerClientSpec
.
do_input_json_envvar
(boolean):
- Instead of writing a file, JSON is stored in an environment variable.
- There is limit to the size of environment variables: Typically 2M, but it could be lower.
input_json_url
(string):
- This option requires the caller to have already made the data available at some URL.
- Managing access and cleaning up this resource is the caller's responsibility.
There are two ways to map incoming requests to containers. The default is path-based routing, but domain-name routing can also be used.
- is simpler, but
- it requires a prefix on every path passed to the containerized webapp.
- ie: You may not use any paths starting with "/".
- is more complex, because it requires
HostnameRoutingMiddleware
, - and you will need to set up a wildcard entry in DNS to capture all subdomains.
- but the webapp can use paths starting with "/".
django_docker_engine
tries to abstract away the differences between different ways of running Docker.
Typically for development, Docker Engine will be installed and running on the same machine as Django: Review their docs for the best information, and then download and install.
But with more load, you'll probably want the containers on a separate machine to avoid resource contention...
In Refinery, the Docker Engine is running on on a separate EC2. You'll need to
provision the server, using boto, cloudformation, terraform, or some other libary,
and then set DOCKER_HOST
to point at the EC2.
TODO: AWS provides its own wrapper around Docker through ECS. We will need to abstract away what the Docker SDK provides so that we can use either interface, as needed.
Here's a basic demo. (This script does start up our demo django instance: for that to work, you will need to checkout the repo and cd, and not just have installed it via pip.)
>>> from django_docker_engine.docker_utils import (
... DockerClientRunWrapper, DockerClientSpec, DockerContainerSpec)
>>> client_spec = DockerClientSpec(do_input_json_envvar=True)
>>> client = DockerClientRunWrapper(client_spec)
# First, confirm no containers are already running:
>>> client.list()
[]
# Then start your own container:
>>> container_name = 'basic-nginx'
>>> from tests import NGINX_IMAGE
>>> container_spec = DockerContainerSpec(
... image_name=NGINX_IMAGE,
... container_name=container_name,
... mem_reservation_mb=15)
>>> container_url = client.run(container_spec)
>>> container_url # doctest:+ELLIPSIS
'http://localhost:...'
# The nginx container is responding to requests:
>>> import requests
>>> text = requests.get(container_url).text
>>> ('Welcome to nginx' in text) or text
True
# Start Django as a subprocess, and give it a moment to start:
>>> import subprocess
>>> process = subprocess.Popen(
... ['./manage.py', 'runserver'],
... stdout=open('/dev/null', 'w'),
... stderr=open('/dev/null', 'w'))
>>> django_url = 'http://localhost:8000'
>>> from time import sleep
>>> sleep(2)
# There is a homepage at '/':
>>> demo_home = requests.get(django_url).text
>>> ('django_docker_engine demo' in demo_home) or demo_home
True
# Under '/docker/, requests are proxied to containers by name:
>>> proxy_url = django_url + '/docker/' + container_name + '/'
>>> proxy_url
'http://localhost:8000/docker/basic-nginx/'
>>> nginx_welcome = requests.get(proxy_url).text
>>> ('Welcome to nginx' in nginx_welcome) or nginx_welcome
True
>>> nginx_404 = requests.get(proxy_url + 'foobar').text
# The history of requests is available:
>>> hist = client.history(container_name)
>>> len(hist)
2
>>> [h[1] for h in hist]
['/', '/foobar']
# and Docker logs are also available:
>>> api_logs = client.logs(container_name)
>>> (b'"GET / HTTP/1.1" 200' in api_logs) or api_logs
True
# ... or from the UI, if `logs_path` was provided as a kwarg to Proxy:
>>> ui_logs = requests.get(proxy_url + 'docker-logs').text
>>> ('"GET / HTTP/1.1" 200' in ui_logs) or ui_logs
True
By default, if the container is not yet responding to requests, the proxy will return a "Please wait" page with both a JS- and a meta-reload. You can customize the content of this page, and the reload behavior.
Note, though, that there is no attempt to distinguish between a container that is taking its time, and one that may never start up: In either case, the user just gets the "Please wait" page by default, and it will continue to refresh indefinitely.
# Make sure Django is still up:
>>> text = requests.get(django_url).text
>>> assert 'django_docker_engine demo' in text, 'unexpected: {}'.format(text)
# Try to get a container that doesn't exist:
>>> container_name = 'please-wait'
>>> proxy_url = django_url + '/docker/' + container_name + '/'
>>> proxy_url
'http://localhost:8000/docker/please-wait/'
>>> text = requests.get(proxy_url).text
>>> assert 'Please wait' in text, 'unexpected: {}'.format(text)
>>> assert 'http-equiv="refresh"' in text, 'unexpected: {}'.format(text)