Skip to content

Commit a7f4c47

Browse files
committed
Add deployment doc.
Fix #445.
1 parent b455d30 commit a7f4c47

File tree

10 files changed

+299
-119
lines changed

10 files changed

+299
-119
lines changed

docs/howto/deployment.rst

Lines changed: 0 additions & 111 deletions
This file was deleted.

docs/howto/index.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ features, which websockets supports fully.
2424

2525
extensions
2626

27-
Once your application is ready, learn how to deploy it with a convenient,
28-
optimized, and secure setup.
27+
.. _deployment-howto:
28+
29+
Once your application is ready, learn how to deploy it on various platforms.
2930

3031
.. toctree::
3132
:maxdepth: 2
3233

33-
deployment
3434
heroku
3535
kubernetes
3636
supervisor

docs/spelling_wordlist.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ api
22
attr
33
augustin
44
auth
5+
autoscaler
56
awaitable
67
aymeric
78
backend
@@ -25,13 +26,17 @@ datastructures
2526
django
2627
dyno
2728
fractalideas
29+
gunicorn
30+
hypercorn
2831
iframe
2932
IPv
33+
istio
3034
iterable
3135
keepalive
3236
KiB
3337
Kubernetes
3438
lifecycle
39+
linkerd
3540
liveness
3641
lookups
3742
MiB
@@ -60,10 +65,12 @@ unparse
6065
unregister
6166
uple
6267
username
68+
uvicorn
6369
virtualenv
6470
WebSocket
6571
websocket
6672
websockets
6773
ws
74+
wsgi
6875
wss
6976
www

docs/topics/deployment.rst

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
Deployment
2+
==========
3+
4+
.. currentmodule:: websockets
5+
6+
When you deploy your websockets server to production, at a high level, your
7+
architecture will almost certainly look like the following diagram:
8+
9+
.. image:: deployment.svg
10+
11+
The basic unit for scaling a websockets server is "one server process". Each
12+
blue box in the diagram represents one server process.
13+
14+
When you prepare the deployment, your should ask yourself two questions:
15+
16+
1. How will I run the appropriate number of server processes?
17+
2. How will I route incoming connections to these processes?
18+
19+
There's a wide range of acceptable answers to these questions, depending on
20+
your goals and your constraints. Also the two questions are interrelated.
21+
22+
You can find a few concrete examples in the :ref:`deployment how-to guides
23+
<deployment-howto>`.
24+
25+
Running server processes
26+
------------------------
27+
28+
How many processes do I need?
29+
.............................
30+
31+
Typically, one server process will manage a few hundreds or a few thousand
32+
connections, depending on the frequency of messages and the amount of work
33+
they require.
34+
35+
CPU and memory usage increase with the number of connections to the server.
36+
Usually, CPU is the limiting factor. If a server process goes to 100% CPU,
37+
then you reached the limit. How much headroom you want to keep is up to you.
38+
39+
Once you know how many connections a server process can manage and how many
40+
connections you need to handle, you can calculate how many processes to run.
41+
42+
You can also automate this calculation by configuring an autoscaler to keep
43+
CPU usage or connection count within acceptable limits.
44+
45+
Don't scale with threads. Threads doesn't make sense for a server built with
46+
:mod:`asyncio`.
47+
48+
How do I run processes?
49+
.......................
50+
51+
Most solutions for running multiple instances of a server process fall into
52+
one of these three buckets:
53+
54+
1. Running N processes on a platform
55+
56+
* a Kubernetes Deployment
57+
58+
* its equivalent on a Platform as a Service provider
59+
60+
2. Running N servers
61+
62+
* an AWS Auto Scaling group, a GCP Managed instance group, etc.
63+
64+
* a fixed set of long-lived servers
65+
66+
3. Running N processes on a server
67+
68+
* preferrably via a process manager or supervisor
69+
70+
Option 1 is easiest of you have access to such a platform.
71+
72+
Option 2 almost always combines with option 3.
73+
74+
How do I start a process?
75+
.........................
76+
77+
Run a Python program that invokes :func:`~serve`. That's it.
78+
79+
Don't run an ASGI server such as Uvicorn, Hypercorn, or Daphne. They're
80+
alternatives to websockets, not complements.
81+
82+
Don't run a WSGI server such as Gunicorn, Waitress, or mod_wsgi. They aren't
83+
designed to run WebSocket applications.
84+
85+
Applications servers handle network connections and expose a Python API. You
86+
don't need one because websockets handles network connections directly.
87+
88+
How do I stop a process?
89+
........................
90+
91+
Process managers send the SIGTERM signal to terminate processes. Catch this
92+
signal and exit the server to ensure a graceful shutdown.
93+
94+
Here's an example:
95+
96+
.. literalinclude:: ../../example/shutdown_server.py
97+
:emphasize-lines: 12-15,18
98+
99+
When exiting the context manager, :func:`~server.serve` closes connections
100+
with code 1001 (going away). As a consequence:
101+
102+
* If the connection handler is awaiting
103+
:meth:`~server.WebSocketServerProtocol.recv`, it receives a
104+
:exc:`~exceptions.ConnectionClosedOK` exception. It can catch the exception
105+
and clean up before exiting.
106+
107+
* Otherwise, it should be waiting on
108+
:meth:`~server.WebSocketServerProtocol.wait_closed`, so it can receive the
109+
:exc:`~exceptions.ConnectionClosedOK` exception and exit.
110+
111+
This example is easily adapted to handle other signals.
112+
113+
If you override the default signal handler for SIGINT, which raises
114+
:exc:`KeyboardInterrupt`, be aware that you won't be able to interrupt a
115+
program with Ctrl-C anymore when it's stuck in a loop.
116+
117+
Routing connections
118+
-------------------
119+
120+
What does routing involve?
121+
..........................
122+
123+
Since the routing layer is directly exposed to the Internet, it should provide
124+
suitable protection against threats ranging from Internet background noise to
125+
targeted attacks.
126+
127+
You should always secure WebSocket connections with TLS. Since the routing
128+
layer carries the public domain name, it should terminate TLS connections.
129+
130+
Finally, it must route connections to the server processes, balancing new
131+
connections across them.
132+
133+
How do I route connections?
134+
...........................
135+
136+
Here are typical solutions for load balancing, split the same buckets as those
137+
for running server processes:
138+
139+
1. If you're running on a platform, it comes with a routing layer
140+
141+
* a Kubernetes Ingress and Service
142+
143+
* a service mesh: Istio, Consul, Linkerd, etc.
144+
145+
* the routing mesh of a Platform as a Service
146+
147+
2. If you're running N servers, you may load balance with:
148+
149+
* a cloud load balancer: AWS Elastic Load Balancing, GCP Cloud Load
150+
Balancing, etc.
151+
152+
* A software load balancer: HAProxy, NGINX, etc.
153+
154+
3. If you're running N processes on a server, you may load balance with:
155+
156+
* A software load balancer: HAProxy, NGINX, etc.
157+
158+
* Nothing beyond the operating system — just tell all processes to listen
159+
on the same port!
160+
161+
You may trust the load balancer to handle encryption and to provide security.
162+
You may add another layer in front of the load balancer for these purposes.
163+
There are many possibilities. Don't add layers that you don't need, though.
164+
165+
How do I implement a health check?
166+
..................................
167+
168+
Load balancers need a way to check whether server processes are up and running
169+
to avoid routing connections to a non-functional backend.
170+
171+
websockets provide minimal support for responding to HTTP requests with the
172+
:meth:`~server.WebSocketServerProtocol.process_request` hook.
173+
174+
Here's an example:
175+
176+
.. literalinclude:: ../../example/health_check_server.py
177+
:emphasize-lines: 9-11,20

0 commit comments

Comments
 (0)