EC2の無料期間が終了してしまう(まずい)
AWSのEC2を使って、アニメの配信通知をつぶやくTwitterボットを動かしているのですが、私のアカウントのAWS無料期間が2022年12月で終わってしまうため、その後は毎月約3000円の料金が発生してしまいます(もったいない!)。
費用圧縮のためにLambdaに移行しようと作業していた所、つまづいた箇所がいくつかありました。
同じようなことで困っている方の参考になればと思います。
lambda(ラムダ)で出来ること
Lambda は必要に応じて関数を実行し、1 日あたり数個から 1 秒あたり数千個のリクエストまで自動的にスケーリングします。課金は実際に消費したコンピューティング時間に対してのみ発生します。コードが実行されていない場合、料金は発生しません。
引用元:Amazon
1) フォルダごと圧縮しても動かない
lambdaでは、プログラムを「lambda_function.py」という名前で作り、圧縮・アップロードして利用します。
しかし、pythonファイルが入っているフォルダをそのまま圧縮しても動いてはくれません。
エラー内容
"errorMessage": "Unable to import module 'lambda_function': No module named 'lambda_function'",
ターミナルで「lambda_function.py」が置いてあるフォルダまで移動してから、以下のコマンドで圧縮してやる必要があります。
zip -r lambda_function.zip ./*
2) パッケージの追加が必要
圧縮ファイルをアップロードし、実行しようとすると
No module named ‘*****’
というエラーで失敗しました。
パッケージを組み込む必要があります。
パッケージ追加方法その1. ARN
lambdaの関数の画面の下の方に移動すると、「レイヤーの追加」というボタンがあるので、そちらを押します。
「ARNを指定」の行には、エラー画面で求められたパッケージのARNをこちらから探して入力します。
※自分のPythonバージョンとAWSリージョンを選択。
パッケージ追加方法その2. フォルダ内に保存
利用したいパッケージが上記URLにない場合、パッケージを「lambda_function.py」と同じフォルダに保存してから、圧縮 → アップロードしてやる必要があります。
「パッケージ名 ダウンロード」などで検索してソースを拾ってきます。
3) IAMでS3読み書き権限の追加が必要
設定情報CSVファイルの置き場として、AWSのS3を利用します。
lambdaからS3を利用するには、読み書きの権限をIAMで設定する必要があります。
1. IAM管理画面からポリシーを作成
以下のような内容でポリシーを作成します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": [
"arn:aws:s3:::*****(S3のバケット名)"
]
},
{
"Sid": "VisualEditor2",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::*****(S3のバケット名)/*"
]
}
]
}
2. lambdaの設定画面からロールを開く
3. ポリシーをアタッチ
4) datetimeの比較ができない
lambdaではタイムゾーンを日本時間に固定できないようです。
「pytz.timezone('Asia/Tokyo')」を使うことにしました。
import datetime
import pytz
dt_now = datetime.datetime.now(pytz.timezone('Asia/Tokyo'))
すると、EC2では動いていた日時の比較がエラーを出すようになってしまいました。
TypeError: can't compare offset-naive and offset-aware datetimes
これまでタイムゾーン情報を付加していなかった変数を以下のように変更しました。
dateFormatter = "%Y-%m-%d %H:%M%z"
progDateTime = datetime.datetime.strptime(YMD +" "+ HourMin + "+09:00", dateFormatter)
これで、
if dt_now >= progDateTime
が動くようになりました。
5) tmpフォルダへの保存が必要
pandasモジュールのpd.read_csvで、S3のCSVを直接読み込むことはできませんでした(何か方法はあるかもしれませんが)。
そこで、CSVファイルをlambdaの一時フォルダに保存してから、pd.read_csvを使います。
import pandas as pd
import boto3
def lambda_handler(event, context):
# S3からCSV読み込み
s3 = boto3.resource('s3')
response = s3.Object(bucket_name, savepath).get()
body = response['Body'].read()
decodedBody = body.decode(encoding)
# 一時ファイルの生成
tmpfilename = "/tmp/" + 'TWBot_' + str(time_now) + '.csv'
with open(tmpfilename, "w") as f:
f.write(decodedBody)
# read_csvでCSV読み込み
arr = pd.read_csv(tmpfilename)
### なんやかんや処理
# 変更した一時ファイルをS3にアップロード
s3.meta.client.upload_file(tmpfilename, bucket_name, savepath)
# 一時ファイル削除
os.remove(tmpfilename)
6) タイムアウトしてしまう
初期設定の3秒だとタイムアウトエラーになることが多かったので、10秒に変更したら動くようになりました。
lambdaの設定画面、一般タブ ⇨ 「編集」ボタンをクリック。
7) トリガーのcronの書き方がEC2と違う
定時実行するために「トリガー」を関数に追加してやります。
この時のcron式の書き方がEC2のcronbtabとは少し異なりました。
EC2の場合(毎時0分に実行)
0 * * * * /usr/bin/python3 /home/ec2-user/*****.py
lambdaのトリガーの場合(毎時0分に実行)
0 * * * ? *
cron(Minutes Hours Day-of-month Month Day-of-week Year)
というAmazonの説明の通り、
左から3番目が日付、左から5番目が曜日になります。
この2つのどちらかをハテナに設定する必要があります。
8) トリガーで実行したログの見方
トリガーで実行した結果は、lambdaの関数の画面から「モニタリング」タブ → 「CloudWatchのログを表示」から確認可能です。
以上です。