Nginx

當 Nginx/Puma Rails App 伺服器通過防火牆聯繫 PostgreSQL 後端時,最終的 504 網關超時

  • May 12, 2017

我遇到了一個問題,即 Rails 應用程序伺服器(nginx/puma)和 PostgreSQL 數據伺服器在我們的 DMZ 上的同一個 VLAN 上保持一致通信,但是當數據庫被隔離到另一個 VLAN 並且應用程序伺服器仍然在DMZ,使用者訪問應用程序伺服器最終只會遇到來自 nginx 的 504(網關超時)錯誤。這些最終的超時似乎與應用程序的實際最終使用者使用情況無關(潛在的連接分配不足、連接用完等),因為我注意到這個問題可能發生在周末,幾乎可以肯定沒有使用者在系統。從第一個 504 網關超時開始,對伺服器的所有後續請求都會出現更多 504 網關超時頁面。我會說這是由於我的連接配置欠佳,*作品。*當配對處於“不良”配置時,連接工作但只能在可變的時間段內工作,通常是一個小時左右。

彪馬配置如下:

#!/usr/bin/env puma

directory "/var/www/my_app/current"
preload_app!
environment "production"
daemonize true
pidfile  "/var/www/my_app/shared/tmp/pids/my_app.pid"
state_path "/var/www/my_app/shared/puma/my_app.state"
stdout_redirect '/var/www/my_app/shared/log/production.log', '/var/www/my_app/shared/log/production_err.log', false
threads 0, 16
bind "unix:///var/www/my_app/shared/tmp/sockets/my_app.sock"
workers 8

on_worker_boot do
 require "active_record"
 ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
 ActiveRecord::Base.establish_connection(YAML.load_file("/var/www/my_app/current/config/database.yml")["production"])
end

before_fork do
 ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
end

Nginx 配置如下:

upstream my_app {
server unix:///var/www/my_app/current/tmp/sockets/my_app.sock;
}

server {
       listen 80 default;
       listen [::]:80 default;
       return 301 https://$host$request_uri;
}


server {
       listen 443 ssl default;
       listen [::]:443 ssl default;
       server_name my_server.domain.com;
       add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";

       root /var/www/my_app/current/public;

       ssl_certificate /etc/ssl/certs/my_app_crt;
       ssl_certificate_key /etc/ssl/private/my_app_key;

       ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';

       ssl_prefer_server_ciphers on;
       #See https://weakdh.org/
       ssl_dhparam /etc/ssl/private/dhparams.pem;

       client_max_body_size 500M;

       location / {

               if (-f $document_root/maintenance.html) {
                       return 503;
               }

               proxy_pass http://my_app; # match the name of upstream directive which is defined above
               proxy_set_header Host $host;
               proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
               proxy_set_header X-Forwarded-Proto https;
       }

       location ~* ^/assets/ {
               # Per RFC2616 - 1 year maximum expiry
               expires 1y;
               add_header Cache-Control public;

               # Some browsers still send conditional-GET requests if there's a
               # Last-Modified header or an ETag header even if they haven't
               # reached the expiry date sent in the Expires header.
               add_header Last-Modified "";
               add_header ETag "";
               break;
       }

       error_page 503 @maintenance;

       location @maintenance {
               rewrite ^(.*)$ /maintenance.html break;
       }

}

我認為防火牆可能是問題所在,但我們在 Palo Alto 防火牆中看不到任何有關阻塞連接的資訊。我們嘗試只打開 postgresql 流量,然後擴大到埠 5432 上的僅 tcp 流量,問題仍然存在。postgres 配置非常標準,max_connections 超過了應用伺服器可以建立的最大可能連接數。

只是一個瘋狂的猜測,但也許防火牆“忘記”了 TCP 會話?許多防火牆對“未使用”的 TCP 會話有超時。

當您的 Rails 應用程序啟動並連接到數據庫時,一切正常。當 Rails 應用程序和數據庫伺服器之間存在較長時間的靜默時,防火牆會達到其 tcp 會話超時並認為會話已關閉,而兩端(rails 和數據庫伺服器)都認為它是打開的。當 rails 現在想要查詢數據庫時,它將被防火牆阻止,因為包與已知的 tcp 會話不匹配。

如果您讓您的導軌定期執行“選擇 1”或類似的東西,則不應再斷開連接。

您也可以嘗試重新配置 postgresql 的 tcp keepalive 行為。在postgresql.conf你可以設置 tcp_keepalives_idle = 60

tcp_keepalives_interval = 1

tcp_keepalives_count = 5 這告訴TCP堆棧每60秒發送一個keepalive數據包,並在5個這樣的封包遺失時將連接標記為死亡。keepalive 數據包本身應該足以使防火牆保持連接打開。

Linux 上 tcp_keepalives_idle 的預設值應為 7200,如果防火牆在 3600 秒後丟棄 tcp 會話,則該值太高。您可能希望通過所有主機上的 sysctl 參數調整核心,以使所有程序在該特定防火牆上更好地工作: net.ipv4.tcp_keepalive_time = 3500 這將預設的保活時間設置為 3500 秒(這比您的防火牆 TCP 超時略短)

引用自:https://serverfault.com/questions/849089