This post will describe how to set up a Django Channels solution using AWS Elastic Beanstalk [EB]. This guide is written in response to there being very little information on the combination of Channels and AWS. Mainly many solutions end with problems regarding websockets, which is a fundamental part of Channels. See here and here. This guide will consist of multiple step-by-step parts including
- How to set up a local Django Channels solution
- How to set up an Application Load Balancer [ALB] to serve websockets
- How to configure EB to deploy Channels in a production environment
We are not going to go through how to create a Django Channels project as it has been very well documented many times, ie. here and here. There is also multiple posts on this blog regarding Django, Channels, Supervisord and websockets, ie. here.
Elastic Beanstalk runs an Apache webserver serving Django projects as wsgi (mod_wsgi), but it does not natively support websockets. There are different Apache configurations possible to serve websockets, ie. reverse proxy (see here), but for this use case we are going to use a load balancer in front of our servers to route different traffic to different backends based on their relative url. We are going to let Apache continue serving our Django project as wsgi on port 80, but we are going to route all traffic with the relative url "/ws/" to port 5000 where we will have Daphne running, serving our websockets. More on running Daphne can be read in previous posts. With AWS we can configure an Application Load Balancer to route this traffic.
Create an Application Load Balancer (here is a good tutorial).
Create 2 target groups. Point one target group to port 80 and add the EC2 instance to Registered instances. Point the second target group to port 5000 and add the same instance.
Under the Load Balancer tab create 2 rules. The default path points to the target group that is pointing to port 80. Set the second path to "/ws/" and point it to the target group that is pointing to port 5000.
NOTE: If we want further allowance on the path, add "/ws/*" instead to catch different paths.
Edit the Security Group of the instance to allow inbound connections on port 80 (type HTTP) and port 5000 (type Custom TCP Rule) from the ALB we just created.
Now the Load Balancer is set up to route requests to two different backends based on the relative url. Next step is to configure our EB deployment files to run and manage a Daphne backend.
We are going to run our Daphne backend through the monitoring program Supervisord. This means we have to configure supervisord.conf and to make sure it is available on both boot and deployment.
Download and edit the supervisord.conf-file found in /opt/python/etc/ to a folder in our Django project .ebextensions/supervisord/. Add Daphne and a few workers. Notice which port and interface Daphne runs on. Notice also the numprocs and process_name for the workers.
[program:Daphne]
environment=PATH="/opt/python/run/venv/bin"
command=/opt/python/run/venv/bin/daphne -b 0.0.0.0 -p 5000 ebdjango.asgi:channel_layer
directory=/opt/python/current/app
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/tmp/daphne.out.log
[program:Worker]
environment=PATH="/opt/python/run/venv/bin"
command=python manage.py runworker
directory=/opt/python/current/app
process_name=%(program_name)s_%(process_num)02d
numprocs=4
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/tmp/workers.out.log
Create a file .ebextensions/channels.config.
container_commands:
01_copy_supervisord_conf:
command: "cp .ebextensions/supervisord/supervisord.conf /opt/python/etc/supervisord.conf"
02_reload_supervisord:
command: "supervisorctl -c /opt/python/etc/supervisord.conf reload"
Now we can use eb deploy and to verify that supervisord is running our backend we can use supervisorctl status. Notice that the Apache server httpd is also monitored by supervisord.
$ sudo /usr/local/bin/supervisorctl -c /opt/python/etc /supervisord.conf status
Daphne RUNNING pid 22726, uptime 0:00:21
Worker:Worker_00 RUNNING pid 22730, uptime 0:00:21
Worker:Worker_01 RUNNING pid 22784, uptime 0:00:20
Worker:Worker_02 RUNNING pid 22785, uptime 0:00:20
Worker:Worker_03 RUNNING pid 22790, uptime 0:00:19
httpd RUNNING pid 22727, uptime 0:00:21
We are now able to serve websockets with Elastic Beanstalk using an Application Load Balancer and Django Channels.
I included full venv path in worker command like
command=/opt/python/run/venv/bin/python manage.py runworker default
this worked for me. By the way, default is name of the channel. You can add multiple channels separated by space like "python manage.py runworker channel1 channel2 channel3 ... "