GoとLambdaでAWS使用料金を毎日LINE通知させる


AWSの使用料金こわい

おっしゃ AWSのコンソールいじるで〜
お、そういや〜AWS今いくらかかっとんねんやろ
マイ請求ダッシュボード ポチ〜っと

んん!??


ECSを放置して$100超えの請求がきたり、
キルしたはずのRDSインスタンスが知らぬ間に復活していて地味に$40くらい請求がきたり、
この通過儀礼をいい加減なんとかしようと簡単な料金通知システムを作りました。

やったこと

Go言語でLINE notifyの通知APIをキックする関数を実装して
AWS LambdaにデプロイCronで定期実行して、毎日AWSの使用料金を自分へLINEしてくれる簡単なシステムを作りました。

実装の準備

LINE notifyからTokenを発行する

LINE notifyにアクセスします。

ログインするアカウントはもちろん通知がきてほしい自分のLINEアカウントです。
アプリ側からメールアドレス・パスワードを設定して、PCからログインします。そしてマイページへ移動

PCからのアクセスならばこのようなコンポーネントが現れトークンを受け取ることができます。

serverless framework

AWS Lambdaをちゃちゃっと実装したいのでもちろんserverless frameworkを使います!
Go用の雛形をさくっとコマンドで準備

$ sls create -u https://github.com/serverless/serverless-golang/ -p goserverless

serverless.yamlは最低限、以下のような記述にあれば動かせます。
cronは日本時間の朝8時に通知がくるように設定してあります。
IAMはCloudWatchReacOnlyAccessのものをまるっと貼り付けてます。

serverless.yaml

service: goserverless

provider:
  name: aws
  runtime: go1.x
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - 'autoscaling:Describe*'
        - 'cloudwatch:Describe*'
        - 'cloudwatch:Get*'
        - 'cloudwatch:List*'
        - 'logs:Get*'
        - 'logs:List*'
        - 'logs:Describe*'
        - 'logs:TestMetricFilter'
        - 'logs:FilterLogEvents'
        - 'sns:Get*'
        - 'sns:List*'
      Resource:
        - '*'

package:
 exclude:
   - ./**
 include:
   - ./bin/**

functions:
  billing_notify:
    handler: bin/main
    events:
      - schedule: cron(0 23 * * ? *)
    environment:
      LINEpostURL: https://notify-api.line.me/api/notify
      LINEnotyfyToken: <Token>

実装

Go言語を使用します。pythonでは割と実装例があるようですが、Goでの実装は私の観測範囲では見当たらなかったのでレシピを載せておきます。

Go言語で CloudWatchから料金情報を取得する処理

CloudWatchの料金情報は以下のように構造体を埋めると取得することができます。料金情報のため、リージョンは us-east-1 です。

main.go
func Handler() (Response, error) {
    svc := cloudwatch.New(session.New(), &aws.Config{Region: aws.String("us-east-1")})

    params := &cloudwatch.GetMetricStatisticsInput{
        Dimensions: []*cloudwatch.Dimension{
            {
                Name:  aws.String("Currency"),
                Value: aws.String("USD"),
            },
        },
        StartTime:  aws.Time(time.Now().Add(time.Hour * -24)),
        EndTime:    aws.Time(time.Now()),
        Period:     aws.Int64(86400),
        Namespace:  aws.String("AWS/Billing"),
        MetricName: aws.String("EstimatedCharges"),
        Statistics: []*string{
            aws.String(cloudwatch.StatisticMaximum),
        },
    }
    resp, err := svc.GetMetricStatistics(params)
    if err != nil {
        fmt.Println(err)
    }

    // ...省略

}

func main() {
    lambda.Start(Handler)
}

Go言語で LINE notify API にPOST

LINE Notify API Documentによれば以下のようなリクエストヘッダーが必要です。
リクエストパラメータに自分のLINEに通知したい内容を載せます。messageは必須で、ほかにもイメージをPOSTして通知として飛ばせます

main.go

func Handler() (Response, error) {

    // ...省略

    val := url.Values{}
    val.Add("message", param)
    request, err := http.NewRequest("POST", os.Getenv("LINEpostURL"), strings.NewReader(val.Encode()))
    if err != nil {
        fmt.Println(err)
    }
    request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    request.Header.Add("Authorization", "Bearer "+os.Getenv("LINEnotyfyToken"))
    lineResp, err := http.DefaultClient.Do(request)
    if err != nil {
        fmt.Println(err)
    }

    return Response{
        Message: fmt.Sprintf("%v", lineResp),
    }, nil
}

無事に毎日AWS使用料金を把握できた

ほんま焦るで〜
もうこれで見落としで使用料金えらいことならんやろ〜

従量課金のAWSサービスを試す前には使用料金を通知する仕組みを是非つくっておくことが大事ですよね。お財布を守るためにも...。