Возможные тайм-ауты шлюза 504, когда сервер приложений Nginx/Puma Rails связывается с бэкэндом PostgreSQL через брандмауэр

Я столкнулся с проблемой, когда сервер приложений 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, который превосходит максимально возможные подключения, которые могут быть установлены сервером приложений.

1 ответ

Решение

Просто дикая догадка, но, может быть, Брандмауэр "забыл" о TCP-сессии? Многие брандмауэры имеют тайм-аут для "неиспользуемых" сеансов TCP.

Когда ваше приложение Rails запускается и подключается к базе данных, все работает нормально. Когда между приложением Rails и сервером базы данных существует более длительный период молчания, брандмауэр устанавливает таймаут сеанса tcp и считает, что сеанс закрыт, а оба конца (rails и сервер базы данных) считают, что он открыт. Когда rails хочет запросить базу данных, она будет заблокирована брандмауэром, поскольку пакеты не соответствуют известному сеансу tcp.

Если вы заставляете ваши рельсы работать "выберите 1" или что-то в этом роде по регулярному расписанию, соединение больше не должно прерываться.

Вы также можете попытаться перенастроить поведение поддержки активности tcp в postgresql. В 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 вашего брандмауэра)

Другие вопросы по тегам