添加负载均衡器时 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 302rewrite 的差异,因为当我取出 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 到登录页面

Python / Django 多租户解决方案

在 django 中间件中使用 HTTP HttpResponsePermanentRedirect 时站点进入重定向循环

Apache 上的重定向(维护 POST 参数)

面试官问:HTTP 的负载均衡你了解么?你不是说了你们用的Nginx么?说一下把。

IIS Sharepoint 站点的重定向规则