我遇到过一个问题,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 超时时间)