LoginSignup
7
2

More than 3 years have passed since last update.

【ALB】さくっと5秒でメンテナンス画面を出す仕組みを作った

Last updated at Posted at 2019-12-13

解決したい課題

  • 複数の用途の WEB サーバ (EC2) が ELB (ALB) 配下に存在しているが、順番にメンテナンス画面を出すのに時間がかかってしまう (Jenkins などで順番にデプロイ)
    • 複数の環境のソースコード上にあるメンテナンス時間を修正して、都度デプロイしないといけない環境など
  • ALB に存在する リクエストルーティング を使ってメンテナンス画面を出す方法もあるが、1024 文字の制限があるので、最小限の HTML かテキストの出力しかできない

解決案

  • メンテナンス用の S3 + CloudFront + route53 (ここでは maintenance.hapitas.jp と定義) をたてる
    • CloudFront は GET, HEAD, OPTIONS 以上を許可
    • CloudFront は Cache Based on Selected Request HeadersWhitelist とし、Access-Control-Request-Headers Access-Control-Request-Method Origin を Whitelist Headers として登録 (同一ドメイン、スキームでアクセスするなら不要)
    • S3 上では CORS 制限をはずす
      • バケットの [Permissions] 設定の [CORS configuration] で下記の内容を登録 (同一ドメイン、スキームでアクセスするなら不要)
<?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

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2