はじめに
前回の記事の続きです。
基礎学習が完了したので、Terraformで実際に構築します。
構成
構成は前回の記事と同じです。
構築
事前準備
まずは、EC2に接続するためのSSHキーペアを作成します。
ssh-keygen -t ed25519 -f ~/.ssh/terraform-sample-key
踏み台のセキュリティグループに設定するため、IPを控えます。
curl ifconfig.me
早速main.tfを書いていきましょう
段階に沿って定義していきます。
1. providerブロック
AWSの東京リージョンを使う。という宣言です。
provider "aws" {
region = "ap-northeast-1"
}
2. VPC
VPCの定義です。
「10.0.0.0/16のアドレス範囲でVPCを作る」 という定義です。
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = { Name = "terraform-vpc" }
}
3. サブネット
サブネットの定義です。
パブリックサブネットとプライベートサブネットは同じ定義です。
後ほど登場するルートテーブルにて、Internet Gatewayにつなげる事でパブリックサブネットになり、区別できます。
resource "aws_subnet" "public_1a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
tags = { Name = "terraform-public-1a" }
}
resource "aws_subnet" "public_1c" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1c"
tags = { Name = "terraform-public-1c" }
}
resource "aws_subnet" "private_1a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.3.0/24"
availability_zone = "ap-northeast-1a"
tags = { Name = "terraform-private-1a" }
}
resource "aws_subnet" "private_1c" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.4.0/24"
availability_zone = "ap-northeast-1c"
tags = { Name = "terraform-private-1c" }
}
4. Internet Gateway
Internet Gatewayの定義です。
アタッチするVPCのみを記載します。
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "terraform-igw" }
}
5. Nat Gateway + Elastic IP
Nat GatewayとElastic IPの定義です。
NAT Gatewayは、パブリックサブネット1aに設定します。
resource "aws_eip" "nat" {
domain = "vpc"
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public_1a.id
tags = { Name = "terraform-nat"}
}
6. ルートテーブル
まずは、パブリックサブネット用のルートテーブルを定義して、各パブリックサブネットに紐づけを行います。
ここで、Internet Gatewayと紐づけることによって、パブリックであると定義されるわけです。
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = { Name = "terraform-public-tr" }
}
resource "aws_route_table_association" "public_1a" {
subnet_id = aws_subnet.public_1a.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "public_1c" {
subnet_id = aws_subnet.public_1c.id
route_table_id = aws_route_table.public.id
}
続いては、プライベートサブネット用のルートテーブルです。
プライベートサブネットは、NATゲートウェイに紐づけを行います。
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = { Name = "terraform-private-tr" }
}
resource "aws_route_table_association" "private_1a" {
subnet_id = aws_subnet.private_1a.id
route_table_id = aws_route_table.private.id
}
resource "aws_route_table_association" "private_1c" {
subnet_id = aws_subnet.private_1c.id
route_table_id = aws_route_table.private.id
}
7. セキュリティグループ
セキュリティグループの定義です。
ALBと踏み台とWebサーバー用の3つが必要です。
ingressは、インバウンド(外→中)。egressは、アウトバウンド(中→外)の定義です。
from_port/to_portは、許可するポートの範囲。
cidr_blocksは、許可するIP。
security_groupsは、許可するSGです。
踏み台のingressは、IPで制限を掛け、IP制限不要の際は全て許可(0.0.0.0/0)にします。
protocolが-1の際は、全プロトコルを許可する設定です。
// ALB用
resource "aws_security_group" "alb" {
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"]
}
tags = { Name = "terraform-sg-alb" }
}
// 踏み台用
resource "aws_security_group" "bastion" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["xxx.x.xxx.xx/32"] // 事前準備で取得したIP
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "terraform-sg-bastion" }
}
// Webサーバー用
resource "aws_security_group" "web" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.bastion.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "terraform-sg-web" }
}
8.キーペア
事前準備で作成した公開鍵をAWSに登録します。
resource "aws_key_pair" "main" {
key_name = "terraform-key"
public_key = file("~/.ssh/terraform-sample-key.pub")
}
9. EC2
EC2の定義です。
今回は特に拘りが無いので、Amazon Linux 2023のt2.microにしました。
associate_public_ip_addressにtrueを設定し、踏み台サーバーにのみパブリックIPを持たせます。
// 踏み台
resource "aws_instance" "bastion" {
ami = "ami-0ebdce61e922f7e5b"
instance_type = "t2.micro"
subnet_id = aws_subnet.public_1a.id
vpc_security_group_ids = [aws_security_group.bastion.id]
associate_public_ip_address = true
key_name = aws_key_pair.main.key_name
tags = { Name = "terraform-bastion" }
}
# WebサーバーEC2(AZ1)
resource "aws_instance" "web_1a" {
ami = "ami-0ebdce61e922f7e5b"
instance_type = "t2.micro"
subnet_id = aws_subnet.private_1a.id
vpc_security_group_ids = [aws_security_group.web.id]
associate_public_ip_address = false
key_name = aws_key_pair.main.key_name
tags = { Name = "terraform-web-1a" }
}
# WebサーバーEC2(AZ2)
resource "aws_instance" "web_1c" {
ami = "ami-0ebdce61e922f7e5b"
instance_type = "t2.micro"
subnet_id = aws_subnet.private_1c.id
vpc_security_group_ids = [aws_security_group.web.id]
associate_public_ip_address = false
key_name = aws_key_pair.main.key_name
tags = { Name = "terraform-web-1c" }
}
10. ALB
最後にALBの定義です。
internalがfalseの場合、インターネット向け。trueの場合、内部向けです。
load_balancer_typeをapplicationにすることで、ALBになります。
ターゲットグループでヘルスチェックとEC2のアタッチを行います。
リスナーを設定することによって、ポート80でリクエストを受け付け、ターゲットグループに転送する訳ですね。
# ALB本体
resource "aws_lb" "main" {
name = "terraform-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = [
aws_subnet.public_1a.id,
aws_subnet.public_1c.id,
]
tags = { Name = "terraform-alb" }
}
# ターゲットグループ
resource "aws_lb_target_group" "web" {
name = "terraform-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
path = "/"
}
}
# ターゲットグループにEC2を登録(AZ1)
resource "aws_lb_target_group_attachment" "web_1a" {
target_group_arn = aws_lb_target_group.web.arn
target_id = aws_instance.web_1a.id
port = 80
}
# ターゲットグループにEC2を登録(AZ2)
resource "aws_lb_target_group_attachment" "web_1c" {
target_group_arn = aws_lb_target_group.web.arn
target_id = aws_instance.web_1c.id
port = 80
}
# リスナー
resource "aws_lb_listener" "web" {
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}
実行
後は以下を実行して完了です!
terraform init
terraform plan
terraform apply
確認
一旦マネジメントコンソール上で、定義したものが作成されたか確認してみましょう。
確認後、以下の手順でEC2にSSH接続してみましょう。
踏み台サーバーのパブリックIPを確認します。
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=terraform-bastion" \
--query "Reservations[0].Instances[0].PublicIpAddress" \
--output text \
--region ap-northeast-1
続いてWebサーバーのプライベートIPを確認します。
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=terraform-web-1a" \
--query "Reservations[0].Instances[0].PrivateIpAddress" \
--output text \
--region ap-northeast-1
踏み台を経由して、Webサーバーに接続します。
ssh -i ~/.ssh/terraform-sample-key \
-o "ProxyCommand ssh -i ~/.ssh/terraform-sample-key -W %h:%p ec2-user@<踏み台のパブリックIP>" \
ec2-user@<WebサーバーのプライベートIP>
無事EC2に接続確認出来ましたら終了です!
放置すると料金が発生するため、学習用として構築した際は削除推奨です。
terraform destroy で削除できます。
最後に
ほぼ模写で構築しましたが、覚えることも多く大変ですね...
ですが基礎は何とか学習できたので、プライベート開発する際は、これをベースに構築してみたいと思います。
今回はmain.tfに全て記載したので、変数化したりモジュール化したり、簡略化する方法を学習し、次回の記事でまとめます。
