0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第18回 Terraform講座|実務演習② 高可用性を意識したAWS設計

0
Posted at

前回は、会社から与えられた要件をもとに、

要件 → AWS構成 → Terraformリソース

という順番で考える練習をした。

今回はそこから一歩進んで、

«「AZ障害が起きてもWebサービスを止めたくない」»

という、より実務に近い要件を考えてみる。


📋 今回の要件

会社から次のような依頼が来たとする。

«「AZ障害が起きてもWebサービスを止めたくない。」»

条件は以下の通り。

  • VPCは1つ
  • Public Subnetは2つ(AZ-A、AZ-C)
  • Private Subnetは2つ(AZ-A、AZ-C)
  • ALBは2つのPublic Subnetに配置
  • EC2は2つのPrivate Subnetに1台ずつ配置
  • RDSはMulti-AZを利用する

構成イメージは以下。

                Internet
                   │
                  IGW
                   │
                  ALB
              ┌────┴────┐
              │         │
          Public-A   Public-C
              │         │
          Private-A  Private-C
              │         │
            EC2-1     EC2-2
                \       /
                 \     /
                   RDS
                Multi-AZ

今回のポイントは、

「1つのAZに障害が起きても、別のAZでサービスを継続できる構成」

を考えること。


🎯 今日の問題

Q1

Terraformリソースとして、新しく増えるものは何だろう?

前回はPublic Subnet、Private Subnet、EC2がそれぞれ1つだった。

今回はAZ-AとAZ-Cの2つに分ける。

前回との違いを考えてみよう。


Q2

なぜALBは、

subnets = [
PublicA,
PublicC
]

のように2つ指定する必要があるんだろう?


⭐ 今日一番重要な問題

もし、

«AZ-Aが丸ごと停止した»

とする。

このとき、

  • ALB
  • EC2
  • RDS

はそれぞれどう動く?

「どれが止まる?どれが動き続ける?」

を、自分の言葉で説明してみよう。


✍️ 今回の回答

Q1

«EC2からRDSの通信をするためにRDSセキュリティグループ、DBサブネットグループ。»


Q2

«AZ障害が起きてもサービスが止まらないようにするにはAZを複数作る必要があるから。»


⭐ AZ-Aが停止した場合

«AZ-Aが停止したらPrivate-AのEC2-1も止まる。
しかし、ALBがAZ-Cに通信を送るので、AZ-C、Private-C、EC2-2からRDSに通信が行く。»


🔎 今回のレビュー

今回の回答で特に良かったのは、

「リソースを覚えている」から「障害が起きたときの動きまで考えている」

ところまで進んでいること。

Terraformのコードを書くだけなら、必要なリソース名を調べれば書ける。

しかし実務では、

«「この構成でAZ-Aが落ちたらどうなる?」»

という問いに答えられることが重要になる。

今回の問題は、まさにそこを確認するための問題だった。


Q1のレビュー

回答:

«EC2からRDSの通信をするためにRDSセキュリティグループ、DBサブネットグループ。»

ここはRDS Security GroupとDB Subnet Groupを思い出せている点は正しい。

ただし、今回の問題では、

«「前回と比べて、AZを2つにしたことで何が増えるか」»

を考えることがポイントだった。

そのため、今回新しく増える代表的なリソースは、

Public Subnet-A
Public Subnet-C

Private Subnet-A
Private Subnet-C

EC2-A
EC2-C

などになる。

Terraformで考えると、

resource "aws_subnet" "public_a" {}

resource "aws_subnet" "public_c" {}

resource "aws_subnet" "private_a" {}

resource "aws_subnet" "private_c" {}

resource "aws_instance" "web_a" {}

resource "aws_instance" "web_c" {}

という形になる。

ここで大事なのは、

AZを増やす=単純にAZというTerraformリソースを追加するわけではない

ということ。

AWSでは、

AZ-A
↓
Subnet-A
↓
EC2-A

AZ-C
↓
Subnet-C
↓
EC2-C

というように、各AZにリソースを分散して配置する。


Q2のレビュー

回答:

«AZ障害が起きてもサービスが止まらないようにするにはAZを複数作る必要があるから。»

これはほぼ完璧。💯

もう少し実務的に表現すると、

«ALBを異なるAZのSubnetに配置することで、片方のAZに障害が発生しても、もう一方のAZにあるALB側の構成を利用してサービスを継続できるようにする。»

ということ。

つまり、

AZ-A
ALB
↓
EC2-A

だけでは、AZ-Aに障害が起きたときにまとめて影響を受ける。

一方、

AZ-A AZ-C
ALB ALB
↓ ↓
EC2-A EC2-C

としておけば、AZ-Aに障害が起きてもAZ-C側の構成を利用できる。

これがマルチAZ構成の基本的な考え方。


⭐ 一番重要な問題のレビュー

回答:

«AZ-Aが停止したらPrivate-AのEC2-1も止まる。しかし、ALBがAZ-Cに通信を送るので、AZ-C、Private-C、EC2-2からRDSに通信が行く。»

この考え方はかなり良い。

まず、

AZ-A障害
↓
Private-Aも影響
↓
EC2-1も停止

というところは正しい。

そして、

AZ-C
↓
Private-C
↓
EC2-2

という別AZ側の構成が残る。

そのため、EC2を複数AZに分散しておくことで、片方のAZの障害時にも残ったAZ側のEC2でサービスを継続できる。


そしてRDSが重要

今回の要件には、

«RDSはMulti-AZを利用する»

という条件があった。

そのためRDSについても、

AZ-A
RDS Primary
│
│ Multi-AZ
↓
AZ-C
RDS Standby

のように、異なるAZに構成される。

仮にPrimary側のAZで障害が発生した場合、RDSはStandby側へのフェイルオーバーを行い、DBサービスを継続できる構成になる。

つまり今回の設計では、

AZ-A 障害
↓
┌──────────────┐
│ AZ-A │
│ │
│ ALB │ ← 影響を受ける可能性
│ EC2-1 │ ← 停止
│ RDS Primary │ ← 障害の影響
└──────────────┘

    ↓

┌──────────────┐
│ AZ-C │
│ │
│ ALB │ ← 利用継続
│ EC2-2 │ ← 利用継続
│ RDS Standby │ ← フェイルオーバー
└──────────────┘

という考え方になる。

ここで大事なのは、

「AZを2つにすること」そのものが目的ではない。

目的は、

«障害が1つのAZに閉じ込められても、サービス全体が止まらないようにすること。»

これが高可用性設計の考え方。


🧠 今回の重要ポイント

今回の問題を通して、AWSの知識が一つにつながってきた。

これまでは、

VPC
Subnet
ALB
EC2
RDS
Security Group

を、それぞれ個別に勉強してきた。

しかし実務では、それぞれを単体で考えるのではなく、

要件
↓
障害を考える
↓
必要な構成を考える
↓
AWSリソースに落とす
↓
Terraformでコード化する

という順番で考える。

今回なら、

«「AZ障害でも止めたくない」»

という要件から、

複数AZ
↓
Subnetを複数AZに分散
↓
ALBを複数AZに配置
↓
EC2を複数AZに配置
↓
RDSをMulti-AZにする

という設計につながっている。

これが設計からTerraformを組み立てるということ。


📄 完成コードのイメージ

ここでは、今回の構成をTerraformで表すとどうなるのかを確認してみる。

まだ細かい値や設定は省略しているが、全体像を見るには十分な形にしている。

provider "aws" {
region = "ap-northeast-1"
}

==================================

VPC

==================================

resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}

==================================

Public Subnet

==================================

resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
}

resource "aws_subnet" "public_c" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1c"
}

==================================

Private Subnet

==================================

resource "aws_subnet" "private_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.11.0/24"
availability_zone = "ap-northeast-1a"
}

resource "aws_subnet" "private_c" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.12.0/24"
availability_zone = "ap-northeast-1c"
}

==================================

Internet Gateway

==================================

resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
}

==================================

Public Route Table

==================================

resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
}

==================================

Route Table Association

==================================

resource "aws_route_table_association" "public_a" {
subnet_id = aws_subnet.public_a.id
route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "public_c" {
subnet_id = aws_subnet.public_c.id
route_table_id = aws_route_table.public.id
}

==================================

NAT Gateway

==================================

resource "aws_eip" "nat" {}

resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public_a.id
}

==================================

ALB

==================================

resource "aws_lb" "alb" {
name = "sample-alb"
internal = false
load_balancer_type = "application"

subnets = [
aws_subnet.public_a.id,
aws_subnet.public_c.id
]

security_groups = [
aws_security_group.alb.id
]
}

==================================

Target Group

==================================

resource "aws_lb_target_group" "app" {
name = "sample-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
}

==================================

Listener

==================================

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

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

==================================

EC2

==================================

resource "aws_instance" "web_a" {
ami = "ami-xxxxxxxx"
instance_type = "t3.micro"

subnet_id = aws_subnet.private_a.id

vpc_security_group_ids = [
aws_security_group.ec2.id
]
}

resource "aws_instance" "web_c" {
ami = "ami-xxxxxxxx"
instance_type = "t3.micro"

subnet_id = aws_subnet.private_c.id

vpc_security_group_ids = [
aws_security_group.ec2.id
]
}

==================================

Target Group Attachment

==================================

resource "aws_lb_target_group_attachment" "web_a" {
target_group_arn = aws_lb_target_group.app.arn
target_id = aws_instance.web_a.id
port = 80
}

resource "aws_lb_target_group_attachment" "web_c" {
target_group_arn = aws_lb_target_group.app.arn
target_id = aws_instance.web_c.id
port = 80
}

==================================

Security Groups

==================================

resource "aws_security_group" "alb" {
name = "alb-sg"
vpc_id = aws_vpc.main.id

ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

resource "aws_security_group" "ec2" {
name = "ec2-sg"
vpc_id = aws_vpc.main.id

ingress {
from_port = 80
to_port = 80
protocol = "tcp"

security_groups = [
  aws_security_group.alb.id
]

}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

resource "aws_security_group" "rds" {
name = "rds-sg"
vpc_id = aws_vpc.main.id

ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"

security_groups = [
  aws_security_group.ec2.id
]

}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

==================================

DB Subnet Group

==================================

resource "aws_db_subnet_group" "main" {
name = "sample-db-subnet-group"

subnet_ids = [
aws_subnet.private_a.id,
aws_subnet.private_c.id
]
}

==================================

RDS

==================================

resource "aws_db_instance" "main" {
identifier = "sample-db"
engine = "mysql"
instance_class = "db.t3.micro"
allocated_storage = 20

username = "admin"
password = "xxxxxxxx"

db_subnet_group_name = aws_db_subnet_group.main.name

vpc_security_group_ids = [
aws_security_group.rds.id
]

publicly_accessible = false

multi_az = true
}


🔍 完成コードを設計図として読む

ここで、いきなりコードを暗記する必要はない。

むしろ、

「このコードはAWSコンソールで何を作っているんだ?」

と考えてみる。

例えば、

subnets = [
aws_subnet.public_a.id,
aws_subnet.public_c.id
]

を見たら、

ALB
↓
Public-A
Public-C

を作っている。


subnet_id = aws_subnet.private_a.id

なら、

EC2
↓
Private-A

という配置。


security_groups = [
aws_security_group.alb.id
]

なら、

ALBのSecurity Group
↓
EC2のSecurity Group
↓
ALBからの通信を許可

という通信関係。


multi_az = true

なら、

RDS
↓
Multi-AZ構成

という設計をTerraformで表現している。

つまりTerraformのコードは、

AWS構成図をコードとして表現したもの

と考えると分かりやすい。


🚀 今回の大きな学び

これまでの授業では、

Terraformのコード
↓
AWSリソース

という方向から勉強してきた。

しかし今回からは逆方向も考えられるようになってきた。

会社の要件
↓
AWSの設計
↓
構成図
↓
必要なリソース
↓
Terraformコード

この考え方ができるようになると、

「Terraformの書き方を全部覚えていないと何もできない」

という状態から抜け出せる。

Terraformの構文は調べられる。

AWSリソースの引数も公式ドキュメントで確認できる。

でも、

«「なぜこの構成にするのか?」»

は自分で考えなければならない。

だから今後は、

コードを書く能力だけではなく、コードになる前の設計能力

を鍛えていく。


📚 次回予告

ここから、いよいよTerraformらしい機能に入っていく。

今までは、

resource "aws_instance" "web_a" {}
resource "aws_instance" "web_c" {}

のように、同じようなリソースを1つずつ書いていた。

では会社から、

«「Webサーバーを3台に増やして。ただしTerraformのコードはなるべく増やしたくない。」»

と言われたらどうする?

ここで登場するのが、

count

や

for_each

だ。

例えば、

resource "aws_instance" "web" {
count = 3

ami = "ami-xxxxxxxx"
instance_type = "t3.micro"
}

と書けば、同じリソースを複数作ることができる。

つまり、

Terraform
↓
同じコードを繰り返す
↓
EC2 × 3台

ということができる。

ここからは、

「AWSを設計する」

だけではなく、

「その設計をTerraformらしく効率的にコード化する」

という段階に進んでいく。

これがTerraformの面白いところ。

次回から、いよいよ

«Terraformだからできること»

を掘り下げていこう。🔥

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?