web-dev-qa-db-ja.com

Nginx / Puma Railsアプリサーバーがファイアウォールを介してPostgreSQLバックエンドに接続している場合の最終的な504ゲートウェイタイムアウト

Railsアプリサーバー(nginx/puma)とPostgreSQLデータサーバーがDMZの同じVLANにある場合でも、データベースが別のデータベースに分離されている場合に、一貫して通信するという問題が発生しています。 VLANそしてアプリサーバーはDMZに残り、アプリサーバーにアクセスしたユーザーは最終的にnginxから504(ゲートウェイタイムアウト)エラーに遭遇します。これらの最終的なタイムアウトは、アプリの実際のエンドユーザーの使用(接続の割り当て不足、接続の使い果たしなど)とは関係がないようです。この問題は、ほぼ確実にユーザーがいない週末に発生する可能性があるためです。システム。最初の504ゲートウェイタイムアウトから、サーバーへの後続のすべての要求は、より多くの504ゲートウェイタイムアウトページでエラーになります。これは私の側の接続構成が最適ではないためだと思いますが、両方のサーバーが同じDMZにあり、ファイアウォールを介して接続していない場合はすべて動作します。ペアの場合が「不良」構成の場合、接続は機能しますが、変動する期間、通常は1時間程度しか機能しません。

Pumaの構成は次のとおりです。

#!/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;
        }

}

ファイアウォールが問題である可能性があると思いますが、パロアルトファイアウォールでブロックされた接続に関しては何も表示されません。 postgresqlトラフィックのみを開いてから、ポート5432でtcpトラフィックのみに拡張しようとしましたが、問題は解決しません。 postgresの構成はかなり標準的であり、max_connectionsは、アプリサーバーが作成できる最大接続数を上回っています。

1
jrkinnard

ただの大げさな推測ですが、ファイアウォールがTCPセッションを「忘れた」のではないでしょうか?多くのファイアウォールには「未使用」TCPセッション」のタイムアウトがあります。

Railsアプリケーションが起動し、データベースに接続すると、すべてが正常に機能します。Railsアプリケーションとデータベースサーバーの間に長い無音期間がある場合、ファイアウォールtcpセッションタイムアウトに達し、両端(Railsとデータベースサーバー)の両方が開いていると信じている間にセッションが閉じられたと見なします。Railsがデータベースにクエリを実行したい場合、データベースはブロックされます。パッケージが既知のtcpセッションと一致しないため、ファイアウォール。

Rails "select 1"などを定期的に実行する場合は、接続が切断されないようにする必要があります。

Postgresqlのtcpキープアライブ動作を再構成することもできます。 postgresql.conf設定できますtcp_keepalives_idle = 60
tcp_keepalives_interval = 1
tcp_keepalives_count = 5これは、TCPスタックに60秒ごとにキープアライブパケットを送信し、そのようなパッケージが5つ失われたときに接続をデッドとしてマークするように指示します。キープアライブパケット自体は、ファイアウォールがキープアライブを維持するのに十分なはずです。接続が開いています。

Linuxでのtcp_keepalives_idleのデフォルト値は7200である必要があります。これは、ファイアウォールが3600秒後にtcpセッションを破棄する場合は高すぎます。すべてのプログラムがその特定のファイアウォールでより適切に機能するように、すべてのホストでsysctlパラメーターを介してカーネルを調整することをお勧めします。net.ipv4.tcp_keepalive_time = 3500これにより、デフォルトのキープアライブ時間が3500秒に設定されます(これはファイアウォールよりも若干短いTCPタイムアウト)

0
Andreas Rogge