AWS ALB/CloudFront でのメンテナンス切り替え方法

AWS ALB/CloudFront でのメンテナンス切り替え方法

ALB, CloudFront がインターフェースにある場合のメンテナンス切り替え方法をまとめました。


ALB の場合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
resource "aws_lb" "app" {
...
}

resource "aws_lb_target_group" "app" {
# CodeDeploy で ECS Service をデプロイする際に blue/green を用意しています。
for_each = toset(["blue", "green"])
...
}

resource "aws_lb_listener" "app" {
load_balancer_arn = aws_lb.app.arn
...

# デフォルトアクションは 404 エラー
default_action {
type = "fixed-response"

fixed_response {
content_type = "text/plain"
status_code = "404"
}
}
}

# NOTE: メンテナンスモード時は社内 IP のみルーティングする
resource "aws_alb_listener_rule" "app_routing_in_maintenance" {
count = var.maintenance_mode ? 1 : 0

listener_arn = aws_alb_listener.app.arn
priority = 10

action {
# ECS Service に紐づけている Target Group ARN を指定する
target_group_arn = element(aws_ecs_service.app.load_balancer[*].target_group_arn, 0)
type = "forward"
}

condition {
# NOTE: ドメイン名で制御する
host_header {
values = [
"example.com",
]
}
}

# 社内 IP のみメンテ時でもアクセスできるようにしておく
condition {
source_ip {
values = office_ips
}
}

# NOTE: CodeDeployによるBlue/Green Deploymentで変更される箇所を無視
lifecycle {
ignore_changes = [
action,
]
}
}

# NOTE: メンテナンスモード時は503の固定レスポンスを返す。
resource "aws_alb_listener_rule" "app_listener_https_maintenance" {
count = var.maintenance_mode ? 1 : 0

listener_arn = aws_alb_listener.app.arn
priority = 20

action {
type = "fixed-response"

fixed_response {
status_code = "503"
content_type = "application/json"
message_body = jsonencode(
{
code = "service_unavailable"
hint = "現在メンテナンス中です。"
}
)
}
}

condition {
# NOTE: ドメイン名で制御する
host_header {
values = [
"example.com",
]
}
}
}

resource "aws_lb_listener_rule" "app_listener_https_host" {
listener_arn = aws_lb_listener.app_https.arn
priority = 100

action {
target_group_arn = aws_lb_target_group.app["blue"].arn
type = "forward"
}

condition {
host_header {
# NOTE: ドメイン名で制御する
values = [
"example.com",
]
}
}

# NOTE: CodeDeployによるBlue/Green Deploymentで変更される箇所を無視
lifecycle {
ignore_changes = [
action,
]
}
}
  • メンテモード有効時に社内 IP のみ、サーバへ接続できるようにし、その他リクエストを 503 で返します。
  • メンテ無効時には上記ルールは削除されます。
  • メンテ時のレスポンスを application/json で返しているのはネイティブアプリケーションとの通信を想定しています。 text/html 等選択可能です。

参考: https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/load-balancer-limits.html

var.maintenance_mode をメンテモード切り替えに利用している理由

ALB リソースを管理する terraform 外で maintenace_mode を管理すると
メンテ切り替えとリソースの更新を別のライフサイクルで管理できるメリットがある為です。

例えば、 以下のような利用想定をしています。

  1. Terraform Cloud 上で mainteance_mode = true にし、 terraform apply を実行しメンテナンスモードにする
  2. インフラの変更を terraform で実施する

ALB のデフォルトアクションを 404 にしている理由

メンテモードの話と逸れますが、
指定ドメイン以外のリクエストを許可しないようにしたい為です。

IP 直指定や ALB ドメイン名指定のアクセスを許可してるとセキュリティ上も SEO 上もよろしくない認識です。

CloudFront の場合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
resource "aws_wafv2_web_acl" "cloudfront_app" {
...
scope = "CLOUDFRONT"

custom_response_body {
content = jsonencode(
{
code = "service_unavailable"
hint = "現在システムのメンテナンス中です。"
}
)
content_type = "APPLICATION_JSON"
key = "maintenance"
}

default_action {
allow {}
}

# アクセス許可 IP にマッチしない場合 block する
dynamic "rule" {
for_each = var.maintenance_mode ? ["1"] : []
content {
...
priority = 10

# ブロック時にメンテナンス時のレスポンスを返す
action {
block {
custom_response {
custom_response_body_key = "maintenance"
response_code = 503
}
}
}

statement {
not_statement {
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.allow_ips.arn
}
}
}
}
...
}
}

...
}

resource "aws_cloudfront_distribution" "app" {
...

web_acl_id = aws_wafv2_web_acl.cloudfront_app.arn

...
}
  • CloudFront にアタッチする WAF でリクエスト許可 IP 以外は 503 メンテ用レスポンスを返します。
  • custom_response_body は、terraform-provider-aws>=3.67.0 でサポートしています。

デフォルトアクションを allow {} にしている理由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
default_action {
allow {}
}

dynamic "rule" {
...
content {
statement {
not_statement {
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.allow_ips.arn
}
}
}
}

メンテモードの話とは逸れますが、
本番環境・ステージング環境でデフォルトのアクションを統一したい為です。

時折見受けられるのは以下の様な設定

  • 本番環境 → default_action { allow {} }
  • ステージング環境 → default_action { block {} }

デフォルトアクションが環境毎に異なることで
追加したルールによってリクエストが意図せずにブロックしたり・許可してしまったりする事故が起きる可能性があり、その防止策の一環として
統一しておくほうが良いという判断です。

まとめ

それぞれのリソースの特性を活かしたメンテ方法が実装できました。

  • ALB はリスナールール
  • CloudFront は WAF

メンテページをよりリッチにしたい場合は S3 にルーティングしても良いと思います。

以上
参考になれば幸いです。

AWS ALB/CloudFront でのメンテナンス切り替え方法

https://kenzo0107.github.io/2022/03/12/2022-03-13-maintenance-mode-for-alb-or-cloudfront/

Author

Kenzo Tanaka

Posted on

2022-03-13

Licensed under

コメント