はじめに
メンテナンスモードの実装方法を調査しましたので、記事にまとめます。
メンテナンスモードとは、文字通り、サービスのメンテナンス時に一時的にサービスを止めたい時に使用するモードです。
いくつか前提条件があるのでそれを紹介します。
前提条件1: 動作検証できるようにする
メンテナンスモード中に、特定のユーザーのみ通常通りWebアプリやモバイルアプリを利用できるようにします。
こうすることで、メンテナンス作業後、一般公開することなく、通常通り動作するか検証が可能です。
前提条件2: 構成
構成は以下になります。
ログイン
Cognito
API(モバイルアプリとWebアプリから利用)
CloudFront -> WAF -> ALB -> ECS
Webページへのアクセス
CloudFront -> WAF -> ALB -> S3
CloudFrontで切替
CloudFront Functionsという機能で、メンテナンス時にメンテナンスのレスポンスを返すように設定します。
設定手順
- S3バケットへメンテナンス用のHTMLとJSONを配置
- 後で、表示する内容やメッセージを変えることが可能なようにS3に配置します
- CloudFront Functionsにリクエストをハンドリングする関数を登録
- APIリクエストの場合は、S3のJSONを返します
- Web画面のリクエストの場合は、S3のHTMLを返します
- 実装はJavaScriptになります
- CloudFront Functionsにレスポンスをハンドリングする関数を登録
- メンテナンス用のパスの場合、レスポンスのHTTPステータスを503に変更します
実装例
AIに書いてもらっただけで、検証はできてないのですが、イメージがわかりやすいかと思うので、実装例を添付します
function handler(event) {
var request = event.request;
var clientIp = event.viewer.ip; // アクセス元のIPを取得
// ★メンテナンス中も許可したい開発者や社内のIPアドレスリスト
var allowedIps = [
'123.45.67.89', // 例:本社の固定IP
'210.98.76.54' // 例:開発メンバーのIP
];
// 1. 許可されたIPからのアクセスの場合は、何もせず通常通りALBへ流す
if (allowedIps.indexOf(clientIp) !== -1) {
return request;
}
// 2. 一般ユーザーの場合、パスをS3のメンテナンスファイル用に強制書き換え
if (request.uri.startsWith('/api/')) {
request.uri = '/maintenance/error.json'; // API通信ならJSONへ
} else {
request.uri = '/maintenance/index.html'; // 画面アクセスならHTMLへ
}
return request;
}
function handler(event) {
var request = event.request;
var response = event.response;
// メンテナンス用のパスから返ってきたレスポンスの場合、ステータスを503に書き換える
if (request.uri === '/maintenance/index.html'
|| request.uri === '/maintenance/error.json') {
response.statusCode = 503;
response.statusDescription = 'Service Unavailable';
}
return response;
}
メンテナンス切替手順
- (変更があれば)S3バケットに配置したメンテナンス用のHTMLとJSONを更新します
- (変更があれば)リクエストハンドリング用に登録した関数の許可するIPアドレスを更新します
- CloudFrontで対象のディストリビューションを選択し、パスパターンがデフォルト (*)のビヘイビアに作成した二つの関数を紐付けます
- リクエストをハンドリングする関数はViewer requestに紐付けます
- レスポンスをハンドリングする関数はViewer responseに紐付けます
- 変更を保存し、数十秒〜1分程度でメンテナンスモードになります
- メンテナンスが終了したら、関数の紐付けを削除し、変更を保存します
コスト
コストはほぼかかりません。
メンテナンス中に、100万回リクエストが発生したと想定すると下記になります。
呼び出しにかかるコスト
100万回で0.1USD x 1リクエストあたり2回実行 -> 0.2USD
データ転送にかかるコスト
- 100万回のリクエストがあり、1回あたり10KBのデータを転送すると仮定
- 合計データ量は 約10GB(= 10KB × 100万回)
- データ転送料は 1GBあたり0.114USD
- 約1.14USDのコスト
二つ合わせて、約1.34USD
ALBで切替
ALBのリスナールールの優先順位を利用し、メンテナンス時にメンテナンスのレスポンスを返すように設定します。
設定手順
- メンテナンス用のHTMLとJSONを準備する
- ファイルのサイズは1KBという制限があります
メンテナンス切替手順
- ALBのルールにメンテナンス時のAPI用ルールを追加します
- パスの判定でAPIの場合、HTTPステータスコード503でメンテナンス用のJSONを返します
- 動作確認用のIPアドレスも判定に含め、一致する場合は適用されないようにします
- 優先度は一番上にしておきます
- ALBのルールにメンテナンス時のWeb画面用のルールを追加します
- HTTPステータスコード503でメンテナンス用のHTMLを返します
- 動作確認用のIPアドレスも判定に含め、一致する場合は適用されないようにします
- 優先度は二番目にしておきます
- 上記の設定後、メンテナンスモードとなります
- メンテナンスが終了したら、二つのルールを削除し、メンテナンスモードを終了します
コスト
ALBはすでに導入済みでしたので追加費用はありませんでした
WAFで切替
CloudFrontに紐づいているWAFのカスタムレスポンス機能を利用し、メンテナンス時にメンテナンスのレスポンスを返すようにルールを設定します。
設定手順
- WAFのカスタムレスポンスにメンテナンス用のHTMLとJSONを登録します
- メンテナンス中に動作確認を行えるように、許可するIPリストを登録します
- WAFにメンテナンス時のルールを追加します
- 通常時は無効となるように「アクション = Count」で設定します
- APIアクセスの場合はJSON、それ以外はHTMLを返します
- 2で登録したIPリストは除外するように設定します
メンテナンス切替手順
- (変更があれば)WAFのカスタムレスポンスに登録しているメンテナンス用のHTMLとJSONを更新します
- (変更があれば)動作確認を許可するIPリストを更新します
- メンテナンス時のルールのアクションをCountからBlockに変更し、カスタムレスポンスを登録します
- メンテナンスが終了したら、Countに戻します
コスト
- 月額数百円のコストがかかります
- 一つのWAFを有効にするのに 5USDかかります
- また1ルール追加で1USDかかります
- リクエストによる従量課金は100 万リクエストで0.6USDです
- 運用の手順は増えますがメンテナンス時のみルールを追加し、終わったら削除するといった運用で安く抑えることは可能です
設計判断
調査の結果、動作検証するIPアドレスの管理や切替手順の単純さからWAFでの切替方法が一番良いかなと思いました。コストはかかりますが、メンテナンスが必要な時だけWAFを有効にし、ルールを追加する方法ならコストは抑えられます。
また、CognitoはWAFでの切替方法以外で対応できなかったので、統一性の観点からもWAF一択となりました。
CognitoでWAFを使用しメンテナンスモードを実装する注意点
Cognitoでメンテナンスモード対応をする時、想定通りにいかなかった点がありました。
CognitoでWAFを有効にし、メンテナンス時はメンテナンス用のHTMLを返すようにしていたのですが、以下の場合はうまくメンテナンス用のHTMLを表示できませんでした。
- Cognitoのログイン画面を表示する
- メンテナンスモードにWAFの設定を切り替える
- ログイン操作をする
- 期待値としては、メンテナンス用のHTMLを表示することなのですが、表示できずエラー画面になります
回避方法は、メンテナンス用のHTMLを返すのではなく、レスポンスコードに302を指定し、カスタムヘッダでKeyにLocation、Valueにリダイレクト先のURL(メンテナンス用のWebページ)を設定する方法となります。
さいごに
最終的には、CognitoでWAFを使用しメンテナンスモードを実装する注意点で書いたように、CognitoではHTMLを返すのではなく、リダイレクトが必要となったので、以下のようになりました。
ログイン
WAFでメンテナンス用のWebページにリダイレクト
API(モバイルアプリとWebアプリから利用)
WAFでメンテナンス用のエラーレスポンスをJSONで返す
Webページへのアクセス
WAFでメンテナンス用のWebページにリダイレクト