当 Nginx/Puma Rails 应用服务器通过防火墙联系 PostgreSQL 后端时,最终会出现 504 网关超时

当 Nginx/Puma Rails 应用服务器通过防火墙联系 PostgreSQL 后端时,最终会出现 504 网关超时

我遇到过一个问题,Rails 应用服务器 (nginx/puma) 和 PostgreSQL 数据服务器在我们的 DMZ 上的同一个 VLAN 上时可以持续通信,但当数据库被隔离到另一个 VLAN 并且应用服务器仍在 DMZ 上时,访问应用服务器的用户最终只会遇到来自 nginx 的 504(网关超时)错误。这些最终的超时似乎与应用程序的实际最终用户使用无关(可能连接分配不足、连接用尽等),因为我注意到这个问题可能发生在周末,那时几乎肯定没有用户在系统中。从第一个 504 网关超时开始,所有后续对服务器的请求都会出错,并出现更多 504 网关超时页面。我想说这是由于我的连接配置不理想,但当两台服务器都在同一个 DMZ 上并且没有通过防火墙连接时,整个事情作品。当线对处于“不良”配置时,连接虽然可以工作,但只能持续一段不定的时间,通常为一小时左右。

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;
        }

}

我认为防火墙可能是问题所在,但我们在 Palo Alto 防火墙中没有看到任何有关阻止连接的信息。我们尝试只打开 postgresql 流量,然后扩展到端口 5432 上只打开 tcp 流量,但问题仍然存在。postgres 配置非常标准,其 max_connections 超过了应用服务器可以建立的最大可能连接数。

答案1

只是猜测,但也许防火墙“忘记”了 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 超时时间)

相关内容