nginx + pserve + supervisord¶
This setup can be accomplished simply and is capable of serving a large amount
of traffic. The advantage in deployment is that by using pserve
, it is not
unlike the basic development environment you're probably using on your local
machine.
nginx is a highly optimized HTTP server, very capable of serving static content as well as acting as a proxy between other applications and the outside world. As a proxy, it also has good support for basic load balancing between multiple instances of an application.
Client <---> nginx [0.0.0.0:80] <---> (static files)
/|\
|-------> WSGI App [localhost:5000]
`-------> WSGI App [localhost:5001]
Our target setup is going to be an nginx server listening on port 80 and load-balancing between 2 pserve processes. It will also serve the static files from our project's directory.
Let's assume a basic project setup:
1/home/example/myapp 2 | 3 |-- env (your virtualenv) 4 | 5 |-- myapp 6 | | 7 | |-- __init__.py (defining your main entry point) 8 | | 9 | `-- static (your static files) 10 | 11 |-- production.ini 12 | 13 `-- supervisord.conf (optional)
Step 1: Configuring nginx¶
nginx needs to be configured as a proxy for your application. An example configuration is shown here:
1# nginx.conf
2
3user www-data;
4worker_processes 4;
5pid /var/run/nginx.pid;
6
7events {
8 worker_connections 1024;
9 # multi_accept on;
10}
11
12http {
13
14 ##
15 # Basic Settings
16 ##
17
18 sendfile on;
19 tcp_nopush on;
20 tcp_nodelay on;
21 keepalive_timeout 65;
22 types_hash_max_size 2048;
23 # server_tokens off;
24
25 # server_names_hash_bucket_size 64;
26 # server_name_in_redirect off;
27
28 include /etc/nginx/mime.types;
29 default_type application/octet-stream;
30
31 ##
32 # Logging Settings
33 ##
34
35 access_log /var/log/nginx/access.log;
36 error_log /var/log/nginx/error.log;
37
38 ##
39 # Gzip Settings
40 ##
41
42 gzip on;
43 gzip_disable "msie6";
44
45 ##
46 # Virtual Host Configs
47 ##
48
49 include /etc/nginx/conf.d/*.conf;
50 include /etc/nginx/sites-enabled/*;
51}
1# myapp.conf
2
3upstream myapp-site {
4 server 127.0.0.1:5000;
5 server 127.0.0.1:5001;
6}
7
8server {
9 listen 80;
10
11 # optional ssl configuration
12
13 listen 443 ssl;
14 ssl_certificate /path/to/ssl/pem_file;
15 ssl_certificate_key /path/to/ssl/certificate_key;
16
17 # end of optional ssl configuration
18
19 server_name example.com;
20
21 access_log /home/example/env/access.log;
22
23 location / {
24 proxy_set_header X-Forwarded-Proto $scheme;
25 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
26 proxy_set_header X-Forwarded-Host $host:$server_port;
27 proxy_set_header X-Forwarded-Port $server_port;
28
29 client_max_body_size 10m;
30 client_body_buffer_size 128k;
31 proxy_connect_timeout 60s;
32 proxy_send_timeout 90s;
33 proxy_read_timeout 90s;
34 proxy_buffering off;
35 proxy_temp_file_write_size 64k;
36 proxy_pass http://myapp-site;
37 proxy_redirect off;
38 }
39}
注釈
myapp.conf
is actually included into the http {}
section of the main
nginx.conf
file.
The optional listen
directive, as well as the 2 following lines,
are the only configuration changes required to enable SSL from the Client
to nginx. You will need to have already created your SSL certificate and
key for this to work. More details on this process can be found in
the OpenSSL wiki for Command Line Utilities.
You will also need to update the paths that are shown to match the actual
path to your SSL certificates.
The upstream
directive sets up a round-robin load-balancer between two
processes. The proxy is then configured to pass requests through the balancer
with the proxy_pass
directive. It's important to investigate the
implications of many of the other settings as they are likely
application-specific.
The proxy_set_header
directives inform our application of the exact deployment
setup. They will help the WSGI server configure our environment's
SCRIPT_NAME
, HTTP_HOST
, and the actual IP address of the client.
Step 2: Starting pserve¶
警告
Be sure to create a production.ini
file to use for
deployment that has debugging turned off and removing the
pyramid_debugtoolbar.
This configuration uses
waitress
to automatically convert the X-Forwarded-Proto
into the correct HTTP scheme in the WSGI
environment. This is important so that the URLs generated by the application
can distinguish between different domains, HTTP vs. HTTPS.
1#---------- App Configuration ----------
2[app:main]
3use = egg:myapp#main
4
5pyramid.reload_templates = false
6pyramid.debug_authorization = false
7pyramid.debug_notfound = false
8pyramid.default_locale_name = en
9
10#---------- Server Configuration ----------
11[server:main]
12use = egg:waitress#main
13host = 127.0.0.1
14port = %(http_port)s
15
16trusted_proxy = 127.0.0.1
17trusted_proxy_count = 1
18trusted_proxy_headers = x-forwarded-for x-forwarded-host x-forwarded-proto x-forwarded-port
19clear_untrusted_proxy_headers = yes
20
21#---------- Logging Configuration ----------
22# ...
Running the pserve processes:
$ pserve production.ini\?http_port=5000
$ pserve production.ini\?http_port=5001
注釈
Daemonization of pserve was deprecated in Pyramid 1.6, then removed in Pyramid 1.8.
Step 3: Serving Static Files with nginx (Optional)¶
Assuming your static files are in a subdirectory of your pyramid application, they can be easily served using nginx's highly optimized web server. This will greatly improve performance because requests for this content will not need to be proxied to your WSGI application and can be served directly.
警告
This is only a good idea if your static content is intended to be public. It will not respect any view permissions you've placed on this directory.
1location / {
2 # all of your proxy configuration
3}
4
5location /static {
6 root /home/example/myapp/myapp;
7 expires 30d;
8 add_header Cache-Control public;
9 access_log off;
10}
It's somewhat odd that the root
doesn't point to the static
directory,
but it works because nginx will append the actual URL to the specified path.
Step 4: Managing Your pserve Processes with Supervisord (Optional)¶
Turning on all of your pserve
processes manually and daemonizing them
works for the simplest setups, but for a really robust server, you're going
to want to automate the startup and shutdown of those processes, as well as
have some way of managing failures.
Enter supervisord
:
$ pip install supervisor
This is a great program that will manage arbitrary processes, restarting them when they fail, providing hooks for sending emails, etc when things change, and even exposing an XML-RPC interface for determining the status of your system.
Below is an example configuration that starts up two instances of the pserve
process, automatically filling in the http_port
based on the
process_num
, thus 5000 and 5001.
This is just a stripped down version of supervisord.conf
, read the docs
for a full breakdown of all of the great options provided.
1[unix_http_server]
2file=%(here)s/env/supervisor.sock
3
4[supervisord]
5pidfile=%(here)s/env/supervisord.pid
6logfile=%(here)s/env/supervisord.log
7logfile_maxbytes=50MB
8logfile_backups=10
9loglevel=info
10nodaemon=false
11minfds=1024
12minprocs=200
13
14[rpcinterface:supervisor]
15supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
16
17[supervisorctl]
18serverurl=unix://%(here)s/env/supervisor.sock
19
20[program:myapp]
21autorestart=true
22command=%(here)s/env/bin/pserve %(here)s/production.ini?http_port=50%(process_num)02d
23process_name=%(program_name)s-%(process_num)01d
24numprocs=2
25numprocs_start=0
26redirect_stderr=true
27stdout_logfile=%(here)s/env/%(program_name)s-%(process_num)01d.log