Help us understand the problem. What is going on with this article?

AWSの使用料金をSlackに投稿するLambdaファンクション

More than 3 years have passed since last update.

スクリーンショット 2017-09-08 22.33.48.png

概要

  • CloudWatchのapiのListMetricsで課金情報が取得できるAWSのサービス一覧を取得して、それを元にapiのGetMetricStatisticsを呼ぶことで各AWSのサービス毎の料金を取得できる
  • AWSの管理画面から請求アラートを受け取るを有効にしておく必要がある

スクリーンショット 2017-09-07 1.01.11.png

  • Promiseを使ってるため、nodejsのバージョンは6.10を選択する

許可が必要なIAMポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": ["cloudwatch:ListMetrics", "cloudwatch:GetMetricStatistics"],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

ソースコード

  • 環境編SLACK_API_TOKENとPOST_CHANNELは任意の値を設定する
package.json
{
  "name": "estimated-charges",
  "version": "1.0.0",
  "main": "index.js"
  "license": "MIT",
  "dependencies": {
    "@slack/client": "^3.10.0",
    "aws-sdk": "^2.85.0",
    "moment": "^2.18.1"
  }
}
index.js
'use strict';

const WebClient = require('@slack/client').WebClient
const AWS = require('aws-sdk')
const moment = require('moment')

const cloudwatch = new AWS.CloudWatch({
  region: 'us-east-1',
  endpoint: 'http://monitoring.us-east-1.amazonaws.com'
})

const listMetrics = () => {
  return new Promise((resolve, reject) => {
    cloudwatch.listMetrics({ MetricName: 'EstimatedCharges' }, (error, data) => {
      if (error) {
        reject(error)
      } else {
        resolve(data)
      }
    })
  })
}

const getMetricStatistics = (serviceName, startTime, endTime) => {
  const dimensions = [
    { Name: 'Currency', Value: 'USD' }
  ]
  if (serviceName) {
    dimensions.push({ Name: 'ServiceName', Value: serviceName })
  }
  return new Promise((resolve, reject) => {
    const params = {
      MetricName: 'EstimatedCharges',
      Namespace: 'AWS/Billing',
      Period: 86400,
      StartTime: startTime,
      EndTime: endTime,
      Statistics: ['Maximum'],
      Dimensions: dimensions
    }
    cloudwatch.getMetricStatistics(params, (error, data) => {
      if (error) {
        reject(error)
      } else {
        resolve({ name: serviceName, data: data })
      }
    })
  })
}

exports.handler = (event, context, callback) => {
  listMetrics().then(data => {
    const now = moment().toISOString()
    const yesterday = moment(now).subtract(1, 'd').toISOString()
    const promises = data['Metrics'].map(metric => {
      return metric['Dimensions'][0]
    }).filter(dimension => {
      return dimension['Name'] == 'ServiceName'
    }).map(dimension => {    
      return dimension['Value']
    }).filter((serviceName, index, self) => {
      return self.indexOf(serviceName) === index
    }).map(serviceName => {
      return getMetricStatistics(serviceName, yesterday, now)
    })
    promises.unshift(getMetricStatistics(null, yesterday, now))
    Promise.all(promises).then(data => {
      const results = data.filter(result => {
        const datapoints = result.data['Datapoints']
        return (datapoints.length != 0 && datapoints[0]['Maximum'] != 0)
      })
      if (results.length == 0) {
        return
      }
      const fields = results.filter(result => {
        return result.name != null
      }).sort((result1, result2) => {
        const charge1 = result1.data['Datapoints'][0]['Maximum']
        const charge2 = result2.data['Datapoints'][0]['Maximum']
        return (charge2 - charge1)
      }).map(result => {
        const datapoint = result.data['Datapoints'][0]
        return { title: result.name, value: `$${datapoint['Maximum']}`, short: true }
      })
      const datapoint = results[0].data['Datapoints'][0]
      const client = new WebClient(process.env.SLACK_API_TOKEN)
      client.chat.postMessage(process.env.POST_CHANNEL, `AWS料金 $${datapoint['Maximum']}`, { attachments: [{ color: 'good', fields: fields }] }, (error, response) => {
        if (error) { console.error(error) }
      })
    }, reason => {
      console.error(reason)
    })
  }, reason => {
    console.error(reason)
  })
}
supership-inc
「すべてが相互につながる『よりよい世界』を実現する」という理念のもと、新たな価値の提供を目指すデータテクノロジーカンパニーです
https://supership.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away