インフラ部の荒井(@ryot_a_rai)です。この記事ではインフラの振る舞いテストのツールであるInfratasterを使ってリバースプロキシの設定のテストをしてみたいと思います。
Infratasterとは
Infratasterはインフラの振る舞いをテストするフレームワークで、RSpecのテストヘルパとして機能します。例えば、
- 特定のヘッダ付きのHTTPリクエストを送信した時にあるレスポンスヘッダが返ってくることをテストする
- Capybaraを使って実際のWebブラウザ上での挙動をテストする
- MySQLのSHOW VARIABLESの結果をテストする
といったことが可能になります。
細かい概要についてはこちらのスライドやREADMEをご覧ください。
Serverspecとの違い
インフラのテストといえばServerspecが有名かと思いますが、InfratasterはServerspecとはテストする領域が異なっています。Serverspecはサーバの内部のミドルウェアやファイルをテストしますが、Infratasterはサーバの外部からテストをします。つまり、Infratasterは中で動いているミドルウェアが何かは関係なく、外から見てどういった振る舞いをするかを検証するためのものです。
Infratasterを使ってnginxの設定のテストをする
上で紹介したInfratasterと仮想マシン(Vagrant, VirtualBox)を使ってnginxの設定が意図したとおり行われているかをテストしてみたいと思います。
本記事のコードは https://github.com/ryotarai/proxy-configtest-sample にあります。
仮想マシンを用意する
Vagrantをつかってテスト用の仮想マシンを用意します。Vagrantで仮想マシンを起動、セットアップすることで再現性のあるテスト環境を用意することが可能になります。
VirtualBox、Vagrantをインストール後、プロキシ用、バックエンド用に1つずつVagrant VMを立てますが、今回はこちらのVagrantfileを使います。このVagrantfileでは、proxy VMに/etc/hostsを置いて、バックエンドへのアクセスをapp VMに向けてテストしやすくしています。
Vagrantfileを置いたあと、vagrant up
を実行しておきます。
Infratasterをインストールする
Infrataster, RSpecをGemとしてインストールします。
# Gemfile source 'https://rubygems.org' gem 'infrataster' gem 'rspec-json_matcher'
rspecコマンドを使って、テストに必要なファイルのひな形を生成します。
$ bundle exec rspec --init
Infrataster(とrspec-json_matcher)を使うために、生成されたspec/spec_helper.rbの先頭に以下を追記します。
require 'rspec/json_matcher' require 'infrataster/rspec' RSpec.configuration.include RSpec::JsonMatcher Infrataster::Server.define( :proxy, # name '192.168.0.0/16', # proxy VM's IP address vagrant: true # for vagrant VM )
200が返ってくることをテストする
準備ができたので、プロキシに対するテストを書いてみます。手始めに、http://foo.example.com
にアクセスした時に200が返ってくることをテストします。
# spec/foo_spec.rb require 'spec_helper' describe server(:proxy) do describe http('http://foo.example.com') do it 'returns 200' do expect(response.status).to eq(200) end end end
まだnginxの設定を書いていないので、テストは失敗します。
$ bundle exec rspec 1) server 'proxy' http 'http://foo.example.com' with {:params=>{}, :method=>:get, :headers=>{}} returns 200 Failure/Error: expect(response.status).to eq(200) Faraday::ConnectionFailed: Connection refused - connect(2) for "192.168.33.10" port 80
テスト対象のnginxの設定を書きます。
# nginx/foo.conf server { listen 80; server_name foo.example.com; location / { proxy_pass http://app-001/; } }
このままだとproxy VM内でapp-001が名前解決できないので、/etc/hostsでapp VMに向けます。
# Vagrantfile -hosts = %w!! +hosts = %w!app-001!
再度テストを走らせると、通ることが確認できると思います。
$ vagrant provision $ bundle exec rspec server 'proxy' http 'http://foo.example.com' with {:params=>{}, :method=>:get, :headers=>{}} returns 200 Finished in 0.01166 seconds 1 example, 0 failures
意図したホストにプロキシされているかをテストする
つぎに、意図したホストにプロキシされているかをテストしてみます。app VMのnginxでX-MOCK-HOST
ヘッダをつけるようにしたので、これを使います。モック用のアプリはリクエストヘッダをそのままJSONにして返すようになっているので、レスポンスをテストすることでプロキシ→バックエンドのリクエストヘッダをテストすることができます。
--- a/spec/foo_spec.rb +++ b/spec/foo_spec.rb @@ -5,6 +5,12 @@ describe server(:proxy) do it 'returns 200' do expect(response.status).to eq(200) end + + it 'proxies to app-001' do + expect(response.body).to be_json_including({ + 'X_MOCK_HOST' => 'app-001', + }) + end end end
$ bundle exec rspec server 'proxy' http 'http://foo.example.com' with {:params=>{}, :method=>:get, :headers=>{}} returns 200 proxies to app-001 Finished in 0.01675 seconds 2 examples, 0 failures
無事、プロキシ先のホストの確認ができました。
レスポンスヘッダをテストする
プロキシでCache-Controlヘッダを返すようにしてみます。先にテストを書いて、失敗させてから設定を書きます。
--- a/spec/foo_spec.rb +++ b/spec/foo_spec.rb @@ -12,5 +12,11 @@ describe server(:proxy) do }) end end + + describe http('http://foo.example.com/isucon') do + it 'returns Cache-Control header' do + expect(response.headers['Cache-Control']).to eq('max-age=86400') + end + end end
$ bundle exec rspec 1) server 'proxy' http 'http://foo.example.com/isucon' with {:params=>{}, :method=>:get, :headers=>{}} returns Cache-Control header Failure/Error: expect(response.headers['Cache-Control']).to eq('max-age=86400') expected: "max-age=86400" got: nil (compared using ==) # ./spec/foo_spec.rb:18:in `block (3 levels) in <top (required)>'
nginxの設定を追加します。
--- a/nginx/foo.conf +++ b/nginx/foo.conf @@ -1,6 +1,12 @@ server { listen 80; server_name foo.example.com; + + location /isucon { + expires 24h; + proxy_pass http://app-001/; + } + location / { proxy_pass http://app-001/; }
再度テストを実行すると、適切に設定されていることが確認できます。
$ vagrant provision $ bundle exec rspec server 'proxy' http 'http://foo.example.com' with {:params=>{}, :method=>:get, :headers=>{}} returns 200 proxies to app-001 http 'http://foo.example.com/isucon' with {:params=>{}, :method=>:get, :headers=>{}} returns Cache-Control header
まとめ
Infratasterを使ってリバースプロキシの設定をテストする方法を紹介しました。本記事ではnginxを例に出しましたが、Apacheやその他のソフトウェアでも同様にテストすることができます。
プロキシの設定は徐々に複雑化していき、挙動が見えなくなっていきがちです。テストを書いておけば、意図しない挙動の変更を防げたり、テスト自体をドキュメントとして使うこともできます。この記事がInfratasterを使ったテストの参考になれば幸いです。