添加负载均衡器时 Django 多租户站点的重定向循环

Redirect loop for Django multi tenanted site when load balancer added

我有两个虚拟主机,每个 运行 五个站点(多租户)。服务器具有三个 IP 地址。两个 public 正面,一个在内部。

两个 public面向的网站都有 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;
    }
}

/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[= 时服务器仍处于无限重定向循环中50=] 重定向。出于显而易见的原因,我不能花很多时间试验实时站点。真的,我需要在 10-15 分钟内完成。有人有什么建议吗?

仔细想想,我意识到问题一定出在 Django 应用程序上。与 nginx 无关。有一个设置

SECURE_SSL_REDIRECT = True

这使得 django 将 http 流量重定向到 https(这在暂存中是错误的)。当您添加负载均衡器时,这会成为一个问题,因为负载均衡器接收 https 流量,但它只会将端口 80 上的不安全流量传送到我的网站 server/application。这就是为什么您在条件

上进行重定向的原因
if ($http_x_forwarded_proto = "http") {...

(由负载均衡器设置)在您的 nginx 配置中。仅从 nginx 中以这种方式删除重定向不会解决问题,我不得不从 django 中完全删除它。

无论你经验多么丰富,如果你赶时间,DNS 会让人感到困惑,因为它的分布式特性,当一个赚钱的网站宕机时,你也很匆忙。

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')

并确保您的代理设置适当 header 将防止重定向循环。发生的事情是你的代理没有告诉 Django 连接是安全的,所以它一直重定向到安全版本,尽管它已经是安全的 - 因此无限循环。提供 header 是解决此问题的方法之一。