daphne/docs/deploying.rst
2016-09-09 13:29:12 +01:00

301 lines
13 KiB
ReStructuredText

Deploying
=========
Deploying applications using channels requires a few more steps than a normal
Django WSGI application, but you have a couple of options as to how to deploy
it and how much of your traffic you wish to route through the channel layers.
Firstly, remember that it's an entirely optional part of Django.
If you leave a project with the default settings (no ``CHANNEL_LAYERS``),
it'll just run and work like a normal WSGI app.
When you want to enable channels in production, you need to do three things:
* Set up a channel backend
* Run worker servers
* Run interface servers
You can set things up in one of two ways; either route all traffic through
a :ref:`HTTP/WebSocket interface server <asgi-alone>`, removing the need
to run a WSGI server at all; or, just route WebSockets and long-poll
HTTP connections to the interface server, and :ref:`leave other pages served
by a standard WSGI server <wsgi-with-asgi>`.
Routing all traffic through the interface server lets you have WebSockets and
long-polling coexist in the same URL tree with no configuration; if you split
the traffic up, you'll need to configure a webserver or layer 7 loadbalancer
in front of the two servers to route requests to the correct place based on
path or domain. Both methods are covered below.
Setting up a channel backend
----------------------------
The first step is to set up a channel backend. If you followed the
:doc:`getting-started` guide, you will have ended up using the in-memory
backend, which is useful for ``runserver``, but as it only works inside the
same process, useless for actually running separate worker and interface
servers.
Instead, take a look at the list of :doc:`backends`, and choose one that
fits your requirements (additionally, you could use a third-party pluggable
backend or write your own - that page also explains the interface and rules
a backend has to follow).
Typically a channel backend will connect to one or more central servers that
serve as the communication layer - for example, the Redis backend connects
to a Redis server. All this goes into the ``CHANNEL_LAYERS`` setting;
here's an example for a remote Redis server::
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgi_redis.RedisChannelLayer",
"CONFIG": {
"hosts": [("redis-server-name", 6379)],
},
"ROUTING": "my_project.routing.channel_routing",
},
}
To use the Redis backend you have to install it::
pip install -U asgi_redis
Some backends, though, don't require an extra server, like the IPC backend,
which works between processes on the same machine but not over the network
(it's available in the ``asgi_ipc`` package)::
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgi_ipc.IPCChannelLayer",
"ROUTING": "my_project.routing.channel_routing",
"CONFIG": {
"prefix": "mysite",
},
},
}
Make sure the same settings file is used across all your workers and interface
servers; without it, they won't be able to talk to each other and things
will just fail to work.
Run worker servers
------------------
Because the work of running consumers is decoupled from the work of talking
to HTTP, WebSocket and other client connections, you need to run a cluster
of "worker servers" to do all the processing.
Each server is single-threaded, so it's recommended you run around one or two per
core on each machine; it's safe to run as many concurrent workers on the same
machine as you like, as they don't open any ports (all they do is talk to
the channel backend).
To run a worker server, just run::
python manage.py runworker
Make sure you run this inside an init system or a program like supervisord that
can take care of restarting the process when it exits; the worker server has
no retry-on-exit logic, though it will absorb tracebacks from inside consumers
and forward them to stderr.
Make sure you keep an eye on how busy your workers are; if they get overloaded,
requests will take longer and longer to return as the messages queue up
(until the expiry or capacity limit is reached, at which point HTTP connections will
start dropping).
In a more complex project, you won't want all your channels being served by the
same workers, especially if you have long-running tasks (if you serve them from
the same workers as HTTP requests, there's a chance long-running tasks could
block up all the workers and delay responding to HTTP requests).
To manage this, it's possible to tell workers to either limit themselves to
just certain channel names or ignore specific channels using the
``--only-channels`` and ``--exclude-channels`` options. Here's an example
of configuring a worker to only serve HTTP and WebSocket requests::
python manage.py runworker --only-channels=http.* --only-channels=websocket.*
Or telling a worker to ignore all messages on the "thumbnail" channel::
python manage.py runworker --exclude-channels=thumbnail
Run interface servers
---------------------
The final piece of the puzzle is the "interface servers", the processes that
do the work of taking incoming requests and loading them into the channels
system.
If you want to support WebSockets, long-poll HTTP requests and other Channels
features, you'll need to run a native ASGI interface server, as the WSGI
specification has no support for running these kinds of requests concurrently.
We ship with an interface server that we recommend you use called
`Daphne <http://github.com/django/daphne/>`_; it supports WebSockets,
long-poll HTTP requests, HTTP/2 *(soon)* and performs quite well.
You can just keep running your Django code as a WSGI app if you like, behind
something like uwsgi or gunicorn; this won't let you support WebSockets, though,
so you'll need to run a separate interface server to terminate those connections
and configure routing in front of your interface and WSGI servers to route
requests appropriately.
If you use Daphne for all traffic, it auto-negotiates between HTTP and WebSocket,
so there's no need to have your WebSockets on a separate port or path (and
they'll be able to share cookies with your normal view code, which isn't
possible if you separate by domain rather than path).
To run Daphne, it just needs to be supplied with a channel backend, in much
the same way a WSGI server needs to be given an application.
First, make sure your project has an ``asgi.py`` file that looks like this
(it should live next to ``wsgi.py``)::
import os
from channels.asgi import get_channel_layer
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_project.settings")
channel_layer = get_channel_layer()
Then, you can run Daphne and supply the channel layer as the argument::
daphne my_project.asgi:channel_layer
Like ``runworker``, you should place this inside an init system or something
like supervisord to ensure it is re-run if it exits unexpectedly.
If you only run Daphne and no workers, all of your page requests will seem to
hang forever; that's because Daphne doesn't have any worker servers to handle
the request and it's waiting for one to appear (while ``runserver`` also uses
Daphne, it launches worker threads along with it in the same process). In this
scenario, it will eventually time out and give you a 503 error after 2 minutes;
you can configure how long it waits with the ``--http-timeout`` command line
argument.
Deploying new versions of code
------------------------------
One of the benefits of decoupling the client connection handling from work
processing is that it means you can run new code without dropping client
connections; this is especially useful for WebSockets.
Just restart your workers when you have new code (by default, if you send
them SIGTERM they'll cleanly exit and finish running any in-process
consumers), and any queued messages or new connections will go to the new
workers. As long as the new code is session-compatible, you can even do staged
rollouts to make sure workers on new code aren't experiencing high error rates.
There's no need to restart the WSGI or WebSocket interface servers unless
you've upgraded the interface server itself or changed the ``CHANNEL_LAYER``
setting; none of your code is used by them, and all middleware and code that can
customize requests is run on the consumers.
You can even use different Python versions for the interface servers and the
workers; the ASGI protocol that channel layers communicate over
is designed to be portable across all Python versions.
.. _asgi-alone:
Running just ASGI
-----------------
If you are just running Daphne to serve all traffic, then the configuration
above is enough where you can just expose it to the Internet and it'll serve
whatever kind of request comes in; for a small site, just the one Daphne
instance and four or five workers is likely enough.
However, larger sites will need to deploy things at a slightly larger scale,
and how you scale things up is different from WSGI; see :ref:`scaling-up`.
.. _wsgi-with-asgi:
Running ASGI alongside WSGI
---------------------------
ASGI and its canonical interface server Daphne are both relatively new,
and so you may not wish to run all your traffic through it yet (or you may
be using specialized features of your existing WSGI server).
If that's the case, that's fine; you can run Daphne and a WSGI server alongside
each other, and only have Daphne serve the requests you need it to (usually
WebSocket and long-poll HTTP requests, as these do not fit into the WSGI model).
To do this, just set up your Daphne to serve as we discussed above, and then
configure your load-balancer or front HTTP server process to dispatch requests
to the correct server - based on either path, domain, or if
you can, the Upgrade header.
Dispatching based on path or domain means you'll need to design your WebSocket
URLs carefully so you can always tell how to route them at the load-balancer
level; the ideal thing is to be able to look for the ``Upgrade: WebSocket``
header and distinguish connections by this, but not all software supports this
and it doesn't help route long-poll HTTP connections at all.
You could also invert this model, and have all connections go to Daphne by
default and selectively route some back to the WSGI server, if you have
particular URLs or domains you want to use that server on.
Running on a PaaS
-----------------
To run Django with channels enabled on a Platform-as-a-Service (PaaS), you will
need to ensure that your PaaS allows you to run multiple processes at different
scaling levels; one group will be running Daphne, as a pure Python application
(not a WSGI application), and the other should be running ``runworker``.
The PaaS will also either have to provide either its own Redis service or
a third process type that lets you run Redis yourself to use the cross-network
channel backend; both interface and worker processes need to be able to see
Redis, but not each other.
If you are only allowed one running process type, it's possible you could
combine both interface server and worker into one process using threading
and the in-memory backend; however, this is not recommended for production
use as you cannot scale up past a single node without groups failing to work.
.. _scaling-up:
Scaling Up
----------
Scaling up a deployment containing channels (and thus running ASGI) is a little
different to scaling a WSGI deployment.
The fundamental difference is that the group mechanic requires all servers serving
the same site to be able to see each other; if you separate the site up and run
it in a few, large clusters, messages to groups will only deliver to WebSockets
connected to the same cluster. For some site designs this will be fine, and if
you think you can live with this and design around it (which means never
designing anything around global notifications or events), this may be a good
way to go.
For most projects, you'll need to run a single channel layer at scale in order
to achieve proper group delivery. Different backends will scale up differently,
but the Redis backend can use multiple Redis servers and spread the load
across them using sharding based on consistent hashing.
The key to a channel layer knowing how to scale a channel's delivery is if it
contains the ``!`` character or not, which signifies a single-reader channel.
Single-reader channels are only ever connected to by a single process, and so
in the Redis case are stored on a single, predictable shard. Other channels
are assumed to have many workers trying to read them, and so messages for
these can be evenly divided across all shards.
Django channels are still relatively new, and so it's likely that we don't yet
know the full story about how to scale things up; we run large load tests to
try and refine and improve large-project scaling, but it's no substitute for
actual traffic. If you're running channels at scale, you're encouraged to
send feedback to the Django team and work with us to hone the design and
performance of the channel layer backends, or you're free to make your own;
the ASGI specification is comprehensive and comes with a conformance test
suite, which should aid in any modification of existing backends or development
of new ones.