09. CloudWatch / SNS / SQS
運用に必須の 「ログ・メトリクス・通知・キュー」。CloudWatch でログとメトリクスを取り、メトリクスのしきい値超過を SNS で通知、Lambda 等の非同期処理は SQS で受ける。AWS 運用の基本セット。
この章の目次
aws_cloudwatch_log_group
CloudWatch Logs はログの保管庫。ロググループ という単位でログを束ねます。Lambda、ECS、API Gateway などはここに書き込みます。
# Lambda 用ロググループ。Lambda の命名規約は /aws/lambda/{関数名}
# これを先に作っておくと、関数の暗黙作成より retention や暗号化が制御できる
resource "aws_cloudwatch_log_group" "lambda" {
name = "/aws/lambda/hello"
retention_in_days = 14 # ← 重要。指定しないと「無期限」になり料金が膨らむ
}
# API Gateway アクセスログ用
resource "aws_cloudwatch_log_group" "api" {
name = "/aws/apigw/hello-api"
retention_in_days = 14
}
# ECS タスクログ用(コンテナの stdout/stderr が流れてくる)
resource "aws_cloudwatch_log_group" "ecs" {
name = "/ecs/myapp"
retention_in_days = 30
kms_key_id = aws_kms_key.logs.arn # 任意: KMS で暗号化
}
retention_in_days を必ず
デフォルトは「無期限保持」= ログが永久に増え続けます。費用事故の代表例。初学者は必ず 7〜30 日で設定。
サブスクリプションフィルタ(外部送信)
# CloudWatch Logs → Kinesis → 外部 SaaS という流れの最初の 1 ステップ
# log_group に流れるログを filter_pattern でフィルタして destination に転送
resource "aws_cloudwatch_log_subscription_filter" "to_kinesis" {
name = "to-kinesis"
log_group_name = aws_cloudwatch_log_group.lambda.name
filter_pattern = "" # 全件("ERROR" などで絞り込みも可)
destination_arn = aws_kinesis_stream.logs.arn
role_arn = aws_iam_role.logs_to_kinesis.arn # CWLogs が Kinesis に書く権限
}
aws_cloudwatch_metric_alarm
「特定のメトリクスが、N 期間連続でしきい値を超えたら、X に通知する」を定義。
# メトリクスアラーム本体
# 「period 秒の期間を evaluation_periods 回連続で threshold を超えたら通知」
resource "aws_cloudwatch_metric_alarm" "lambda_errors" {
alarm_name = "lambda-hello-errors"
alarm_description = "Lambda hello のエラーが 5 分間で 5 回を超えた"
comparison_operator = "GreaterThanThreshold" # >, >=, <, <= 等
evaluation_periods = 1 # 評価対象の連続期間数
threshold = 5 # しきい値
metric_name = "Errors" # メトリクス名
namespace = "AWS/Lambda" # サービス名前空間
period = 300 # 5 分(メトリクス集計期間 秒)
statistic = "Sum" # Sum / Average / Maximum など
# 同じメトリクス名でも対象を絞るためのキー
dimensions = {
FunctionName = aws_lambda_function.hello.function_name
}
# ALARM 状態に入った時 / OK に戻った時の通知先(複数指定可)
alarm_actions = [aws_sns_topic.alerts.arn]
ok_actions = [aws_sns_topic.alerts.arn]
# データ無し(呼ばれていない)時の扱い。breaching/notBreaching/missing/ignore
treat_missing_data = "notBreaching"
}
典型的なアラーム例
| 監視対象 | namespace | metric_name |
|---|---|---|
| Lambda エラー | AWS/Lambda | Errors |
| EC2 CPU | AWS/EC2 | CPUUtilization |
| RDS CPU | AWS/RDS | CPUUtilization |
| RDS 接続数 | AWS/RDS | DatabaseConnections |
| ALB 5xx | AWS/ApplicationELB | HTTPCode_Target_5XX_Count |
| SQS 積み残し | AWS/SQS | ApproximateNumberOfMessagesVisible |
aws_sns_topic と購読
SNS は 「pub/sub」 の仕組み。トピックに publish したメッセージが、購読者全員に配信されます。アラートメール/Slack 連携/他サービスへの fan-out で頻出。
# SNS トピック(pub/sub の購読者を束ねる箱)
resource "aws_sns_topic" "alerts" {
name = "alerts"
kms_master_key_id = "alias/aws/sns" # AWS 管理 KMS で保管時暗号化(無料)
}
# メール購読
# 作成後 AWS から確認メールが届く → ユーザーがクリックして初めて有効化
resource "aws_sns_topic_subscription" "email" {
topic_arn = aws_sns_topic.alerts.arn
protocol = "email"
endpoint = "ops@example.com"
}
# Slack: AWS Chatbot 経由(推奨)
resource "aws_sns_topic_subscription" "slack" {
topic_arn = aws_sns_topic.alerts.arn
protocol = "https"
endpoint = var.chatbot_webhook # AWS Chatbot で発行した URL
}
# Lambda 購読(イベント駆動の処理)
resource "aws_sns_topic_subscription" "lambda" {
topic_arn = aws_sns_topic.alerts.arn
protocol = "lambda"
endpoint = aws_lambda_function.notify.arn
}
# Lambda 側のリソースベース許可(SNS がこの関数を呼んで OK)
resource "aws_lambda_permission" "from_sns" {
statement_id = "AllowSNSInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.notify.function_name
principal = "sns.amazonaws.com"
source_arn = aws_sns_topic.alerts.arn # この topic だけ許可
}
メール購読は AWS から確認メールが飛び、ユーザーが「Confirm subscription」リンクをクリックして初めて有効になります。
aws_sqs_queue(DLQ 含む)
SQS は キュー。送信側と受信側を疎結合にし、受信側がダウンしてもメッセージが滞留するだけで消えません。
# DLQ(Dead Letter Queue): 失敗メッセージの最終受け皿。先に作っておく
resource "aws_sqs_queue" "jobs_dlq" {
name = "jobs-dlq"
message_retention_seconds = 1209600 # 14 日(最大)。原因調査の時間を確保
kms_master_key_id = "alias/aws/sqs"
}
# メインキュー
resource "aws_sqs_queue" "jobs" {
name = "jobs"
# ワーカがメッセージ受信後 N 秒間はそのメッセージが他に見えない
# ワーカの処理時間 + マージンを設定。短すぎると重複処理の原因
visibility_timeout_seconds = 60 # ワーカが処理する想定時間
message_retention_seconds = 345600 # 4 日
kms_master_key_id = "alias/aws/sqs"
# 失敗メッセージを DLQ に流す設定(JSON エンコードで渡す)
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.jobs_dlq.arn
maxReceiveCount = 5 # 5 回失敗したら DLQ へ
})
}
キューポリシー(送信元の制限)
# キューポリシー:「特定の SNS トピックからの SendMessage だけ許可」
data "aws_iam_policy_document" "sqs_jobs" {
statement {
sid = "AllowSNSToPublish"
effect = "Allow"
actions = ["sqs:SendMessage"]
principals {
type = "Service"
identifiers = ["sns.amazonaws.com"] # SNS サービス
}
resources = [aws_sqs_queue.jobs.arn]
# ソース ARN を絞らないと他の SNS トピックからも投げ込まれてしまう
condition {
test = "ArnEquals"
variable = "aws:SourceArn"
values = [aws_sns_topic.events.arn]
}
}
}
# 上で組み立てた policy_document をキューに割り当て
resource "aws_sqs_queue_policy" "jobs" {
queue_url = aws_sqs_queue.jobs.id
policy = data.aws_iam_policy_document.sqs_jobs.json
}
SNS → SQS の fan-out パターン
同じイベントを複数の処理系に届けたい時の定番。
# 同じ SNS トピックに 2 つの SQS を購読 → 同じメッセージが両方に届く
# 各キューはそれぞれ別のワーカが独立した速度で処理できる
resource "aws_sns_topic_subscription" "to_sqs_a" {
topic_arn = aws_sns_topic.events.arn
protocol = "sqs"
endpoint = aws_sqs_queue.a.arn
}
resource "aws_sns_topic_subscription" "to_sqs_b" {
topic_arn = aws_sns_topic.events.arn
protocol = "sqs"
endpoint = aws_sqs_queue.b.arn
}
SNS が 1 メッセージを受けると、SQS A・B の両方に同じ内容が配信される。それぞれのワーカは自分のキューを独立した速度で処理できる、というのがこのパターンの強み。