データアナリティクス事業本部のueharaです。
今回は、Lambda+Glue+Step Functionsの構成をServerless FrameworkとAWS SAMのそれぞれでデプロイしてみたいと思います。
はじめに
2023年の10月に、Serverless FrameworkがV.4から有料化されることが発表されました。
これまでAWS上でETL処理を行うリソースのデプロイにServerless Frameworkを多用していたのですが、有料化に伴い別の手段も検討したく、本記事ではAWS Serverless Application Model (AWS SAM)を利用したいと思います。
今回デプロイを検証するのは、比較的軽量なETLでありがちな以下の構成になります。
S3の特定のパスにファイルがPutされたことをEventBridgeで検知し、Step Functionsを起動します。
Step FunctionsのワークフローとしてはLambda→Glueと処理が進むような流れを考えます。
※S3は既に構築済みのものとし、Serverless Framework及びSAMでのデプロイは今回は行いません。
Serverless Frameworkでのデプロイ
まずはServerless Frameworkによるデプロイを行います。
フォルダは構成は以下の通りです。
.
├── glue_scripts
│ └── test_glue.py
├── handler
│ └── test_func.py
├── package.json
└── serverless.yml
ファイルの用意
test_glue.py
test_glue.py
にはGlueで実行するスクリプトを記載します。
今回は検証なので一定時間Sleepするだけの処理を記載することにします。
test_glue.py
import sys
import time
def main(argv):
print("start")
# sleep 5 minutes
time.sleep(300)
print("end")
main(sys.argv)
こちらのファイルをGlueから参照できるようにするため任意のS3バケットにアップロードしておきます。
私は s3://cm-da-uehara/glue-scripts/test_glue.py
としてアップロードしました。
test_func.py
test_func.py
にはLambdaで実行するスクリプトを記載します。
Glueのスクリプトと同様に、単純に一定時間Sleepするだけの処理を記載することにします。
test_func.py
import time
def lambda_handler(event, context):
print("start")
# sleep 1 minute
time.sleep(60)
print("end")
return {"message": "success"}
package.json
package.json
に今回のデプロイに関連するリソースの依存関係を記載しておきます。
package.json
{
"dependencies": {
"serverless": "^3.19.0"
},
"devDependencies": {
"serverless-python-requirements": "^5.4.0",
"serverless-step-functions": "^3.20.1"
},
"name": "uehara-sls-test"
}
Serverless Frameworkを用いたStep Functionsのデプロイにはserverless-step-functionsプラグインが必要になるため、そちらも記載をしています。
serverless.yml
Serverless Frameworkでのデプロイにおいて一番の肝になる serverless.yml
は以下の通りに記載します。
serverless.yml
service: uehara-test-app-sls
frameworkVersion: '3'
configValidationMode: error
plugins:
- serverless-step-functions
provider:
name: aws
runtime: python3.9
stackName: ${self:service}
stage: ${env:ENV, 'dev'}
region: ap-northeast-1
deploymentBucket:
name: cm-da-uehara
timeout: 180 # 180 seconds
memorySize: 128
package:
individually: true
patterns:
- '!handler/**'
- '!.git/**'
- '!.gitignore'
- '!.serverless'
- '!.serverless/**'
- '!package.json'
- '!package-lock.json'
- '!serverless.yml'
- '!yarn.lock'
- '!node_modules'
- '!node_modules/**'
- '!__pycache__'
functions:
# Lambda
MyLambdaFunction:
handler: handler/test_func.lambda_handler
name: "uehara-sls-test-lambda"
role: MyLambdaFunctionRole
package:
patterns:
- 'handler/test_func.py'
resources:
Resources:
# Lambda Role
MyLambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
RoleName: "uehara-sls-test-lambda-role"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- lambda.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Glue Job
MyGlueJob:
Type: AWS::Glue::Job
Properties:
Role: !GetAtt GlueJobRole.Arn
GlueVersion: '3.0'
Name: uehara-sls-test-glueJob
DefaultArguments:
"library-set": "analytics"
Command:
Name: pythonshell
ScriptLocation: "s3://cm-da-uehara/glue-scripts/test_glue.py"
PythonVersion: "3.9"
ExecutionProperty:
MaxConcurrentRuns: 3
MaxCapacity: 0.0625
MaxRetries: 0
# Glue Job Role
GlueJobRole:
Type: AWS::IAM::Role
Properties:
RoleName: "uehara-sls-test-glueJob-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: glue.amazonaws.com
Action: "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole
- arn:aws:iam::aws:policy/AmazonS3FullAccess
# Step Functions Role
MyStepFunctionsRole:
Type: AWS::IAM::Role
Properties:
RoleName: "uehara-sls-test-sf-role"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- states.ap-northeast-1.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaRole
- arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole
# EventBridge Role
MyEventBridgeRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: EventBridgePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- "states:StartExecution"
Resource:
- "arn:aws:states:*:*:stateMachine:$uehara-sls-test-sf"
RoleName: uehara-sls-test-eventbridge-role
stepFunctions:
# Step Functions
stateMachines:
MyStepFunctions:
name: "uehara-sls-test-sf"
role: !GetAtt MyStepFunctionsRole.Arn
definition:
Comment: "Test Step Functions"
StartAt: InvokeLambda
States:
InvokeLambda:
Type: Task
Resource: !GetAtt MyLambdaFunction.Arn
Next: InvokeGlueJob
InvokeGlueJob:
Type: Task
Resource: "arn:aws:states:::glue:startJobRun.sync"
Parameters:
JobName: !Ref MyGlueJob
End: true
events:
- cloudwatchEvent:
name: "uehara-sls-test-sf-event"
iamRole: !GetAtt MyEventBridgeRole.Arn
event:
source:
- "aws.s3"
detail-type:
- "Object Created"
detail:
bucket:
name:
- "cm-da-uehara"
object:
key:
- prefix: 'tmp/'
特徴として、Lambda関数についてはfunctions
セクションに、Step FunctionsについてはstepFunctions
セクションに記載を行います。
GlueのScriptLocation
については、ご自身でアップロードしたGlueのスクリプトを保管しているS3のURIを指定して下さい。
Step Functionsの起動トリガーについて、cm-da-uehara
というS3バケットにオブジェクトキーのPrefixにtmp/
がついたものがPutされたら起動するというものになっています。
デプロイ
以下コマンドでデプロイを行います。
$ sls deploy --verbose
デプロイが完了すると、以下のようにStep Functionsが実行できるようになっているかと思います。
実行
指定したS3バケットのtmp/
に適当なデータをPutしてStep Functionsを起動すると、Lambda→Glueの順に処理が実施されます。
それぞれの処理はただ単にSleepをしているだけなので、問題なく終了するかと思います。
AWS SAMでのデプロイ
次に、SAMで同じことをしてみます。
フォルダは構成は以下の通りです。
.
├── glue_scripts
│ └── test_glue.py
├── handler
│ └── test_func.py
├── samconfig.toml
└── template.yaml
ファイルの用意
【注】 test_glue.py
とtest_func.py
はServerless Frameworkで記載した内容と同じのため、ここでは割愛します。
samconfig.toml
samconfig.toml
はAWS SAM CLIの設定ファイルになります。
記載方法についてはこちらのドキュメントに記載がありますので、そちらを参考頂ければと思います。
今回は以下のように設定してみました。
samconfig.toml
version = 0.1
[default]
region = "ap-northeast-1"
[default.build.parameters]
debug = true
[default.deploy.parameters]
stack_name = "uehara-test-app"
s3_bucket = "cm-da-uehara"
s3_prefix = "sam-deploy"
capabilities = "CAPABILITY_NAMED_IAM"
confirm_changeset = true
1点特筆しておくと、カスタム名を持つIAMリソースを作成する場合は、CAPABILITY_NAMED_IAM
の指定が必要になりますので、今回そちらを設定しています。
template.yaml
template.yaml
がSAMのテンプレートファイルになります。
※Serverless Frameworkでいうところのserverless.yml
に相当。
記載内容は以下の通りです。
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: "Test SAM Application"
Globals:
Function:
Timeout: 180 # 180 seconds
MemorySize: 128
Resources:
# Lambda
MyLambdaFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: "uehara-sam-test-lambda"
Role: !GetAtt MyLambdaFunctionRole.Arn
CodeUri: handler/
Handler: test_func.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
# Lambda Role
MyLambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
RoleName: "uehara-sam-test-lambda-role"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- lambda.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Glue Job
MyGlueJob:
Type: AWS::Glue::Job
Properties:
Role: !GetAtt MyGlueJobRole.Arn
GlueVersion: '3.0'
Name: "uehara-sam-test-glueJob"
DefaultArguments:
"library-set": "analytics"
Command:
Name: pythonshell
ScriptLocation: "s3://cm-da-uehara/glue-scripts/test_glue.py"
PythonVersion: "3.9"
ExecutionProperty:
MaxConcurrentRuns: 3
MaxCapacity: 0.0625
MaxRetries: 0
# Glue Job Role
MyGlueJobRole:
Type: AWS::IAM::Role
Properties:
RoleName: "uehara-sam-test-glueJob-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: glue.amazonaws.com
Action: "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole
- arn:aws:iam::aws:policy/AmazonS3FullAccess
# Step Functions
MyStepFunctions:
Type: AWS::Serverless::StateMachine
Properties:
Name: "uehara-sam-test-sf"
Definition:
Comment: "Test Step Functions"
StartAt: InvokeLambda
States:
InvokeLambda:
Type: Task
Resource: !GetAtt MyLambdaFunction.Arn
Next: InvokeGlueJob
InvokeGlueJob:
Type: Task
Resource: "arn:aws:states:::glue:startJobRun.sync"
Parameters:
JobName: !Ref MyGlueJob
End: true
Role: !GetAtt MyStepFunctionsRole.Arn
Events:
S3Event:
Type: EventBridgeRule
Properties:
RuleName: "uehara-sam-test-sf-event"
Pattern:
source:
- aws.s3
detail-type:
- "Object Created"
detail:
bucket:
name:
- "cm-da-uehara"
object:
key:
- prefix: "tmp/"
# Step Functions Role
MyStepFunctionsRole:
Type: AWS::IAM::Role
Properties:
RoleName: "uehara-sam-test-sf-role"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- states.ap-northeast-1.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaRole
- arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole
Lambdaのメモリサイズやタイムアウト等の設定値については、先にServerless Frameworkでデプロイしたものと同じ値にしています。
Serverless Frameworkと違い、LambdaやStep Functionsについても全てResources
セクションに記載をします。
Lambdaについて、Serverless Frameworkではserverless.yml
のトップのpackage
→patterns
で明示的に除外設定を行わなければトップ配下の全てのリソースが各関数にアップロードされる仕組みなのですが、SAMではCodeUri
にてLambdaにアップロードするディレクトリを明示的に指定します。
Step Functionsについて、起動のためのEventBridgeのルールはEvents
プロパティに記載することができます。
Serverless Frameworkの時と違いEventBridge用のIAMロールを明示的に用意しておりませんが、上記の記載で指定のステートマシンのみを起動することが許可されたポリシーを持つIAMロールが自動で作成されアタッチされます。
また、Step Functionsの定義についてはDefinition
プロパティにてyaml形式で記載することができますが、以下のように別ファイルに外出しして記載することもできます。
MyStepFunctions:
Type: AWS::Serverless::StateMachine
Properties:
Name: "uehara-sam-test-sf"
DefinitionUri: sfs/xxx.json
Role: !GetAtt MyStepFunctionsRole.Arn
...
デプロイ
以下コマンドでデプロイを行います。
$ sam deploy
デプロイが完了すると、以下のようにStep Functionsが実行できるようになっているかと思います。
明示的に作成していなかったEventBridgeのIAMロールついても念のため確認すると、以下の通り指定のStep Functionsを起動できるのポリシーのみを持つものになっていました。
実行
Serverless Frameworkでのデプロイと同じ内容のものをデプロイしているので、挙動に違いはありません。
Step Functionsが起動されると、Lambda→Glueの順に処理が実施されます。
SAMを利用して気になった点
基本的にはCloudFormationベースでの記載となるため、Serverless FrameworkとSAMで記述方法が似ているところも多いですが、個人的にSAMについて以下の点が気になりました。
yamlファイルが分割できない
Serverless Frameworkではyamlファイルの記述を分割し、ファイルを分けることができます。
例:
serverless.yml
functions:
# Lambda関数を3つ用意
- ${file(lambda/lambda_a.yml):functions}
- ${file(lambda/lambda_b.yml):functions}
- ${file(config/lambda_c.yml):functions}
これにより、複数人での共同開発においてserverless.yml
の競合をなるべく抑えることができ、またserverless.yml
がダラダラと長くならないため可読性も向上します。
SAMでもファイル分割自体をすることができますが、それは「スタックも別に作成する」ということになり、スタックを別々に作成しネストする他ありません。
SAMで1つのスタックにまとめたいとなるとtemplate.yaml
が肥大化することになるので、その点1つのスタックを複数ファイルに分割して記載できるServerless Frameworkは便利だと感じました。
変数が使えない
Serverless Frameworkにはcustom
というセクションがあり、自由に変数を定義できます。
SAMでもMappings
セクションにパラメータ値をゴリゴリ書くこともできますが、Serverless Frameworkの変数ほどの汎用性はないためその点についても惜しいポイントだと感じます。
最後に
今回は、Lambda+Glue+Step Functionsの構成をServerless FrameworkとAWS SAMのそれぞれでデプロイしてみました。
参考になりましたら幸いです。