事の始まり

今回 2 回目の高額請求です! 前回は RDS の起動しっぱなしで起きました。

shocking price

その当時は請求アラートすら設定していなかったので、これさえ、設定しておけば大丈夫だろうと思っていました。

しかし…

Slack や LINE, 電話で事足りる世の中になり、メールを見なくなる毎日。

そして、今回事件が起きました…。

shocking price

メール通知だけだと、ダメだと思い知ったので、SNS にも通知するような仕組みを作らないといけないと思い、その方法を模索しました。

Slack 通知を設定しよう!

一定額超えたら、Slack に通知するようにすれば、早めに気づけるという結論に至ったので設定してみました。

マイ請求ダッシュボードの AWS Budgets から『予算を作成』をクリックします。

billing/home#budgets

Budgets top

『予算タイプの選択』を『コスト予算』にします。

Budgets type

あとは予算などを設定していきます…が。

なんと! AWS チャットボットのアラートというのを設定しないと、Slack 通知ができないみたいです…。

先にそちらを設定しましょう。

AWS Chatbot の設定

AWS Chatbot にアクセスします。 chatbot/home#/home

Chatbot

チャットクライアントを Slack に設定し、自身の作成した Slack のワークスペースと連携します。 連携が終わると、以下の画面に遷移します。

slack

やっと、Slack 通知のための Chatbot が作れると喜びましたが、なんと… 新しいチャンネルを設定するためには既に作成された SNS が必要だそうです!

心折れそうですが、がんばりましょう。

SNS の設定

Amazon Simple Notification Service にアクセスします! sns/v3/home?region=ap-northeast-1#/homepage

sns

トピック名を適当に作成し、次のステップに移ります。

SNS は AWS Budgets から実行したいので、アクセスポリシーを修正します。 デフォルトでは以下のようになってます (メソッドを『高度な』に変えると 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:Receive",
            "SNS:AddPermission",
            "SNS:Subscribe",
          ],
        "Resource": "arn:aws:sns:ap-northeast-1:<マイアカウントの番号>:<トピック名>",
        "Condition":
          { "StringEquals": { "AWS:SourceOwner": "<マイアカウントの番号>" } },
      },
    ],
}

AWS Budgets から SNS を push するように、これを Statement に追加します。

{
  "Sid": "AWSBudgets-notification-1",
  "Effect": "Allow",
  "Principal": { "Service": "budgets.amazonaws.com" },
  "Action": "SNS:Publish",
  "Resource": "arn:aws:sns:ap-northeast-1:<マイアカウントの番号>:<トピック名>",
}

最終的にこうなります。

{
  "Version": "2008-10-17",
  "Id": "__default_policy_ID",
  "Statement":
    [
      {        "Sid": "AWSBudgets-notification-1",        "Effect": "Allow",        "Principal": { "Service": "budgets.amazonaws.com" },        "Action": "SNS:Publish",        "Resource": "arn:aws:sns:ap-northeast-1:<マイアカウントの番号>:<トピック名>",      },      {
        "Sid": "__default_statement_ID",
        "Effect": "Allow",
        "Principal": { "AWS": "*" },
        "Action":
          [
            "SNS:Publish",
            "SNS:RemovePermission",
            "SNS:SetTopicAttributes",
            "SNS:DeleteTopic",
            "SNS:ListSubscriptionsByTopic",
            "SNS:GetTopicAttributes",
            "SNS:Receive",
            "SNS:AddPermission",
            "SNS:Subscribe",
          ],
        "Resource": "arn:aws:sns:ap-northeast-1:<マイアカウントの番号>:<トピック名>",
        "Condition":
          { "StringEquals": { "AWS:SourceOwner": "<マイアカウントの番号>" } },
      },
    ],
}

トピックを作成したら、ARN をコピーしておきます。

arn:aws:sns:ap-northeast-1:<アカウント番号>:<SNSトピック名>

AWS Chatbot のチャンネル設定に戻る

詳細設定は適当にします。 CloudWatch のログを有効化すると、デバッグしやすいです。

cloudwatch

Slack チャンネルはパブリックにしておきます。 プライベートの場合は権限など別途設定が必要そうなので、今回はプライベートで設定します (まずは動くものを作りましょう!)。

アクセス権限も適当に選択しておきます。

role

通知オプションは SNS で作成したものを使います。

notification option

AWS Budgets から『予算を作成』に戻る

SNS の設定で、作成した ARN を貼り付けます。 sns arn

予算を 1$にし、アラート通知を 1%にすれば、通知のテストができます。 こんな感じで Slack に通知がきます (AWS は自動でメンバーに追加されます)。

slack notification

AWS Budgets が SNS を push する。 AWS Chatbot が SNS の push を検知して、Slack に通知を送る… って感じですね。 … LINE Bot でもできそう。

LINE Bot を設定しよう!

一応、Slack で通知できましたが、念のため、LINE Bot の設定もしておきます。 その前に、Lambda を作りましょう!

Lambda の設定

Node.js が好きなので、Node で作ります。

lambda

SNS でトリガーして、Lambda が動くようにします。

lambda

Lambda に何が来るかわからないので、console.log で確認してみます。 コードを変更したら、Deployを忘れずに!

exports.handler = async event => {
  console.log("AWS Budgets Test")
  console.log(event)
  console.log(JSON.stringify(event))
  // TODO implement
  const response = {
    statusCode: 200,
    body: JSON.stringify("Hello from Lambda!"),
  }
  return response
}

CloudWatch では以下が返ってきました。 大雑把な値を知りたい場合は、AWS Budgets で設定する予算名に値段を入れておくと良さそうです (Sns の Subject で取れます)。 詳細を取り出したい場合は、Message を使います。/n とか入ってますが、LINE がいい感じに整形するので、そのまま投げても大丈夫です。

{
  "Records":
    [
      {
        "EventSource": "aws:sns",
        "EventVersion": "1.0",
        "EventSubscriptionArn": "xxx",
        "Sns":
          {
            "Type": "Notification",
            "MessageId": "xxx",
            "TopicArn": "xxxx",
            "Subject": "AWS Budgets: <AWS Budgetsで設定した予算名> has exceeded your alert threshold",
            "Message": "xxxxxxxx",
            "Timestamp": "2020-11-02T15:43:38.009Z",
            "SignatureVersion": "1",
            "Signature": "xxxxx",
            "SigningCertUrl": "xxxx",
            "UnsubscribeUrl": "xxxx",
            "MessageAttributes": {},
          },
      },
    ],
}

どちらにせよ、以下で Subject と Message が取れることがわかります。 Deploy して、テストしましょう!

exports.handler = async event => {
  console.log("AWS Budgets Subject")
  console.log(event.Records[0].Sns.Subject)
  console.log("AWS Budgets Contents")
  console.log(event.Records[0].Sns.Message)
  // TODO implement
  const response = {
    statusCode: 200,
    body: JSON.stringify("Hello from Lambda!"),
  }
  return response
}

上記の CloudWatch の結果をコピーし、テストをクリックします (SNS のテンプレートを選んで使っても大丈夫です!)。

test

新しいテストイベントを作成し、先ほどの CloudWatch の結果を貼り付けます。

test event

再度テストをクリックすると、テストが実行され、結果が見れます。

test result

必要な値だけ取れてますね!

log

LINE Bot の設定

developer コンソールにログインします。 https://developers.line.biz/console/

(本筋とは離れますが、LINE が WebAuthn 対応していて驚きました)

ログインして、Provider を作成します。

provider

LINE Bot に予算アラートを喋らせるので、Messaging API を選びます。

line

カテゴリなどを適当に設定しておきます。 設定が終わると、この画面になります。 Basic Setting の一番下にシークレットアクセスキーとユーザー ID、Messaging API にアクセスキーがあるので、控えておきます。

line setting

Lambda に反映

ローカルの適当なディレクトリで、以下のコマンドを実行します。

$ touch index.js
$ yarn init
$ yarn add @line/bot-sdk

ディレクトリ構成はこんな感じです。

lambda_test
 ├── apis
 |    └── line.js
 ├── index.js
 ├── package.json
 ├── node_modules/
 └── yarn.lock

line.js は次のようにします。

apis/line.js
"use strict"

const line = require("@line/bot-sdk")
const configs = {}

if (process.env.LINE_CHANNEL_TOKEN && process.env.LINE_CHANNEL_SECRET) {
  configs.channelAccessToken = process.env.LINE_CHANNEL_TOKEN
  configs.channelSecret = process.env.LINE_CHANNEL_SECRET
} else {
  console.warn("LINE Channel Token or Secret Token is missing...")
}

module.exports = new line.Client(configs)

index.js はこうします。 lineClient の処理は await を付けないと、メッセージを送る前に Lambda が終了してしまうので、気をつけましょう。

index.js
"use strict"

exports.handler = async event => {
  const lineClient = require("./apis/line")
  console.log("AWS Budgets Subject")
  await lineClient.pushMessage(process.env.USER_ID, {
    type: "text",
    text: event.Records[0].Sns.Subject,
  })
  console.log("AWS Budgets Contents")
  await lineClient.pushMessage(process.env.USER_ID, {
    type: "text",
    text: event.Records[0].Sns.Message,
  })

  // TODO implement
  const response = {
    statusCode: 200,
    body: JSON.stringify("Hello from Lambda!"),
  }
  return response
}

作成した以下のファイルを zip 化し、Lambda にデプロイします。

lambda_test
 ├── apis
 |    └── line.js
 ├── index.js
 ├── package.json
 ├── node_modules/
 └── yarn.lock

先ほどのように Lambda でテストをしたり、適当な予算を作ってみたりすると、LINE にもアラートが飛ぶようになります。

last

総括

これで、設定した予算よりも利用料が超えそうになると、Slack と LINE にメッセージが飛ぶようになりました。


コメント