nginx обслуживает контент с неправильного прокси

nginx обслуживает контент с неправильного прокси

У меня есть один сервер, на котором размещено несколько сайтов Rails. Все сайты идентифицируются уникальными именами хостов (все доменные имена сопоставляются с одним и тем же IP-адресом). И Rails, и nginx работают в контейнерах Docker. Я использую nginx 1.23.1, работающий в образе Docker, созданном из официального образа Docker (я добавил только certbot для обработки сертификатов TLS).

После недавнего добавления еще одного сайта, начала происходить очень странная вещь. Сразу после запуска nginx все работает как и ожидалось: весь возвращаемый контент соответствует имени хоста в запросе. Но через несколько часов возвращаемый контент не соответствует запрошенному имени хоста. Но это влияет только на контент прокси; все статические ресурсы по-прежнему обслуживаются правильно на основе имени хоста.

Например, когда я запрашиваюhttps://www.meaaa.com(все домены здесь являются примерами, а не реальными доменами), я получаю HTML-контент с bbb.me.com. И поскольку контент с bbb.me.com запрашивает стили и изображения, которые он ожидает найти на bbb.me.com, сервер отвечает на все эти запросы с 404 (потому что статические активы обслуживаются сwww.meaaa.comфайлы, так как имя хоста запроса -www.meaaa.com).

И если я попрошуhttps://bbb.me.com, я получаю HTML-контент изwww.meaaa.com. Опять же, активы, указанные в разметке, как ожидается, будут поступать изwww.meaaa.com, но поскольку статические ресурсы извлекаются правильно в соответствии с именем хоста bbb.me.com в запросе, они не найдены.

Таким образом, содержимое Rails на двух сайтах, похоже, поменялось местами, в то время как статические ресурсы обслуживаются корректно.

Я годами использовал не-Docker nginx с несколькими сайтами Rails и никогда не видел, чтобы что-то подобное случалось. Это не вопрос запроса неопределенного хоста; оба хоста объявлены в конфигурации. Если один хост перестал распознаваться, то я мог бы предположить, что возвращаемый контент был просто сервером по умолчанию, но на самом деле они оба распознаются, просто поменялись местами. Тот факт, что переключается только контент прокси, а не статические активы, показывает, что распознаются оба имени хоста.

Подводя итог симптомам, вот что показывает curl:

$ curl -s https://www.meaaa.com | grep '<title>'
  <title>BBB Site</title>

$ curl -s https://bbb.me.com | grep '<title>'
  <title>MEAAA Site</title>

Запрашивающийwww.meaaa.comстатические активы индивидуально с использованиемwww.meaaa.comИмя хоста работает нормально, как и запрос ресурсов bbb.me.com с bbb.me.com.

Я также убедился, что проблема не в Docker. Внутри контейнера nginx я могу скрутить каждый бэкенд и получить нужный контент:

$ curl -s http://aaa:3000 | grep '<title>'
  <title>MEAAA Site</title>

$ curl -s http://bbb:3000 | grep '<title>'
  <title>BBB Site</title>

Вот конфигурация дляwww.meaaa.comсайт:

upstream aaa-rails {
    server aaa:3000;
}

server {
    server_name www.meaaa.com source.meaaa.com aaa.meinternal.com;
    root /var/www/aaa-rails/public;
    index index.html index.htm;

    location /cms {
      deny 172.22.188.2; # public network interface
      try_files $uri @app;
    }
    location / {
        try_files $uri/index.html $uri @app;
    }

    location @app {
        proxy_pass http://aaa-rails;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Origin $scheme://$http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header X-Forwarded-Host $host;
        proxy_redirect off;
    }

    location ~* ~/assets/ {
        try_files $uri @app;
    }

    listen 80;

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/www.meaaa.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/www.meaaa.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

А вот конфигурация для сайта bbb.me.com:

upstream bbb-rails {
    server bbb:3000;
}

server {
    server_name bbb.me.com bbb-source.me.com bbb.meinternal.com;
    root /var/www/bbb-rails/public;
    index index.html index.htm;

    client_max_body_size 50m;

    location / {
        try_files $uri @app;
    }

    location @app {
        proxy_pass http://bbb-rails;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Origin $scheme://$http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header X-Forwarded-Host $host;
        proxy_redirect off;
    }

    location ~* ~/assets/ {
        try_files $uri @app;
    }

    listen 80;

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/bbb.me.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/bbb.me.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

Для меня самое странное, что перезапуск nginx решает проблему, но только временно. Я не думаю, что происходит какое-либо кэширование, и я не вижу никаких ошибок в логах nginx. Любые предложения, на что обратить внимание, были бы очень признательны.

ОБНОВЛЯТЬ

Два сайта, которые поменялись местами, использовали HTTPS. Я изменил несколько других, чтобы они использовали HTTPS, и теперь три из них неверны в своего рода циклическом режиме: запрос aaa, получаем bbb; запрос bbb, получаем ccc; запрос ccc, получаем aaa. Два других сайта просто не отвечают. Как будто какое-то непредсказуемое событие заставляет nginx повреждать любые таблицы маршрутизации, которые он использует для обслуживания прокси-контента.

На данный момент, поскольку это производственный сервер, я перезапускаю nginx каждые 60 минут. Я пытаюсь настроить промежуточный сервер как дубликат производственного сервера, надеясь, что та же проблема всплывет и там, так что я смогу попытаться выяснить проблему, не обрушая сайты.

решение1

Оказывается, проблема возникла из-за того, что nginx разрешает каждое имя бэкенда только один раз (когда загружает свою конфигурацию), а затем предполагает, что IP-адрес никогда не изменится. Спасибо Ángel из списка рассылки nginx, который указал на это.

В моем случае, поскольку каждый бэкенд представляет собой веб-приложение, мне приходится ежедневно запускать logrotate, чтобы, если журналы станут достаточно большими, файлы журналов были ротированы и сжаты, а соответствующее приложение Rails перезапущено.

Легко может случиться, что два или более приложений Rails перезапускаются более или менее одновременно, но нет гарантии, что они будут перезапущены в точном порядке, чтобы вернуть свой предыдущий IP-адрес во внутреннюю сеть Docker. Это объясняет, что происходило: время от времени приложения Rails обменивались IP-адресами, но nginx не знал об этом, поэтому он продолжал пересылать запросы на старые IP-адреса и в то же время правильно обслуживать статические ресурсы. Я смоделировал это, остановив и перезапустив два приложения, и смог точно воспроизвести проблему. Кроме того, я добавил скрипт, который будет отслеживать сайты каждые пять минут, и журналы показали, что проблема всегда, казалось, возникала примерно в одно и то же время, что соответствовало тому, что logrotate был триггером.

Анхель указал, что вместо перезапуска nginx можно просто заставить его перезагрузить конфигурацию, что должно привести к меньшему прерыванию для посетителей сайтов. Он также указал, что можно заставить nginx искать имена каждые несколько минут, а не только один раз (см.https://forum.nginx.org/read.php?2,215830,215832#msg-215832).

Чтобы решить эту проблему, по крайней мере на данный момент я настроил Docker на назначение фиксированного IP-адреса всем контейнерам. Таким образом, nginx может продолжать разрешать имена только один раз. Однако это имеет некоторые последствия, поскольку теперь я не могу запускать такие команды, как db:migrate или assets:precompile, используя тот же файл docker-compose.yml, что и запущенное приложение Rails (вы получаете ошибку «address is in use»); на данный момент я использую docker compose execвместо run, но это, похоже, влияет на docker compose restartкоманду (вы получаете ошибку «address is in use» в течение нескольких секунд после запуска exec). Если это станет проблемой, я могу вернуться к нефиксированным IP-адресам и заставить nginx перезагрузить свою конфигурацию как часть процесса logrotate.

Связанный контент