nginx が間違ったプロキシからコンテンツを提供している

nginx が間違ったプロキシからコンテンツを提供している

私は、複数の Rails サイトをホストする単一のサーバーを持っています。すべてのサイトは、一意のホスト名で識別されます (すべてのドメイン名は同じ IP アドレスにマップされます)。Rails と nginx は両方とも Docker コンテナーで実行されています。私は、公式 Docker イメージから構築された Docker イメージで実行されている nginx 1.23.1 を使用しています (TLS 証明書処理用に certbot のみを追加しました)。

最近別のサイトを追加した後、非常に奇妙なことが起こり始めました。nginx を起動した直後は、すべてが期待どおりに動作します。返されるすべてのコンテンツは、リクエスト内のホスト名と一致します。しかし、数時間後、返されるコンテンツは、要求されたホスト名と一致しなくなります。ただし、これはプロキシ コンテンツにのみ影響します。すべての静的リソースは、ホスト名に基づいて正しく提供されます。

例えば、私がhttps://www.meaaa.com(ここでのドメインはすべて例であり、実際のドメインではありません)私はbbb.me.comからHTMLコンテンツを取得します。そして、bbb.me.comのコンテンツはbbb.me.comで見つかると予想されるスタイルと画像を要求しているので、サーバーはそれらのリクエストすべてに404で応答します(静的アセットは翻訳:リクエストホスト名が翻訳:)。

そして私が要求すればhttps://bbb.me.com より、HTMLコンテンツを取得します翻訳:繰り返しになりますが、マークアップで指定されるアセットは、翻訳:ただし、静的アセットはリクエスト内のホスト名 bbb.me.com に従って正しく取得されるため、見つかりません。

そのため、2 つのサイトのアップストリーム Rails コンテンツは入れ替わったように見えますが、静的アセットは正しく提供されています。

私は何年もの間、複数の Rails サイトで非 Docker nginx を使用していますが、このようなことは一度も見たことがありません。これは、未定義のホストを要求しているという問題ではありません。両方のホストが構成で宣言されています。1 つのホストが認識されなくなった場合、返されたコンテンツは単にデフォルト サーバーであると想定できますが、実際には両方とも認識されており、単に入れ替わっているだけです。プロキシ コンテンツのみが切り替わり、静的アセットは切り替わっていないという事実は、両方のホスト名が認識されていることを示しています。

症状を要約すると、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>

リクエスト翻訳:静的アセットを個別に使用して翻訳:ホスト名は正常に機能し、bbb.me.com から bbb.me.com アセットを要求する場合も正常に機能します。

また、問題が Docker ではないことも確認しました。nginx コンテナ内で、各バックエンドを curl して適切なコンテンツを取得できます。

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

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

設定は次のとおりです翻訳:サイト:

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 ログにもエラーは表示されません。何を確認すればよいか、何かアドバイスがあれば、ぜひお願いします。

アップデート

場所が変わった 2 つのサイトは、HTTPS を使用していたサイトです。他のいくつかのサイトも HTTPS を使用するように変更しましたが、今では 3 つのサイトが一種のラウンドロビンで間違っています。aaa を要求すると bbb が返される、bbb を要求すると ccc が返される、ccc を要求すると aaa が返されるなどです。他の 2 つのサイトはまったく応答しません。予期しないイベントによって nginx がプロキシ コンテンツの提供に使用するルーティング テーブルを破損しているかのようです。

今のところ、これは本番サーバーなので、60 分ごとに nginx を再起動しています。本番サーバーの複製としてステージング サーバーをセットアップして、同じ問題がそこで発生し、サイトを停止せずに問題を解決できることを期待しています。

答え1

結局、問題は nginx が各バックエンド名を 1 回だけ (構成をロードするとき) 解決し、その後は IP アドレスが変更されないと想定していることから生じていることがわかりました。これを指摘してくれた nginx メーリング リストの Ángel に感謝します。

私の場合、各バックエンドは Web アプリケーションなので、ログが十分に大きくなるとログ ファイルがローテーションされて圧縮され、対応する Rails アプリが再起動されるように、毎日 logrotate を実行する必要があります。

2 つ以上の Rails アプリがほぼ同時に再起動されることはよくありますが、Docker 内部ネットワークで以前の IP アドレスを再び取得するために、正確に正しい順序で再起動されるという保証はありません。そこで何が起こっていたのかが説明できます。Rails アプリはときどき IP アドレスを交換していましたが、nginx はそれを認識していなかったため、古い IP アドレスにリクエストを転送し続け、同時に静的アセットを正しく提供していました。2 つのアプリケーションを停止して再起動することでこれをシミュレートし、問題を正確に再現することができました。また、サイトを 5 分ごとに監視するスクリプトを追加しておいたので、ログを見ると、問題は常にほぼ同時に発生しているようでした。これは、logrotate がトリガーであることと一致していました。

アンヘル氏は、nginx を再起動する代わりに、設定を再読み込みするだけで、サイト訪問者の中断が少なくなるはずだと指摘しました。また、nginx に名前の検索を 1 回だけではなく数分おきに実行させることも可能だと指摘しました (https://forum.nginx.org/read.php?2,215830,215832#msg-215832)。

この問題を解決するために、少なくとも今のところは、すべてのコンテナに固定 IP アドレスを割り当てるように Docker を設定しました。こうすることで、nginx は名前を 1 回だけ解決し続けることができます。ただし、これにはいくつかの影響があり、実行中の Rails アプリと同じ docker-compose.yml ファイルを使用して db:migrate や asset:precompile などのコマンドを実行できなくなりました (「アドレスが使用中です」というエラーが表示されます)。今のところ、docker compose execの代わりにを使用していますrunが、これはコマンドに影響しているようですdocker compose restart( を実行した後、数秒間「アドレスが使用中です」というエラーが表示されますexec)。これが問題になる場合は、固定 IP アドレスを戻して、logrotate プロセスの一部として nginx に構成を再読み込みさせる可能性があります。

関連情報