学習のために構築した環境を紹介します。
S3やLambdaなどの主要リソースおよびTerraformを触ったことがある人向けの記事です。
本記事は「①設計・構成解説編」と「②実装詳細編」の2章構成です。
本章では実装詳細の解説をします。
①設計・構成解説編はこちらです。
目次
コード全体は以下から確認ください。
1. 構成の説明
下記を実装し動作させることが今回の学習目的です。
- 静的フロントエンド: S3 + CloudFront
- 動的バックエンド: ALB + ECS Fargate
- 認証・認可: Amazon Cognito による「ALB認証」の導入
- セキュリティ: AWS WAFによる保護、ACMによるHTTPS化、S3のOAC
- 管理: TerraformによるIaC
2. 作成方法
2-1. 前提条件
- AWS アカウント
- Route53 に パブリック Hosted Zoneを作成済み
- Terraform
- AWS CLI(認証済み)
2-2. 構築順序
Terraform上は依存関係で解決されますが、
マネジメントコンソールを使う場合は以下の順に作成します。
- Route53 Hosted Zone(既存)
- ACM(us-east-1)
- S3(静的サイト用)
- CloudFront(OAC + WAF)
- API Gateway
- Lambda
- CloudFront の API オリジン設定
- 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設定が適用されるオリジンの種類を指定
}
- OACを作成
- CloudFrontでS3にアクセスする時にこのOACを使うように設定
- 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ドキュメント
