👾

ログにエラーが出たときに AWS Chatbot で Slack に通知してみる

2024/03/29に公開

はじめに

CloudWatch のログにエラーなど特定の文字列がある場合に PagerDuty を鳴らすまでも無いけど通知は受けたいというケースがあったので AWS Chatbot を使って Slack に通知をしてみようと思います。

今回の構成

連携の流れは以下です。

  • 適当な Lambda を作成して CloudWatch にログを出します。
  • カスタムメトリクスフィルターで閾値が1以上になったら SNS topic にメッセージを送信します。
  • SNS topic のサブスクリプションとして Slack と連携した Chatbot が Slack に通知します。

サンプルのファイル構成は以下です。

./
├── README.md
├── alarm.tf
├── chatbot.tf
├── functions
│   └── main.py
├── lambda.tf
├── log.tf
├── main.tf
├── metric.tf
├── outputs
│   └── functions.zip
├── topic.tf
└── variables.tf

実際に作成したものは以下で公開しています。
https://github.com/Mo3g4u/aws-chatbot-sample

ログにエラーを吐く Lambda を作成する

Lambda がログを出力するロググループを作成します。

log.tf
resource "aws_cloudwatch_log_group" "default" {
  name              = "/aws/lambda/chatbot-sample-lambda"
  retention_in_days = 7
}

Lamda を作成します。

lambda.tf
data "archive_file" "lambda" {
  type        = "zip"
  source_dir  = "${path.module}/functions"
  output_path = "${path.module}/outputs/functions.zip"
}

resource "aws_lambda_function" "default" {
  depends_on = [
    aws_cloudwatch_log_group.default,
  ]

  function_name    = "chatbot-sample-lambda"
  runtime          = "python3.12"
  handler          = "main.lambda_handler"
  filename         = data.archive_file.lambda.output_path
  source_code_hash = data.archive_file.lambda.output_base64sha256
  role             = aws_iam_role.default.arn

}

resource "aws_iam_role" "default" {
  name               = "chatbot-sample-lambda-role"
  assume_role_policy = data.aws_iam_policy_document.assume_lambda.json

  inline_policy {
    name   = "chatbot-sample-lambda-policy"
    policy = data.aws_iam_policy_document.default.json
  }
}

data "aws_iam_policy_document" "default" {
  statement {
    effect = "Allow"
    actions = [
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ]
    resources = [
      "${aws_cloudwatch_log_group.default.arn}:*",
    ]
  }
}

data "aws_iam_policy_document" "assume_lambda" {
  statement {
    actions = [
      "sts:AssumeRole"
    ]

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

実際に Lambda で実行する python です。標準出力に Error: xxxxxxxxx を出力します。
今回はこの Error の文字で通知を行います。

functions/main.py
import json

def lambda_handler(event, context):
    print("Error: xxxxxxxxx")

    return {
        'statusCode': 200,
        'body': json.dumps('Chatbot sample lambda function!')
    }

SNS Topic を作成する

アラームを受けるトピックを作成します。

topic.tf
resource "aws_sns_topic" "default" {
  name = "terraform-chatbot-sample-sns-topic"
}

Chatbot を作成する

Chatbot を作成します。 slack_channel_id, slack_workspace_id は引数で受け取ります。
Slack との連携や ワークスペースID、 チャンネルIDの取得方法は次のページを参考にさせていただきました。
https://qiita.com/akis1215/items/50648b2d2d4accedcc4f

chatbot.tf
resource "awscc_chatbot_slack_channel_configuration" "default" {
  configuration_name = "terraform-chatbot-sample-channel"
  iam_role_arn = awscc_iam_role.default.arn
  slack_channel_id = var.slack_channel_id
  slack_workspace_id = var.slack_workspace_id
  sns_topic_arns = [aws_sns_topic.default.arn]
}

# Chatbotが使用するIAMロールを作成するリソース
resource "awscc_iam_role" "default" {
  role_name = "Terraform-ChatBot-Sample-Channel-Role"

  # このポリシーはChatbotサービスがこのロールを引き受けることを許可する
  assume_role_policy_document = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "chatbot.amazonaws.com"
        }
      },
    ]
  })

  # このロールにアタッチする管理ポリシーのARN
  managed_policy_arns = ["arn:aws:iam::aws:policy/AWSResourceExplorerReadOnlyAccess"]
}

カスタムメトリクスフィルターを作成する

ログに出力された Error を検知するためにカスタムメトリクスフィルターを作成します。

metric.tf
resource "aws_cloudwatch_log_metric_filter" "default" {
  name           = "ErrorFromChatbotSampleLambda"
  pattern        = "Error"
  log_group_name = aws_cloudwatch_log_group.default.name

  metric_transformation {
    name          = "ErrorFromChatbotSampleLambda"
    namespace     = "chatbot-sample-lambda"
    value         = "1"
    unit          = "Count"
    default_value = "0"
  }
}

アラームを作成する

カスタムメトリクスフィルターで Error のカウントが 1 になったら SNS Topic に通知するアラームを作成します。

alarm.tf
resource "aws_cloudwatch_metric_alarm" "default" {
  alarm_name          = "ErrorFromChatbotSampleLambdaAlarm"
  alarm_description   = "This metric monitors the error from chatbot sample lambda"
  evaluation_periods  = 1
  datapoints_to_alarm = 1
  namespace           = aws_cloudwatch_log_metric_filter.default.metric_transformation[0].namespace
  metric_name         = aws_cloudwatch_log_metric_filter.default.metric_transformation[0].name
  period              = 60
  statistic           = "Sum"
  threshold           = 1
  comparison_operator = "GreaterThanOrEqualToThreshold"
  alarm_actions       = [aws_sns_topic.default.arn]
}

Terraform を実行する

今回は variables.tf で以下を指定しているので slack_channel_idslack_workspace_id を指定して plan, apply する

variables.tf
variable "region" {
  description = "AWS region"
  default     = "ap-northeast-1"
  type        = string
}

variable "slack_channel_id" {
  description = "value of slack channel id"
  type        = string
}

variable "slack_workspace_id" {
  description = "value of slack workspace id"
  type        = string
}
% terraform init
% terraform validate
% terraform fmt -recursive
% terraform plan -var 'slack_channel_id=xxxxxxxxxxx' -var 'slack_workspace_id=xxxxxxxxxxxxxxxxx'
% terraform apply -var 'slack_channel_id=xxxxxxxxxxx' -var 'slack_workspace_id=xxxxxxxxxxxxxxxxx'

これで Lambda を実行するとログに Error の文字が出力されて Slack に通知が来る様になります💪

お掃除

不要になったら忘れずにお掃除します。

% terraform destroy -var 'slack_channel_id=xxxxxxxxxxxxxxxxx' -var 'slack_workspace_id=xxxxxxxxxxxxxxxxx'

メモ

AWS Chatbot は AWS Provider で作成することができず、awscc という、AWS Cloud Control API を使用するところで戸惑いました。
https://kakakakakku.hatenablog.com/entry/2023/10/11/091320

https://registry.terraform.io/providers/hashicorp/awscc/latest

また、事前にコンソール上でも同じ構成を作成していたのですが、 デフォルトの状態で画面から作成していくと Chatbot の権限が広めに設定されていたので最小権限にするところで少し悩みました🐹

おわりに

実際にやってみると AWS Chatbot 以外のところは今までやってきたことの組み合わせだったのであまり詰まることなく Terraform 化できました!

レスキューナウテックブログ

Discussion