CloudWatchメトリクスのグラフ画像をSlackに定期通知するLambdaを作る

CloudWatchメトリクスのグラフ画像をSlackに定期通知するLambdaを作る
公開日時
更新日時

先日の記事でAWS Chatbotを使って不快指数アラートをSlackに通知できるようになった。

これに加えて、朝起きた時に昨夜のセンサ情報を確認して振り返りができるようにしたい。

ただ、Chatbotの場合はアラートの発生/解決時しか通知ができないため、グラフ画像の定期通知は独自のLambdaを作る必要があった。

AWS SAMでLambda開発

  • sam initでテンプレート作成
sam init --runtime nodejs
  • s3バケットへの書き込みとCloudWatchの読み込み権限を付与

Scheduleで毎日朝9:00に通知がされるように設定。

# template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

Globals:
  Function:
    Timeout: 30

Resources:
  HomeMetricsImageFunction:
    Type: AWS::Serverless::Function
    Properties:
      Policies:
        - S3CrudPolicy:
            BucketName: !Ref BucketName
        - CloudWatchReadOnlyAccess
      CodeUri: src/
      Handler: app.lambdaHandler
      Runtime: nodejs10.x
      Events:
        HomeMetricsImage:
          Type: Schedule
          Properties:
            Schedule: cron(0 0 * * ? *) # JST 9:00
      Environment:
        Variables:
          SLACK_WEBHOOK_URL: !Ref SlackWebhookUrl
          BUCKET_NAME: !Ref BucketName

Outputs:
  HomeMetricsImageFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HomeMetricsImageFunction.Arn
  HomeMetricsImageFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HomeMetricsImageFunctionRole.Arn

Parameters:
  BucketName:
    Type: String
    Default: metrics-image
  SlackWebhookUrl:
    Type: String
  • 日付操作用のライブラリを追加
yarn add date-utils
  • lambdaコードの実装

CloudWatchメトリクスからグラフ画像を取得し、s3に保存する。

そして、s3のダウンロードURLを取得してSlackのWebhookで通知する。

// app.js

require("date-utils");
const axios = require("axios");
const aws = require("aws-sdk");
const bucket = process.env.BUCKET_NAME;

const cloudwatch = new aws.CloudWatch();
const s3 = new aws.S3();

const dt = new Date();
const formattedDate = dt.toFormat("YYYYMMDD");
let response;

const metricsImageApi = {
  metrics: [
    ["Home", "RaspberryPiZero/discomfort_index", { period: 60 }],
    [".", "RaspberryPiZero/humidity", { period: 60 }],
    [".", "RaspberryPiZero/temperature", { period: 60 }]
  ],
  view: "timeSeries",
  stacked: false,
  title: "BME280",
  width: 800,
  height: 400,
  start: "-PT12H",
  end: "P0D",
  timezone: "+0900"
};

const postToSlack = async imageUrl => {
  const params = new URLSearchParams();
  const payload = {
    text: "Good morning :home:",
    attachments: [
      {
        fields: [
          {
            title: `${formattedDate}.png`
          }
        ],
        image_url: imageUrl
      }
    ]
  };
  params.append("payload", JSON.stringify(payload));
  const result = await axios.post(process.env.SLACK_WEBHOOK_URL, params);
  console.log(result);
};

exports.lambdaHandler = async (event, context) => {
  try {
    const result = await cloudwatch
      .getMetricWidgetImage({ MetricWidget: JSON.stringify(metricsImageApi) })
      .promise();

    const key = `metrics/${formattedDate}.png`;
    await s3
      .putObject({
        Bucket: bucket,
        Key: key,
        Body: result.MetricWidgetImage
      })
      .promise();

    const downloadUrl = s3.getSignedUrl("getObject", {
      Bucket: bucket,
      Key: key,
      Expires: 60*60*24*7 // max 7days
    });

    await postToSlack(downloadUrl);

    response = {
      statusCode: 200,
      body: JSON.stringify({
        message: "success"
      })
    };
  } catch (err) {
    console.log(err);
    return err;
  }

  return response;
};

metricsImageApiの部分にはメトリクスの表示画面にある「ソース」=>「イメージAPI」で出力される内容を記述する。

cloudwatch metrics json

  • ローカルで動作確認
sam local invoke -e event.json

問題なくSlack通知されることを確認したらデプロイ。

  • deploy.shを作成
# deploy.sh
sam package \
    --template-file template.yaml \
    --output-template-file packaged.yaml \
    --s3-bucket {bucket-name}

sam deploy \
    --template-file packaged.yaml \
    --stack-name home-metrics-image \
    --capabilities CAPABILITY_IAM \
    --parameter-overrides SlackWebhookUrl=$SLACK_WEBHOOK_URL
  • デプロイ実行
./deploy.sh

午前9時になると昨夜の状況がSlack通知される。

slack home

これでメトリクスのグラフ画像がSlackに定期通知されるようになり、都度マネージメントコンソールから確認する必要がなくなった。

追記

Expiresの指定より前にトークンが期限切れになってしまう問題があったので、専用のIAMユーザを利用する方法に変更した。

s3の署名付きURLが有効期限より前に見れなくなってしまう

参考


Related #aws

Alexaに気温と二酸化炭素濃度を教えてもらう

「Alexa、気温」でセンサー情報を教えてくれるようになった

AWS SAMで作ったLambdaアプリをCircleCIでデプロイする

circleci/aws-serverlessのOrbを使った

s3の署名付きURLが有効期限より前に見れなくなってしまう

IAMロールではなくIAMユーザの権限でURLを生成する必要があった

AWS CDK使用時にリージョンを指定する

new Stackの際にenvで指定できる