t.marcusの外部記憶装置

忘備録とかちょっとした考えとかをつらつらと...

nginx mirror module の罠?の話

注意

  • nginx v1.24.0で確認しています。
  • ソースコードを呼んだとかではないので、利用する際には十分検証を行ってください。

内容

Nginx には、ミラーモジュール ( ngx_http_mirror_module ) というモジュールがあり、

server {
    listen :80;

    location /mirror {
        internal;

        proxy_pass http://origin2;
    }

    location / {
        mirror /mirror

        proxy_pass http://origin1;
    }
}

このような ForwardProxy の設定を行うことで

[Client] -> [nginx] -+-------------------> [origin1]
                      `- - -(mirror)- - -> [origin2]

このように origin1 にリクエストを行いつつ、同様のリクエストを origin2 にも行い、origin2 のレスポンスは無視することができるというものです。

しかし、実際に使ってみると罠?と思われる挙動が合ったので、ここにメモしておきます。

🪤1:ミラー側が遅延するとレスポンスが遅延する

以下のように origin1 と origin2 のレイテンシが異なる場合、遅い方に引きずられます。

[Client] -> [nginx] <---+--(100ms)--> [origin1]
                        +--(300ms)--------------> [origin2]

負荷試験など、遅いレイテンシに引きずられたくない場合は、以下のように、mirror側にタイムアウト設定を実施すると回避できます。

server {
    listen :80;

    location /mirror {
        internal;

        proxy_pass            http://origin2;
        proxy_connect_timeout 100ms;
        proxy_read_timeout    100ms;
        proxy_send_timeout    100ms;
    }

    location / {
        mirror /mirror

        proxy_pass http://origin1;
    }
}

🪤2:ミラー側のKeepAliveが有効にならない

以下のように、origin1 / origin2ともにKeepAliveの設定を設定しているにも関わらず、

server {
    listen :80;

    location /mirror {
        internal;

        proxy_http_version  1.1;
        proxy_set_header    Connection "";
        proxy_pass          http://origin2;
    }

    location / {
        mirror /mirror

        proxy_http_version  1.1;
        proxy_set_header    Connection "";
        proxy_pass          http://origin1;
    }
}

以下のように、mirror側のKeepAliveが有効にならず都度接続増えてしまう場合、

[Client] ---> [nginx] <-+--(KeepAlive有効)----> [origin1]
                        +--(KeepAlive無効)----> [origin2]

mirrorの中ではKeepAliveが有効にならないようなので、別サーバとしてKeepAliveを有効にして、proxy_passの先に指定することで回避することが可能です。

upstream nginx_proxy_socket {
    unix:/var/run/nginx-proxy.sock
}

server {
    listen unix:/var/run/nginx-proxy.sock

    location / {
        internal;

        proxy_http_version  1.1;
        proxy_set_header    Connection "";
        proxy_pass          http://origin2;
    }
}

server {
    listen :80;

    location /mirror {
        internal;

        proxy_http_version  1.1;
        proxy_set_header    Connection "";
        proxy_pass          http://nginx_proxy_socket$request_uri;
    }

    location / {
        mirror /mirror

        proxy_http_version  1.1;
        proxy_set_header    Connection "";
        proxy_pass          http://origin1;
    }
}