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

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

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

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

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

## 今回作成する予定のAWS構成図について
詳細は省略しますが、今回は受講しているITスクールで、以下のようなAWSインフラ構成をコード化しています。

![Screenshot 2025-01-25 at 1.44.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/c06d3316-95b7-4b5d-61e1-67827deee5e2.png)

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

- VPC（仮想プライベートクラウド）の作成
  - IPアドレス範囲（CIDRブロック）を「10.0.0.0/16」に設定。
  - DNSサポートとDNSホスト名を有効化。

- サブネットの作成
  - **パブリックサブネット**  
    - 2つのサブネットを作成（`10.0.0.0/20`、`10.0.16.0/20`）。
    - 各サブネットを異なるアベイラビリティゾーン（`ap-northeast-1a`、`ap-northeast-1c`）に配置。
  - **プライベートサブネット**  
    - 4つのプライベートサブネットを作成（例：`10.0.128.0/20` など）。
    - アベイラビリティゾーンごとに分けて配置。

- インターネットゲートウェイとNATゲートウェイの作成
  - 外部インターネット通信を可能にするインターネットゲートウェイを作成。
  - プライベートサブネットからインターネットアクセスを可能にするNATゲートウェイを設定。

- ルートテーブルとルート設定
  - 各サブネットに対応するルートテーブルを作成。
  - **パブリックサブネット**: インターネットゲートウェイへのルートを設定。
  - **プライベートサブネット**: NATゲートウェイを経由するルートを設定。

- セキュリティグループの設定
  - **ECS用**: アプリケーション用セキュリティグループを作成。
  - **ALB用**: HTTPS通信（443ポート）を許可。
  - **RDS用**: データベースアクセス（3306ポート）を許可。

- ECSクラスターとサービスの作成
  - Fargateを使用したECSクラスターを作成。
  - タスク定義、コンテナ設定、ネットワーク構成を含むサービスを設定。

- アプリケーションロードバランサー（ALB）の作成
  - HTTPSリスナー（443ポート）を設定。
  - ターゲットグループと接続。

- RDS（データベース）インスタンスの作成
  - MySQLエンジンを使用したRDSインスタンスをプライベートサブネットにデプロイ。
  - 無料利用枠に対応した構成（`db.t4g.micro`インスタンス、20GBストレージ）。

- S3バケットとCloudFrontの設定
  - **S3バケット**: Reactアプリを格納予定の非公開バケットを作成。
  - **CloudFront**: S3バケットをオリジンとするディストリビューションを設定。
  - ウェブホスティング用に`index.html`をアップロードし、HTTPS経由でアクセス可能に。

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

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

# 変数の定義
variable "acm_certificate_arn" {
  default     = "arn:aws:acm:ap-northeast-1:881490128743:certificate/0a6a2ded-e495-48c8-ac31-a52832197d5f"
  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      = "881490128743.dkr.ecr.ap-northeast-1.amazonaws.com/my-app-repo"
      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
}

# S3バケットの作成
resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-cloudfront-bucket-tokyo"
  acl    = "private" # バケットは非公開に設定（CloudFront経由でアクセス）

  tags = {
    Name        = "MyS3Bucket"
    Environment = "Production"
  }
}

# S3バケットのウェブホスティング設定
resource "aws_s3_bucket_website_configuration" "my_bucket_website" {
  bucket = aws_s3_bucket.my_bucket.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "error.html"
  }
}

# CloudFront用オリジンアクセスアイデンティティ（OAI）を作成
resource "aws_cloudfront_origin_access_identity" "my_oai" {
  comment = "OAI for S3 bucket"
}

# S3バケットポリシー（CloudFrontからのアクセスを許可）
resource "aws_s3_bucket_policy" "my_bucket_policy" {
  bucket = aws_s3_bucket.my_bucket.id

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect    = "Allow",
        Principal = {
          AWS = aws_cloudfront_origin_access_identity.my_oai.iam_arn
        },
        Action   = "s3:GetObject",
        Resource = "${aws_s3_bucket.my_bucket.arn}/*"
      }
    ]
  })
}

# 簡易的なindex.htmlファイルを作成
resource "local_file" "index_html" {
  content  = "<html><body><h1>React in S3</h1></body></html>"
  filename = "index.html"
}

# index.htmlをS3バケットにアップロード
resource "aws_s3_object" "index_file" {
  bucket       = aws_s3_bucket.my_bucket.id
  key          = "index.html"
  source       = local_file.index_html.filename
  content_type = "text/html"
  acl          = "private" # オブジェクトは非公開に設定
}

# CloudFrontディストリビューションを作成
resource "aws_cloudfront_distribution" "my_distribution" {
  origin {
    domain_name = aws_s3_bucket.my_bucket.bucket_regional_domain_name
    origin_id   = "S3-my-cloudfront-bucket"

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.my_oai.cloudfront_access_identity_path
    }
  }

  enabled             = true
  is_ipv6_enabled     = true
  comment             = "CloudFront Distribution for S3 bucket in Tokyo region"
  default_root_object = "index.html"

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-my-cloudfront-bucket"

    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https" # HTTPをHTTPSにリダイレクト
  }

  restrictions {
    geo_restriction {
      restriction_type = "none" # 地理的制限なし
    }
  }

  price_class = "PriceClass_100"

  viewer_certificate {
    cloudfront_default_certificate = true # デフォルトのCloudFront証明書を使用
  }

  tags = {
    Name        = "MyCloudFrontDistribution"
    Environment = "Production"
  }
}
```

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

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

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