自分用メモ

プログラミングとかのメモを書きたいです

AWSで価格取得してLineBotに送る

やったこと

  • AWSの価格を取得して、定期的にLineで配信する
  • 言語はPython
  • 概要図は下記
    • なお、本記事でぜんぶ網羅して説明できていなくて、上から順にやっても手順漏れまくりなことに注意。ポイントっぽい部分のメモでしかないです。

f:id:ebinafactory:20211123223009p:plain
概要

Lineに投稿される内容

f:id:ebinafactory:20211123233643p:plain

背景

  • AWSをいろいろ触ってみる間に、AWS価格を配信してくれる仕組みが欲しくなった。
    • メール配信よりも、Lineに投げてみたい
  • AWSそのものの勉強もしたかった。
    • Lambda や その周辺の権限など

概要

  • (1) LambdaでAWSの価格取得する関数を作成
  • (2-1) LambdaのLayerでlinebot用のPythonライブラリのLayerを用意した
    • Layerというのは、Lambda用のライブラリセット。Lambda本体に同梱しても良いはずだけど、Line送信するライブラリは将来作成した別のBotでも使いそうな気がしたので、Layer化してみた
  • (2-2) LambdaからLineに対してメッセージ送信。
    • 自分しか使っていないLineBotアカウントからブロードキャスト送信する形とした。(自分のLineのuseridがわかればToでできそうだったけど、自分のLineのuseridがわからなかった・・・。)
  • (3) これらをまとめるStepFunctionを作成

    • このStepFunctionを定期実行するようにした
  • その他メモ

    • 権限は結構ややこしかった。上手な設計や運用方法はまだつかめていない上にたぶん漏れがあるけど、記憶の範囲で記載する。
      • Lambdaなどは、設定したIAMロールで動くことになる。このIAMロールにはLambdaを実行するための権限であったり、ログ出力(CloudWatch Logs)に書き込むための権限が必要になる。今回、コスト参照を行っているため、CostExplorer関係の権限を付与した。
    • boto3
      • PythonからAPIを実行するときの公式ライブラリ。lambda環境はデフォルトでインストール済みであり公式サンプルもこれ。
        • lambda環境からの実行:lambda実行ユーザとして動ける(ソース内にIAMユーザのトークンなどは不要)
        • ローカル環境からの実行:IAMユーザのトークンなどを使って使う(IAMユーザに権限付与も必要)
    • ソースとトークン(パスワード的な文字列)の分離
      • Lambdaには環境変数がセットできる。パスワードは環境変数にセットして、ソースは環境変数からパスワードを取得するようにするのが定番の方法。

手順

(1)LamndaでAWS価格の取得

  • 環境メモ
  • 開発時のメモ
    • VSCodeなどでローカル開発したほうがやりやすい。その際、boto3のclientは下記のように情報を追加すれば、ローカルからもつながる。
    • IAMユーザのシークレットアクセスキーは、IAMユーザ作成時にしか出てこないのでちゃんとメモする。(メモしそこねたらリセットができる)
client = boto3.client('ce',
    aws_access_key_id='IAMユーザのアクセスキーを入れる',
    aws_secret_access_key='IAMユーザのシークレットアクセスキーを入れる',
    region_name='us-east-1'
)
from datetime import datetime, timedelta, date
import boto3
import json

# 今日-start ~ 今日+end日前までのコストを求める
def get_cost_for_oneday(client, start, end):
    # 日付を計算
    start_date = date.today() - timedelta(days=start)
    end_date = date.today() + timedelta(days=end)
    # API用に文字列にする
    start_str = start_date.strftime('%Y-%m-%d')
    end_str = end_date.strftime('%Y-%m-%d')
    # APIを実行する
    response = client.get_cost_and_usage(
        TimePeriod={
            'Start': start_str,
            'End': end_str
        },
        Granularity='DAILY',
        Metrics=[
            'AmortizedCost'
        ]
    )
    # Metricsの参考。
    # https://qiita.com/tamura_CD/items/4a9a412faf379b334986

    # responseはdictで、['ResultsByTime']に各日付の情報が入っている
    ret_list = []
    for d in response['ResultsByTime']:
        tmp = {
            'start' : d['TimePeriod']['Start'],
            'end' : d['TimePeriod']['End'],
            'billing' :  d['Total']['AmortizedCost']['Amount']
        }
        ret_list.append(tmp)
    return ret_list


def lambda_handler(event, context):
    # boto3で接続
    client = boto3.client('ce', region_name='us-east-1')
    # 3日前~今日を取得する
    ret = get_cost_for_oneday(client, 3,0)
    return {
        'statusCode': 200,
        'body': ret
    }

(2-1)LambdaのLayer(Line送信用ライブラリ)

(2-2)LambdaのLine送信

  • ソースは下記
  • Layerに(2-1)で作ったものを指定する。
  • 環境変数にLineのアクセストークンを入れる必要がある。(これを見つけるのに結構はまった。後術。)

f:id:ebinafactory:20211123231147p:plain

from linebot import LineBotApi
from linebot.models import TextSendMessage
import json
import os


def lambda_handler(event, context):
    # ドル円のレート
    rate = 115

    
    # 各日の文字列を入れる
    ans_str_list = []
    # 新しい順に文字列を作る
    costdata_list = reversed(event['body'])
    for d in costdata_list:
        doller, start, end = d['billing'], d['start'], d['end']
        yen = int(float(doller) * rate)
        tmp = f'{yen}円: 期間[{start}~{end}], (${doller})'
        ans_str_list.append(tmp)
        # print(tmp)

    mes_text = "\n".join(ans_str_list)
    #print(mes_text)

    messages = TextSendMessage(text=mes_text)

    # 環境変数でセットする(Line DevelopersのMessagingAPIのChannel access token (long-lived))
    CHANNEL_ACCESS_TOKEN = os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)

    

    # Lineにメッセージを投げる
    line_bot_api.broadcast(messages)
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

(2-2)Line developers

正直良くわかっていないけど、いくつかわかったこと。 * Line Developerに登録する。チャンネルを作るとBot用のアカウントが作成されるような形になる。APIからはこのアカウントが操作できる感じ。Bot用アカウントと個人Lineアカウントを友だち状態にしておいて、ブロードキャスト送信するかたちとした。(ピンポイントで送信する方法がよくわからなかったので) * PythonでLine botを作ってみた - Qiita * CHANNEL_ACCESS_TOKEN は、LINE Developers の MessagingAPIの画面の一番したのほうにある、Channel access token(long-lived)の値を使う * Lineのuseridはスルッとは分からないようだった(Lineアプリから見えているものとは別のuseridが内部にあり、APIなどからはそのuseridを使う)。個人のuseridがわかれば、broadcastではなく、ピンポイントなメッセージ送信に切り替えられるのに・・・。

(3)StepFunction

  • StepFunctionは、Lambdaを複数つなげたもの。JP1/AJSみたいな感じ。
  • 先に動いたLambdaの戻り値を、後続のLambdaが受け取ることができる。
    • 今回は、1つ目はAWS価格を返すようにして、2つ目はそれを受け取ってLineに投げる作りとした。2つ目を単体でテスト実行するときは、実行時のパラメータにダミーデータを入れることができる。
  • StepFunctionが動くときのロールも注意が必要
    • Lambdaを実行する権限があればOKだけど、Lambda(リソース)に対して実行できるかの権限までちゃんと指定しなければいけない。

f:id:ebinafactory:20211123233904p:plain

EventBridgeでの定期実行。

f:id:ebinafactory:20211123233343p:plain

スクリーンショット