リバースプロキシのテストを書く方法
6月30日です。今日で6月も終わりです。でっていう。
Nginx や OpenResty 、 Varnish などをリバースプロキシとして使用していることが多いと思いますが、 リバースプロキシ単体で設定が正しいかどうかをテストするのって難しそうなイメージがありませんか?
ここではリバースプロキシの後ろのバックエンドをモック化して、
リバースプロキシ単体のテストを書く方法を紹介します。
本当はリバースプロキシのIP制限のテストのために、ソケット通信でネットワークインターフェースを指定して
接続元のクライアントIPを自由に書き換える方法を紹介しようと思いましたが、
Dockerコンテナ内でテストを動かさないといけなかったのでやめました。でっていう。
目的は、 Nginx の conf が意図したとおりの動作をするかどうかのテストをすることです。
バックエンドのモック化のために MockServer を使用します。
Java の API を使ってランタイムで MockServer を起動することもできますが、 Docker image が提供されているので いつも通り Docker Compose で環境を準備します。
reverseproxy-mockserver/docker-compose.yml at master · innossh/reverseproxy-mockserver · GitHub
version: '3' services: reverseproxy: image: nginx:1.12.0-alpine ports: - "80:80" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf depends_on: - backend1 - backend2 backend1: image: jamesdbloom/mockserver ports: - "1081:11111" entrypoint: /opt/mockserver/run_mockserver.sh -serverPort 11111 backend2: image: jamesdbloom/mockserver ports: - "1082:22222" entrypoint: /opt/mockserver/run_mockserver.sh -serverPort 22222
backend1 というサービスが backend1コンテナの11111番ポートで、 backend2 というサービスが backend2コンテナの22222番ポートで動いていると想定します。
Nginx のコンテナを用意し、 テストしたい Nginx の conf に各 backend へリクエストを流すような設定を入れて動かします。
Nginx の設定内容はこちら。
reverseproxy-mockserver/nginx.conf at master · innossh/reverseproxy-mockserver · GitHub
... http { ... set_real_ip_from 172.29.0.0/16; real_ip_header X-Forwarded-For; real_ip_recursive on; geo $denied { default 1; 127.0.0.1 0; 172.29.0.0/16 0; 1.2.3.4 0; } ... server { listen 80; server_name localhost; ... location /backend1 { rewrite ^/backend1(.*)$ $1 break; proxy_pass http://backend1:11111; } location /backend2 { if ($denied) { return 403 "Forbidden"; } rewrite ^/backend2(.*)$ $1 break; proxy_pass http://backend2:22222; } } }
だいぶ適当な設定ですね。でっていう。
/backend1
へのリクエストは http://backend1:11111
に、 /backend2
へのリクエストは http://backend2:22222
に流れるはずですね。
それでは docker-compose up -d
した後にJavaのテストを実行してみましょう。
Javaのテストの内容はこちらです。
reverseproxy-mockserver/AccessDeniedTest.java at master · innossh/reverseproxy-mockserver · GitHub
@Test @Parameters({ "1.2.3.4,204", "4.3.2.1,403" }) public void testAccessDenied(String clientIP, int expectedStatus) throws IOException { clients.getBackend2() .when( request() .withMethod("GET") .withPath("/bar") ) .respond( response() .withStatusCode(204) ); OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .header("X-Forwarded-For", clientIP) .url("http://localhost/backend2/bar") .get() .build(); Response response = client.newCall(request).execute(); assertEquals(expectedStatus, response.code()); clients.getBackend2().verify( request() .withMethod("GET") .withPath("/bar"), VerificationTimes.exactly(expectedStatus == 204 ? 1 : 0) ); }
上記のコードは
- backend2 に対して期待する動作を設定する。 # 裏で backend2 の MockServer にリクエストを投げて expectation を登録している
- OKHttpClient で実際に Nginx に対してHTTPリクエストを実行する。 # URL は
http://localhost/backend2/bar
- レスポンスコードの比較と backend2 の MockServer が正しく呼び出されたか検証する。
という感じの流れです。
2つのクライアントIPをヘッダに設定して 1.2.3.4
は 204
に、 4.3.2.1
は 403
になることをテストしています。
実行はもちろん
$ ./gradlew test :compileJava UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :compileTestJava :processTestResources UP-TO-DATE :testClasses :test innossh.reverseproxy.mockserver.AccessDeniedTest > testAccessDenied(1.2.3.4,204) [0] STARTED innossh.reverseproxy.mockserver.AccessDeniedTest > testAccessDenied(1.2.3.4,204) [0] PASSED innossh.reverseproxy.mockserver.AccessDeniedTest > testAccessDenied(4.3.2.1,403) [1] STARTED innossh.reverseproxy.mockserver.AccessDeniedTest > testAccessDenied(4.3.2.1,403) [1] PASSED innossh.reverseproxy.mockserver.AccessDeniedTest > testNormalToBackend1 STARTED innossh.reverseproxy.mockserver.AccessDeniedTest > testNormalToBackend1 PASSED innossh.reverseproxy.mockserver.AccessDeniedTest > testNormalToBackend2 STARTED innossh.reverseproxy.mockserver.AccessDeniedTest > testNormalToBackend2 PASSED BUILD SUCCESSFUL Total time: 8.079 secs
成功しますね。
これで基本形ができましたので、あとはひたすらいろんな path へのリクエストやいろんな header のあるなしなどをテストしていけば、
リバースプロキシ単体の動きのテストができることになります。
リバースプロキシのテストでお困りの人は試してみてはいかがでしょうか。