AWSサービスをTerraformでコード化する:AWS Chatbot編

AWS

2024.3.11

Topics

はじめに

こんにちは、Shunです。

今回は、ChatbotをTerraformを使ってコード化する機会があったため、その方法をご紹介します。

本記事では以下のコード化を行っています。

関連記事
AWS Chatbotを使って、Slack通知をカスタマイズする

想定読者

  • ChatbotやSNSなどの関連リソースを使用している方
  • AWSのコード化を検討している方

本記事で取り扱う内容

  • Chatbotのコード化の方法
  • コード化を実装する上での注意点

本記事で取り扱わない内容

  • ChatbotやSNSのサービス説明
  • Chatbotなどの設定値(これらは上述したブログでご紹介しています。)

実施すること

以下でご紹介する内容をTerraformでコード化したものです。

関連記事
AWS Chatbotを使って、Slack通知をカスタマイズする

Chatbotなどの関連サービスを初めて使用する方は、先に上記の記事を読むことをお勧めします。

料金

  • Chatbot: 無料
  • SNS: $0.5/100万通知
  • CloudWatch Alarm: $0.1/アラームメトリクス(月)
  • EventBridge: $1.00/100万イベント

今回の検証を通して、発生する費用は数円です。

構成図

今回実装するTerraformの構成は以下の通りです。

前提

  • Terraform環境がセットアップ済み
  • SlackのワークスペースとChatbotが連携済み

tfファイルとJSONファイルの解説

tfファイルと各ポリシーをJSON形式で切り出しています。

ディレクトリ構造は以下です。

├─ provider.tf
├─ chatbot.tf
├─ cloudwatch.tf
├─ eventbridge.tf
├─ sns.tf
├─ alarm_event.json
├─ alarm_template.json
├─ ok_event.json
├─ ok_template.json
└─ sns_policy.json

provider.tf

provider.tfは以下の通りです。

locals {
  aws_id             = "[accout_id]"
  name_prefix        = "[xxxxx]"
  region             = "ap-northeast-1"
  Environment        = "[xxxxx]"
  slack_workspace_id = "[workspace_id]"
  slack_channel_id   = "[channel_id]"
  instanceId         = "[監視対象EC2のインスタンスID]"
}

terraform {
  required_version = "~> 1.7.0"
  backend "s3" {
    bucket         = "[bucket_name]"
    region         = "ap-northeast-1"
    key            = "chatbot/terraform.tfstate"
    encrypt        = true
  }
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.19.0"
    }
    awscc = {
      source  = "hashicorp/awscc"
      version = "0.70.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

provider "awscc" {
  region = "ap-northeast-1"
}

特に注目すべき点は、providerにawsccを指定していることです。

TerraformでAWSリソースを作成する際、通常はawsをproviderとして指定しますが、Chatbotには対応していません。

そのため、AWS Cloud Control APIを使用するawsccを使用します。

AWS Cloud Control API

AWS Cloud Control API を使用して、幅広いサービス (AWS およびサードパーティの両方) に属するクラウドリソースを作成、読み取り、更新、削除、一覧表示 (CRUD-L、Create, Read, Update, Delete, and List) します。Cloud Control API のアプリケーションプログラミングインターフェイス (API) の標準化されたセットを使用すると、AWS アカウント でサポートされているあらゆるリソースで CRUD-L オペレーションを実行できます。Cloud Control API を使用すると、リソースを担当する個々のサービスに固有のコードやスクリプトを生成する必要がなくなります。

引用: AWS Cloud Control API の概要

providerはこちらです。

chatbot.tf

chatbot.tfは以下の通りです。

resource "awscc_chatbot_slack_channel_configuration" "chatbot" {
  configuration_name = "${local.name_prefix}-${local.Environment}"
  slack_workspace_id = local.slack_workspace_id
  slack_channel_id   = local.slack_channel_id
  iam_role_arn       = aws_iam_role.chatbot.arn
  sns_topic_arns     = [aws_sns_topic.chatbot.arn]
  user_role_required = false
  logging_level      = "ERROR"
  guardrail_policies = ["arn:aws:iam::aws:policy/CloudWatchFullAccess"]
}

resource "aws_iam_role" "chatbot" {
  name               = "${local.name_prefix}-${local.Environment}"
  assume_role_policy = data.aws_iam_policy_document.chatbot_assume.json
}

data "aws_iam_policy_document" "chatbot" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

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

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

resource "awscc_chatbot_slack_channel_configuration" "chatbot"で示しているように、awsccのproviderを指定して、リソースを作成しています。

また、9行目で定義しているguardrail_policiesはChatbotへ付与されるIAMロールのガードレールです。

Chatbotは、SlackからCLIコマンドを打つことができる機能があります。

付与されたIAMロールによって、チャネルにいるメンバー全員に同様の権限を付与することができます。

そのため、大きな権限を付与すると事故に繋がりかねないので、IAMロールのガードレールという形で予防線が定義されています。

cloudwatch.tf

cloudwatch.tfは以下の通りです。

resource "aws_cloudwatch_metric_alarm" "chatbot" {
  actions_enabled     = true
  alarm_actions       = []
  ok_actions          = []
  alarm_description   = "chatbot"
  alarm_name          = "${local.name_prefix}-${local.Environment}"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  datapoints_to_alarm = 1
  dimensions = {
    InstanceId = local.instanceId
  }
  evaluate_low_sample_count_percentiles = null
  evaluation_periods                    = 1
  extended_statistic                    = null
  insufficient_data_actions             = []
  metric_name                           = "CPUUtilization"
  namespace                             = "AWS/EC2"
  period                                = 300
  statistic                             = "Average"
  threshold                             = 80
  threshold_metric_id                   = null
  treat_missing_data                    = "missing"
  unit                                  = null
}

EC2インスタンスのCPU使用率が80%以上になった時にアラームが発生する設定を行います。

アラーム時のアクションは、EventBridgeのイベントルールで取得します。

eventbridge.tf

eventbridge.tfは以下の通りです。

resource "aws_cloudwatch_event_rule" "alarm" {
  event_bus_name = "default"
  event_pattern  = file("alarm_event.json")
  is_enabled     = true
  name           = "${local.name_prefix}-${local.Environment}-alarm"
}

resource "aws_cloudwatch_event_target" "alarm" {
  arn  = aws_sns_topic.chatbot.arn
  rule = aws_cloudwatch_event_rule.alarm.id

  input_transformer {
    input_paths = {
      "account" : "$.account",
      "alarm" : "$.detail.alarmName",
      "description" : "$.detail.configuration.description",
      "instance" : "$.detail.configuration.metrics[0].metricStat.metric.dimensions.InstanceId",
      "metrics_name" : "$.detail.configuration.metrics[0].metricStat.metric.name",
      "state" : "$.detail.state.value",
      "timestamp" : "$.detail.state.timestamp"
    }
    input_template = file("alarm_template.json")
  }
}

resource "aws_cloudwatch_event_rule" "ok" {
  event_bus_name = "default"
  event_pattern  = file("ok_event.json")
  is_enabled     = true
  name           = "${local.name_prefix}-${local.Environment}-ok"
}

resource "aws_cloudwatch_event_target" "ok" {
  arn  = aws_sns_topic.chatbot.arn
  rule = aws_cloudwatch_event_rule.ok.id

  input_transformer {
    input_paths = {
      "account" : "$.account",
      "alarm" : "$.detail.alarmName",
      "description" : "$.detail.configuration.description",
      "instance" : "$.detail.configuration.metrics[0].metricStat.metric.dimensions.InstanceId",
      "metrics_name" : "$.detail.configuration.metrics[0].metricStat.metric.name",
      "state" : "$.detail.state.value",
      "timestamp" : "$.detail.state.timestamp"
    }
    input_template = file("ok_template.json")
  }
}

ここでは、EventBridgeのルールとターゲットを定義しています。

3,28行目では、EventBridgeが駆動するためのルールをJSONで定めています。

12,37行目では、22,47行目で指定しているJSONで使用する変数を定義しています。

各ファイルの中身は以下です。

alarm_event.json

{
    "source": [
      "aws.cloudwatch"
    ],
    "detail-type": [
      "CloudWatch Alarm State Change"
    ],
    "resources": [
      "arn:aws:cloudwatch:ap-northeast-1:xxxxxx:alarm:xxxxxxxx"
    ],
      "detail": {
          "state": {
            "value": ["ALARM"]
          }
        }
  }

ok_event.json

{
    "source": [
      "aws.cloudwatch"
    ],
    "detail-type": [
      "CloudWatch Alarm State Change"
    ],
    "resources": [
      "arn:aws:cloudwatch:ap-northeast-1:xxxxxx:alarm:xxxxxxxx"
    ],
      "detail": {
          "state": {
            "value": ["OK"]
          }
        }
  }

alarm_template.json

{
  "version": "1.0",
  "source": "custom",
  "content": {
    "textType": "client-markdown",
    "title": ":red_circle: <state>:EC2インスタンス(<instance>)ステータスチェックアラート",
    "description": "発生時刻 : `<timestamp>`\nアカウント : <account>\nインスタンス : `<instance>`\nメトリクス : <metrics_name>\nアラーム : <alarm>",
    "nextSteps": [
      "CPU増加の原因を調査してください。"
    ]
  }
}

ok_template.json

{
  "version": "1.0",
  "source": "custom",
  "content": {
    "textType": "client-markdown",
    "title": ":large_blue_circle: <state>:EC2インスタンス(<instance>)CPU使用率80%",
    "description": "EC2インスタンスは復旧しました \n発生時刻 : `<timestamp>`\nアカウント : <account>\nインスタンス : `<instance>`\nメトリクス : <metrics_name>\nアラーム : <alarm>"
  }
}

sns.tf

sns.tfの中身は以下です。

resource "aws_sns_topic" "chatbot" {
  name = "${local.name_prefix}-${local.Environment}"
}

resource "aws_sns_topic_policy" "chatbot" {
  arn    = aws_sns_topic.chatbot.arn
  policy = file("sns_policy.json")
}

コンソールからSNSを作成する際は、ポリシーを設定しなくとも自動で設定を行ってくれるのですが、Terraformで記述する場合は、明示的にポリシーを記述する必要があります。

明示的に追加する必要がある部分をハイライトしております。

sns_policy.json

{
    "Version": "2008-10-17",
    "Id": "__default_policy_ID",
    "Statement": [
      {
        "Sid": "__default_statement_ID",
        "Effect": "Allow",
        "Principal": {
          "AWS": "*"
        },
        "Action": [
          "SNS:Publish",
          "SNS:RemovePermission",
          "SNS:SetTopicAttributes",
          "SNS:DeleteTopic",
          "SNS:ListSubscriptionsByTopic",
          "SNS:GetTopicAttributes",
          "SNS:AddPermission",
          "SNS:Subscribe"
        ],
        "Resource": "arn:aws:sns:ap-northeast-1:xxxxx:xxxxx",
        "Condition": {
          "StringEquals": {
            "AWS:SourceOwner": "xxxxxxxxxx"
          }
        }
      },
      {
        "Sid": "AWSEvents-chatbot-ok_Id2f80f695-2648-4cdf-b32d-f296630b6c35",
        "Effect": "Allow",
        "Principal": {
          "Service": "events.amazonaws.com"
        },
        "Action": "sns:Publish",
        "Resource": "arn:aws:sns:ap-northeast-1:xxxxx:xxxxx"
      }
    ]
  }

コンソールとTerraformでの挙動の違いを以下のブログで解説しておりますので、気になる方はご覧ください。

関連記事
TerraformでAWS ChatbotとAmazon SNSを設定していて、詰まった話

まとめ

今回ご紹介したTerraformファイルは、provider.tflocalsセクションに必要な設定を追加し、各種リソースに対応するJSONファイルを編集することで簡単に利用開始できます。

最後まで読んでいただきありがとうございます!

Shun

好きな言葉は生ビール199円です。日本ビール協会認定1冠、AWS12冠、Google Cloud11冠

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら