Я столкнулся с проблемой, когда сервер приложений Rails (nginx / puma) и сервер данных PostgreSQL постоянно обмениваются данными, когда они находятся в одной и той же VLAN в нашей DMZ, но когда база данных изолирована от другой VLAN, а сервер приложений остается в DMZ, пользователь, попадающий на сервер приложений, в конечном итоге сталкивается с ошибкой 504 (тайм-аут шлюза) от nginx. Эти возможные тайм-ауты не кажутся связанными с фактическим использованием приложений конечным пользователем (потенциальное недоработка подключений, использованные подключения и т. Д.), Поскольку я заметил, что эта проблема может возникнуть в выходные, когда почти наверняка нет пользователей в система. С момента первого тайм-аута шлюза 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;
}
}
Я думаю, что проблема может быть в брандмауэре, но мы ничего не видим о заблокированных соединениях в нашем брандмауэре Пало-Альто. Мы попытались открыть только трафик postgresql, а затем расширить его до трафика TCP на порт 5432, и проблема не устранена. Конфигурация postgres довольно стандартна, с max_connections, которая превосходит максимально возможное количество подключений, которые могут быть установлены сервером приложений.
Просто дикая догадка, но, может быть, межсетевой экран «забыл» о TCP-сеансе? Многие брандмауэры имеют тайм-аут для «неиспользуемых» TCP-сессий.
Когда ваше приложение Rails запускается и подключается к базе данных, все работает нормально. Когда между приложением Rails и сервером базы данных существует более длительный период молчания, брандмауэр достигает своего тайм-аута tcp-сеанса и думает, что сеанс закрыт, в то время как оба конца (рельсы и сервер базы данных) считают, что он открыт. Когда rails хочет запросить базу данных сейчас, это будет заблокировано брандмауэром, поскольку пакеты не соответствуют известному сеансу tcp.
Если вы заставляете свои рельсы запускать "select 1" или что-то в этом роде по регулярному графику, соединение больше не должно прерываться.
Вы также можете попытаться перенастроить поведение postgresql tcp keepalive. В postgresql.conf
вы можете установить tcp_keepalives_idle = 60
tcp_keepalives_interval = 1
tcp_keepalives_count = 5
Это указывает стеку TCP отправлять пакеты поддержки активности каждые 60 секунд и отмечать соединение как мертвое, когда 5 таких пакетов потеряны. Самого пакета keepalive должно быть достаточно, чтобы брандмауэр оставил соединение открытым.
Значение по умолчанию для tcp_keepalives_idle в Linux должно быть 7200, что является слишком большим, если ваш брандмауэр отбрасывает сеансы tcp через 3600 секунд. Вы можете настроить ядро с помощью параметров sysctl на всех ваших хостах, чтобы все программы лучше работали с этим конкретным межсетевым экраном: net.ipv4.tcp_keepalive_time = 3500
Это устанавливает время поддержки активности по умолчанию на 3500 секунд (что несколько меньше таймаута TCP вашего брандмауэра).