Cross-Region SNS の落とし穴と AWS Budgets — CloudFront アラームの罠
CloudFront 4xx アラームを作ろうとして、半日くらい悩んだ罠について書きます。「provider alias を正しく書いてるのに、なぜか us-east-1 で alarm が作れない」現象の正体は、SNS Topic の region 不一致 でした。
事象
CloudFront 4xx アラームは us-east-1 必須なので、Terraform でこう書きました:
resource "aws_cloudwatch_metric_alarm" "cf_4xx_rate" {
provider = aws.us_east_1 # us-east-1 alias
alarm_name = "iigtn-lab-prod-cf-4xx-rate"
metric_name = "4xxErrorRate"
namespace = "AWS/CloudFront"
...
dimensions = {
DistributionId = var.cloudfront_distribution_id
Region = "Global"
}
alarm_actions = [aws_sns_topic.alarms.arn] # ← 東京の SNS topic
}
この terraform apply がエラーで落ちる:
Error: creating CloudWatch Metric Alarm: ValidationError:
Invalid region ap-northeast-1 specified. Only us-east-1 is supported.
「provider が ap-northeast-1 になってる?でも provider = aws.us_east_1 と書いてるのに…」と混乱。
原因の特定
plan 出力をよく見ると:
# aws_cloudwatch_metric_alarm.cf_4xx_rate will be created
+ resource "aws_cloudwatch_metric_alarm" "cf_4xx_rate" {
+ alarm_actions = [
+ "arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:iigtn-lab-prod-alarms", # ← 東京の SNS!
]
...
}
ここで気付きました。「alarm_actions に渡している SNS Topic ARN が ap-northeast-1」。CloudFront アラームは us-east-1 で作る必要があるが、AWS の制約として 「アラーム本体と alarm_actions の SNS は同一 region 必須」 という規則がある。
エラーメッセージの「Invalid region ap-northeast-1」は、provider の region ではなく、alarm_actions に含まれる ARN の region が us-east-1 と整合してないことを示していた、というのが正解。
解決策: us-east-1 用 SNS Topic を別途作る
SNS Topic も us-east-1 に作って、CloudFront アラームはそちらを参照する設計に変更:
# envs/prod/main.tf に追加
# us-east-1 用 SNS Topic
resource "aws_sns_topic" "alarms_us_east_1" {
provider = aws.us_east_1
name = "iigtn-lab-prod-alarms-use1"
}
resource "aws_sns_topic_subscription" "alarms_us_east_1_email" {
count = var.alarm_email == "" ? 0 : 1
provider = aws.us_east_1
topic_arn = aws_sns_topic.alarms_us_east_1.arn
protocol = "email"
endpoint = var.alarm_email
}
# CloudFront 4xx アラーム
resource "aws_cloudwatch_metric_alarm" "cf_4xx_rate" {
provider = aws.us_east_1
alarm_name = "iigtn-lab-prod-cf-4xx-rate"
metric_name = "4xxErrorRate"
namespace = "AWS/CloudFront"
...
alarm_actions = [aws_sns_topic.alarms_us_east_1.arn] # ← us-east-1 SNS
}
これで apply が通り、CloudFront 4xx アラームが us-east-1 で動き始めました。
結果として 2 つの SNS Topic を持つ構成
| SNS Topic | Region | 用途 |
|---|---|---|
iigtn-lab-prod-alarms | ap-northeast-1(東京) | Lambda / API Gateway アラーム |
iigtn-lab-prod-alarms-use1 | us-east-1(バージニア北部) | CloudFront アラーム |
email subscription も両方に設定。同じメアドが両方に登録される設計。Confirm メールが 2 通来ます。
クロスリージョン制約の他の例
AWS でリージョン跨ぎがダメなパターンは結構あります:
| パターン | 制約 |
|---|---|
| CloudWatch Alarm ↔ SNS Topic | 同一 region 必須 |
| CloudWatch Logs ↔ Subscription Filter | 同一 region 必須 |
| CloudFront ↔ ACM 証明書 | ACM が us-east-1 必須 |
| CloudFront メトリクス | us-east-1 でのみ取得可能 |
| S3 Bucket Policy ↔ Bucket | 同一(バケットの region) |
CloudFront 系は特に「グローバルだけど中身は us-east-1」という設計が多く、初学者の混乱の元。
今回のハマりで得た教訓
- エラーメッセージの「Invalid region xxx」を読み違えない。provider の region ではなく、参照している ARN の region を疑う
- plan 出力をよく読む。「期待しない値」が混入してないか確認
- クロスリージョン制約は AWS の至るところに散在。CloudFront / WAF / Global Accelerator などグローバル系は要注意
- 同じ責務のリソース(SNS Topic)が region ごとに 2 個ある状態は許容。料金も微々たるもの
AWS Budgets の細かい設定
前記事で Budgets の基本を書きましたが、もう少し細かい設定について。
サービス別予算
「全体予算」ではなく「Lambda 予算」「S3 予算」など特定サービスに絞った予算も作れます:
resource "aws_budgets_budget" "lambda_only" {
name = "lambda-only-monthly"
budget_type = "COST"
limit_amount = "5"
limit_unit = "USD"
time_unit = "MONTHLY"
cost_filter {
name = "Service"
values = ["Amazon Elastic Compute Cloud - Compute"] # Lambda
}
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "ACTUAL"
subscriber_email_addresses = [var.alarm_email]
}
}
cost_filter で他にも絞れる
| filter | 意味 |
|---|---|
Service | サービス別(Lambda / S3 / EC2 等) |
LinkedAccount | サブアカウント別(Organizations 構成時) |
Operation | API オペレーション別 |
UsageType | 料金タイプ別(DataTransfer-Out 等) |
TagKeyValue | リソースタグ別(Project=iigtn 等) |
本シリーズでは default_tags で全リソースに Project=iigtn を付けているので、後で「iigtn プロジェクトだけの予算」を Tag フィルタで作れます。
Cost Explorer との併用
Budgets はアラート、Cost Explorer は分析ツール。これで月末に「何にいくら使ったか」を可視化:
aws ce get-cost-and-usage \
--time-period Start=2026-04-01,End=2026-05-01 \
--granularity MONTHLY \
--metrics "UnblendedCost" \
--group-by Type=DIMENSION,Key=SERVICE
Cost Explorer は GUI だと Web コンソールから「Bills」「Cost Explorer」で確認可能。Budget アラートが鳴った時の犯人特定に必須。
本シリーズの実際の月額(参考)
| サービス | 月額(円・概算) |
|---|---|
| S3 (state + web) | ~5 |
| DynamoDB (lock + contacts) | ~2 |
| CloudFront 転送量 (5 GB) | ~85 |
| API Gateway HTTP API | ~0.2 |
| Lambda 実行 | 0(無料枠内) |
| Lambda CloudWatch Logs | ~10 |
| CloudWatch Alarms (4 個) | ~60 |
| SNS | ~0 |
| 合計 | 約 170〜200 円 |
個人運用の最安ライン。月額 1,000 円超えたら何かおかしいと思って Cost Explorer で詳細見る、というのが運用ルール。
次の記事
監視・コスト管理が一通り揃ったところで、次は 「ドキュメントとアーキテクチャ図を作る」 話に入ります。本シリーズではアーキテクチャ図を Mermaid → drawio (AWS 公式アイコン) に進化させた経緯があり、その記録を書きます。
📚 用語集
- クロスリージョン制約
- AWS で「リソース A は region X、リソース B は region Y にある時、両者を直接参照できない」という制約。CloudWatch Alarm ↔ SNS / CloudFront ↔ ACM 等で発生。
- 4xxErrorRate (CloudFront メトリクス)
- CloudFront のレスポンスが 4xx エラーだった割合(パーセント)。攻撃や Bot 流入の早期検知に使える。
- Region dimension (CloudFront)
- CloudFront メトリクスのディメンション。値は
Global(全 Edge 合算)/ 個別 region コード(特定 Edge)が指定できる。 - cost_filter (AWS Budgets)
- 予算を特定サービス・タグ・アカウント等に絞り込むフィルタ。「Lambda だけ月 $5」のような設定が可能。
- UnblendedCost
- AWS の請求データの基本となるコスト形式。Reserved Instance 等の割引適用前。
- Cost Explorer
- AWS の課金分析ツール。月別・サービス別・タグ別等で課金を可視化できる。Budget アラート発火時の原因調査に必須。
- default_tags (Provider)
- AWS provider のオプション。指定したタグがその provider で作られる全リソースに自動付与される。Cost Allocation Tag として活用可能。
- Cost Allocation Tag
- AWS の課金レポートで「タグ別の課金集計」を有効にしたタグ。Billing コンソールで有効化が必要。
- Confirm subscription (SNS)
- SNS Email Subscription が有効になるための確認ステップ。AWS から送られる確認メールのリンクをクリック。
- region 跨ぎ参照
- Terraform で別 region のリソースを参照する設計。provider alias を駆使する。本シリーズでは us-east-1 と ap-northeast-1 を併用。
- provider alias 経由のリソース
- Terraform で
provider = aws.us_east_1を付けたリソース。指定 region に作られる。 - SNS Topic ARN の region 部分
- ARN の 4 番目フィールド(
arn:aws:sns:<REGION>:...)。これと CloudWatch Alarm の region が一致しないとアラーム作成失敗。 - FORECASTED (Budgets)
- AWS の機械学習による「月末予測値」。実績で気付くより早く異常を検知できる予算閾値タイプ。