0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【AWS × Terraform】CloudFront + S3 + ALB + ECS + Cognito 構成をIaCで構築②【実装詳細】

0
Last updated at Posted at 2026-01-01

学習のために構築した環境を紹介します。
S3やLambdaなどの主要リソースおよびTerraformを触ったことがある人向けの記事です。
本記事は「①設計・構成解説編」と「②実装詳細編」の2章構成です。
本章では実装詳細の解説をします。

①設計・構成解説編はこちらです。

目次

コード全体は以下から確認ください。

1. 構成の説明

下記を実装し動作させることが今回の学習目的です。

  • 静的フロントエンド: S3 + CloudFront
  • 動的バックエンド: ALB + ECS Fargate
  • 認証・認可: Amazon Cognito による「ALB認証」の導入
  • セキュリティ: AWS WAFによる保護、ACMによるHTTPS化、S3のOAC
  • 管理: TerraformによるIaC

17669366212026421859020893644490.jpg


2. 作成方法

2-1. 前提条件

  • AWS アカウント
  • Route53 に パブリック Hosted Zoneを作成済み
  • Terraform
  • AWS CLI(認証済み)

2-2. 構築順序

Terraform上は依存関係で解決されますが、
マネジメントコンソールを使う場合は以下の順に作成します。

  1. Route53 Hosted Zone(既存)
  2. ACM(us-east-1)
  3. S3(静的サイト用)
  4. CloudFront(OAC + WAF)
  5. API Gateway
  6. Lambda
  7. CloudFront の API オリジン設定
  8. Route53 Alias レコード

3. Terraform構成

3-1. フォルダ構成例

.
├── Dockerfile
├── backend.tf
├── ecs.tf
└── frontend.tf

※今回の学習目的から逸れるためモジュール化はしていません。


3-2. 構成詳細

パブリックホストゾーンの情報取得

data "aws_route53_zone" "primary" {
    name         = var.domain_name //ドメイン名を指定
    private_zone = false //取得対象がパブリックホストゾーン
}

ACM証明書発行

resource "aws_acm_certificate" "cert" {
    provider          = aws.us-east-1 # us-east-1を指定
    domain_name       = var.domain_name # ドメイン名を指定
    validation_method = "DNS" # DNS検証を指定
    subject_alternative_names = ["*.${var.domain_name}"] # www 付きのドメインも使用するのでサブドメインも含めて検証
    lifecycle {
        create_before_destroy = true # 既存の証明書を消す前に新規作成することでダウンタイムをなくす
    }
}

証明書の検証

resource "aws_route53_record" "cert_validation" {
    for_each = { # DNSレコード検証に必要な情報(レコードセット : レコード名、タイプ、値など) を格納
        for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
            name   = dvo.resource_record_name
            type   = dvo.resource_record_type
            record = dvo.resource_record_value
        }
    }
    allow_overwrite = true  # レコードの上書きを許可
    zone_id = data.aws_route53_zone.primary.zone_id # DNSレコードを作成するホストゾーンを指定
    name    = each.value.name # for_eachで取得したnameの値を取得
    type    = each.value.type # CNAMEなどのレコードタイプを取得
    ttl     = 60              # レコードのキャッシュ有効期限を60秒に設定
    records = [each.value.record] # DNSレコードの値を取得
}

ACM証明書の所有権を証明するために、Route 53にDNSレコードを登録するリソースです。

ステップ リソース名 役割
1. 申請 aws_acm_certificate 証明書をリクエストし、検証用レコードの情報を生成
2. 登録 aws_route53_record (今回のコード) 生成された情報を Route 53 の DNS レコードとして書き込む
3. 待機 aws_acm_certificate_validation レコードが反映され、ACMが「検証成功」の状態になるまで待機

パブリックアクセスブロック

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
}

S3への直接アクセスを禁止し、CloudFrontのみを入り口にすることで、WAFによる保護を確実に適用できます。
また、S3の設定ミスによる情報の流出を防ぐことができます。


バケットポリシー

resource "aws_s3_bucket_policy" "frontend" {
    bucket = aws_s3_bucket.frontend.id # バケットを指定
    policy = data.aws_iam_policy_document.s3_policy.json # ポリシーを設定
}

# OACからのアクセスを許可するためのポリシーを定義
data "aws_iam_policy_document" "s3_policy" {
    statement {
       principals {
            type        = "Service"
            identifiers = ["cloudfront.amazonaws.com"]
        }

    actions = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.frontend.arn}/*"]
    
    condition {
    test     = "StringEquals"
    variable = "AWS:SourceAccount"
    values   = [data.aws_caller_identity.current.account_id]
    }
  }
}

CloudFrontからのアクセスのみ許可するバケットポリシーを設定しています。


OAC

resource "aws_cloudfront_origin_access_control" "frontend_oac" {
    name                              = "frontend-oac" # OAC設定につける名前
    description                       = "OAC for frontend distribution"
    signing_behavior                  = "always" # CloudFrontがS3バケットにアクセスする際に、常にリクエストに署名するよう指定
    signing_protocol                  = "sigv4" # リクエストに署名するために使用するプロトコルを指定
    origin_access_control_origin_type = "s3" # OAC設定が適用されるオリジンの種類を指定
}
  1. OACを作成
  2. CloudFrontでS3にアクセスする時にこのOACを使うように設定
  3. S3のバケットポリシーでこのOACを持っているCloudFrontからのアクセスのみ許可

CloudFrontディストリビューション

1. 基本設定・全般

resource "aws_cloudfront_distribution" "s3_distribution" {
    provider = aws.us-east-1
    enabled = true                                        # CloudFrontディストリビューションを有効化
    aliases = [var.domain_name, "www.${var.domain_name}"] # wwwありなし両方でCloudFrontディストリビューションにアクセス可能
    default_root_object = "index.html"                   # ルートパスアクセス時のデフォルトファイル

S3(静的コンテンツ)と ALB(動的API)を1つのドメインで共存させるための設定です。

2. オリジン設定

    # S3のオリジン設定
    origin {
        domain_name                = aws_s3_bucket.frontend.bucket_regional_domain_name # CloudFrontがコンテンツを取得するS3バケットのドメイン名を指定
        origin_id                  = aws_s3_bucket.frontend.id # オリジンを一意に識別するためのIDを指定
        origin_access_control_id   = aws_cloudfront_origin_access_control.frontend_oac.id # *オリジンアクセス制御(OAC)**のIDを指定
    }

   # ALBへのオリジン設定
    origin {
    origin_id = "alb-origin"
    domain_name = aws_lb.backend_alb.dns_name
    origin_path = ""

    custom_origin_config {
    http_port              = 80
    https_port             = 443
    origin_protocol_policy = "https-only"
    origin_ssl_protocols   = ["TLSv1.2"]
    origin_read_timeout    = 60
    origin_keepalive_timeout = 5
    }
    }
項目 意味
S3オリジン フロントエンドのHTML/CSS/JSファイルを配置している場所
origin_access_control_id OACを使用し、S3への直接アクセスを禁止してCloudFront経由に限定
origin_protocol_policy オリジン(ALB)との通信を必ずHTTPS(暗号化)で行う設定

3. キャッシュ挙動

# デフォルト(S3)のキャッシュ挙動
    default_cache_behavior {
        allowed_methods = ["GET", "HEAD", "OPTIONS"] # CloudFrontがオリジンにリクエストを送信する際に許可されるHTTPメソッド
        cached_methods  = ["GET", "HEAD"]            # CloudFrontがキャッシュできるHTTPメソッド
        target_origin_id = aws_s3_bucket.frontend.id # デフォルトのキャッシュ動作が適用されるオリジンのID
        viewer_protocol_policy = "redirect-to-https" # HTTPリクエストをHTTPSにリダイレクト
        compress = true                              # CloudFrontがコンテンツを圧縮して配信
        cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" # Managed-CachingOptimized ポリシーのID。静的コンテンツ(例:画像、CSS、JavaScriptファイルなど)のキャッシュに最適化された設定
    }

# APIエンドポイントへのリクエストをALBにルーティング
    ordered_cache_behavior {
    path_pattern     = "/api/*"
    target_origin_id = "alb-origin"
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods = ["GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH", "DELETE"]
    cached_methods  = ["GET", "HEAD"]
    compress = true
    cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" # Managed-CachingDisabled ポリシーのID
    origin_request_policy_id = data.aws_cloudfront_origin_request_policy.all_viewer.id
    }

    # 既存のprodパスもALBにルーティング
    ordered_cache_behavior {
    path_pattern     = "/prod/*"
    target_origin_id = "alb-origin"
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods = ["GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH", "DELETE"]
    cached_methods  = ["GET", "HEAD"]
    compress = true
    cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" # Managed-CachingDisabled ポリシーのID
    origin_request_policy_id = data.aws_cloudfront_origin_request_policy.all_viewer.id
    }

パスによって、S3に行くかAPIに行くかを振り分けています。

設定ブロック 対象パス 内容
default_cache_behavior 全て(S3) S3用。画像やJSを効率よく配信するためキャッシュを有効化
ordered_cache_behavior /api/*、/prod/* API用。リアルタイム性が重要なためキャッシュを無効化
viewer_protocol_policy 共通 HTTPでのアクセスを自動的にHTTPSへリダイレクト
allowed_methods 共通 API用ではPOSTやPUTなど、全HTTPメソッドの通過を許可

4. セキュリティ・証明書

    restrictions {
    geo_restriction {
        restriction_type = "whitelist" # リストにある国からのアクセスを許可する
        locations        = ["JP"]      # 許可する国を日本(JP)に設定
      }
    }

    viewer_certificate {
        acm_certificate_arn = aws_acm_certificate_validation.cert.certificate_arn # CloudFrontがHTTPS接続を確立するために使用するACM証明書のARNを指定
        ssl_support_method  = "sni-only" # SSL/TLSのサポート方法を指定
        minimum_protocol_version = "TLSv1.2_2021" # 最低限のTLSバージョンを指定
    }
項目 意味
geo_restriction 地理制限。今回は「日本 (JP)」からのアクセスのみ許可
acm_certificate_arn HTTPS通信に必要なSSL証明書を適用
minimum_protocol_version 古い脆弱な通信プロトコルを禁止

WAF(CloudFront用)

1. 基本設定

resource "aws_wafv2_web_acl" "frontend_waf" {
    provider = aws.us-east-1
    name = "frontend-waf" # WAFの名前
    scope = "CLOUDFRONT" # WAFの適用範囲
    description = "WAF for frontend distribution"

    default_action { # Web ACLに含まれるどのルールにも一致しなかったリクエストに対して、WAFが取るべきアクションを定義
        allow {} # デフォルトのアクションを「許可」に設定。つまり、明示的に拒否するルールがない限り、すべてのトラフィックが通過
    }
項目 設定値 意味
provider aws.us-east-1 CloudFront用WAFは、必ずバージニア北部リージョンで作成
scope CLOUDFRONT このWAFがCloudFront用であることを指定
default_action allow {} 基本は通す設定。どのルールにも該当しない通信を許可

2. ルール設定

# クロスサイトスクリプティングをブロックするルールを追加
    rule {
        name     = "AWSManagedRulesCommonRuleSetRule"
        priority = 1
        
        statement {
            managed_rule_group_statement {
                name        = "AWSManagedRulesCommonRuleSet"
                vendor_name = "AWS"
            }
        }
        
        visibility_config {
            cloudwatch_metrics_enabled = true
            metric_name                = "AWSManagedRulesCommonRuleSetRuleMetric"
            sampled_requests_enabled   = true
        }
        
        override_action {
            none {}
        }
    }

    visibility_config {
        cloudwatch_metrics_enabled = true
        metric_name                = "FrontendWebAcl"
        sampled_requests_enabled   = true
    }
}

マネージドルールを利用することで、一般的なウェブ攻撃を自動で防ぐように設定しています。

項目 意味
name (Rule) AWSManagedRulesCommonRuleSet
priority 1
override_action none {}

visibility_config ブロックは、WAFがどう動いているかを記録するための設定です。

項目 設定値 意味
cloudwatch_metrics_enabled true 検知したリクエスト数をCloudWatchでグラフ化
metric_name 任意 CloudWatch上で表示されるメトリクスの名前
sampled_requests_enabled true ブロックされたリクエストのIPアドレスやURLをサンプルとして数件確認できるようにする

visibility_config ブロックが2箇所にあるのは、個別のルール用とWeb ACL全体用で役割が分かれているからです。


Route53レコード

resource "aws_route53_record" "root" {
  zone_id = data.aws_route53_zone.primary.zone_id # レコードを追加する対象のホストゾーンID
  name    = var.domain_name # 作成するレコード名
  type    = "A"

  alias {
    name                   = aws_cloudfront_distribution.s3_distribution.domain_name # CloudFrontのドメイン名
    zone_id                = aws_cloudfront_distribution.s3_distribution.hosted_zone_id # CloudFrontサービス自体のホストゾーンID
    evaluate_target_health = false
  }
}

取得した独自ドメインへのアクセスを、CloudFrontに繋げるための設定です。
CloudFrontは特定のIPアドレスを持たないので通常のAレコードは使えません。
そのためAlias機能を使い、Route 53がCloudFrontのIPアドレスを解決してAレコードを生成するようにします。
example.com のようなドメイン名そのものに対しては、CNAMEを設定できないのでAレコードでAlias機能を使います。


ALB

resource "aws_lb" "backend_alb" {
  name               = "backend-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb_sg.id]
  subnets            = [aws_subnet.public_1.id, aws_subnet.public_2.id]

  enable_deletion_protection = false
パラメータ名 設定値 / 内容 説明
internal false インターネット向けかどうかの設定
load_balancer_type "application" ALB を指定
security_groups [aws_security_group.alb_sg.id] 80/443番ポートを許可するSGを適用
subnets [...public_1.id, ...public_2.id] 2つの異なるAZのパブリックサブネットを指定
enable_deletion_protection false Terraformから削除操作が可能

ALBのターゲットグループ

resource "aws_lb_target_group" "backend_alb_tg" {
  name        = "backend-alb-tg"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id
  target_type = "ip"
パラメータ名 設定値 説明
port 80 ターゲットが待ち受けているポート番号
protocol "HTTP" ALBからターゲットへの通信に使用するプロトコル
vpc_id aws_vpc.main.id ターゲットが存在するVPCを指定
target_type "ip" Fargateを使用する場合は ip を指定

AWS Fargateを利用する場合、各タスクに個別のIPアドレスが割り当てられるため、ターゲットタイプを ip に設定するのが一般的です。

ALBのヘルスチェック

 health_check {
    enabled             = true
    healthy_threshold   = 2
    interval            = 30
    matcher             = "200"
    path                = "/"
    port                = "traffic-port"
    protocol            = "HTTP"
    timeout             = 5
    unhealthy_threshold = 2
  }

パラメータ名 設定値 説明
enabled true ヘルスチェックを有効化
path "/" 正常確認のためにALBがアクセスするパス
matcher "200" 成功とみなすHTTPステータスコード
interval 30 チェックを行う間隔(秒)
timeout 5 応答を待つタイムアウト時間(秒)
healthy_threshold 2 連続何回成功したら「正常」とみなすか
unhealthy_threshold 2 連続何回失敗したら「異常」とみなし、通信を遮断するか

ALBのリスナー

resource "aws_lb_listener" "backend_alb_https_listener" {
  load_balancer_arn = aws_lb.backend_alb.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS-1-2-2017-01"
  certificate_arn   = aws_acm_certificate_validation.alb_cert.certificate_arn
パラメータ名 設定値 説明
load_balancer_arn aws_lb.backend_alb.arn 紐付けるALB本体の識別子
port "443" 待ち受けるポート(HTTPS標準)
protocol "HTTPS" 通信プロトコル
ssl_policy "ELBSecurityPolicy-TLS-1-2-2017-01" 使用するSSL/TLSのセキュリティポリシー
certificate_arn aws_acm_certificate_validation... ACM(AWS Certificate Manager)で管理しているSSL証明書を指定

ALBのリスナーのデフォルトアクション

  default_action {
    order = 1
    type = "authenticate-cognito"
    
    authenticate_cognito {
      user_pool_arn       = aws_cognito_user_pool.backend_user_pool.arn
      user_pool_client_id = aws_cognito_user_pool_client.backend_user_pool_client.id
      user_pool_domain    = aws_cognito_user_pool_domain.backend_user_pool_domain.domain
    }
  }

  default_action {
    order = 2
    type  = "forward"
    
    forward {
      target_group {
        arn = aws_lb_target_group.backend_alb_tg.arn
      }
    }
  }

デフォルトアクション
このコードでは order(優先順位)を使って、2つのアクションを順番に実行させています。

順序 (Order) アクションタイプ 内容
1 authenticate-cognito 認証: AWS Cognitoを使用してユーザーがログインしているか確認します。未ログインならログイン画面へ飛ばします。
2 forward 転送: 認証が成功した場合のみ、ターゲットグループ(ECS)へリクエストを流します。
  • マネージドな認証実装(Cognito連携)
    アプリケーション側にログイン機能を実装しなくても、ALBがフロントに立って認証を代行してくれます。
  • HTTPS(SSL/TLS)の必須化
    Cognito認証を利用する場合、通信の安全性を確保するためにHTTPS(443番ポート)とSSL証明書の紐付けが必須となります。
  • SSLポリシーの指定
    ssl_policy により、古い脆弱なプロトコル(TLS 1.0など)を拒否し、安全な通信のみを許可するように制限しています。
  • 順序(Order)の重要性
    必ず order = 1 で認証を行い、それが通った時だけ order = 2 で転送されるように設計されています。

ALBのリスナーのリダイレクトのアクション

resource "aws_lb_listener" "backend_alb_http_listener" {
  load_balancer_arn = aws_lb.backend_alb.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

HTTPアクセスを、強制的に安全なHTTPSアクセスへ転送する設定です。

パラメータ名 設定値 説明
load_balancer_arn aws_lb.backend_alb.arn 設定を適用するALB本体の識別子
port "80" 待ち受けるポート番号
protocol "HTTP" 通信プロトコルとしてHTTPを指定
default_action.type "redirect" 受け取ったリクエストを別のURLに飛ばす

リダイレクト(Redirect)ブロックの内容

項目 設定値 説明
port "443" 転送先のポート番号
protocol "HTTPS" 転送後のプロトコル
status_code "HTTP_301" 恒久的な移動を意味するステータスコード

認証不要のAPIエンドポイント用リスナールール

resource "aws_lb_listener_rule" "public_api" {
  listener_arn = aws_lb_listener.backend_alb_https_listener.arn
  priority     = 100

  action {
    type = "forward"
    forward {
      target_group {
        arn = aws_lb_target_group.backend_alb_tg.arn
      }
    }
  }

  condition {
    path_pattern {
      values = ["/api/public/*"]
    }
  }
}

ALBのリスナーにおいて特定のパス(URL)に限って、Cognitoによるログイン認証をスキップさせるための設定です。

パラメータ名 設定値 説明
listener_arn backend_alb_https_listener.arn 紐付ける対象のHTTPSリスナー(443ポート)を指定
priority 100 ルールの優先順位
action.type "forward" リクエストの処理方法
target_group_arn backend_alb_tg.arn 転送先のバックエンド(ECSなど)のターゲットグループ
path_pattern "/api/public/*" このルールが適用されるURLのパターン

この設定が必要な理由

  • ヘルスチェックの正常動作
    ALBがターゲット(ECSコンテナなど)の生存確認をする際、認証がかかっていると「302 Redirect(ログイン画面へ)」が返り、ALBが異常と判断してコンテナを強制終了させます。これを防ぐために一部のパスを解放します。
  • ユーザー登録・ログイン前の機能提供
    「新規会員登録API」や「パスワードリセットAPI」などは、ログイン前(認証前)に叩ける必要があるため、この設定で除外します。

設定のポイント

  • 優先度
    デフォルトのCognito認証よりも優先度を高くすることでログインを求められないようにします。

  • パスの設計
    values = ["/api/public/*"] のように、特定のプレフィックス(/public/)をつけておくことでどのAPIが認証不要かコードを見ただけでわかるようにします。

ALB用のセキュリティグループ

項目 設定値 / 内容
インバウンド (80) 0.0.0.0/0 からの TCP 80番
インバウンド (443) 0.0.0.0/0 からの TCP 443番
アウトバウンド 全てのポート、全ての宛先 (-1, 0.0.0.0/0)
  • Web標準ポートの開放
    WebサイトやAPI公開に必須となる 80 (HTTP) と 443 (HTTPS) のみに絞って受け入れ(Ingress)を許可
  • フルオープンのアウトバウンド
    egress(送信)の設定で protocol = "-1" となっているのは、すべてのプロトコルを許可し、ALBがバックエンドのターゲットグループ(ECS)へリクエストを転送する際に通信がブロックされるのを防ぐ

WAF(ALB用)

resource "aws_wafv2_web_acl" "backend_waf" {
  name  = "backend-waf"
  scope = "REGIONAL"
  description = "WAF for backend ALB"

  default_action {
    allow {}
  }

  rule {
    name     = "AWSManagedRulesCommonRuleSetRule"
    priority = 1
    
    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"
      }
    }
    
    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesCommonRuleSetRuleMetric"
      sampled_requests_enabled   = true
    }
    
    override_action {
      none {}
    }
  }

  rule {
    name     = "AWSManagedRulesKnownBadInputsRuleSetRule"
    priority = 2
    
    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesKnownBadInputsRuleSet"
        vendor_name = "AWS"
      }
    }
    
    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "AWSManagedRulesKnownBadInputsRuleSetRuleMetric"
      sampled_requests_enabled   = true
    }
    
    override_action {
      none {}
    }
  }
パラメータ名 設定値 説明
scope "REGIONAL" ALBに適用するためにリージョン指定
default_action allow {} どのルールにも合致しなかったリクエストを、デフォルトで許可

適用されているマネージドルール詳細

ルール名 (Priority) マネージドルール名 防御する主な脅威
AWSManagedRulesCommonRuleSetRule (1) CommonRuleSet (コアルールセット) 一般的なWeb攻撃、OWASP Top 10に含まれるような脆弱性、サイズ制限超過など
AWSManagedRulesKnownBadInputsRuleSetRule (2) KnownBadInputsRuleSet 悪意のあることが判明している入力パターンや、不正なリクエスト元からの攻撃

可視化・モニタリング設定(visibility_config)

項目 設定値 説明
cloudwatch_metrics_enabled true ルールごとの検知状況をCloudWatchメトリクスとして送信
metric_name (各ルール固有の名称) CloudWatch上でグラフ表示される際のメトリクス名
sampled_requests_enabled true 実際に検知・ブロックされたリクエストの詳細を確認

CloudFrontとALBの両方にWAFを導入することで、ALBのパブリックIP/DNSが漏洩した場合でも、バックエンドが直接攻撃にさらされるのを防ぐことができます。


ECS

# ECS クラスターの作成
resource "aws_ecs_cluster" "backend_cluster" {
  name = "backend-cluster"

  configuration {
    execute_command_configuration {
      logging = "DEFAULT"
    }
  }
}

# ECS タスク定義
resource "aws_ecs_task_definition" "backend_task" {
  family                   = "backend-task"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.ecs_execution_role.arn

  container_definitions = jsonencode([
    {
      name  = "nginx-container"
      image = "${var.ecr_uri}:${var.ecr_version}"
      portMappings = [
        {
          containerPort = 80
          protocol      = "tcp"
        }
      ]
      essential = true
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = aws_cloudwatch_log_group.ecs_logs.name
          awslogs-region        = "ap-northeast-1"
          awslogs-stream-prefix = "ecs"
        }
      }
      healthCheck = {
        command = ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
        interval = 30
        timeout = 5
        retries = 3
      }
    }
  ])
}

# CloudWatch ログ グループ
resource "aws_cloudwatch_log_group" "ecs_logs" {
  name              = "/ecs/backend-task"
  retention_in_days = 7
}

# ECS サービス
resource "aws_ecs_service" "backend_service" {
  name            = "backend-service"
  cluster         = aws_ecs_cluster.backend_cluster.id
  task_definition = aws_ecs_task_definition.backend_task.arn
  desired_count   = 1
  launch_type     = "FARGATE"

  network_configuration {
    subnets          = [aws_subnet.private_1.id, aws_subnet.private_2.id]
    security_groups  = [aws_security_group.ecs_sg.id]
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.backend_alb_tg.arn
    container_name   = "nginx-container"
    container_port   = 80
  }

  depends_on = [aws_lb_listener.backend_alb_https_listener]
}

# ECS 実行ロール
resource "aws_iam_role" "ecs_execution_role" {
  name = "ecs-execution-role"

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

# ECS 実行ロールにポリシーをアタッチ
resource "aws_iam_role_policy_attachment" "ecs_execution_role_policy" {
  role       = aws_iam_role.ecs_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

# ECS用セキュリティグループ
resource "aws_security_group" "ecs_sg" {
  name        = "ecs-sg"
  description = "Allow inbound traffic from ALB to ECS"
  vpc_id      = aws_vpc.main.id

  # ALBからのHTTPアクセスを許可
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.alb_sg.id]
  }

  # 外部への通信を許可(ECRからのイメージプルなどに必要)
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "ecs-sg"
  }
}

1 ECSの核となる3つのリソース

リソース パラメータ 説明
Cluster name コンテナを実行する論理的なグループ
Task Definition cpu, memory 1つのコンテナを動かすのに必要なリソース量
Service desired_count 常に起動させておくタスクの数

2 コンテナ内部とログの設定

設定項目 内容 説明
image ${var.ecr_uri} ECRから取得するイメージを指定
logConfiguration awslogs コンテナの標準出力をCloudWatch Logsへ送信し、ブラウザからログを確認
healthCheck curl -f... コンテナ単体で正常にアプリが動いているか、内部から定期的にチェック

3 ネットワークとセキュリティ(Service / SG内)

設定項目 設定値 説明
subnets private_1, 2 タスクをプライベートサブネットに配置
assign_public_ip false プライベート配置のため、パブリックIPは不要
Ingress (SG) Port 80 ALBのセキュリティグループからのみ接続を許可
Egress (SG) All ECRからのイメージ取得や、外部APIとの通信のために全開放

設定のポイント

  • 依存関係の制御 (depends_on)
    aws_lb_listener が完成してからサービスを起動するように指定しています。これにより、入口(ALB)が準備できていないのにコンテナが先に立ち上がってエラーになるのを防いでいます。
  • 高セキュアなネットワーク設計
    コンテナをプライベートサブネットに隠し、通信はALB経由のみに絞ることで、インフラ層での防御を固めています。

4. 改善点

  • CI/CDの導入
  • Terraform の module 化
  • 各リソースの改善(下記に詳細を記載)

ECS

可用性の向上(マルチAZの活用)

  • 改善点: desired_count = 2 に設定。
    タスク数を増やしてマルチAZにすることで可用性が向上します。

デプロイ・サーキットブレーカーの導入

  • 改善点: deployment_circuit_breaker { enable = true, rollback = true } を追加。
    ECSサービスのデプロイに失敗した際、いつまでもリトライを繰り返してデプロイが完了しない状況を防ぎます。

WAF

  • 改善点:コード内のマネージドポリシーIDの動的取得。
    マネージドポリシーのIDそのものが更新されて無効になることはほとんどありませんが、Dataソースを用いることでIDを動的に取得でき、コードの意図も明確になります。
data "aws_cloudfront_cache_policy" "optimized" {
  name = "Managed-CachingOptimized"
}

# 使うときはIDを自動で代入
cache_policy_id = data.aws_cloudfront_cache_policy.optimized.id

S3

S3バケットのバージョニング

  • 改善点: aws_s3_bucket_versioning を有効化。
    ファイルを誤って削除・更新しても過去のバージョンに戻せます。

SPA(Single Page Application)への対応

  • 改善点: aws_cloudfront_distribution に custom_error_response ブロックを追加し、403/404エラーを /index.html にリダイレクト(ステータス200)するように設定。

ルート以外のパス(例:/about)に直接アクセスした際、 S3 で 403 エラーが発生するのを防ぎます。

Route53

  • 改善点: Route53のevaluate_target_health が false になっているのを true に変更。
    CloudFrontのヘルスチェックと連動し、以下のようなメリットがあります。

Route 53 ヘルスチェック有効化の主要なメリット

メリット 具体的な内容 導入後の効果
正常な接続先への誘導 異常を検知した接続先(CloudFront/ALB)をDNSの回答から自動的に除外します。 ユーザーがエラー画面を見る確率を下げ、常に稼働している環境へ繋ぎます。
エラーの切り分け DNSレベルでの「健全性」が可視化され、インフラ層かアプリ層のどちらに問題があるか即座に判断できます。 障害時の調査時間を大幅に短縮し、見当違いな修正による二次被害を防ぎます。
自己修復のトリガー CloudWatchアラートと連携し、異常検知を合図に予備システムの起動や管理者の呼び出しを自動化します。 人の手を介さない「セルフヒーリング」の仕組みを構築でき、復旧までの時間を短縮します。
無駄なリクエストの削減 そもそも「死んでいる(応答できない)」場所へリクエストを流さないようにします。 無駄な通信コストの抑制や、障害中のサーバーへのさらなる負荷(追い打ち)を防ぎます。

Cognito

  • 改善点: advanced_security_mode = "OFF"を AUDIT または ENFORCE への変更。
    漏洩した資格情報によるログイン試行を検知したり、リスクベースの認証(普段と違う場所からのログインに警告を出す等)が可能になります。

ALB

  • 改善点: CloudFrontが付与するカスタムヘッダーをALB側で検証し、ヘッダーがないリクエストを拒否。
    ALBに直接攻撃されるのを防ぐためです。

5. 参照

CloudFrontドキュメント
S3ドキュメント
ECSドキュメント
ELBドキュメント
Terraformドキュメント

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?