CloudFrontで配信しているサービスで一定時間の間、開発者以外はメンテナンスをCloudFront Functionを使って導入する方法を紹介します。
CloudFront Functionsでメンテナンスを導入する
CloudFrontを導入しているサービスのアップデートを行う時に一般ユーザはメンテナンス画面を表示、開発者は通常画面が見られるようにしたいです。
- 設定の変更が必要な対象サービスはできるだけ少なくしたい(CloudFrontだけとか)
- 変更範囲はできるだけ狭く
- 現状の設定への影響はできるだけ少なく
- できるだけ簡単・低コスト
様々な方法がありますが、今回はCloudFront Functionsで実現します。
CloudFront Functionsを用いる理由はCloudFront側に導入可能で、現状のCloudFrontの設定変更せずに導入できるからです。
ユーザと開発者をIPで区別してメンテページ
ユーザと開発者を見分ける方法も複数ありますが、今回はIPで区別します。
開発者のIPを許可IPとして通常表示、それ以外の一般ユーザにはメンテページを表示するようにリダイレクトします。
function handler(event) {
var request = event.request;
var clientIP = event.viewer.ip;
var uri = request.uri;
var host = request.headers.host.value;
var redirecturi = `https://${host}/mente.html`
// メンテページを表示しないIPを↓に記述
var allowIP = [
'192.0.2.11',
'192.0.2.30'
];
// allowIPに含まれるipならtrueでオリジンにそのままリクエストを渡す
// falseなら302でmenteページにリダイレクトする。ループ防止のためmente.htmlではない条件を入れる
if (allowIP.includes(clientIP)) {
return request;
} else if (!(uri == 'mente.html' || uri == '/mente.html')) {
var response = {
statusCode: 302,
statusDescription: 'Found',
headers: {
'location': { value: redirecturi },
'cloudfront-functions': { value: 'by CFF' }
}
};
return response;
} else {
return request;
}
}
関数の関連付けはCloudFront Functionsの関数の項目の部分から行うと複数のビヘイビアがある時に指定するのが楽です。
ただし、この方法ではmente.htmlがレスポンスステータスコード200で返ってきます。気にする場合はどうしたらよいでしょうか。
S3に存在しないページにリダイレクトしてカスタムエラーレスポンスを利用しよう!
メンテンナンスページは503(一時的に利用不可)で返すのが良いとされています(SEO的に問題)。
しかし、S3ではwebサーバのようにレスポンスコードを設定できません(メンテページだけオリジンをEC2に設定できれば別)。
そこで、少々無理やりな方法ですがCloudFrontの機能であるカスタムエラーレスポンスを利用します。
カスタムエラーレスポンスでは、オリジンがエラーを返した時のCloudFrontの挙動を設定できます。
- オリジンが返したエラーコードを任意の時間キャッシュする
- オリジンの代わりにCloudFront側で用意したレスポンスを返す(エラーコードを変更できる)
この2番目の機能を利用します。
S3では当然、存在するページへのアクセスは200で返します。
そして、存在しないページに対しては404ではなく、403で返します。このエラーレスポンスをcloudfrontのカスタムエラーレスポンスで処理します。
具体的な流れは下記になります。
- 開発者以外のIPを持つユーザのアクセス
- CloudFront FunctionsでオリジンS3の存在しない/mentenance.htmlへリダイレクト
- オリジンS3は403を返す
- CloudFrontのカスタムエラーレスポンスで403を受け取ったので/mente.html (s3に存在)を503で返す。
CloudFrontのカスタムエラーレスポンスは以下のようになります。
CloudFront Functionsの関数を作成
開発者の許可したIP以外は、存在しないページmentenance.htmlにリダイレクトします。
function handler(event) {
var request = event.request;
var clientIP = event.viewer.ip;
var uri = request.uri;
var host = request.headers.host.value;
var redirecturi = `https://${host}/mentenance.html`
// メンテページを表示しないIPを↓に記述
var allowIP = [
'192.0.2.11',
'192.0.2.30'
];
var excludeUri = [
'mente.html',
'/mente.html',
'mentenance.html',
'/mentenance.html'
];
// allowIPに含まれるipならtrueでオリジンにそのままリクエストを渡す、falseなら302で存在しないmentenanceページにリダイレクト
if (allowIP.includes(clientIP)) {
return request;
} else if (!(excludeUri.includes(uri))) {
var response = {
statusCode: 302,
statusDescription: 'Found',
headers: {
'location': { value: redirecturi },
'cloudfront-functions': { value: 'by CFF' }
}
};
return response;
} else {
return request;
}
}
この関数をディストリビューションのビューワリクエストに関連付けしてください
許可IP以外でexample.cloudfront.netにアクセスするとexample.cloudfront.net/mentenacne.htmlに飛ばされてメンテページが表示されます。(URIはmentenacne.htmlのまま実際はs3オリジンのmente.htmlが表示(503))
# curl -iL https://example.cloudfront.net
HTTP/2 302
server: CloudFront
location: https://example.cloudfront.net/mentenance.html
HTTP/2 503
content-type: text/html
content-length: 318
server: AmazonS3
x-cache: Error from cloudfront
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>メンテページ</title>
</head>
<body>
<div>メンテンナンス中です。</div>
</body>