kakakakakku blog

Weekly Tech Blog: Keep on Learning!

nginx でリクエストを複製できるモジュール「ngx_http_mirror_module」

nginx でリクエストを複製できるモジュール「ngx_http_mirror_module」を使うと,簡易的な「Shadow Proxy」を構築することができる.例えば,本番環境のリクエストの一部を開発環境に流せるようになる.この「ngx_http_mirror_module」は nginx 1.13.4 で実装された機能で,2017年8月リリースなので,最近のバージョンだとデフォルトで使えるようになっている.今回は「リクエストの複製」を試すため,Docker Compose を使って検証環境を構築した.

nginx.org

検証環境

今回 Docker Compose を使って,nginx と Sinatra を起動する検証環境を構築した.コンテナは計3種類で,以下の構成図にまとめた.基本は FrontendBackend でリクエストを処理し,今回は Mirror にもリクエストを複製する構成にしている.ドキュメントにも記載されている通り,Mirror からのレスポンスは無視される仕様になっている.

  • Frontend (nginx)
  • Backend (Sinatra)
  • Mirror (Sinatra)

f:id:kakku22:20190722223752p:plain

特に設定はなく,すぐに docker-compose up で起動できるようにした.今回エンドポイントは Sinatra で //ping を実装している.なお,Docker Compose の設定などは GitHub に公開した.

$ docker-compose up

$ curl http://localhost
Hello, backend!

$ curl http://localhost/ping
Pong, backend!

github.com

Frontend (nginx)

ドキュメントを参考に nginx.confmirror を設定した.リクエストは proxy_passBackend に転送し,さらに複製されたリクエストは proxy_passMirror に転送される.ポイントは複製用の /mirror ロケーションを内部用にしておくことと,転送先の URL に $request_uri を追加する点で,ドキュメントにも記載されている.なお,デフォルトで mirror_request_bodyon になっているため body も転送される.

location / {
    mirror /mirror;
    proxy_pass http://backend;
}

location /mirror {
    internal;
    proxy_pass http://mirror$request_uri;
}

動作確認

さっそく //ping にリクエストを投げる.

$ curl http://localhost
Hello, backend!

$ curl http://localhost/ping
Pong, backend!

すると,Docker Compose のログに以下のようなアクセスログが出力される.今回 WEBrick のアクセスログは抑止しているため,nginx と Sinatra のアクセスログとなる.そして,期待通りに BackendMirror に転送されていることが確認できる.簡単にリクエストを複製できる「ngx_http_mirror_module」は便利だ!

mirror_1    | 172.22.0.4 - - [22/Jul/2019:13:17:44 +0000] "GET / HTTP/1.1" 200 14 0.0073
backend_1   | 172.22.0.4 - - [22/Jul/2019:13:17:44 +0000] "GET / HTTP/1.1" 200 15 0.0074
frontend_1  | 172.22.0.1 - - [22/Jul/2019:13:17:44 +0000] "GET / HTTP/1.1" 200 14 "-" "curl/7.54.0" "-"

backend_1   | 172.22.0.4 - - [22/Jul/2019:13:18:20 +0000] "GET /ping HTTP/1.1" 200 14 0.0071
mirror_1    | 172.22.0.4 - - [22/Jul/2019:13:18:20 +0000] "GET /ping HTTP/1.1" 200 14 0.0118
frontend_1  | 172.22.0.1 - - [22/Jul/2019:13:18:20 +0000] "GET /ping HTTP/1.1" 200 14 "-" "curl/7.54.0" "-"

nginx : resolver 127.0.0.11

検証中にうまくリクエストを Mirror に転送できず,nginx から出力される no resolver defined to resolve mirror というエラーに悩まされていた.実際に出力されていたエラーは以下となる.

frontend_1  | 2019/07/22 13:28:06 [error] 6#6: *3 no resolver defined to resolve mirror, client: 172.22.0.1, server: localhost, request: "GET / HTTP/1.1", subrequest: "/mirror", host: "localhost"
frontend_1  | 2019/07/22 13:28:11 [error] 6#6: *1 no resolver defined to resolve mirror, client: 172.22.0.1, server: localhost, request: "GET /ping HTTP/1.1", subrequest: "/mirror", host: "localhost"

調査したところ,Docker の Embedded DNS に関係していることに気付き,今回は nginx.confresolver を指定して解決をした.Embedded DNS の詳細は以下のドキュメントに載っている.正直結構ハマった!

server {
    (中略)
    resolver 127.0.0.11;
    (中略)
}

docs.docker.com

まとめ

nginx でリクエストを複製できるモジュール「ngx_http_mirror_module」を試した.簡易的な「Shadow Proxy」を構築することができる.今回は Docker Compose を使って検証環境を構築して,疎通確認をしたけど,トラフィックを増やした場合に複製による負荷がどの程度なのか,追加で検証してみたいと思う.