メンテナンス画面の表示方法いろいろ

コンテナの話(AWSコンテナ系アーキテクチャの選択肢を最適化する)をした時にメンテナンス画面の表示についても軽く触れました。

改めて整理すると他にもいろいろあるということで、上から順に超ザックリと並べていきたいと思います。一応 AWS でを想定していますが、一般的な方法論でもあるので、どこだろうと何かしらの足しにはなるかもです。



条件

どのようなメンテナンス状態にしたいかによりますが、満たすべき条件はおそらくこのようなものがありますよ、ということで整理します。

  • 1回の変更操作で、一括したメンテインを保証すること
  • 管理者はメンテにならず通常アクセスする手段があること
  • メンテ機能の仕込みによって悪影響がないこと
  • 希望するメンテ用レスポンス内容を実現可能であること
    • 静的 or 動的
    • Status Code 503
    • Content-Type
    • レスポンス・サイズ

例えば DNS のレコード値を変更して切り替えるなんてのは、クライアントが TTL を守る保証などないので全然無理ですし、

昔ながらのWEBサーバー全台に並列で変更を加えてメンテ状態にするってのも、完全一括な切り替えではないし、Autoscaling による Increase を確実に止めないといけない、といった弱点があります。コンテナだと、変更を加えないというお作法にも反しますしね。

ちゃんと考えると、より適して楽な手法があるかもということで、課題としても実に程よいネタになるのではないでしょうか。


WAF

最初に通過するのが WAF ということで、設定したルールによってメンテナンス画面を返したり、通過させたりするというものです。


よくある事例では送信元IPアドレスをルールに用いて、管理者のアドレスの場合は通過させて、それ以外は全てにメンテナンスを返しています。だいたいの環境は会社のアドレスを現地やVPN経由で利用できるでしょうし、決められた串となるリソースを経由することでも、管理者としての運用が可能です。

他に使えるルールの種類としては、リクエストヘッダーも可能なので、任意の Key / Value を定めることでパスワード的な利用をすることもできるため、IPアドレスに頼れない条件下では有効となります。

WAFは条件の存在に若干の費用がかかること、レスポンスサイズが 4KB まで、複雑なレスポンス構造には適していない、といったことはありますが、最前線でビシッと止めるという意味でも無難な選択肢の1つとなります。


CloudFront

これは CDN としてなので少し色合いが違いますが、存在として知っておいて損はないでしょう。


CloudFront Functions や Lambda@Edge を使って、CloudFront イベントに対してコードを実行することで、メンテナンスのレスポンスを生成します。

動的コンテンツが中心なサービスだと、こちらに気を使うことは少なそうですが、CDN に寄せる構成をしていれば役に立つことがあるかもしれません。


ALB

ALB 単体でレスポンスを返す方法として、固定レスポンスと ListenerRule の合せ技があります。


例えば、固定レスポンスを含めた Rule の優先度をこのように並べておき、
  • 101 : admin.example.com → 管理者用TargetGroup
  • 201 : app.example.com → 一般ユーザー用TargetGroup
  • 300 : 固定レスポンス status 503

メンテインする時は、優先度 300 を 200 に変えることで、10秒以内に表示を切り替えられます。メンテアウトは 200 を 300 に戻すだけです。

こういう順番にしておけば1つの ALB でも、管理者用ページは見れる状態で、一般ユーザーの処理をせき止めることができるので、あとは管理者用処理にささやかなアクセス制御でも入れておくとよいでしょう。

非常に簡単な仕組みですが、全リクエストに対して1つの内容になることや、レスポンスサイズが 1KB までという制約があるので、条件に合えばということにはなります。


Lambda

ALB ListenerRule を用いる方法は同じですが、メンテ用レスポンスの生成を Lambda に投げることで自由度を得ることが可能です。


イベントデータを使ってリクエストパスごとに Content-Type を動的に変えるといった、たいていのことはできるので、せいぜいコードの管理とレスポンス内容の編集管理を整頓できれば、便利で強い選択肢になるでしょう。


WEB Server

ここでは WEBサーバー Nginx での例としますが、複数サーバーが存在する箇所での対応となるので、注意深く考えていく必要があります。なぜなら、複数台から共通する何か、が必要になるからです。


特定のローカルファイルが存在した場合に 503 を返す方法です。昔は、SSH などで全台に一斉配布して実現していたかもしれませんが、先に書いた通りの条件を満たさないので推奨しません。

もし、このファイル置き場を EFS にしたらどうでしょうか。全台がマウント済みなら、1つのファイルをどこからか置いてあげれば、一括してメンテ状態に入ることは可能でしょう。しかし、それも推奨はできません。

なぜなら、これだと常時 EFS にアクセスすることになるからです。メンテナンスは数週間~数ヶ月毎に1回あるとして、1回あたり数時間だとすると、そのメンテナンス外の全ての時間において、本筋の機能に無関係な処理が入ることになり、レスポンス速度に影響したり、障害要因をいたずらに増やすことになります。

また、WEBサーバーの設定は通常、複雑な処理に合わない構文なので、ちょっと分岐するにも汚いコードになる上に、実現自体が不可能だったりもするので、ここで頑張ろうとするのは避けたほうがよいでしょう。


APP Server

メインのアプリケーション・コードをそのまま用いて、メンテナンスのレスポンスと切り替える方法を考えます。

ECS の場合、メインと別のメンテ用 Service を作成し、メインと同じタスク定義に1つだけ環境変数 MAINTENANCE = 1 を加えたものを用意します。アプリケーションは、環境変数がある場合に、メンテ用のリクエストパスの扱いにすることで、動的に任意の内容を返すことが可能です。

切替方法は ALB 固定レスポンスと同じで、ルールの優先度を切り替えることでメンテ用Serviceにのみ行くようにします。通常時はタスクは不要なので、メンテ前にのみ起動することで節約にもなります。

考えられる弱みとしては、メンテ用処理でもフレームワークの都合でDB接続などが発生している場合、メンテ中なのにDB再起動をできなくなる、といったものがあります。なんでもできるという意味では Lambda とそこまで差はないものの、システムの複雑な部分にまで入り込んでしまうことから、仕組みにシンプルさを欠く可能性があります。


また、なんでもできるといっても、WEBサーバーの話と同様に、何かしらのデータを毎回チェックすることでメンテ状態か否かを判定するのは推奨できません。

S3ファイルの存在や、KVS のチェックなど、共有データならなんでも実現可能ですが、全リクエストに入れると確実に無駄とリスクが生じるので、そういう設計にしようとしていたり、そうなってしまっている場合は、おそらく改善の余地があると判断してよいでしょう。


おわりに

システムにメンテナンスはつきもので、メンテナンス時間は正常稼働時間の1%以下だとしても、ユーザーが確実に目にする状態でもあります。

メンテナンスやエラー画面などは、サービスによって、ワケのわからないシステムメッセージだったり、マスコットキャラだったりと色々で、それに遭遇することによるユーザーの体感や心象には結構な差があると考えたほうがよいでしょう。

また、メンテナンス自体はシステム運用者にとって、開始から終了までそれなりの緊張と集中が必要な作業であるため、メンテナンス状態の管理は確実かつ簡素にしてストレスが少ないようにするべきです。

システムの本筋でパツパツだと最初は適当になりがちな箇所ですが、ユーザーとシステム運用者のことを踏まえれば、どちらにも余裕を感じてもらえる仕組みと表示であることが、思っているよりもサービスとしての品質に寄与するのではないかと思います:-)