はじめに
CloudWatchメトリクスは色々な情報を一画面で加工して表示することができる素晴らしいサービスだが、そのために都度AWSコンソールにログインしたりコンソールをごちゃごちゃいじって表示を復元させたり、それを回避するにはJSONを打ち込みなおしたりしなければいけないという面倒くささがある。
もっと簡単にサクッとメトリクスを見ることができないか考えて、Slackで定期で確認してしまえばいいじゃない、という結論が出た。
ということで今回は、SlackでCloudWatchメトリクスのグラフ表示を定期実行するまでを自動化する。
前提知識は以下の通り。
- CloudWatchメトリクスの基本的な知識がある
- Terraformの基本的な知識がある
AWS Chatbotの作成に必要なTerraformプロバイダ
AWS ChatbotはGoSDKが対応していないため、Terraformの標準AWSプロバイダでは提供されていない。
ただし、AWS Cloud Control APIをラップしたAWSCCというプロバイダがあるため、今回はこれを活用する。
以下のようにawsccのprovider設定を入れておけば良い。
バージョン指定が雑なのは気にしないでいただければ。
################################################################################
# Provider #
################################################################################
provider "aws" {
region = "ap-northeast-1"
}
provider "awscc" {
region = "ap-northeast-1"
}
AWS Chatbotを使う前の事前作業
ChatbotからSlackにアクセスできるよう認証をしておく必要がある。
ここだけはどうしても自動化できなかったので、AWSのマネージメントコンソールから設定しよう。
その後表示されるダイアログで、クライアントの種類で「Slack」を選択して「設定」を押下する。
AWS ChatbotのTerraform記述
以下のように設定する。
################################################################################
# Chatbot #
################################################################################
resource "awscc_chatbot_slack_channel_configuration" "example" {
configuration_name = "example"
slack_workspace_id = "XXXXXXXXXXX" // ①
slack_channel_id = "YYYYYYYYYYY" // ②
iam_role_arn = aws_iam_role.chatbot.arn
guardrail_policies = [
aws_iam_policy.chatbot_guardrail.arn,
]
logging_level = "INFO"
}
slack_workspace_id
とslack_channel_id
は、SlackのURLの以下の部分に該当するので、コピペしよう。
https://app.slack.com/client/XXXXXXXXXXX/YYYYYYYYYYY
① ②
AWS側からイベントトリガで投稿する場合はSNSトピックも必要になるが、今回のSlack側からAPIを実行するユースケースでは不要だ。
IAMロールの設定
IAMは以下のように設定する。
Chatbot用のサービスポリシと、チャンネルで何ができるかというガードレールポリシというものが必要だ。
サービスポリシ ⊃ ガードレールポリシという包含関係らしく、今回チャンネル側で必要になるcloudwatch:GetMetricWidgetImage
をサービスポリシから外したらエラーになったので、面倒だが両方に入れておこう。
逆に、チャンネル側ではCloudWatchは不要であるため、ガードレールポリシからは外してある。
resource "aws_iam_role" "chatbot" {
name = local.iam_role_name
assume_role_policy = data.aws_iam_policy_document.chatbot_assume.json
}
data "aws_iam_policy_document" "chatbot_assume" {
statement {
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = [
"chatbot.amazonaws.com",
]
}
}
}
resource "aws_iam_role_policy" "chatbot" {
name = local.iam_policy_name
role = aws_iam_role.chatbot.name
policy = data.aws_iam_policy_document.chatbot_custom.json
}
data "aws_iam_policy_document" "chatbot_custom" {
statement {
effect = "Allow"
actions = [
"cloudwatch:GetMetricWidgetImage",
"logs:PutLogEvents",
"logs:CreateLogStream",
"logs:DescribeLogStreams",
"logs:CreateLogGroup",
"logs:DescribeLogGroups",
]
resources = [
"*",
]
}
}
resource "aws_iam_policy" "chatbot_guardrail" {
name = local.iam_chatbot_guardrail_policy_name
policy = data.aws_iam_policy_document.chatbot_guardrail.json
}
data "aws_iam_policy_document" "chatbot_guardrail" {
statement {
effect = "Allow"
actions = [
"cloudwatch:GetMetricWidgetImage",
]
resources = [
"*",
]
}
}
実行
上記でterraform apply
実行後、Slack側で以下のように入力してみよう。
@aws cloudwatch get-metric-widget-image --region us-east-1 --metric-widget '{
"view": "timeSeries",
"stacked": false,
"metrics": [
[ "AWS/Billing", "EstimatedCharges", "Currency", "USD", { "period": 21600, "stat": "Maximum" } ]
],
"width": 1576,
"height": 200,
"start": "-PT672H",
"end": "P0D"
}'
ついでによく分からないサジェストが毎回表示されてうざいのでこれを消したいが、消し方がよく分からなかった……
ちなみに、以前の記事で紹介したクロスアカウントのメトリクス取得についても、設定を入れておけばcloudwatch get-metric-widget-image
で取得できるところまでは試してみた。
やっぱりダメだった。クロスアカウントのメトリクス取得をするには、対象アカウントのCloudWatch-CrossAccountSharingRole
にAssumeRoleする必要があるが、ChatbotではSTS関連の操作が封じられている上に一つの命令内でセッションを変えることもできないので、Chatbotでの実現は不可能だった。
念のため、ガードレールポリシで指定していないコマンドを実行しようとすると、しっかりエラーになってくれた。
ここで問題発生
時限実行には、Slackのリマインド機能を使えば問題ないと考えていたが、Slackのリマインド機能ではどうしても「リマインド:」という文言が入ってしまい、Chatbotがこれを解釈できずに実行エラーになってしまう。余計なリソースを作りたくないので、Slack機能で完結したいが、この「リマインド:」を消す手段、無いのだろうか……。
解決策
結局、Slackのリマインド機能は使えないので、以下のようなEventBridgeのスケジュール機能を利用する。
EventBridge Schedulerは、SlackのAPI Destinationsが使え無さそうなので、従来のEventBridgeのスケジュールルールを使う。
事前準備
API Destinationsを使用するには、Slack側の設定が必要になる。
クラスメソッド先生の記事を参考に、追加でボットを作っておいて、OAuth用のトークンを取得しておこう。
OAuth用トークンはvar.slack_api_key
で指定できるようにしておく。
IaCを書く
EventBridgeに関連する情報を以下のように定義する。
input_transformerにはターゲットに送る情報を記載する。
ここではusername
とicon_emoji
を変えることで、メッセージ通知元の見た目を変えることができる。
本文でメンションを送る場合は<>
でユーザ名を囲めば良い。これで囲っておかないととチャットボットが反応してくれない。
事前に取得したボット用のOAuth用トークンは、aws_cloudwatch_event_connection
のauth_parameters
ブロックで設定する。ここは認証用のヘッダ情報を設定するので、Authorization
ヘッダの先頭にBearer
を付与しないといけないので忘れないよう気を付けよう。
resource "aws_cloudwatch_event_rule" "example" {
name = local.eventbridge_rule_name
schedule_expression = "<任意の起動周期を設定>"
is_enabled = true
}
resource "aws_cloudwatch_event_target" "example" {
rule = aws_cloudwatch_event_rule.example.name
arn = aws_cloudwatch_event_api_destination.example.arn
role_arn = aws_iam_role.eventbridge.arn
input_transformer {
input_template = jsonencode({
channel : var.slack_channel_id,
username : "料金お知らせ定期便",
icon_emoji : ":dollar:",
text : "<@aws> cloudwatch get-metric-widget-image --region us-east-1 --metric-widget ${templatefile("./15_metric_widget_image_template.json", {})}"
})
}
}
resource "aws_cloudwatch_event_api_destination" "example" {
name = "slack"
invocation_endpoint = "https://slack.com/api/chat.postMessage"
http_method = "POST"
invocation_rate_limit_per_second = 1
connection_arn = aws_cloudwatch_event_connection.example.arn
}
resource "aws_cloudwatch_event_connection" "example" {
name = "slack-connection"
authorization_type = "API_KEY"
auth_parameters {
api_key {
key = "Authorization"
value = "Bearer ${var.slack_api_key}"
}
}
}
これを動かすと、時限周期が来たタイミングで以下のように表示される。
これで、Chatbotだけに閉じて対応できないのがややイケていないが、時限で好きな情報をSlackに送れるようになった!