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でCloudWatchアラームをSlack通知してみよう!

0
Posted at

はじめに

CloudWatchアラームが飛んだら即Slackに通知が来てほしい……というのは最近のインフラ運用あるあるですよね。

案件で必要になったのですが、AWSのマネコンでの実装/設定が原則禁止されていたため、Terraformで実装することにしました。そこで「AWS Chatbot Terraform」でググったところ、公式ドキュメントのタイトルが軒並み「Amazon Q Developer in chat applications」になっていて、あれ、これ何????となったんですね。どうやらサービス名がいつの間にか変わっていたようでして。。

改めて調べてみたところ、単なる名前変更かと思いきや、生成AI機能の統合という機能追加も伴う改名だったようです。詳細は次のセクションで整理しますね。

というわけで本記事では、CloudWatchアラームをSlackに通知する仕組みを、原則Terraformで構築する手順を整理しました。ただ正式名称で書くと毎回長くて読みにくいので、本記事内では「Chatbot」と呼ぶことにします(Terraformのリソース名もaws_chatbot_slack_channel_configurationのまま変わっていないので、コード的にも「Chatbot」呼びの方が良さそうですね)。

実際に手を動かしてみたら、GUIでの初期設定とTerraformでのコード管理が思わぬところで衝突してハマったので、その過程も含めて共有します。

Chatbot(Amazon Q Developer in chat applications)とは

CloudWatchなどのAWSサービスからのSNS通知を受け取り、Slack・Microsoft Teamsのチャットチャンネルに転送してくれるサービスです。

  • 2025年2月19日に「AWS Chatbot」から「Amazon Q Developer in chat applications」に改名
  • 通知送信・CLIコマンド実行・アクションボタンといった既存の非生成AI機能は変更なし・追加料金なし
  • 同時にAmazon Q Developerの生成AI機能(チャットでの質問応答、Amazon Bedrock Agentsの呼び出しなど)が統合された。ただしオプトイン方式で、IAMロールとガードレールポリシーの両方にAmazonQDeveloperAccessポリシーを追加しないと使えない。しかもFree tier限定(Pro tier契約者でもchat内はFree tier制限)
  • TerraformのAWS providerにおけるリソース名(aws_chatbot_slack_channel_configurationなど)は変更なし

全体構成

今回作る仕組みは以下のようなイメージです。

全体構成図

検証用にLambda関数を1つ用意し、意図的にエラーを起こしてCloudWatch Alarmを発火させ、Slackまで通知が届くことを確認します。

GUIでしか出来ないこと・Terraformで出来ること

一通り構築してみて分かったのですが、Chatbotまわりは「全部Terraform化できる」わけではなく、明確に境界線がありました。

作業 実施方法 理由
Slackワークスペースの認可(OAuth許可) AWSコンソール(GUI)必須 Slack側の同意画面を経由するOAuthフローのため、対応するAPI/CLI/Terraformリソースが存在しない
Slackチャンネル設定(IAMロール・SNSトピックの紐付けなど) Terraformで管理可能 aws_chatbot_slack_channel_configuration リソースとして定義できる

つまり「ワークスペースの認可は最初に手作業で1回だけ、それ以降のチャンネル設定はIaCで管理」というのが境界でした。この境界線を知らずに突っ込んだせいで、後述の通りきっちりハマることになりました。。。

補足:マルチアカウントで運用する場合

なお、ワークスペース認可は「AWSアカウントごとに独立して行う」仕組みなので、1つのSlackワークスペースに複数のAWSアカウントを紐づけることもできます。dev・stg・prodといった複数アカウントの通知を同じワークスペースに集める、マルチアカウント監視の構成も普通に組めるようです。その場合のポイントは以下の通りです。

  • ワークスペース認可(OAuth)はアカウントごとに1回ずつGUIで実施が必要(前述の手作業ポイントがアカウント数分発生する)
  • ワークスペースID(slack_team_id)は、どのアカウントから認可しても同じ値になる
  • 後述する「1つのSlackチャンネルに紐付けられるチャンネル設定は1つだけ」という制約はあくまでアカウント内での話なので、複数アカウントから同一チャンネルへ通知を送ること自体は可能

ただし、同じチャンネルに複数アカウントの通知を集約すると「これどのアカウントのアラーム?」となりがちなので、#alarm-prod#alarm-devのようにアカウントごとに通知チャンネルを分けるのがおすすめです。

やってみる!

まずはGUIでワークスペース認可とチャンネル設定をやってみる

まずは肩慣らしとして、AWSコンソール(GUI)から一通り設定して動作確認してみることにしました。

① Slackワークスペースの認可

  1. AWSマネジメントコンソールで「Amazon Q Developer in chat applications」を開く(検索窓に "chatbot" と入れても出てきます。旧称がちゃんと併記されているのは親切ですね)

    Amazon Q Developer in chat applicationsのトップ画面

  2. 「チャットクライアントを設定」から「Slack」を選択して「クライアントを設定」

  3. Slack側の認可画面に遷移するので、通知を送りたいワークスペースを選んで「許可する」

    Slack側のOAuth認可画面

  4. 認可が完了すると、ワークスペース詳細画面に遷移する。この時点では設定済みチャネルは0件

    認可直後は設定済みチャネルが0件

ここでワークスペースID(slack_team_idTから始まるID)が確認できるので控えておきましょう。

② Slackチャンネルの設定

ワークスペース認可だけだとまだSlackに通知は飛ばないので、続けて「新しいチャネルを設定」から、通知先チャンネルの設定も作成します。設定名はchatbot-notification、IAMロールはウィザードのデフォルト名AWSChatbot-role、ガードレールポリシーはReadOnlyAccessのまま、SNSトピックは未選択(動作確認だけなのでこの時点では不要)で作成しました。

設定が完了すると「設定済みチャネル (1)」になり、Slack側の「テストメッセージを送信」機能で疎通確認もできました。
よし、ちゃんと設定できた。。。と、この時点では特に何も問題ないと思っていました。。。

GUIでのチャンネル設定完了後の画面

Slack側では、チャンネル詳細の一番下からチャンネルID(Cから始まるID)を確認できます。

Slack側でのチャンネルID確認

Terraformで実装する

以下のリポジトリにコード一式を公開しているので、細部まで見たい方はこちらをご覧ください。

ディレクトリ構成

terraform/
├── providers.tf
├── variables.tf
├── lambda.tf     # 検証用Lambda
├── sns.tf        # SNS Topic
├── alarm.tf      # CloudWatch Alarm
├── chatbot.tf    # Chatbot(IAM Role + Slackチャンネル設定)
├── outputs.tf
└── src/
    └── index.py  # Lambdaのソース

provider / variables

providers.tf
terraform {
  required_version = ">= 1.15.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.56"
    }
    archive = {
      source  = "hashicorp/archive"
      version = "~> 2.8"
    }
  }
}

provider "aws" {
  region = var.aws_region
}
variables.tf
variable "aws_region" {
  description = "AWSリージョン"
  type        = string
  default     = "ap-northeast-1"
}

variable "project_prefix" {
  description = "リソース名のプレフィックス"
  type        = string
  default     = "cw-slack-alarm-demo"
}

variable "slack_team_id" {
  description = "Chatbot(Amazon Q Developer in chat applications)で認可済みのSlackワークスペースID"
  type        = string
}

variable "slack_channel_id" {
  description = "通知先SlackチャンネルのID"
  type        = string
}

検証用Lambda

エラーを意図的に起こせるだけのシンプルな関数。

src/index.py
def handler(event, context):
    if event.get("force_error"):
        raise Exception("Intentional error for CloudWatch Alarm verification")

    return {"statusCode": 200, "body": "OK"}
lambda.tf
data "archive_file" "demo" {
  type        = "zip"
  source_dir  = "${path.module}/src"
  output_path = "${path.module}/build/demo.zip"
}

resource "aws_iam_role" "lambda_exec" {
  name = "${var.project_prefix}-lambda-exec"

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

resource "aws_iam_role_policy_attachment" "lambda_basic" {
  role       = aws_iam_role.lambda_exec.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

resource "aws_lambda_function" "demo" {
  function_name    = "${var.project_prefix}-func"
  role             = aws_iam_role.lambda_exec.arn
  handler          = "index.handler"
  runtime          = "python3.13"
  filename         = data.archive_file.demo.output_path
  source_code_hash = data.archive_file.demo.output_base64sha256
  timeout          = 10
}

SNS Topic

CloudWatch AlarmからSNSにPublishできるよう、Topic Policyでcloudwatch.amazonaws.comを明示的に許可しておく。同一アカウント内ならデフォルトポリシーのままでも動作はするものの、aws:SourceAccount条件付きで明示的に許可しておく方が、confused deputy問題への対策としてもベストプラクティス。

sns.tf
data "aws_caller_identity" "current" {}

resource "aws_sns_topic" "alarm" {
  name = "${var.project_prefix}-alarm-topic"
}

data "aws_iam_policy_document" "alarm_topic" {
  statement {
    sid     = "AllowCloudWatchAlarmsToPublish"
    effect  = "Allow"
    actions = ["sns:Publish"]

    principals {
      type        = "Service"
      identifiers = ["cloudwatch.amazonaws.com"]
    }

    resources = [aws_sns_topic.alarm.arn]

    condition {
      test     = "StringEquals"
      variable = "aws:SourceAccount"
      values   = [data.aws_caller_identity.current.account_id]
    }
  }
}

resource "aws_sns_topic_policy" "alarm" {
  arn    = aws_sns_topic.alarm.arn
  policy = data.aws_iam_policy_document.alarm_topic.json
}

CloudWatch Alarm

LambdaのErrorsメトリクスを監視し、1分間に1回でもエラーが出たらALARMにする(検証しやすいようevaluation_periods/thresholdは緩めに設定)。

alarm.tf
resource "aws_cloudwatch_metric_alarm" "lambda_errors" {
  alarm_name          = "${var.project_prefix}-lambda-errors"
  alarm_description   = "Lambda関数でエラーが発生した場合にアラーム"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  threshold           = 1

  metric_name = "Errors"
  namespace   = "AWS/Lambda"
  period      = 60
  statistic   = "Sum"

  dimensions = {
    FunctionName = aws_lambda_function.demo.function_name
  }

  alarm_actions = [aws_sns_topic.alarm.arn]
  ok_actions    = [aws_sns_topic.alarm.arn]

  treat_missing_data = "notBreaching"
}

Chatbot(IAM Role + Slackチャンネル設定)

aws_chatbot_slack_channel_configurationの必須パラメータはconfiguration_name / iam_role_arn / slack_channel_id / slack_team_idの4つ。SNSトピックとの紐付けはsns_topic_arnsに配列で渡す。

chatbot.tf
resource "aws_iam_role" "chatbot" {
  name = "${var.project_prefix}-chatbot-role"

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

resource "aws_iam_role_policy_attachment" "chatbot_cloudwatch_read" {
  role       = aws_iam_role.chatbot.name
  policy_arn = "arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess"
}

resource "aws_chatbot_slack_channel_configuration" "alarm" {
  configuration_name = "${var.project_prefix}-slack"
  iam_role_arn       = aws_iam_role.chatbot.arn
  slack_channel_id   = var.slack_channel_id
  slack_team_id      = var.slack_team_id

  sns_topic_arns = [aws_sns_topic.alarm.arn]

  guardrail_policy_arns = ["arn:aws:iam::aws:policy/ReadOnlyAccess"]

  logging_level = "ERROR"

  tags = {
    Name = "${var.project_prefix}-slack"
  }
}

iam_role_arnに指定するロールは、Chatbotが引き受ける(AssumeRoleする)専用ロールです。今回は通知の閲覧用途がメインなのでCloudWatchReadOnlyAccessのみアタッチしていますが、Slack側の「View Details」ボタンから他サービスの詳細を見たい場合は、必要な範囲で読み取り系ポリシーを追加する必要があります。

guardrail_policy_arnsは未指定だとAWS管理のAdministratorAccessポリシーがデフォルトで適用されます(Terraform公式ドキュメントに明記あり)。実効権限はチャンネルロールとガードレールの重なる範囲に制限されるため即座に危険というわけではありませんが、GUIウィザードのデフォルト(ReadOnlyAccess)と挙動が異なるので、上記のように明示的に指定しておくのが安心です。

いざapply!→エラーで詰まった

ということで、ここまでのコードでterraform applyしたところ、こんなエラーで落ちてしまいました。

Error: creating AWS Chatbot Slack Channel Configuration

  with aws_chatbot_slack_channel_configuration.alarm,
  on chatbot.tf line 19, in resource "aws_chatbot_slack_channel_configuration" "alarm":
  19: resource "aws_chatbot_slack_channel_configuration" "alarm" {

operation error chatbot: CreateSlackChannelConfiguration, https response
error StatusCode: 400, RequestID: ...,
InvalidRequestException: Slack channel with ID CXXXXXXXXXX in Slack team
TXXXXXXXXXX has already been configured for AWS account xxxxxxxxxxxx.

「もう設定済みだよ」というエラーですね。原因は明らかですが、先ほどGUIから作った、あの動作確認用のチャンネル設定と重複している、、、ということですね。

原因はGUIで作った動作確認用の設定

AWS CLIのCloud Control API経由で確認すると、以下のように設定が見つかります。

aws cloudcontrol list-resources \
  --type-name AWS::Chatbot::SlackChannelConfiguration \
  --region ap-northeast-1
{
  "ResourceDescriptions": [
    {
      "Identifier": "arn:aws:chatbot::xxxxxxxxxxxx:chat-configuration/slack-channel/chatbot-notification",
      "Properties": "{\"ConfigurationName\":\"chatbot-notification\",\"IamRoleArn\":\"arn:aws:iam::xxxxxxxxxxxx:role/service-role/AWSChatbot-role\", ...}"
    }
  ]
}

さっき作ったchatbot-notificationという設定が、そのまま残っていますね。同じSlackチャンネルに対してChatbotのチャンネル設定は1つしか紐付けられない仕様のようで、先にGUIで作った設定と、後からTerraformで作ろうとした設定が衝突していたわけですね。考えてみれば当たり前ではあるものの、「動作確認のつもりで作ったリソースが、後のコード化を妨げる」というのはある意味よくある話ですね。。。残念ながら。

ただ、aws chatbot describe-slack-channel-configurationsコマンドでも同様の情報は取れるはずなのですが、手元の環境ではなぜか取得ができませんでした。Cloud Control API経由なら問題なく引けたので、詰まったら代替手段として覚えておくと良さそうです。とはいえ、これが今後参考になる知識かはわかりませんが^^;

一旦削除してTerraformで作り直す

今回はGUI側の設定を削除し、Terraformでゼロから作り直す方針にしました。terraform import(importブロック)で既存設定をTerraform管理下に取り込む手もあるのですが、GUIで作った設定は設定名やIAMロールがウィザードのデフォルト名のままになってしまうため、コード側の命名に揃えたい今回は作り直しの方がシンプルと判断しました。

aws cloudcontrol delete-resource \
  --type-name AWS::Chatbot::SlackChannelConfiguration \
  --identifier "arn:aws:chatbot::xxxxxxxxxxxx:chat-configuration/slack-channel/chatbot-notification" \
  --region ap-northeast-1

削除できたことを確認してから、改めてterraform applyを実行します。そして、、、今度は無事に作成できました!

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

chatbot_configuration_arn = "arn:aws:chatbot::xxxxxxxxxxxx:chat-configuration/slack-channel/cw-slack-alarm-demo-slack"

疎通確認

では、Lambdaにforce_error: trueを渡して、意図的にエラーを起こしてみましょう。

aws lambda invoke \
  --function-name cw-slack-alarm-demo-func \
  --payload '{"force_error": true}' \
  --cli-binary-format raw-in-base64-out \
  /tmp/lambda-out.json
{"errorMessage": "Intentional error for CloudWatch Alarm verification", "errorType": "Exception", ...}

想定通りエラーが発生しました。1分ほど待ってCloudWatch Alarmの状態を確認してみます。

aws cloudwatch describe-alarms \
  --alarm-names cw-slack-alarm-demo-lambda-errors \
  --query 'MetricAlarms[0].StateValue'
"ALARM"

無事ALARM状態に遷移してくれました。そしてSlackチャンネルを確認すると、ちゃんと通知が届いていました。

Slackに届いたCloudWatch Alarm通知

Namespace・Metric・Timestamp・Alarm Descriptionまできれいに表示されていて、CloudWatchのコンソールを開かなくても状況が把握できるのは素晴らしいですね!!!
メール通知だと不要な記載も多くわかりにくいため、Lambda等による加工が実質必須、、、でしたが、マネージドに必要な情報だけに整形してくれるのは本当に楽で助かる!

コンソールの「テストメッセージを送信」機能も地味に便利

記事を書きながら気づいたのですが、Chatbotのコンソールには「テストメッセージを送信」ボタンが存在します。

Terraform管理下のチャンネル設定一覧

「テストメッセージを送信」を押すと、設定されているSNSトピック宛に1件メッセージがディスパッチされました。

テストメッセージがディスパッチされた

Slack側にもきちんとテストメッセージが届くことが確認できました。

Slackに届いたテストメッセージ

CloudWatch Alarmまわりの設定に自信が無いときや、「Slackへの経路自体は生きているか」を切り分けたいときに、わざわざLambdaをエラーさせなくても、この機能だけでSNSトピックからSlackまでの疎通確認ができます。リソース作成の役割が縦割りにされている現場だと、アラーム通知テストの実施だけで時間がかかりがちなので、これは案件でも大変助かりそうです。

おわりに

CloudWatchアラームのSlack通知自体は、Lambda・SNS・CloudWatch Alarmの部分だけ見れば特に難しいことではありません。ハマったのは完全に自分の段取りが原因でした。振り返ってみれば、「ワークスペース認可はGUI、チャンネル設定はTerraform」という境界線を最初に知ってさえいれば避けられたハマり方だったなぁと反省です。。。

チームで運用しているCloudWatchアラームがまだ通知先未設定という場合は、まずこの構成で通知先を整備して、「テストメッセージを送信」機能で疎通確認までぜひ試してみましょう!!

参考

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?