【AWS SAM】 Lambda + Step Functions + EventBridge
こんにちは、 iwanaga です。
表題のコードを垂れ流します。
サムネイルは以下から拝借しています
簡単な経緯
このブログは、3 名の怠惰さんが休日を充実させるべく、何かしらのアウトプットを持ち回りで行う趣味ブログである
持ち回りの担当回をサボった場合は、ペナルティとして他の怠惰さんに金銭を支払うというルール付き
3 名は怠惰なので、自分の担当日を覚えておくのが面倒、考えるのも面倒
なので、リマインドの仕組みが必要
メールだと見ないので Slack でメンションが欲しい
ペナルティも覚えておくのが面倒なので、ゆくゆくはシステムで全てを管理したい
…ということで、まずは担当日に通知するシステムを構築することになりました。
なんで SAM?
最優先は金額が安いこと
アプリケーションは従量課金の Lambda
DB も従量課金の DynamoDB
サーバレス構成になりそう
関数ローカル実行やロールスニペットも提供されている SAM を採用
SAM が想定するユースケースに合致していれば短いコードでいい感じに IaC を実現できる
CloudFormation の記述も共存可能なので、定義できないリソースがあるという懸念もない
あと単純に技術的興味
構成図
AWS リソース
StepFunction 定義
SAM テンプレート
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
SAM Template for weekly_report
Parameters:
DynamoTableName:
Type: String
NotifyWeeklyReportSlackUrl:
Type: String
Globals:
Function:
Timeout: 10
Environment:
Variables:
NOTIFY_WEEKLY_REPORT_SLACK_URL: !Ref NotifyWeeklyReportSlackUrl
DYNAMO_DB_TABLE: !Ref DynamoTableName
Resources:
#####################################
# 次週報を書くユーザ取得
#####################################
GetNextWeeklyReportUserFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: build/lambda
Handler: get-next-weekly-report-user.handler
Runtime: nodejs12.x
FunctionName: nippo-get-next-weekly-report-user
Policies:
- DynamoDBReadPolicy:
TableName: !Ref DynamoTableName
#####################################
# 週報提出者にリマインド
#####################################
RemindWeeklyReportSlackFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: build/lambda
Handler: remind-weekly-report-slack.handler
Runtime: nodejs12.x
FunctionName: nippo-remind-weekly-report-slack
Policies:
- DynamoDBReadPolicy:
TableName: !Ref DynamoTableName
#####################################
# 最後に週報書いたユーザ番号を更新
#####################################
UpdateLastWeeklyReporterIndexFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: build/lambda
Handler: update-last-weekly-reporter-index.handler
Runtime: nodejs12.x
FunctionName: nippo-update-last-weekly-reporter-index
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DynamoTableName
#####################################
# State Machine Remind Weekly Report to Slack
#####################################
RemindWeeklyReportToSlack:
Type: AWS::Serverless::StateMachine
Properties:
Name: remind-weekly-report-to-slack-state-machine
DefinitionUri: infrastructure/remind-weekly-report-job.asl.json
DefinitionSubstitutions:
NippoGetNextWeeklyReportUserArn: !GetAtt GetNextWeeklyReportUserFunction.Arn
NippoRemindWeeklyReportSlackArn: !GetAtt RemindWeeklyReportSlackFunction.Arn
NippoUpdateLastWeeklyReporterIndexArn: !GetAtt UpdateLastWeeklyReporterIndexFunction.Arn
Role: !GetAtt StateMachineRemindWeeklyReportToSlackRole.Arn
Logging:
Level: ALL
IncludeExecutionData: True
Destinations:
- CloudWatchLogsLogGroup:
LogGroupArn: !GetAtt StateMachineRemindWeeklyReportToSlackLogGroup.Arn
Events:
ScheduledEvent:
Type: Schedule
Properties:
Name: remind-weekly-report-to-slack-scheduled-event
Enabled: true
Schedule: cron(0 0 ? * SAT-SUN *)
#####################################
# State Machine Remind Weekly Report to Slack Log Group
#####################################
StateMachineRemindWeeklyReportToSlackLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: remind-weekly-report-to-slack-logs
StateMachineRemindWeeklyReportToSlackRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- states.amazonaws.com
Action:
- sts:AssumeRole
Path: /service-role/
ManagedPolicyArns:
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess # 妥協 https://docs.aws.amazon.com/step-functions/latest/dg/cw-logs.html#cloudwatch-iam-policy
Policies:
- PolicyName: lambda-scoped-access
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: lambda:InvokeFunction
Resource:
- !GetAtt GetNextWeeklyReportUserFunction.Arn
- !GetAtt RemindWeeklyReportSlackFunction.Arn
- !GetAtt UpdateLastWeeklyReporterIndexFunction.Arn
補足: ブログ投稿のことを便宜的に「週報」「WeeklyReport」という概念で呼称しています。
起動コマンド
- name: Sam Package
run: |
sam package --template-file template.yaml \
--output-template-file packaged-template.yaml \
--s3-bucket nippo-scheduled-task-sam
- name: Sam Deploy
run: |
echo y | sam deploy --no-fail-on-empty-changeset \
--stack-name nippo-scheduled-task \
--template-file packaged-template.yaml \
--parameter-overrides \
DynamoTableName=${{ secrets.NIPPO_TABLE }} \
NotifyWeeklyReportSlackUrl=${{secrets.NOTIFY_WEEKLY_REPORT_SLACK_URL}} \
--capabilities CAPABILITY_IAM \
--role-arn ${{secrets.SAM_EXECUTION_ROLE_ARN}}
簡単な解説
Transform
SAM を使うことを明示します。
Transform: AWS::Serverless-2016-10-31
Parameters
コマンドラインから受け取るパラメタを定義します。
!Ref DynamoTableName みたいな感じで参照できます。
Parameters:
DynamoTableName:
Type: String
Globals
テンプレート内の全リソースに適用する設定を行います(デフォルトの設定のようなイメージ)。
今回の例では、Function (Lambda 関数) に適用する設定を行います。Timeout 10 秒と環境変数を設定しています。
Globals:
Function:
Timeout: 10
Environment:
Variables:
NOTIFY_WEEKLY_REPORT_SLACK_URL: !Ref NotifyWeeklyReportSlackUrl
DYNAMO_DB_TABLE: !Ref DynamoTableName
Lambda 関数
以下の定義リファレンスに従っています(雑)。
GetNextWeeklyReportUserFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: build/lambda
Handler: get-next-weekly-report-user.handler
Runtime: nodejs12.x
FunctionName: nippo-get-next-weekly-report-user
Policies:
- DynamoDBReadPolicy:
TableName: !Ref DynamoTableName
StepFunctions
DefinitionUri
定義は別ファイル(remind-weekly-report-job.asl.json)で持っているのでそれを指定
DefinitionSubstitutions
該当定義ファイルのプレースホルダーを埋めます。このブロックに書いておけば勝手に埋めてくれるので便利。
定義ファイル側で ${NippoGetNextWeeklyReportUserArn} のように定義しておくことでまるっと埋めてくれる
Events
StateMachine の実行トリガーを設定できる
StateMachine 側から実行トリガーを指定するのは若干変な感じはするが…
ここに書いておけば、別定義することなく EventBridge リソースが作られる…!! これが非常に便利。
ただし、CloudFormation ほどの柔軟な設定はできないので、SAM で対応できない設定をする場合は CloudFormation リソース定義に切り替える必要がありそう
注意: StateMachine のリソース名と Events のリソース名の合計が長すぎるとデプロイエラーになる。文字数制限があるっぽい。
RemindWeeklyReportToSlack:
Type: AWS::Serverless::StateMachine
Properties:
Name: remind-weekly-report-to-slack-state-machine
DefinitionUri: infrastructure/remind-weekly-report-job.asl.json
DefinitionSubstitutions:
NippoGetNextWeeklyReportUserArn: !GetAtt GetNextWeeklyReportUserFunction.Arn
NippoRemindWeeklyReportSlackArn: !GetAtt RemindWeeklyReportSlackFunction.Arn
NippoUpdateLastWeeklyReporterIndexArn: !GetAtt UpdateLastWeeklyReporterIndexFunction.Arn
Role: !GetAtt StateMachineRemindWeeklyReportToSlackRole.Arn
Logging:
Level: ALL
IncludeExecutionData: True
Destinations:
- CloudWatchLogsLogGroup:
LogGroupArn: !GetAtt StateMachineRemindWeeklyReportToSlackLogGroup.Arn
Events:
ScheduledEvent:
Type: Schedule
Properties:
Name: remind-weekly-report-to-slack-scheduled-event
Enabled: true
Schedule: cron(0 0 ? * SAT-SUN *)
ロググループと IAM ロール
CloudFormation の記述に従っているのであまり書くことはなさそう
起動コマンド
デプロイコマンド実行は、ベストプラクティスに則り、 IAM ユーザに IAM ロールを引き受けさせる形で実行します。
IaC を実現すると、広範囲のアクセス権限が必要になり、ユーザとして持たせておくと、その鍵管理をどうするか問題が発生するからです。
secrets は、GitHub Actions に設定した secrets を使用する想定です。
具体的なコマンドについては、この辺を参考に…
まとめ
SAM テンプレートを雑にみてきました。
AWS の公式ドキュメントは探すのも読むのも大変でしたが、SAM のおかげでパッと構築することができました。
今後は、EventBridge 複数起動しても大丈夫なようにする、ペナルティの計測などを実装していければ良いかなと思います。
他にも構築したシステムがあるので、それも今後記事にできればと思っています。
もっとこうした方が良いよ的な知見、お待ちしております。
では、ごきげんよう。