Назад | Перейти на главную страницу

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

Просто дикая догадка, но, может быть, межсетевой экран «забыл» о 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 вашего брандмауэра).