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?

Terraformで個人開発のAWSインフラを段階的に構築した手順

0
Last updated at Posted at 2026-04-07

はじめに

個人開発でRails API + Next.jsのWebアプリをAWSにデプロイするにあたり、Terraformでインフラをコード化しました。

この記事では、実際にコマンドを打ってインフラを段階的に構築した手順をまとめています。

最終的な構成

インターネット
     │
     ▼
┌──────────────────────────────────────┐
│          CloudFront (CDN)            │
│  /api/* → ALB (バックエンド)          │
│  /*     → S3  (フロントエンド)        │
└──────────────┬───────────────────────┘
               │
       ┌───────┴────────┐
       ▼                ▼
┌────────────┐   ┌────────────────────────┐
│  S3 Bucket │   │  ALB (ロードバランサー) │
│ (Next.js   │   │  public subnet AZ-a/c  │
│  静的ファイ │   └──────────┬─────────────┘
│  ル)       │              │
└────────────┘              ▼
                   ┌─────────────────┐
                   │  ECS Fargate    │
                   │  (Rails API)    │
                   │ private subnet  │
                   └────────┬────────┘
                            │
                            ▼
                   ┌─────────────────┐
                   │  RDS PostgreSQL │
                   │  Primary/Replica│
                   │ private subnet  │
                   └─────────────────┘

【CI/CD】
GitHub Actions
  → OIDC認証でAWS IAMロールを取得
  → ECRへDockerイメージをpush
  → ECSサービスを更新 / S3へフロントエンドをデプロイ

この構成の注意点

  • CloudFront → ALB間はHTTP通信: CloudFrontがHTTPS終端を担い、ALBにはHTTPで転送しています。ALBにSSL証明書は設定していません
  • Railsに assume_ssl = true が必要: CloudFrontでHTTPSが終端されているのに、ALB→ECS間がHTTPのため、Railsが「HTTPSではない」と判断してリダイレクトループになります。この設定でRailsにSSL終端済みと伝えます
  • /api/* はキャッシュTTL=0: CloudFrontはCDN(キャッシュサーバー)なので、デフォルトではレスポンスをキャッシュして使い回します。静的ファイルなら問題ないですが、APIだと「ユーザーAのレスポンスがキャッシュされ、ユーザーBに返る」事故が起きます。これを防ぐためにTTL=0(キャッシュなし)にし、全ヘッダー・Cookieを転送して認証トークンをそのままRailsに渡しています。つまりAPIに関してはCloudFrontはキャッシュではなくただのプロキシとして動いています
  • なぜCloudFrontでAPIをプロキシするのか: フロントエンド(S3)とAPI(ALB)を同じCloudFrontドメインにまとめることで、CORS問題を回避するためです。本来CloudFrontはAPIに使うものではなく、本番サービスでは独自ドメイン+ALBにSSL証明書を設定してCORS設定するのが理想です

構築手順

以下の順序で .tf ファイルを追加しながら terraform apply を繰り返してインフラを段階的に構築しました。

Step 0: 初期設定

mkdir infra && cd infra

main.tf(プロバイダー設定)

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  # tfstateをS3で管理(初回はコメントアウトしてローカルで実行)
  # backend "s3" {
  #   bucket  = "first-step-tfstate"
  #   key     = "terraform.tfstate"
  #   region  = "ap-northeast-1"
  #   profile = "your-profile"
  # }
}

provider "aws" {
  region  = "ap-northeast-1"
  # profile = "your-profile"  ← 自分のAWS CLIプロファイル名に変更
}

variables.tf(変数の型定義)

各.tfファイルで使う変数の名前・説明・デフォルト値を定義します。プログラミングでいう型定義のようなものです。

variable "app_name" {
  description = "アプリケーション名"
  default     = "first-step"
}

variable "environment" {
  description = "環境名"
  default     = "production"
}

variable "aws_region" {
  description = "AWSリージョン"
  default     = "ap-northeast-1"
}

variable "db_username" {
  description = "DBユーザー名"
  default     = "postgres"
}

variable "db_password" {
  description = "DBパスワード"
  sensitive   = true
}

variable "rails_master_key" {
  description = "Railsのマスターキー"
  sensitive   = true
}

variable "github_repository" {
  description = "GitHubリポジトリ名(例: username/first-step)"
}

terraform.tfvars(変数の実値)

variables.tf は変数の「型と名前」を定義するだけで、値は入っていません(default があるものを除く)。terraform.tfvars に実際の値を書きます。terraform apply 時にこのファイルが自動で読み込まれ、var.db_password 等として各.tfファイルから参照できるようになります。

# variables.tf で default がない変数 = ここで値を指定する必要がある
db_password       = "xxxxxxxx"
rails_master_key  = "xxxxxxxx"
github_repository = "username/first-step"

注意: 機密情報を含むため .gitignore に追加してgit管理外にしてください。

terraform init    # AWSプロバイダーをダウンロード

Step 1: ECRリポジトリを作成

Dockerイメージの保管先を先に用意します。

ecr.tf

resource "aws_ecr_repository" "backend" {
  name                 = "${var.app_name}-backend"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }
}

resource "aws_ecr_lifecycle_policy" "backend" {
  repository = aws_ecr_repository.backend.name

  policy = jsonencode({
    rules = [
      {
        rulePriority = 1
        description  = "最新5世代のイメージだけ保持"
        selection = {
          tagStatus   = "any"
          countType   = "imageCountMoreThan"
          countNumber = 5
        }
        action = {
          type = "expire"
        }
      }
    ]
  })
}
terraform apply

この時点でECRリポジトリができるので、GitHub ActionsからDockerイメージをpushできるようになります。

Step 2: VPC・サブネット・ネットワークを構築

vpc.tf

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = { Name = "${var.app_name}-vpc" }
}

# パブリックサブネット(ALB・NATゲートウェイを配置)
resource "aws_subnet" "public_a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "${var.aws_region}a"
  tags = { Name = "${var.app_name}-public-a" }
}

resource "aws_subnet" "public_c" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "${var.aws_region}c"
  tags = { Name = "${var.app_name}-public-c" }
}

# プライベートサブネット(ECS・RDSを配置)
resource "aws_subnet" "private_a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.3.0/24"
  availability_zone = "${var.aws_region}a"
  tags = { Name = "${var.app_name}-private-a" }
}

resource "aws_subnet" "private_c" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.4.0/24"
  availability_zone = "${var.aws_region}c"
  tags = { Name = "${var.app_name}-private-c" }
}

# インターネットゲートウェイ
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags = { Name = "${var.app_name}-igw" }
}

# NATゲートウェイ(プライベートサブネットからのアウトバウンド通信用)
resource "aws_eip" "nat" {
  domain = "vpc"
  tags = { Name = "${var.app_name}-nat-eip" }
}

resource "aws_nat_gateway" "main" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public_a.id
  tags = { Name = "${var.app_name}-nat" }
}

# ルートテーブル
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 = "${var.app_name}-public-rt" }
}

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 = "${var.app_name}-private-rt" }
}

# ルートテーブルをサブネットに紐付け
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
}

resource "aws_route_table_association" "private_a" {
  subnet_id      = aws_subnet.private_a.id
  route_table_id = aws_route_table.private.id
}

resource "aws_route_table_association" "private_c" {
  subnet_id      = aws_subnet.private_c.id
  route_table_id = aws_route_table.private.id
}
terraform apply

Step 3: セキュリティグループを作成

通信の許可ルールをチェーン型で定義します。

インターネット → ALB-SG (80/443)
                  → ECS-SG (3000)
                    → RDS-SG (5432)

security_group.tf

# ALB用(インターネットからHTTP/HTTPSを受け付ける)
resource "aws_security_group" "alb" {
  name   = "${var.app_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"]
  }

  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"]
  }
}

# ECS用(ALBからの通信のみ受け付ける)
resource "aws_security_group" "ecs" {
  name   = "${var.app_name}-ecs-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 3000
    to_port         = 3000
    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"]
  }
}

# RDS用(ECSからの通信のみ受け付ける)
resource "aws_security_group" "rds" {
  name   = "${var.app_name}-rds-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.ecs.id]
  }

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

Step 4: RDSを作成

PostgreSQL 16のPrimary + Read Replicaを構築します。

rds.tf

resource "aws_db_subnet_group" "main" {
  name       = "${var.app_name}-db-subnet-group"
  subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_c.id]
}

resource "aws_db_instance" "primary" {
  identifier        = "${var.app_name}-db-primary"
  engine            = "postgres"
  engine_version    = "16"
  instance_class    = "db.t3.micro"
  allocated_storage = 20

  db_name  = "first_step_production"
  username = var.db_username
  password = var.db_password

  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds.id]

  deletion_protection     = false
  backup_retention_period = 7
  backup_window           = "03:00-04:00"
  maintenance_window      = "Mon:04:00-Mon:05:00"
  skip_final_snapshot     = true
}

resource "aws_db_instance" "replica" {
  identifier          = "${var.app_name}-db-replica"
  instance_class      = "db.t3.micro"
  replicate_source_db = aws_db_instance.primary.identifier

  vpc_security_group_ids = [aws_security_group.rds.id]
  skip_final_snapshot    = true
}
terraform apply    # RDSの作成は数分かかる

Step 5: ALB・ECS・SSMを作成

alb.tf

resource "aws_lb" "main" {
  name               = "${var.app_name}-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = [aws_subnet.public_a.id, aws_subnet.public_c.id]
}

resource "aws_lb_target_group" "backend" {
  name        = "${var.app_name}-tg"
  port        = 3000
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id
  target_type = "ip"

  health_check {
    path                = "/up"
    matcher             = "200-499"
    healthy_threshold   = 2
    unhealthy_threshold = 5
    interval            = 60
    timeout             = 30
  }
}

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

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

ecs.tf

resource "aws_ecs_cluster" "main" {
  name = "${var.app_name}-cluster"
}

# タスク実行ロール(ECRからイメージをpull・SSMからシークレットを取得)
resource "aws_iam_role" "ecs_task_execution" {
  name = "${var.app_name}-ecs-task-execution-role"

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

# タスクロール(ECS Exec用)
resource "aws_iam_role" "ecs_task" {
  name = "${var.app_name}-ecs-task-role"

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

resource "aws_iam_role_policy" "ecs_task_exec" {
  name = "${var.app_name}-ecs-task-exec-policy"
  role = aws_iam_role.ecs_task.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = [
        "ssmmessages:CreateControlChannel",
        "ssmmessages:CreateDataChannel",
        "ssmmessages:OpenControlChannel",
        "ssmmessages:OpenDataChannel"
      ]
      Resource = "*"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "ecs_task_execution" {
  role       = aws_iam_role.ecs_task_execution.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

resource "aws_iam_role_policy" "ecs_ssm" {
  name = "${var.app_name}-ecs-ssm-policy"
  role = aws_iam_role.ecs_task_execution.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = ["ssm:GetParameters", "ssm:GetParameter"]
      Resource = "arn:aws:ssm:${var.aws_region}:*:parameter/${var.app_name}/*"
    }]
  })
}

resource "aws_ecs_task_definition" "backend" {
  family                   = "${var.app_name}-backend"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.ecs_task_execution.arn
  task_role_arn            = aws_iam_role.ecs_task.arn

  container_definitions = jsonencode([{
    name  = "backend"
    image = "${aws_ecr_repository.backend.repository_url}:latest"

    portMappings = [{ containerPort = 3000, protocol = "tcp" }]

    environment = [
      { name = "RAILS_ENV", value = "production" },
      { name = "RAILS_LOG_TO_STDOUT", value = "true" },
      { name = "FRONTEND_URL", value = "https://xxxxx.cloudfront.net" }
    ]

    secrets = [
      { name = "DATABASE_URL", valueFrom = aws_ssm_parameter.database_url.arn },
      { name = "DATABASE_REPLICA_URL", valueFrom = aws_ssm_parameter.database_replica_url.arn },
      { name = "RAILS_MASTER_KEY", valueFrom = aws_ssm_parameter.rails_master_key.arn }
    ]

    logConfiguration = {
      logDriver = "awslogs"
      options = {
        "awslogs-group"         = "/ecs/${var.app_name}-backend"
        "awslogs-region"        = var.aws_region
        "awslogs-stream-prefix" = "ecs"
      }
    }
  }])
}

resource "aws_cloudwatch_log_group" "backend" {
  name              = "/ecs/${var.app_name}-backend"
  retention_in_days = 30
}

resource "aws_ecs_service" "backend" {
  name                   = "${var.app_name}-backend-service"
  cluster                = aws_ecs_cluster.main.id
  task_definition        = aws_ecs_task_definition.backend.arn
  desired_count          = 1
  launch_type            = "FARGATE"
  enable_execute_command = true

  network_configuration {
    subnets          = [aws_subnet.private_a.id]
    security_groups  = [aws_security_group.ecs.id]
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.backend.arn
    container_name   = "backend"
    container_port   = 3000
  }

  lifecycle {
    ignore_changes = [task_definition]
  }
}

ssm.tf

resource "aws_ssm_parameter" "database_url" {
  name  = "/${var.app_name}/DATABASE_URL"
  type  = "SecureString"
  value = "postgresql://${var.db_username}:${var.db_password}@${aws_db_instance.primary.endpoint}/first_step_production"
}

resource "aws_ssm_parameter" "database_replica_url" {
  name  = "/${var.app_name}/DATABASE_REPLICA_URL"
  type  = "SecureString"
  value = "postgresql://${var.db_username}:${var.db_password}@${aws_db_instance.replica.endpoint}/first_step_production"
}

resource "aws_ssm_parameter" "rails_master_key" {
  name  = "/${var.app_name}/RAILS_MASTER_KEY"
  type  = "SecureString"
  value = var.rails_master_key
}
terraform apply

この時点でバックエンドAPIが動く状態になります。

Step 6: S3・CloudFrontを作成

s3.tf

resource "aws_s3_bucket" "frontend" {
  bucket = "${var.app_name}-frontend-${data.aws_caller_identity.current.account_id}"
}

resource "aws_s3_bucket_public_access_block" "frontend" {
  bucket = aws_s3_bucket.frontend.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_policy" "frontend" {
  bucket = aws_s3_bucket.frontend.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Sid       = "AllowCloudFrontAccess"
      Effect    = "Allow"
      Principal = { Service = "cloudfront.amazonaws.com" }
      Action    = "s3:GetObject"
      Resource  = "${aws_s3_bucket.frontend.arn}/*"
      Condition = {
        StringEquals = {
          "AWS:SourceArn" = aws_cloudfront_distribution.frontend.arn
        }
      }
    }]
  })
}

cloudfront.tf

resource "aws_cloudfront_origin_access_control" "frontend" {
  name                              = "${var.app_name}-frontend-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

resource "aws_cloudfront_distribution" "frontend" {
  enabled             = true
  default_root_object = "index.html"

  # S3オリジン(フロントエンド)
  origin {
    domain_name              = aws_s3_bucket.frontend.bucket_regional_domain_name
    origin_id                = "S3-${var.app_name}-frontend"
    origin_access_control_id = aws_cloudfront_origin_access_control.frontend.id
  }

  # ALBオリジン(APIプロキシ)
  origin {
    domain_name = aws_lb.main.dns_name
    origin_id   = "ALB-${var.app_name}-backend"

    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "http-only"
      origin_ssl_protocols   = ["TLSv1.2"]
    }
  }

  # /api/* はALBに転送(キャッシュなし・全ヘッダー転送)
  ordered_cache_behavior {
    path_pattern           = "/api/*"
    target_origin_id       = "ALB-${var.app_name}-backend"
    viewer_protocol_policy = "https-only"
    allowed_methods        = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
    cached_methods         = ["GET", "HEAD"]

    forwarded_values {
      query_string = true
      headers      = ["*"]
      cookies { forward = "all" }
    }

    min_ttl     = 0
    default_ttl = 0
    max_ttl     = 0
  }

  # /up もALBに転送
  ordered_cache_behavior {
    path_pattern           = "/up"
    target_origin_id       = "ALB-${var.app_name}-backend"
    viewer_protocol_policy = "https-only"
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]

    forwarded_values {
      query_string = false
      cookies { forward = "none" }
    }

    min_ttl     = 0
    default_ttl = 0
    max_ttl     = 0
  }

  # デフォルトはS3(フロントエンド)
  default_cache_behavior {
    target_origin_id       = "S3-${var.app_name}-frontend"
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]

    forwarded_values {
      query_string = false
      cookies { forward = "none" }
    }

    min_ttl     = 0
    default_ttl = 3600
    max_ttl     = 86400
  }

  # SPA対応(404/403をindex.htmlにフォールバック)
  custom_error_response {
    error_code         = 404
    response_code      = 200
    response_page_path = "/index.html"
  }

  custom_error_response {
    error_code         = 403
    response_code      = 200
    response_page_path = "/index.html"
  }

  restrictions {
    geo_restriction { restriction_type = "none" }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}
terraform apply

Step 7: GitHub Actions OIDC を設定

GitHub ActionsからAWSにパスワードレス認証でアクセスするための設定です。

oidc.tf

resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}

resource "aws_iam_role" "github_actions" {
  name = "${var.app_name}-github-actions-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Federated = aws_iam_openid_connect_provider.github.arn }
      Action    = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringLike = {
          "token.actions.githubusercontent.com:sub" = "repo:${var.github_repository}:*"
        }
      }
    }]
  })
}

# ECRへのpush権限
resource "aws_iam_role_policy" "github_actions_ecr" {
  name = "${var.app_name}-github-actions-ecr-policy"
  role = aws_iam_role.github_actions.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:PutImage",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload"
      ]
      Resource = "*"
    }]
  })
}

# S3・CloudFrontのデプロイ権限
resource "aws_iam_role_policy" "github_actions_frontend" {
  name = "${var.app_name}-github-actions-frontend-policy"
  role = aws_iam_role.github_actions.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket"]
        Resource = [
          aws_s3_bucket.frontend.arn,
          "${aws_s3_bucket.frontend.arn}/*"
        ]
      },
      {
        Effect   = "Allow"
        Action   = "cloudfront:CreateInvalidation"
        Resource = "*"
      }
    ]
  })
}

# ECSデプロイ権限
resource "aws_iam_role_policy" "github_actions_ecs" {
  name = "${var.app_name}-github-actions-ecs-policy"
  role = aws_iam_role.github_actions.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = [
        "ecs:UpdateService",
        "ecs:DescribeServices",
        "ecs:DescribeTaskDefinition",
        "ecs:RegisterTaskDefinition",
        "iam:PassRole"
      ]
      Resource = "*"
    }]
  })
}
terraform apply

outputs.tf

output "github_actions_role_arn" {
  description = "GitHub ActionsのIAMロールARN(GitHub Secretsに登録する)"
  value       = aws_iam_role.github_actions.arn
}

output "ecr_repository_url" {
  description = "ECRリポジトリURL"
  value       = aws_ecr_repository.backend.repository_url
}

output "alb_dns_name" {
  description = "ALBのDNS名"
  value       = aws_lb.main.dns_name
}

output "cloudfront_domain_name" {
  description = "CloudFrontのドメイン名(FRONTEND_URLに設定する)"
  value       = aws_cloudfront_distribution.frontend.domain_name
}

Step 8: 出力値を確認

terraform output
github_actions_role_arn  = "arn:aws:iam::xxxxx:role/first-step-github-actions-role"
ecr_repository_url       = "xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/first-step-backend"
alb_dns_name             = "first-step-alb-xxxxx.ap-northeast-1.elb.amazonaws.com"
cloudfront_domain_name   = "xxxxx.cloudfront.net"

これらをGitHub SecretsやフロントエンドのAPI URLとして設定します。


まとめ

Terraformの本質はシンプルです。

  1. resource で「こうなってほしい」を宣言する
  2. variable で値をパラメータ化する
  3. リソース間は 種別.名前.属性 で参照する
  4. terraform apply で差分だけ反映される

難しいのはTerraform自体ではなく、AWSのアーキテクチャ設計(ネットワーク設計・セキュリティ・CI/CD認証など)の方でした。

参考

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?