解決したい課題
- 複数の用途の WEB サーバ (EC2) が ELB (ALB) 配下に存在しているが、順番にメンテナンス画面を出すのに時間がかかってしまう (Jenkins などで順番にデプロイ)
- 複数の環境のソースコード上にあるメンテナンス時間を修正して、都度デプロイしないといけない環境など
- ALB に存在する リクエストルーティング を使ってメンテナンス画面を出す方法もあるが、1024 文字の制限があるので、最小限の HTML かテキストの出力しかできない
解決案
- メンテナンス用の S3 + CloudFront + route53 (ここでは maintenance.hapitas.jp と定義) をたてる
- CloudFront は
GET, HEAD, OPTIONS
以上を許可 - CloudFront は
Cache Based on Selected Request Headers
をWhitelist
とし、Access-Control-Request-Headers
Access-Control-Request-Method
Origin
を Whitelist Headers として登録 (同一ドメイン、スキームでアクセスするなら不要) - S3 上では CORS 制限をはずす
- バケットの [Permissions] 設定の [CORS configuration] で下記の内容を登録 (同一ドメイン、スキームでアクセスするなら不要)
- CloudFront は
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>
- S3 バケットに
maintenance.json
ファイルを以下の内容で配置
{
"start_date": "Fri, 13 Dec 2019 21:00:00 +0900",
"end_date": "Fri, 13 Dec 2019 22:00:00 +0900"
}
- S3 バケットにこだわったデザインの HTML (mainte.html) を配置 (適当ですがw)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no">
<title>こだわったメンテ画面</title>
</head>
<body>
<div>
<h1>ただいまメンテナンス中です</h1>
<div id="mainte-period"></div>
<p>ご不便をおかけしてしまい申し訳ございませんが、<br>
メンテナンス完了までしばらくお待ちください。</p>
</div>
<script id="template" type="text/x-jquery-tmpl">
<div id="mainte-period">
<p>実施期間: ${start} ~ ${end}<br>
期間中はすべてのサービスがご利用いただけません。</p>
</div>
</script>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
<script>
"use strict";
$(function(){
var maintenance = {
template: $('#template'),
targetHtml: $('#mainte-period'),
init: function() {
this.getJson().done(function(data) {
if (typeof data.start_date == 'string' || typeof data.end_date == 'string') {
if (maintenance.isMainteNow(data.start_date, data.end_date)) {
maintenance.targetHtml.replaceWith(
maintenance.template.tmpl({
"start": maintenance.generateFormat(data.start_date),
"end": maintenance.generateFormat(data.end_date)
})
);
}
}
});
},
isMainteNow: function(start, end) {
if (!start || !end) {
return false;
}
var now = new Date();
var start = new Date(start);
var end = new Date(end);
return start.getTime() <= now.getTime() && now.getTime() <= end.getTime();
},
generateFormat: function(string) {
var dateObject = new Date(string);
var year = dateObject.getFullYear();
var month = dateObject.getMonth() + 1;
var date = dateObject.getDate();
var hours = dateObject.getHours();
var minutes = dateObject.getMinutes();
var week = [ "日", "月", "火", "水", "木", "金", "土" ][dateObject.getDay()];
return year + '年' + month + '月' + date + '日 ' + ' (' + week + ') ' + ('0' + hours).slice(-2) + ':' + ('0' + minutes).slice(-2);
},
getJson: function() {
var deferred = $.Deferred();
var date = new Date();
$.ajax({
type: 'GET',
url: 'https://maintenance.hapitas.jp/maintenance.json?' + date.getTime(),
dataType: 'json',
success: deferred.resolve
});
return deferred.promise();
}
};
maintenance.init();
});
</script>
</body>
</html>
- ALB の リクエストルーティングの優先度は default action の手前の優先度で ルールを追加 (※ default action より優先度は下げられないため、手前に配置する。ただし、非メンテ時は default action に重要なアクションがあると困る → derault action と同じ内容のルールを Source IP 0.0.0.0/0 でメンテナンスのルールより手前に追加しておく)
- IF に Source IP を 0.0.0.0/0 とする
- THEN に Return fixed response をいれ Response code: 503, Content-Type: test/html, Response body に以下をいれる (なんと 301 文字)
<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8"></head><body><script onload="$.ajax({type: 'GET',url: 'https://maintenance.hapitas.jp/mainte.html',dataType: 'html',success: function(data) {document.write(data);}});" src="https://code.jquery.com/jquery-latest.min.js"></script></body></html>
これで準備は完了!
メンテ時は json のメンテ開始日、終了日をいれ、上記レスポンスの優先度を一番上に上げるだけ
緊急メンテで、開始日、終了日が決まっていない場合は、 json の更新はせず、優先度を上げればOK