nginx リバース プロキシ - アップストリーム A、B、A の順に試す

nginx リバース プロキシ - アップストリーム A、B、A の順に試す

多数のバックエンド サーバーを備えたリバース プロキシとして nginx を設定しようとしています。オンデマンド (最初に受信したリクエストで) でバックエンドを起動したいので、受信したリクエストに応じてバックエンドを起動する制御プロセス (HTTP リクエストによって制御) を用意しています。

私の問題は、それを実行するための nginx の設定です。これまでのところ、次のようになっています。

server {
    listen 80;
    server_name $DOMAINS;

    location / {
        # redirect to named location
        #error_page 418 = @backend;
        #return 418; # doesn't work - error_page doesn't work after redirect

        try_files /nonexisting-file @backend;
    }

    location @backend {
        proxy_pass http://$BACKEND-IP;
        error_page 502 @handle_502; # Backend server down? Try to start it
    }

    location @handle_502 { # What to do when the backend server is not up
        # Ping our control server to start the backend
        proxy_pass http://127.0.0.1:82;
        # Look at the status codes returned from control server
        proxy_intercept_errors on;
        # Fallback to error page if control server is down
        error_page 502 /fatal_error.html;
        # Fallback to error page if control server ran into an error
        error_page 503 /fatal_error.html;
        # Control server started backend successfully, retry the backend
        # Let's use HTTP 451 to communicate a successful backend startup
        error_page 451 @backend;
    }

    location = /fatal_error.html {
        # Error page shown when control server is down too
        root /home/nginx/www;
        internal;
    }
}

error_pageこれは機能しません。nginx は、制御サーバーから返されるステータス コードを無視するようです。場所のディレクティブはどれも@handle_502機能せず、451 コードがそのままクライアントに送信されます。

私は、このために内部 nginx リダイレクトを使用することをあきらめ、コントロール サーバーを変更して同じ場所に 307 リダイレクトを発行しようとしました (クライアントが同じリクエストを再試行しますが、今度はバックエンド サーバーが起動されます)。しかし、コントロール サーバーが "Location" ヘッダーを送信しているにもかかわらず、nginx はバックエンド リクエスト試行から取得したステータス コード (502) でステータス コードを上書きしてしまいます。error_page 行を に変更して、error_page 502 =307 @handle_502;すべてのコントロール サーバーの応答が 307 コードでクライアントに返されるようにすることで、ようやく "動作" するようになりました。これは非常にハッキーで望ましくありません。1) コントロール サーバーの応答に応じて nginx が次に行うべきことを制御できない (理想的には、コントロール サーバーが成功を報告した場合にのみバックエンドを再試行したい)、2) すべての HTTP クライアントが HTTP リダイレクトをサポートしているわけではない (例: curl ユーザーおよび libcurl を使用するアプリケーションは、次のリダイレクトを明示的に有効にする必要がある) ためです。

nginx が上流サーバー A、B、そして再び A (理想的には、B が特定のステータス コードを返す場合のみ) にプロキシするようにする適切な方法は何ですか?

答え1

キーポイント:

  • 1 つのサーバーに ping すると別のサーバーが起動する場合は、フェイルオーバーのブロックを気にする必要はありません。最初のサーバーが再び起動したことを nginx (少なくとも FOSS バージョンではない) に通知する方法はありません。nginx は、、または設定にupstream関係なく、最初の要求では順番にサーバーを試行しますが、後続の要求では試行しません。backupweightfail_timeout
  • あなたしなければならないrecursive_error_pages名前付き場所を使用してフェイルオーバーを実装するときに有効にしますerror_page
  • proxy_intercept_errorsアップストリーム サーバーから送信されたエラー コードを処理できるようにします。
  • 指定された場所のエラー コードを正しく処理するには、構文=(例error_page 502 = @handle_502;) が必要です。=が使用されていない場合、nginx は前のブロックのエラー コードを使用します。

要約すると次のようになります。

server {
    listen ...;
    server_name $DOMAINS;

    recursive_error_pages on;

    # First, try "Upstream A"
    location / {
        error_page 418 = @backend;
        return 418;
    }

    # Define "Upstream A"
    location @backend {
        proxy_pass http://$IP:81;
        proxy_set_header  X-Real-IP     $remote_addr;
        # Add your proxy_* options here
    }

    # On error, go to "Upstream B"
    error_page 502 @handle_502;

    # Fallback static error page, in case "Upstream B" fails
    root /home/nginx/www;
    location = /_static_error.html {
        internal;
    }

    # Define "Upstream B"
    location @handle_502 { # What to do when the backend server is not up
        proxy_pass ...;
        # Add your proxy_* options here
        proxy_intercept_errors on;          # Look at the error codes returned from "Upstream B"
        error_page 502 /_static_error.html; # Fallback to error page if "Upstream B" is down
        error_page 451 = @backend;          # Try "Upstream A" again
    }
}

元の回答/調査ログは以下の通りです:


私が見つけたより良い回避策は次のとおりです。これはクライアントのリダイレクトを必要としないため、改善されています。

upstream aba {
    server $BACKEND-IP;
    server 127.0.0.1:82 backup;
    server $BACKEND-IP  backup;
}

...

location / {
    proxy_pass http://aba;
    proxy_next_upstream error http_502;
}

次に、制御サーバーが「成功」時に 502 を返すようにし、そのコードがバックエンドによって返されないことを期待します。


更新: nginx はブロックの最初のエントリをupstreamダウンとしてマークし続けるため、後続のリクエストでサーバーを順番に試しません。weight=1000000000 fail_timeout=1最初のエントリに追加してみましたが、効果はありませんでした。これまでのところ、クライアント リダイレクトを伴わない解決策は見つかりませんでした。


編集: もう 1 つ知っておきたかったことがあります。error_pageハンドラーからエラー ステータスを取得するには、次の構文を使用します。error_page 502 = @handle_502;- この等号により、nginx はハンドラーからエラー ステータスを取得します。


編集: そして、動作しました!error_page上記の修正に加えて、必要なのは を有効にすることだけでしたrecursive_error_pages!

答え2

次のようなことを試してみてください

upstream backend {
    server a.example.net;
    server b.example.net backup;
}

server {
    listen   80;
    server_name www.example.net;

    proxy_next_upstream error timeout http_502;

    location / {
        proxy_pass http://backend;
        proxy_redirect      off;
        proxy_set_header    Host              $host;
        proxy_set_header    X-Real-IP         $remote_addr;
        proxy_set_header    X-Forwarded-for   $remote_addr;
    }

}

関連情報