Terraform個人メモ：VPC、6つのサブネット、IGW、RT、NAT、ALB、SG、ACM、ECS、RDSのコード化

## はじめに
まず、大前提として、ここから先は現在キャッチアップしているTerraformを使いながら、さまざまなコードを自分用に書いていきます。

:::note alert
**※補足事項**
***そのため、内容は完全に自分向けとなっています。***
:::

また、このブログ投稿サイトのプラットフォームについても、自分自身の備忘録としてメモを残す場として活用していきたいと考えています。***このプラットフォーム上に記録を積み重ねていく予定です。***

あらかじめ、その点をご理解いただければ幸いです。

## 実際にコードで行っていること

前回の続きです。

https://qiita.com/free-honda/items/1772a6718dd4a34202a4

https://qiita.com/free-honda/items/91b71aa6ac238510a2e6

https://qiita.com/free-honda/items/7189e84e4c2bc5135355

https://qiita.com/free-honda/items/960458cc17c8612c1a32

https://qiita.com/free-honda/items/facd031ed6ab0fcf868c

https://qiita.com/free-honda/items/825709ed952bf47027c3

- **AWSプロバイダー設定**
  - 東京リージョンでリソースを構築する設定。

- **VPC作成**
  - CIDRブロック範囲 `10.0.0.0/16` のVPCを作成。
  - DNSサポートとDNSホスト名を有効化。

- **パブリックサブネット作成**
  - 2つのパブリックサブネットを異なるアベイラビリティゾーンで作成。
  - インターネットゲートウェイを通じてインターネットにアクセス可能。

- **プライベートサブネット作成**
  - 4つのプライベートサブネットを異なるCIDRブロックとアベイラビリティゾーンで作成。
  - NATゲートウェイを経由してインターネットにアクセス可能。

- **インターネットゲートウェイ設定**
  - パブリックサブネットのインターネットアクセスを提供。

- **ルートテーブルの設定**
  - パブリックサブネット用のルートテーブルを作成し、インターネットゲートウェイをルートに設定。
  - プライベートサブネット用のルートテーブルを作成し、NATゲートウェイをルートに設定。

- **NATゲートウェイ設定**
  - Elastic IPを割り当てたNATゲートウェイを作成。
  - プライベートサブネットのアウトバウンド通信をサポート。

- **サブネットとルートテーブルの関連付け**
  - 各サブネットを対応するルートテーブルに関連付け。

- **セキュリティグループ設定**
  - SSH（22番）とALB（443番）のインバウンド通信を許可。
  - 全アウトバウンドトラフィックを許可。

- **ALBとターゲットグループの設定**
  - 外部公開用のALBを作成し、HTTPリクエストを処理するターゲットグループを設定（ヘルスチェックとスティッキーセッションを有効化）。

- **ECSインスタンスの登録**
  - プライベートサブネット内のECSインスタンスをターゲットグループに登録し、ALBを介してリクエストを転送。

- **ECSクラスターとタスク実行ロールの作成**
  - **ECSクラスター**: `aws_ecs_cluster.main`  
    ECSサービスを実行するためのクラスターを作成。  
  - **タスク実行ロール**: `aws_iam_role.ecs_task_execution_role`  
    ECRアクセスやログ送信権限を付与するIAMロール。

- **ECSタスク定義の作成**
  - **リソース名**: `aws_ecs_task_definition.main`  
    Fargate用のタスク（コンテナ）設定を定義。  
    - CPU: 256  
    - メモリ: 512  
    - コンテナ: ECRイメージを使用し、ポート1323を開放。  

- **ECSサービスの作成**
  - **リソース名**: `aws_ecs_service.main`  
    ECSクラスター内でタスクを実行するサービスを作成。  
    - ALBとターゲットグループを使用してアクセスを管理。  
    - タスクをFargateで1つ実行（`desired_count = 1`）。

- **ACMによるHTTPS通信の有効化**
  - **リソース名**: `acm_certificate_arn`  
    ACM (AWS Certificate Manager) を利用して SSL/TLS 証明書を設定し、HTTPS 通信を暗号化。  
    - ユーザーがアクセスするすべてのトラフィックが HTTPS によって保護。  

- **RDSインスタンスの作成**
  - **リソース名**: `aws_db_instance`  
    RDSインスタンスを作成。  
    - MySQL 8.0を使用したシングルAZのRDSインスタンスをプライベートサブネット内にデプロイ。
    - プライベートサブネットを指定して、RDSインスタンスが配置可能なサブネットグループを作成

## 事前準備と補足事項

**1. お名前.comで購入したドメインを使用してSSL証明書を取得する方法**

AWS ACMとRoute53を利用してSSL証明書を取得する手順については、以下の記事をご参照ください：

https://qiita.com/free-honda/items/8fb5124ae36616d64dd1

**2. ALBのAレコードをRoute53のホストゾーンに追加する方法**

Terraformで自動デプロイ後、ALBのAレコードをRoute53に追加する手順については、以下の記事をご参照ください：

https://qiita.com/free-honda/items/1d8810054b556739afaf#route-53-%E3%81%AE%E3%83%9B%E3%82%B9%E3%83%88%E3%82%BE%E3%83%BC%E3%83%B3%E3%81%AB-a-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E8%BF%BD%E5%8A%A0

## 作ったコードについて

```main.tf
# AWSプロバイダーの設定
provider "aws" {
  region = "ap-northeast-1" # 東京リージョン
}

# 変数の定義
variable "acm_certificate_arn" {
  default     = "arn:aws:acm:ap-northeast-1:xxx:certificate/e1c32c45-669b-45bf-b75b-6caf1f2c6e96"
  description = "ARN of the ACM Certificate"
}

# VPCの作成
resource "aws_vpc" "main" {
  cidr_block       = "10.0.0.0/16" # VPCのCIDRブロック
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "Terraform-vpc" # VPCの名前タグ
  }
}

# パブリックサブネット1の作成
resource "aws_subnet" "public1" {
  vpc_id            = aws_vpc.main.id        # 対象のVPC ID
  cidr_block        = "10.0.0.0/20"         # サブネットのCIDRブロック
  availability_zone = "ap-northeast-1a"    # 対象のアベイラビリティゾーン

  tags = {
    Name = "Terraform-subnet-public1-ap-northeast-1a" # サブネットの名前タグ
  }
}

# パブリックサブネット2の作成
resource "aws_subnet" "public2" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.16.0/20"        # 別のCIDRブロックを使用
  availability_zone = "ap-northeast-1c"    # 別のアベイラビリティゾーンを指定

  tags = {
    Name = "Terraform-subnet-public2-ap-northeast-1c"
  }
}

# プライベートサブネット1の作成
resource "aws_subnet" "private1" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.128.0/20"       # プライベート用CIDRブロック
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "Terraform-subnet-private1-ap-northeast-1a"
  }
}

# プライベートサブネット2の作成
resource "aws_subnet" "private2" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.144.0/20"       # 別のCIDRブロックを使用
  availability_zone = "ap-northeast-1c"

  tags = {
    Name = "Terraform-subnet-private2-ap-northeast-1c"
  }
}

# プライベートサブネット3の作成
resource "aws_subnet" "private3" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.160.0/20"       # さらに別のCIDRブロック
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "Terraform-subnet-private3-ap-northeast-1a"
  }
}

# プライベートサブネット4の作成
resource "aws_subnet" "private4" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.176.0/20"       # 最後のCIDRブロック
  availability_zone = "ap-northeast-1c"

  tags = {
    Name = "Terraform-subnet-private4-ap-northeast-1c"
  }
}

# インターネットゲートウェイの作成
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id # 対象のVPC ID

  tags = {
    Name = "Terraform-igw" # インターネットゲートウェイの名前タグ
  }
}

# パブリックルートテーブルの作成
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id # 対象のVPC ID

  tags = {
    Name = "Terraform-rtb-public" # ルートテーブルの名前タグ
  }
}

# パブリックルートの設定
resource "aws_route" "public_internet_access" {
  route_table_id         = aws_route_table.public.id  # パブリックルートテーブルのID
  destination_cidr_block = "0.0.0.0/0"               # 全トラフィックを許可
  gateway_id             = aws_internet_gateway.main.id # インターネットゲートウェイを指定
}

# パブリックサブネット1をルートテーブルに関連付け
resource "aws_route_table_association" "public1" {
  subnet_id      = aws_subnet.public1.id # パブリックサブネット1のID
  route_table_id = aws_route_table.public.id # パブリックルートテーブルのID
}

# パブリックサブネット2をルートテーブルに関連付け
resource "aws_route_table_association" "public2" {
  subnet_id      = aws_subnet.public2.id # パブリックサブネット2のID
  route_table_id = aws_route_table.public.id
}

# NATゲートウェイ用のEIP（Elastic IP）を作成
resource "aws_eip" "nat" {
  tags = {
    Name = "Terraform-eip-ap-northeast-1a" # Elastic IPの名前タグ
  }
}

# NATゲートウェイの作成
resource "aws_nat_gateway" "public1" {
  subnet_id     = aws_subnet.public1.id # NATゲートウェイを配置するサブネット
  allocation_id = aws_eip.nat.id        # Elastic IPを割り当て

  tags = {
    Name = "Terraform-nat-public1-ap-northeast-1a" # NATゲートウェイの名前タグ
  }
}

# プライベートルートテーブル1の作成
resource "aws_route_table" "private1" {
  vpc_id = aws_vpc.main.id # 対象のVPC ID

  tags = {
    Name = "Terraform-rtb-private1-ap-northeast-1a" # プライベートルートテーブルの名前タグ
  }
}

# プライベートルート1の設定（NATゲートウェイ経由）
resource "aws_route" "private1_nat_access" {
  route_table_id         = aws_route_table.private1.id # プライベートルートテーブル1のID
  destination_cidr_block = "0.0.0.0/0"                # 全トラフィックを許可
  nat_gateway_id         = aws_nat_gateway.public1.id # NATゲートウェイのID
}

# プライベートサブネット1をルートテーブル1に関連付け
resource "aws_route_table_association" "private1" {
  subnet_id      = aws_subnet.private1.id # プライベートサブネット1のID
  route_table_id = aws_route_table.private1.id # プライベートルートテーブル1のID
}

# プライベートルートテーブル2の作成
resource "aws_route_table" "private2" {
  vpc_id = aws_vpc.main.id # 対象のVPC ID

  tags = {
    Name = "Terraform-rtb-private2-ap-northeast-1c" # プライベートルートテーブルの名前タグ
  }
}

# プライベートルート2の設定（NATゲートウェイ経由）
resource "aws_route" "private2_nat_access" {
  route_table_id         = aws_route_table.private2.id # プライベートルートテーブル2のID
  destination_cidr_block = "0.0.0.0/0"                # 全トラフィックを許可
  nat_gateway_id         = aws_nat_gateway.public1.id # NATゲートウェイのID
}

# プライベートサブネット2をプライベートルートテーブル2に関連付け
resource "aws_route_table_association" "private2" {
  subnet_id      = aws_subnet.private2.id # プライベートサブネット2のID
  route_table_id = aws_route_table.private2.id # プライベートルートテーブル2のID
}

# プライベートルートテーブル3の作成
resource "aws_route_table" "private3" {
  vpc_id = aws_vpc.main.id # 対象のVPC ID

  tags = {
    Name = "Terraform-rtb-private3-ap-northeast-1a" # プライベートルートテーブル3の名前タグ
  }
}

# プライベートルート3の設定（NATゲートウェイ経由）
resource "aws_route" "private3_nat_access" {
  route_table_id         = aws_route_table.private3.id # プライベートルートテーブル3のID
  destination_cidr_block = "0.0.0.0/0"                # 全トラフィックを許可
  nat_gateway_id         = aws_nat_gateway.public1.id # NATゲートウェイのID
}

# プライベートサブネット3をプライベートルートテーブル3に関連付け
resource "aws_route_table_association" "private3" {
  subnet_id      = aws_subnet.private3.id # プライベートサブネット3のID
  route_table_id = aws_route_table.private3.id # プライベートルートテーブル3のID
}

# プライベートルートテーブル4の作成
resource "aws_route_table" "private4" {
  vpc_id = aws_vpc.main.id # 対象のVPC ID

  tags = {
    Name = "Terraform-rtb-private4-ap-northeast-1c" # プライベートルートテーブル4の名前タグ
  }
}

# プライベートルート4の設定（NATゲートウェイ経由）
resource "aws_route" "private4_nat_access" {
  route_table_id         = aws_route_table.private4.id # プライベートルートテーブル4のID
  destination_cidr_block = "0.0.0.0/0"                # 全トラフィックを許可
  nat_gateway_id         = aws_nat_gateway.public1.id # NATゲートウェイのID
}

# プライベートサブネット4をプライベートルートテーブル4に関連付け
resource "aws_route_table_association" "private4" {
  subnet_id      = aws_subnet.private4.id # プライベートサブネット4のID
  route_table_id = aws_route_table.private4.id # プライベートルートテーブル4のID
}

# セキュリティグループの作成（ECS用）
resource "aws_security_group" "ecs_sg" {
  vpc_id = aws_vpc.main.id

  # ALBからのHTTPリクエスト（1323番ポート）を許可
  ingress {
    from_port       = 1323
    to_port         = 1323
    protocol        = "tcp"
    security_groups = [aws_security_group.public_sg.id] # ALBのセキュリティグループを指定
  }

  # 全トラフィックの送信を許可（アウトバウンド）
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "Terraform-private-instance-sg"
  }
}

# ECSクラスターの作成
resource "aws_ecs_cluster" "main" {
  name = "my-ecs-cluster"
}

# ECSタスク実行ロールの作成
resource "aws_iam_role" "ecs_task_execution_role" {
  name = "ecs-task-execution-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "ecs-tasks.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })

  inline_policy {
    name = "ecs-task-execution-policy"
    policy = jsonencode({
      Version = "2012-10-17"
      Statement = [
        {
          Effect   = "Allow"
          Action   = [
            "ecr:GetAuthorizationToken",
            "ecr:GetDownloadUrlForLayer",
            "ecr:BatchGetImage",
            "logs:CreateLogStream",
            "logs:PutLogEvents",
            "s3:GetObject"
          ]
          Resource = "*"
        }
      ]
    })
  }
}

# ECSタスク定義の作成
resource "aws_ecs_task_definition" "main" {
  family                   = "my-app-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.ecs_task_execution_role.arn

  container_definitions = jsonencode([
    {
      name       = "my-app-repo"
      image      = "xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-app-repo:latest"
      portMappings = [
        {
          containerPort = 1323
          hostPort      = 1323
          protocol      = "tcp"
        }
      ]
    }
  ])
}

# ECSサービスの作成
resource "aws_ecs_service" "main" {
  name            = "my-ecs-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.main.arn
  desired_count   = 1
  launch_type     = "FARGATE"

  network_configuration {
    subnets         = [aws_subnet.public1.id]
    security_groups = [aws_security_group.ecs_sg.id]
    assign_public_ip = true
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.main_target_group.arn
    container_name   = "my-app-repo"
    container_port   = 1323
  }
}

# パブリック（ALB）用セキュリティグループ作成
resource "aws_security_group" "public_sg" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 443
    to_port     = 443
    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-public-sg"
  }
}

# アプリケーションロードバランサー(ALB)作成
resource "aws_lb" "main_alb" {
  name               = "terraform-main-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.public_sg.id]
  subnets            = [aws_subnet.public1.id, aws_subnet.public2.id]

  enable_deletion_protection = false

  tags = {
    Name = "terraform-main-alb"
  }
}

# ターゲットグループ作成
resource "aws_lb_target_group" "main_target_group" {
  name     = "terraform-main-target-group"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id
  target_type = "ip" # ターゲットタイプを 'ip' に設定

  health_check {
    interval = 30
    path     = "/"
    protocol = "HTTP"
    timeout  = 5
    healthy_threshold   = 3
    unhealthy_threshold = 3
  }

  stickiness {
    type            = "lb_cookie"
    enabled         = true
    cookie_duration = 86400
  }

  tags = {
    Name = "terraform-main-target-group"
  }
}

# ALBのリスナー作成
resource "aws_lb_listener" "http_listener" {
  load_balancer_arn = aws_lb.main_alb.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"

  certificate_arn = var.acm_certificate_arn

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

# RDS用セキュリティグループ
resource "aws_security_group" "rds_sg" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 3306
    to_port     = 3306
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/16"]
  }

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

  tags = {
    Name = "Terraform-rds-sg"
  }
}

# RDS用サブネットグループ
resource "aws_db_subnet_group" "rds_subnet_group" {
  name       = "terraform-rds-subnet-group"
  subnet_ids = [aws_subnet.private1.id, aws_subnet.private2.id]

  tags = {
    Name = "Terraform-rds-subnet-group"
  }
}

# RDSインスタンス
resource "aws_db_instance" "rds_instance" {
  allocated_storage    = 20                   # 無料利用枠に合わせたストレージ
  storage_type         = "gp2"                # ストレージタイプ
  engine               = "mysql"              # データベースエンジン
  engine_version       = "8.0.39"             # MySQLバージョン
  instance_class       = "db.t4g.micro"       # Graviton2プロセッサを使用したインスタンス
  username             = "admin"             # 管理ユーザー名
  password             = "mypassword123"     # 管理ユーザーパスワード
  parameter_group_name = "default.mysql8.0"   # デフォルトパラメータグループ

  db_subnet_group_name    = aws_db_subnet_group.rds_subnet_group.name # サブネットグループ
  vpc_security_group_ids  = [aws_security_group.rds_sg.id]            # セキュリティグループ

  publicly_accessible = false                 # プライベートサブネットで利用
  multi_az            = false                 # シングルAZ構成（無料利用枠対象）

  tags = {
    Name = "Terraform-rds-instance"
  }

  # 最終スナップショットをスキップ
  skip_final_snapshot = true
}
```


## まとめ
この記事は、自分用の備忘録としてまとめたものです。その点をご理解いただけると幸いです。

:::note info
***もし、この記事の内容が少しでも参考になれば嬉しく思います。***
:::

今後も同様の内容を継続して投稿していきますので、温かく見守っていただけるとありがたいです。

## おまけ
お名前.comで取得したドメイン名を使用してアクセスしたところ、以下のようにブラウザで正常に表示されることを確認しました。コード化は大成功です！

![Screenshot 2025-01-22 at 16.55.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/d9e351db-7c9e-8f45-39c9-433dae1bfdca.png)

また、今回作成したALBのDNS名でも、以下のように正常に表示されることを確認しています。

![Screenshot 2025-01-22 at 17.17.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/0293116a-f076-fdf0-78df-82121bbf219c.png)

今回追加で実装したRDSインスタンスについても、マネジメントコンソール上で正しく作成されていることを確認しました。

![Screenshot 2025-01-22 at 19.05.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/29aab67c-a297-c76c-7a1e-5402962cd024.png)