この記事は、AWS LambdaとServerless Advent Calendar 2020の11日目の記事です
昨日の記事は_kenshさんによる「AWS Lambda - Provisioned Concurrencyのベストプラクティス」でした
こんにちは、meiheiです
Clash Royaleというゲームでクランの運営をしています
このゲームではAPI(Clash Royale API)が提供されており、ゲーム内のデータを取得することが出来ます
このAPIを使って、1週間以上の放置しているクランメンバーを取得し、LINEにアラートを送るシステムを作っていきます
概要
完成したもの
こんな感じに、プレイヤー名
と放置している期間
がLINEに届きます
構成
今回作っていくシステムの構成は以下の通りです
クラロワAPIはIPアドレスを指定する必要があるので、NAT GatewayにEIPを割り当てています
- VPCの設定
- Clash Royale APIのKeyの作成
- Line Notifyの設定
- Lambda関数の作成・デプロイ(Serverless Framework)
の順で説明していきます
VPCの設定
以下の記事を参考(というか全く同じ)に設定していきます
まず、VPCを作成します
適当な名前タグを設定して、IPv4 CIDR ブロック
は10.0.0.0/16
にします
次に適当な名前のインターネットゲートウェイを作成して、先程作ったVPCにアタッチします
そしてVPCの中に、publicとprivateの2つのサブネットを作成します
「サブネットを作成」からVPCにvpc-lambda
を選択
適当な名前のサブネット名を設定して、IPv4 CIDR ブロック
は10.0.10.0/24
にします
同様にprivateのサブネットも設定します。IPv4 CIDR ブロック
は10.0.1.0/24
にします
次にVPC作成時に作られたルートテーブルを選択して、送信先に0.0.0.0/0
、ターゲットに先ほど作成したインターネットゲートウェイを設定します
次に、IPアドレスを固定するNAT Gateway&EIPを作成します(VPCの設定はあとちょっとです!)
適当な名前を入力し、サブネットに先程作ったpublicの方のサブネットを選択します。そしてElastic IP 割り当て
というボタンを押してEIPを生成し、NAT Gatewayを作成します。
最後に、VPCの新規ルートテーブルを作成し、送信先0.0.0.0/0
、ターゲットに先ほど作成したNAT Gatewayを設定します
サブネット関連付けの編集
からprivateのサブネットを関連付けで完了です
これで、Elastic IPの割り当てられた IPv4 アドレス
に表示されているIPアドレスが、LambdaからAPIを叩く時に使われる固定IPアドレスになります
クラロワAPIのKeyの作成
前提として、クラロワのdeveloperアカウントを作成してあるとします。
developer.clashroyale.com の右上の名前のところからMy Account
を選択し、Create New Key
から新しい鍵を作成します
KEY NAME
にわかりやすい名前、DESCRIPTION
にわかりやすい説明文、ALLOWED IP ADDRESS
に先程作成したIPアドレスを入力し、Create Key
で作成します。
こちらのTOKEN
を使います
LINE Notifyの設定
LINE Notify はWEBサービスから簡単にLINEに通知を送れるサービスです
まずログインしてもらって、右上の名前のところからマイページ
に移動し、下の方にあるアクセストークンの発行(開発者向け)
からトークンを発行する
を選択します
適当なトークン名を記入し、1:1でLINE Notifyから通知を受け取る
を選択してトークンを発行します
発行したトークンが表示されるので、こちらをコピーして保存します(トークンが表示されるのはコレきりなので、コピーを忘れたら再発行しましょう)
Lambda関数の作成・デプロイ(Serverless Framework)
前提として、Serverless Frameworkが使える環境にします。以下の記事がおすすめです
新規のサービスを作成します
$ serverless create --template aws-python3 -p cr-abandon-to-line
次に、必要なプラグインを入れます
$ npm install --save serverless-python-requirements
$ npm install -D serverless-dotenv-plugin
serverless-python-requirements
はpip install
した外部モジュールを取り込みます(参考)
serverless-dotenv-plugin
はserverless.yml
に直接書きたくない環境変数を設定ファイルからいい感じ取り出してくれます(参考)
プロジェクトディレクトリ下に.env
ファイルを作成し、先程発行したトークンなどを入れておきます
CR_ACCESS_KEY=[クラロワAPIのトークン]
LINE_NOTIFY_ACCESS_TOKEN=[LINE Notify用に発行したトークン]
VPC_SECURITY_GROUP_IDS=[VPCのセキュリティグループのID]
VPC_SUBNET_IDS=[private-subnetのid]
LambdaがVPCのprivateサブネットに入るようにserverless.yml
に記述します(参考)
iamRoleStatements:
- Effect: "Allow"
Action:
- "ec2:CreateNetworkInterface"
- "ec2:DescribeNetworkInterfaces"
- "ec2:DeleteNetworkInterface"
Resource:
- "*"
vpc:
securityGroupIds:
${env:VPC_SECURITY_GROUP_IDS}
subnetIds:
${env:VPC_SUBNET_IDS}
また、Pythonから環境変数として呼び出しを行うので、その設定もserverless.yml
に記述します
environment:
CR_ACCESS_KEY: ${env:CR_ACCESS_KEY}
LINE_NOTIFY_ACCESS_TOKEN: ${env:LINE_NOTIFY_ACCESS_TOKEN}
そして、クラロワAPIを叩いて、データを整形し、LINE Notifyに送るように実装します。
import json
import os
import requests
from datetime import datetime, timedelta
from typing import Dict, List
CR_BASE_URL = 'https://api.clashroyale.com/v1'
LINE_NOTIFY_URL = "https://notify-api.line.me/api/notify"
CR_ACCESS_KEY = os.environ['CR_ACCESS_KEY']
LINE_NOTIFY_ACCESS_TOKEN = os.environ['LINE_NOTIFY_ACCESS_TOKEN']
def init_headers(api_key: str) -> Dict[str, str]:
"""初期化されたヘッダー情報の辞書を返す"""
return {'authorization': f'Bearer {api_key}'}
def get_member(clan_tag: str) -> Dict[str, str]:
"""クラロワAPIからclan_tagのメンバー情報をGETする"""
clan_tag = clan_tag.replace('#', '%23')
url = CR_BASE_URL + f'/clans/{clan_tag}/members'
headers = init_headers(CR_ACCESS_KEY)
res = requests.get(url, headers=headers)
return res.json()
def last_seen_to_datetime(last_seen: str) -> datetime:
"""last_seenの記法からdatetime型に変換する"""
return datetime.strptime(last_seen, '%Y%m%dT%H%M%S.000Z')
def filter_by_last_seen(items: List[dict], dead_line: datetime) -> List[dict]:
"""最終ログインがdead_lineのクラメンの情報を抽出する"""
before_last_seen = lambda i: last_seen_to_datetime(i['lastSeen']) < dead_line
return [item for item in items if before_last_seen(item)]
def generate_message(cr_items: List[dict]) -> str:
"""line notify送信用のメッセージを作成する"""
message = ''
for member in cr_items:
last_seen_diff = str(datetime.now() - last_seen_to_datetime(member['lastSeen'])).split('.')[0]
message += f'\n{member["name"]}: {last_seen_diff}'
return message
def send_line(message: str):
"""LINE Notifyにmessageを送る"""
headers = init_headers(LINE_NOTIFY_ACCESS_TOKEN)
data = {'message': message}
requests.post(LINE_NOTIFY_URL, data=data, headers=headers)
def lambda_function(event, context) -> Dict[str, str]:
"""実行用の関数"""
data = get_member('#228UCY92')
dead_line = datetime.now() - timedelta(days=1)
filtered_data = filter_by_last_seen(data['items'], dead_line)
if (filtered_data):
message = generate_message(filtered_data)
send_line(message)
return f'ok: {len(filtered_data)}件取得しました'
最後に、hundler.py
のlambda_function
が定期的に実行されるようにserverless.yml
に記述します(参考)
functions:
abandon-member-to-line-notify:
handler: handler.lambda_function
events:
- schedule: cron(0 0 * * ? *) # 日本時間で朝の9時
AWSにデプロイしたら、このシステムの完成です
$ serverless deploy
終わりに
何でも楽にやってくれるLambdaさんだと思っていたら、IPアドレスが固定できないため、なかなか面倒くさい構成になりました
気になるお値段は11日ぐらい使って1,500円ぐらい。月換算だと4,000円ぐらいです(高い)
NAT Gatewayが動いているだけでお金がかかるので、こんなにも高くついているようです
AWSの勉強目的だといいですけど、クラロワAPI+Lambdaは普通にオススメできないですね。
ソースコード
今回のソースコードはすべてGitHubにあげてあります。READMEは書いてません…
クランメンバー募集
僕の運営しているクラン「ことりカフェ」ではメンバーを募集しています
ほぼ毎日クラロワやってるけど、そこまでガチ勢じゃない、まったりプレイヤーが集うクランです
興味ある方はぜひTwitterにてご連絡ください!
明日は22tさんによる「chaliceで簡単pythonボット制作」です。お楽しみに!