添加负载均衡器时 Django 多租户站点的重定向循环
Posted
技术标签:
【中文标题】添加负载均衡器时 Django 多租户站点的重定向循环【英文标题】:Redirect loop for Django multi tenanted site when load balancer added 【发布时间】:2017-06-12 17:39:49 【问题描述】:我有两台虚拟主机,每台运行五个站点(多租户)。服务器具有三个 IP 地址。两个是公开的,一个是内部的。
这两个面向公众的网站都有 SSL 证书。一个站点是我的临时站点,有一个letsencrypt SSL证书,另一个是实时站点,有一个godaddy SSL证书。
我首先用一个节点(我的云实例)设置了一个 Rackspace 负载均衡器,将证书和密钥从我的服务器复制到均衡器,并成功使用以下 nginx 配置让负载均衡器从我的站点代理我的站点在面向内部的 IP 上服务的 Web 服务器
upstream django
server unix:///run/uwsgi/app/introtest/socket;
# configuration of the server, first redirect http to https...
server
listen 10.181.104.195:80;
if ($http_x_forwarded_proto = "http")
return 302 https://$http_host$request_uri;
# the domain name it will serve for
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media
alias /srv/test/media;
location /static
alias /srv/test/static;
# Finally, send all non-media requests to the Django server.
location /
if (-f /srv/maintenance_test.html)
return 503;
uwsgi_pass django;
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
uwsgi_param X-Real-IP $remote_addr;
uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
uwsgi_param X-Forwarded-Host $server_name;
# Error pages
error_page 503 /maintenance_test.html;
location = /maintenance_test.html
root /srv;
顺便说一句,如果我能提供帮助,我不会使用永久重定向,也不会使用登台服务器。实时服务器已经设置好并且永久重定向到 https,但我认为我们总是希望将实时站点重定向到 SSL,所以重定向是 301。
将暂存站点的根域的 DNS 条目更改为负载平衡器后,效果很好。
tail -f /var/log/nginx/access.log
表明请求来自负载平衡器的内部 IP 地址,并且页面被正确提供。
我将所有内容都改回来了(即 nginx conf 和暂存根域的 DNS 条目),并且可以从网络服务器进行暂存服务。然后我将 godaddy SSL 证书信息复制到负载均衡器。然后为实时服务器使用以下 nginx 配置:
upstream intro_live
server unix:///run/uwsgi/app/introsites/socket;
server
listen <SERVER PUBLIC IP>:80;
listen <SERVER PUBLIC IP>:443;
location /
return 503;
error_page 503 /interruption.html;
location = /interruption.html
root /srv;
# configuration of the server
server
# the port your site will be served on
listen 10.181.104.195:80;
# reidrect http to https from load balancer
if ($http_x_forwarded_proto = "http")
set $http_test S$http_host;
if ($http_test = 'Sintrotravel.com')
rewrite ^ https://www.introtravel.com$request_uri;
if ($http_test = 'Sozintro.com')
rewrite ^ https://www.ozintro.com$request_uri permanent;
if ($http_test = 'Sbalintro.com')
rewrite ^ https://www.balintro.com$request_uri permanent;
if ($http_test = 'Sthaintro.com')
rewrite ^ https://www.thaintro.com$request_uri permanent;
if ($http_test = 'Svietnamintro.com')
rewrite ^ https://www.vietnamintro.com$request_uri permanent;
charset utf-8;
# max upload size
client_max_body_size 75M; # adjust to taste
# Django media
location /media
alias /srv/intro/media;
expires 7d;
add_header Pragma public;
add_header Cache-Control "public";
location /static
alias /srv/intro/static;
expires 1d;
add_header Pragma public;
add_header Cache-Control "public";
# Finally, send all non-media requests to the Django server.
location /
if (-f /srv/maintenance_on.html)
return 503;
uwsgi_pass intro_live;
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
uwsgi_param X-Real-IP $remote_addr;
uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
uwsgi_param X-Forwarded-Host $server_name;
# Error pages
error_page 503 /maintenance_on.html;
location = /maintenance_on.html
root /srv;
第一个upstream django
指向我的uwsgi 配置。
第二个服务器配置监听服务器的公共地址,因此如果某人的网站 DNS 条目没有更新,他们会得到一个静态页面,说明服务器正在维护中。
第三个服务器配置监听内部地址的 80 端口,检查临时服务器是否设置了http_x_forwarded_proto
,以及是否设置了域的测试变量。我专门将这五个站点的 http 流量重定向到 https。
最后的 503 东西检测到文件是否存在,如果存在,则站点进入维护模式。
/etc/nginx/conf.d/ 中没有任何内容,而我的 /etc/nginx/nginx.conf 看起来像这样:
user www-data;
worker_processes 20;
pid /run/nginx.pid;
events
worker_connections 768;
# multi_accept on;
http
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
一旦我将五个站点的 DNS A 记录设置为指向负载平衡器,重定向到 https 就可以了,但是我在每个页面中都得到了一个重定向循环。所以,我从配置中取出了所有的重定向,即
if ($http_test = 'Sintrotravel.com')
rewrite ^ https://www.introtravel.com$request_uri;
if ($http_test = 'Sozintro.com')
rewrite ^ https://www.ozintro.com$request_uri permanent;
if ($http_test = 'Sbalintro.com')
rewrite ^ https://www.balintro.com$request_uri permanent;
if ($http_test = 'Sthaintro.com')
rewrite ^ https://www.thaintro.com$request_uri permanent;
if ($http_test = 'Svietnamintro.com')
rewrite ^ https://www.vietnamintro.com$request_uri permanent;
并重新启动 nginx。我仍然有一个重定向循环。为什么???我知道重写是永久性的,但这不是因为return 302
和rewrite
的差异,因为当我取出 all 重定向时,服务器仍处于无限重定向循环中。由于显而易见的原因,我不能花很多时间在现场进行试验。我真的需要在 10 到 15 分钟内完成。有人有什么建议吗?
【问题讨论】:
【参考方案1】:想了想,我意识到问题出在 Django 应用程序上。与nginx无关。有一个设置
SECURE_SSL_REDIRECT = True
这使得 django 将 http 流量重定向到 https(这在 staging 中是错误的)。当您添加负载均衡器时,这会成为一个问题,因为负载均衡器会接收 https 流量,但它只会在端口 80 上向我的 Web 服务器/应用程序提供不安全的流量。这就是您在条件下重定向的原因
if ($http_x_forwarded_proto = "http") ...
(由负载均衡器设置)在您的 nginx 配置中。仅从 nginx 以这种方式删除重定向并不能解决问题,我必须将其完全从 django 中删除。
无论您多么有经验,如果您因为其分布式特性而匆忙,DNS 都会让人感到困惑,而当一个赚钱的网站出现故障时,您就很着急。
【讨论】:
【参考方案2】:Django 可以通过以下方式解决这个问题:
https://docs.djangoproject.com/en/1.11/ref/settings/#std:setting-SECURE_PROXY_SSL_HEADER
添加: SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
并确保您的代理设置适当的标头将防止重定向循环。发生的情况是您的代理没有告诉 Django 连接是安全的,因此尽管它已经是安全的,但它会继续重定向到安全版本 - 因此是无限循环。提供标头是解决此问题的方法之一。
【讨论】:
以上是关于添加负载均衡器时 Django 多租户站点的重定向循环的主要内容,如果未能解决你的问题,请参考以下文章
Django 身份验证 - 错误的重定向 url 到登录页面
在 django 中间件中使用 HTTP HttpResponsePermanentRedirect 时站点进入重定向循环