2017年3月7日火曜日

AWSを使ってソーリーサーバを構築しました。割と便利だと思う…。

とてもお久しぶりです。
恵比寿のITベンチャーでサービス運営やっています。
仲間募集中です。ご興味ある方はTwitterなどでご連絡ください。

今回はAWSを使って、わりと便利なソーリーサーバを構築しましたというお話です。
構成は下の図のようになっています。



通信フローとしては、まずエンドユーザがELBにアクセスします。 このELBは既存のWebサービスやWebサイトをバランシングしていたものです。 諸事情によりメンテナンスしたり、ドメインを畳むことになったという話を想定しています。
そのELBに接続されていたWebサーバやアプリケーションサーバの代わりにソーリーサーバを接続します。 するとエンドユーザはソーリーサーバにアクセスしてきますよね。
ソーリーサーバはリクエストを受けたら、各ドメインに対応させたいCloudFrontへリクエストをパスします。 CloudFrontはS3のコンテンツを返すのですが、このS3に静的なソーリーページを格納しておきます。 それにより、ドメインにアクセスしてきたエンドユーザにソーリーページを見せることができます。
ポイントはソーリーサーバとCloudFrontですがやってることは簡単です。 まず、ソーリーサーバはただのNginxです。 proxy_pass を使って、特定ドメインに来たリクエストを対応するCloudFrontのドメインへパスします。 その設定は、/etc/nginx/conf.d/virtual.conf に書きました。
server {
        listen 80;
        server_name example.com;
        if ($http_x_forwarded_proto != https) {
                return 301 https://$host$request_uri;
        }
        location / {
                proxy_pass http://domain-a.sorry.example.com/;
        }
}
上記の server ディレクティブのかたまりを、ソーリーページの数だけ用意します。 1ドメイン1種類のページでよければ、3ドメインなら3つのディレクティブになります。
if文のおかげで、自動でHTTPSになります。
しかしここで一つ問題があります。 ELBごとにヘルスチェックエンドポイントが違うのです。 なのでそのままではそもそもNginxがヘルスチェックに合格せず、ELBがNginxにリクエストを渡してくれません。
それはNginxの素敵機能 empty_gif を使って解決します。 今回は /etc/nginx/nginx.conf に以下のように書きました。
http {
    (略)
    server { 
        (略)
        location / {
                 empty_gif;
                break;
        }
    (略)
    } 
    (略)
}
これにより、どんなパスへのアクセスに対しても 1ピクセルのgifイメージを生成して返します。 そのおかげで、ELBのどんなヘルスチェックパスも合格し、めでたく InService になることができます。
残りは CloudFront ですね。
CloudFrontはソーリーページごとにインスタンスを用意する必要があります。
理由はカスタムエラーページを利用したいからです。
CloudFrontのカスタムエラーページでは、特定のHTTPステータースコードの場合、任意のステータスコードに変更することができ、さらに任意のコンテンツを表示させることができます。
これを使って、全ての403アクセスを404へ変更して、ついでにソーリーページのhtmlを表示させちゃうことができます。
なんで403にするかというと、CloudFront + S3 でのコンテンツ配信方式で、ページが存在しない場合、CloudFront側が 403 Access Denied を返すからです。
これを404+ソーリーページにすることで、どんな存在しないURLにアクセスが来ても、自分が用意した404用のhtmlが表示されることになります。
ただし、「正しいソーリーページのパス」にアクセスされると、ステータスコードは200になってしまいます。
よほどの物好きなエンドユーザ意外はそんなことしないので、気にしないことにしました。
Nginxのコンフィグの書き方でもうちょっとCloudFrontのインスタンスを減らせる気もしますが、料金体系的にデータ転送量とリクエスト数のみで、インスタンス数による固定料金はないので、管理は面倒くさいですがこれでいいかなという感じです。

あ、あと、ACMを使って無料証明書を取得してCloudFrontに登録しています。
そのために、 sorry.example.com のような、ソーリーページ専用サブドメインを用意して、 それのワイルドカード証明書をACMから発行させてもらっています。

ところで何が便利なの?
まず、ステータスコードを任意に指定できます。404とか、500とか。それから、ソーリーページにhttpsが使えます。労力の割には些細なリターンのような気もしますが…。
NginxのコンフィグはGitで管理してAnsibleなどで配布できます。
1台に全部のソーリーページについて書かなきゃいけないですが、書いてしまえば、1台をマスターサーバにして楽にスケールアウトできます。
マスターサーバのコンフィグに全てのソーリーページの設定が書かれてあるので、ドメインに対して透過的なソーリーサーバが出来上がります。
どのELBにどのサーバを突っ込んでもOKということです。