1. masaminh

    Posted

    masaminh
Changes in title
+pythonで定時実行するAWS Lambda処理を作った
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,179 @@
+# はじめに
+
+相変わらず競馬関係のプログラムを書いています。
+これまではHeroku上で開発していたのですが、仕事でAWSを使っていることもあり、個人開発でもAWSを使い始めました。
+
+Herokuだと毎日決まった時間に動作する処理を書く場合、Heroku Schedulerを使う形になりますが、AWSだとLambdaをCloudWatchイベントでキックする形…とはいうもののひとまとめになっている情報が見当たらなかったので、やってみたことを残しておきたいと思います。
+
+※2019年7月時点の情報です。AWSはサービス改善のスピードが速すぎてWebにまとめた情報があっという間に陳腐化するので、そこはご注意を…
+
+# AWS SAMのテンプレート
+
+AWS SAMを使ってみたのですが、元になるテンプレートを作成するためにsam initコマンドを使うとAPIゲートウェイからキックされる処理のテンプレートになります。
+
+定時実行する処理用のテンプレートは…ということで調べたところ、以下にありました。
+[CloudWatch イベント アプリケーションの AWS SAM テンプレート](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-scheduledevents-example-use-app-spec.html)
+
+だけど、これは定時実行ではなくて定期実行、かつ今回必要ないようなリソースも使っていたりだったので、定期実行する最低限のテンプレートを作りました。
+
+```yaml:template.yaml
+AWSTemplateFormatVersion: '2010-09-09'
+Transform: AWS::Serverless-2016-10-31
+Description: >
+ notify_hitokuchi
+
+ Sample SAM Template for notify_hitokuchi
+
+Globals:
+ Function:
+ Timeout: 10
+
+Resources:
+ NotifyHitokuchiFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ CodeUri: notify_hitokuchi/
+ Handler: app.lambda_handler
+ Runtime: python3.7
+ Events:
+ NotifyHitokuchi:
+ Type: Schedule
+ Properties:
+ Schedule: cron(30 12 * * ? *)
+```
+
+一番下の行のScheduleの行が定時実行する時刻の指定。式の記述方法は以下を参照。
+[ルールのスケジュール式](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html)
+
+# ハンドラでもらえるイベント情報の時刻
+
+先程のテンプレートだと、notify_hitokuchi/app.py内のlambda_handler関数が実行時に呼び出される関数になります。
+
+```python:app.py
+def lambda_handler(event, context):
+```
+
+eventには辞書形式でイベント情報が入ってきます。
+今回はCloudWatchイベントでキックされた時刻が欲しかったのですが、実行時刻はevent['time']に[ISO 8601](https://ja.wikipedia.org/wiki/ISO_8601)形式で入ってきます。
+それをそのまま「datetime.fromisoformat()」関数に渡せばdatetimeが入手できる…と思いきや、「2019-07-10T12:30:00Z」という形だと「datetime.fromisoformat()」は処理できないようでして…
+以下のようなコードでdatetimeを入手しました。
+
+```python
+# event['time']に'2019-07-10T12:30:00Z'形式の文字列が入ってくる
+datetime_str = event['time'].replace('Z', '+00:00')
+
+# '2019-07-10T12:30:00+00:00'形式だとfromisoformatで処理できる
+datetime_utc = datetime.fromisoformat(date_str)
+```
+
+# pip等でインストールしたライブラリ
+
+pip等でインストールしたライブラリをAWS Lambdaで使う場合、ライブラリを1箇所にまとめてZIPで固めて…という話をよく見かけますが、samを使っている分にはsam buildで済むようです。
+
+```
+$ sam build
+2019-07-19 07:42:34 Building resource 'NotifyHitokuchiFunction'
+2019-07-19 07:42:34 Running PythonPipBuilder:ResolveDependencies
+2019-07-19 07:42:44 Running PythonPipBuilder:CopySource
+
+Build Succeeded
+
+Built Artifacts : .aws-sam/build
+Built Template : .aws-sam/build/template.yaml
+
+Commands you can use next
+=========================
+[*] Invoke Function: sam local invoke
+[*] Package: sam package --s3-bucket <yourbucket>
+```
+
+# ローカルでの動作確認
+
+まずハンドラに渡されるイベント情報を作っておく必要がありますが、その元はsam local generate-eventを使って取得できます。
+今回はCloudWatchのイベントですが、以下で作成しました。
+
+```
+$ sam local generate-event cloudwatch scheduled-event > event.json
+$ cat event.json
+{
+ "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
+ "detail-type": "Scheduled Event",
+ "source": "aws.events",
+ "account": "",
+ "time": "1970-01-01T00:00:00Z",
+ "region": "us-east-1",
+ "resources": [
+ "arn:aws:events:us-east-1:123456789012:rule/ExampleRule"
+ ],
+ "detail": {}
+}
+```
+
+実際にローカルで動作確認を行う際には、
+
+```
+$ sam local invoke -e event.json NotifyHitokuchiFunction
+2019-07-19 08:03:10 Invoking app.lambda_handler (python3.7)
+2019-07-19 08:03:10 Found credentials in shared credentials file: ~/.aws/credentials
+
+Fetching lambci/lambda:python3.7 Docker container image......
+```
+
+という感じで呼び出すことができます。Dockerコンテナ上で動くので起動するのに少し時間がかかります。
+
+# デプロイ
+
+まずパッケージを作成。
+
+```
+$ sam package --s3-bucket バケット名 --output-template-file package.yaml
+
+Successfully packaged artifacts and wrote output template to file package.yaml.
+Execute the following command to deploy the packaged template
+aws cloudformation deploy --template-file /Users/masaminh/develop/notify_hitokuchi_aws/package.yaml --stack-name <YOUR STACK NAME>
+```
+
+引き続き、メッセージに従ってデプロイ...
+
+```
+$ aws cloudformation deploy --template-file package.yaml --stack-name notify-hitokuchi
+
+Waiting for changeset to be created..
+
+Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state Status: FAILED. Reason: Requires capabilities : [CAPABILITY_IAM]
+```
+
+メッセージのままだと上記のように怒られるので、capabilitiesにCAPABILITY_IAMを設定します。
+
+```
+$ aws cloudformation deploy --template-file package.yaml --stack-name notify-hitokuchi --capabilities CAPABILITY_IAM
+
+Waiting for changeset to be created..
+Waiting for stack create/update to complete
+Successfully created/updated stack - notify-hitokuchi
+```
+
+デプロイできました。
+
+# ログ出しに関して
+
+```python:app.py
+import logging
+
+logger = logging.getLogger()
+logger.setLevel(logging.INFO)
+
+def lambda_handler(event, context):
+ logger.info('start: event.time=%s', event['time'])
+```
+
+こんな感じで普通にloggerから出力すれば、CloudWatchで見られます。
+
+```
+[INFO] 2019-07-18T12:30:28.336Z 51865e95-cacd-4f9d-80e9-6e1f148357b9 start: event.time=2019-07-18T12:30:00Z
+```
+
+# おわりに
+
+今回は競馬的なことは書かずにノウハウ的なところのみでした。(Lambda関数名からして競馬系の処理だなぁという感じではありますが・笑)
+引き続き競馬系の個人開発はやってますので、またそのうち競馬開発系の記事を書きたいな〜と思います(笑)