AWS ALB + Lambdaでメンテナンスページを作る


概要

  • AWS ALBにEC2やFargateなどをぶら下げてサービスを稼働させていて、時々メンテナンス作業のためにサービスを停止し、代わりにメンテナンスページを表示させるようにしたいときがある
  • ここではALB+Lambdaに切り替えることを想定してメンテナンスページを表示させてみた

別解:ALB単独でメンテナンスページを表示させる場合

  • Lambdaを使わずともALB単独で簡単なメンテナンスページを表示させることも可能である
  • 下記のようにALBのリスナールールの設定で固定レスポンスを返す設定を使えばALB単独でメンテナンスページを表示させることは可能だ
  • しかし、この方法には制約がある
    • レスポンス本文は最大1024文字が上限
    • HTTPレスポンスヘッダーはカスタマイズできない
    • 画像などで別ファイルを使うには別サーバーを使う必要がある

構成

  • 通常はALBとEC2やFargateを紐付けてサービスを運用しているが、メンテナンス時はEC2やFargateを切り離して、代わりにLambdaをALBに紐づける
  • このときLambdaはVPCの中に置く必要はないが、同じリージョン内に設置する

手順

  1. Lambda関数を作成する
  2. HTMLファイルと画像ファイルをLambdaにアップロードする
  3. Lambda関数のコードを作成・テストする
  4. ターゲットグループを作成し、Lambda関数を加える
  5. ALBのリスナールールを設定して、LambdaとALBを紐づける
  6. 実際にアクセスして動作を確認する

手順1: Lambda関数を作成する

何も特別なことは必要ないので空のLambda関数を作成する
ここではランタイムをnode.js 14.xとしているが、他のものでも良い
IAM権限なども最小限以外は必要ない

手順2: HTMLファイルと画像ファイルをLambdaにアップロードする

ここではHTMLファイル1つとSVGの画像ファイルを1つ用意した
HTMLファイルの中で画像を呼び出すパスは「/image」としている
この2つをzipファイルに固めて、Lambdaにアップロードする

ma.html
<html>
<head>
    <meta charset="utf-8">
    <title>メンテナンス</title>
</head>
<body>
<h1>メンテナンス中</h1>
<img src='/image'>
</body>
</html>

手順3: Lambda関数のコードを作成・テストする

index.jsのコードを作成する
あまり難しい処理はなく、HTTPリクエストのパスが「/image」なら画像ファイルを返し、それ以外ならHTMLファイルを返す

index.js
const fs = require('fs');

const html = 'ma.html';
const img = 'gnu-head.svg';


exports.handler = async (event) => {

    // 画像を返す
    if (event.path == '/image') {
        var response = {
            statusCode: 200,
            statusDescription: '200 OK',
            isBase64Encoded: false,
            headers: {
                'Content-Type': 'image/svg+xml',
                'Cache-Control': 'no-store',
            }
        };
        response.body = fs.readFileSync(img, 'utf8');
    } else {
    // HTMLを返す
        response = {
            statusCode: 503,
            statusDescription: '503 Service Unavailable',
            isBase64Encoded: false,
            headers: {
                'Content-Type': 'text/html; Charset=UTF-8',
                'Cache-Control': 'no-store',
            }
        };
        response.body = fs.readFileSync(html, 'utf8');
    }

    return response;
};

ALBから渡されるデータはだいたい次のようになっている

{
    "requestContext": {
        "elb": {
            "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a"
        }
    },
    "httpMethod": "GET",
    "path": "/lambda",
    "queryStringParameters": {
        "query": "1234ABCD"
    },
    "headers": {
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "accept-encoding": "gzip",
        "accept-language": "en-US,en;q=0.9",
        "connection": "keep-alive",
        "host": "lambda-alb-123578498.us-east-2.elb.amazonaws.com",
        "upgrade-insecure-requests": "1",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
        "x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476",
        "x-forwarded-for": "72.12.164.125",
        "x-forwarded-port": "80",
        "x-forwarded-proto": "http",
        "x-imforwards": "20"
    },
    "body": "",
    "isBase64Encoded": false
}

引用元:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-alb.html

手順4: ターゲットグループを作成し、Lambda関数を加える

AWSコンソールなどでALBに紐づけるためのターゲットグループを作成し
前項までで作成したLambda関数をグループに加える

手順5: ALBのリスナールールを設定して、LambdaとALBを紐づける

  • EC2やFargateなどと同じような感じで、前項で作成したターゲットグループをALBに紐づける
  • ヘルスチェックをすると503を返して失敗してしまうしLambda実行コストが無駄なのでヘルスチェックはしない

手順6: 実際にアクセスして動作を確認する

ブラウザでアクセスしてみてHTMLと画像が表示されることを確認する