Running Django on Docker: a workflow and code

9,452 views

Published on

An introduction to Pallet and Forklift, a standard and tool for deploying and developing web applications on top of Docker.

Published in: Software

Running Django on Docker: a workflow and code

  1. 1. Running Django on Docker a workflow and code Alexey Kotlyarov • Danielle Madeley
  2. 2. The problem
  3. 3. A diverse world of applications
  4. 4. Reproducible deployments
  5. 5. Base OS (Debian bootstrap) App dependencies (Python, libs) Built application Transient runtime Immutable External Storage
  6. 6. But there's no standards! 12factor.net is a must read
  7. 7. Pallet An interface for Docker containers github.com/infoxchange/pallet
  8. 8. What's in deploy? database migrations loading fixtures install static content to static web server (CDN, Ceph, nginx, etc.)
  9. 9. What's in serve? Start app server Starting supporting services, e.g. Celery
  10. 10. Keep it lean
  11. 11. $ docker build .
  12. 12. Dockerfile FROM debian/ubuntu/fedora/etc. RUN apt-get -qq update && apt-get -qq install git mercurial python python-virtualenv python-pip ...
  13. 13. RUN useradd -d /app -r app WORKDIR /app
  14. 14. ADD requirements.txt /app/requirements.txt RUN virtualenv python_env && . python_env/bin/activate && pip install -r requirements.txt ADD . /app
  15. 15. VOLUME ["/static", "/storage"] RUN mkdir -p /static /storage && chown -R app /static /storage
  16. 16. RUN echo "__version__ = '`git describe`'" > myapp/__version__.py RUN ./invoke.sh install ENTRYPOINT ["./invoke.sh"] EXPOSE 8000
  17. 17. invoke.sh #!/bin/sh # default parameters : ${APP_USER:=app} : ${WEB_CONCURRENCY:=1} export WEB_CONCURRENCY if [ "x$(whoami)" != "x$APP_USER" ]; then # ensure we own our storage chown -R "$APP_USER" /static /storage # Call back into ourselves as the app user exec sudo -sE -u "$APP_USER" -- "$0" "$@" else
  18. 18. else . ./startenv case "$1" in deploy) shift 1 # consume command from $@ ./manage.py migrate "$@" ;; serve) gunicorn -w "$WEB_CONCURRENCY" -b 0.0.0.0:8000 "${APP}.wsgi:applicatio ;; *) ./manage.py "$@" ;; esac fi
  19. 19. Django settings.py
  20. 20. from dj_database_url import parse DATABASES = { 'default': parse(os.environ['DB_DEFAULT_URL']), }
  21. 21. # Logging is complex LOGGING['handlers']['logstash'] = { 'level': 'DEBUG' if DEBUG else 'INFO', 'class': 'logging.handlers.SysLogHandler', 'address': (os.environ['SYSLOG_SERVER'], int(os.environ['SYSLOG_PORT'])) 'socktype': socket.SOCK_STREAM if os.environ['SYSLOG_PROTO'] == else socket.SOCK_DGRAM, }
  22. 22. # Trust our nginx server USE_X_FORWARDED_HOST = True SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', MY_SITE_DOMAIN = os.environ.get('SITE_DOMAIN') if MY_SITE_DOMAIN: ALLOWED_HOSTS = (MY_SITE_DOMAIN,)
  23. 23. IXDjango pallet configuration for Django pip install IXDjango from ixdjango.docker_settings import * github.com/infoxchange/ixdjango
  24. 24. An example (in Flask) github.com/danni/linux-conf-au-flask- tute/tree/dockerify
  25. 25. Running the container
  26. 26. docker run -p 8000:8000 -e DB_DEFAULT_URL=postgres://user:pass@db3:5432/mya -e SITE_DOMAIN=myapp-staging.company.com -e SITE_PROTO=https -e ENVIRONMENT=staging -e ELASTICSEARCH_URLS=http://elastic-1:9200/myapp -v /mnt/docker-storage/myapp:/storage -h WHY_ARE_YOU_STILL_READING_THIS myapp serve
  27. 27. Urgh!
  28. 28. Forklift a tool for loading pallets github.com/infoxchange/docker-forklift
  29. 29. myapp/forklift.yaml services: - postgres - elasticsearch
  30. 30. $ forklift myapp serve
  31. 31. Developing with Forklift
  32. 32. $ forklift ./invoke.sh serve $ forklift ./manage.py test
  33. 33. Poking around inside containers (aka troubleshooting)
  34. 34. $ forklift --mount-root /tmp/myapp myapp sshd
  35. 35. Extending Forklift
  36. 36. forklift.services.memcache @register('memcache') class Memcache(Service): providers = ('localhost', 'container') DEFAULT_PORT = 11211 def __init__(self, key_prefix='', hosts=None): self.key_prefix = key_prefix self.hosts = hosts or []
  37. 37. def environment(self): return { 'MEMCACHE_HOSTS': '|'.join(self.hosts), 'MEMCACHE_PREFIX': self.key_prefix, } def available(self): """ Check whether memcache is available """ ...
  38. 38. @classmethod def localhost(cls, application_id): """The default memcached provider""" return cls( key_prefix=application_id, hosts=['localhost:{0}'.format(cls.DEFAULT
  39. 39. @classmethod @transient_provider def container(cls, application_id): """Memcached provided by a container.""" container = ensure_container( image='fedora/memcached', port=cls.DEFAULT_PORT, application_id=application_id, ) return cls( key_prefix=application_id, hosts=['localhost:{0}'.format(container.p )
  40. 40. Continuous integration
  41. 41. forklift --cleanroom myapp test
  42. 42. Legacy applications
  43. 43. Fin ;-P Questions? github.com/infoxchange/pallet github.com/infoxchange/docker-forklift

×