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

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

AWSの使用料金こわい

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

んん!?? :innocent::innocent::innocent:


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

やったこと

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

実装の準備

LINE notifyからTokenを発行する

LINE notifyにアクセスします。
line_notify_header.png

ログインするアカウントはもちろん通知がきてほしい自分のLINEアカウントです。
アプリ側からメールアドレス・パスワードを設定して、PCからログインします。そしてマイページへ移動:nerd:
スクリーンショット 2020-01-11 0.23.20.png
PCからのアクセスならばこのようなコンポーネントが現れトークンを受け取ることができます。

serverless framework

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

$ 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での実装は私の観測範囲では見当たらなかったのでレシピを載せておきます。:nerd:

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して通知として飛ばせます:nerd:

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使用料金を把握できた

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

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

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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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