kakakakakku blog

Weekly Tech Blog: Keep on Learning!

ALB の固定レスポンスで実現するメンテナンス画面の仕組みを Terraform で構築する

Application Load Balancer (ALB) のリスナー設定で「固定レスポンス」を使うと ALB から直接 HTML を返せる❗️

docs.aws.amazon.com

「固定レスポンス」は2018年7月頃にリリースされていて,さらに2019年3月頃にリリースされた「高度なリクエストルーティング(送信元 IP アドレスなど)」と組み合わせたメンテナンス画面の仕組みを使うことがある✌️過去に NGINX の rewrite で頑張ってメンテナンス画面を制御してた経験があるからこそ便利に感じるのかも〜 \( 'ω')/

aws.amazon.com

aws.amazon.com

設定

最終的には要件次第ではあるけど,個人的によく使っている設定を単純化したサンプルを載せる👇

通常時は Forward(優先度 1)ですべてのリクエストをターゲットグループ(アプリケーション)に転送する.そして Maintenance Bypass(優先度 101)と Maintenance(優先度 102)は使わずとも設定としては仕込んでおく✔️

通常時

そしてメンテナンス時は以下のように優先度を更新すれば OK👌

  • Forward: 優先度 1 → 優先度 101
  • Maintenance Bypass: 優先度 101 → 優先度 1
  • Maintenance: 優先度 102 → 優先度 2

AWS CLI で優先度を更新するなら aws elbv2 set-rule-priorities コマンドを使えば簡単❗️

$ FORWARD=xxx
$ BYPASS=yyy
$ MAINTENANCE=xxx

# メンテナンス ON
$ aws elbv2 set-rule-priorities --rule-priorities \
    RuleArn=${BYPASS},Priority=1 \
    RuleArn=${MAINTENANCE},Priority=2 \
    RuleArn=${FORWARD},Priority=101

# メンテナンス OFF
$ aws elbv2 set-rule-priorities --rule-priorities \
    RuleArn=${FORWARD},Priority=1 \
    RuleArn=${BYPASS},Priority=101 \
    RuleArn=${MAINTENANCE},Priority=102

ちなみにメンテナンス画面を表示する "だけ" なら固定レスポンスを返す Maintenance があれば十分だけど,運用面を考えると管理者はメンテナンス中にも動作確認をする必要があるため「送信元 IP アドレス(100.100.100.100/32 はサンプル)」によってメンテナンス画面を迂回できるように設定している❗️ちなみに IP ではなく「特定の HTTP ヘッダー」などでも実現できる👌

メンテナンス中

Terraform コード

キャプチャを載せたサンプルを Terraform で構築する場合は以下のリソースを使って設定できる.

resource "aws_lb" "sandbox" {
  name                       = "sandbox"
  load_balancer_type         = "application"
  drop_invalid_header_fields = true

  subnets = [
    "subnet-xxxxxxxxxxxxxxxxx",
    "subnet-xxxxxxxxxxxxxxxxx"
  ]
  security_groups = [
    "sg-xxxxxxxxxxxxxxxxx"
  ]
}

resource "aws_lb_listener" "sandbox" {
  load_balancer_arn = aws_lb.sandbox.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.sandbox.arn
  }
}

resource "aws_lb_listener_rule" "forward" {
  listener_arn = aws_lb_listener.sandbox.arn
  priority     = 1

  condition {
    path_pattern {
      values = ["*"]
    }
  }

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.sandbox.arn
  }

  tags = {
    name = "Forward"
  }
}

resource "aws_lb_listener_rule" "bypass" {
  listener_arn = aws_lb_listener.sandbox.arn
  priority     = 101

  condition {
    source_ip {
      values = ["100.100.100.100/32"]
    }
  }

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.sandbox.arn
  }

  tags = {
    name = "Maintenance Bypass"
  }
}

resource "aws_lb_listener_rule" "maintenance" {
  listener_arn = aws_lb_listener.sandbox.arn
  priority     = 102

  condition {
    path_pattern {
      values = ["*"]
    }
  }

  action {
    type = "fixed-response"

    fixed_response {
      content_type = "text/html"
      message_body = file("maintenance.html")
      status_code  = "503"
    }
  }

  tags = {
    name = "Maintenance"
  }
}

resource "aws_lb_target_group" "sandbox" {
  name        = "sandbox"
  vpc_id      = "vpc-xxxxxxxxxxxxxxxxx"
  target_type = "ip"
  port        = 80
  protocol    = "HTTP"
}

maintenance.html は適当に👀

<html>
<head>
  <title>Under Maintenance</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      text-align: center;
      padding: 50px;
    }
  </style>
</head>
<body>
  <h1>The site is currently undergoing maintenance.</h1>
</body>
</html>