Serverless FrameworkのStep Functions用プラグインでState LanguageをYAMLで記述する #reinvent
はじめに
こんにちは、中山です。
昨日feedlyを眺めていたらかなり便利そうなツールを発見したのでご紹介したいと思います。Serverless FrameworkでStep Functionsを実行するプラグインがリリースされました!最高。作者の方が書かれたQiitaエントリとプラグインのGitHubリポジトリは以下の通りです。
このプラグインを利用することで以下のことができます。かなり便利そうです。この冬はこれで決まり感がありますね。最高!
- Step FunctionsのステートをJSONではなくYAMLで書ける
- ある程度の長さになってくるとやはりJSONではツライです
- ステートマシンの作成/更新/削除をCLIから操作できる
- AWS CLIを生で叩くのはツライです
現時点(2016/12/31)でプラグインに指定可能なオプションは以下の通りです。
# デプロイ用オプション $ sls deploy stepf -h Plugin: ServerlessStepFunctions deploy stepf .................. Deploy Step functions --state / -t (required) ............ Name of the State Machine # Invoke用オプション $ sls invoke stepf -h Plugin: ServerlessStepFunctions invoke stepf .................. Remove Step functions --state / -t (required) ............ Name of the State Machine --data / -d ........................ String data to be passed as an event to your step function # 削除用オプション $ sls remove stepf -h Plugin: ServerlessStepFunctions remove stepf .................. Remove Step functions --state / -t (required) ............ Name of the State Machine
それでは早速使ってみましょう。
使ってみる
検証に利用した各種ツールのバージョンは以下の通りです。バージョンが異なる場合は結果が変わる可能性があります。その点ご了承ください。
- Serverless Framework: 1.4.0
- serverless-step-functions: 0.1.2
プラグインのインストールは以下のコマンドで実行します。
$ npm install --save serverless-step-functions
まずは手っ取り早くテンプレートからLambda関数と serverless.yml
を生成してプラグインの動作を確認してみます。
$ sls create -t aws-python -n step-functions-plugin-test Serverless: Generating boilerplate... _______ __ | _ .-----.----.--.--.-----.----| .-----.-----.-----. | |___| -__| _| | | -__| _| | -__|__ --|__ --| |____ |_____|__| \___/|_____|__| |__|_____|_____|_____| | | | The Serverless Application Framework | | serverless.com, v1.4.0 -------' Serverless: Successfully generated boilerplate for template: "aws-python"
Lambda関数をデプロイしておきます。
$ sls deploy -v Serverless: Creating Stack... Serverless: Checking Stack create progress... <snip>
READMEを参考に serverless.yml
を以下のようにしてみました。
serverless.yml
service: step-functions-plugin-test provider: name: aws runtime: python2.7 region: us-east-1 stage: dev plugins: - serverless-step-functions functions: hellofunc: handler: handler.hello stepFunctions: hellostepfunc: Comment: "A Hello World example of the Amazon States Language using an AWS Lambda Function" StartAt: HelloWorld States: HelloWorld: Type: Task Resource: hellofunc End: true
ステートマシンをデプロイしてみます。
$ sls deploy stepf -t hellostepfunc -v Serverless: Start to deploy hellostepfunc step function... Serverless: Finish to deploy hellostepfunc-dev step function
デプロイした内容を確認してみます。
# ステートマシンが作成されたことを確認 $ aws stepfunctions list-state-machines { "stateMachines": [ { "creationDate": 1483102366.55, "stateMachineArn": "arn:aws:states:us-east-1:************:stateMachine:hellostepfunc-dev", "name": "hellostepfunc-dev" } ] } # 内容を確認 $ aws stepfunctions describe-state-machine \ --state-machine-arn "$(aws stepfunctions list-state-machines \ --query 'stateMachines[?name==`hellostepfunc-dev`].stateMachineArn' \ --output text)" { "status": "ACTIVE", "definition": "{\"Comment\":\"A Hello World example of the Amazon States Language using an AWS Lambda Function\",\"StartAt\":\"HelloWorld\",\"States\":{\"HelloWorld\":{\"Type\":\"Task\",\"Resource\":\"arn:aws:lambda:us-east-1:************:function:step-functions-plugin-test-dev-hellofunc\",\"End\":true}}}", "name": "hellostepfunc-dev", "roleArn": "arn:aws:iam::************:role/serverless-step-functions-executerole-us-east-1", "stateMachineArn": "arn:aws:states:us-east-1:************:stateMachine:hellostepfunc-dev", "creationDate": 1483116193.335 }
YAMLで定義したState LanguageがJSONに変換されています。最高。出力が見づらい場合は以下のようにすればOKです。
$ aws stepfunctions describe-state-machine \ --state-machine-arn "$(aws stepfunctions list-state-machines \ --query 'stateMachines[?name==`hellostepfunc-dev`].stateMachineArn' \ --output text)" \ --query 'definition' \ --output text \ | jq . { "Comment": "A Hello World example of the Amazon States Language using an AWS Lambda Function", "StartAt": "HelloWorld", "States": { "HelloWorld": { "Type": "Task", "Resource": "arn:aws:lambda:us-east-1:************:function:step-functions-plugin-test-dev-hellofunc", "End": true } } }
ステートマシンに関連付けるIAM Roleも作成してくれているようです。内容を確認してみるとStep Functions用のAssume RoleとLambda関数をInvokeするためのポリシーが付いています。
$ aws iam get-role \ --role-name serverless-step-functions-executerole-us-east-1 { "Role": { "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { "Service": "states.us-east-1.amazonaws.com" } } ] }, "RoleId": "*********************", "CreateDate": "2016-12-30T16:43:09Z", "RoleName": "serverless-step-functions-executerole-us-east-1", "Path": "/", "Arn": "arn:aws:iam::************:role/serverless-step-functions-executerole-us-east-1" } } $ aws iam list-attached-role-policies \ --role-name serverless-step-functions-executerole-us-east-1 { "AttachedPolicies": [ { "PolicyName": "serverless-step-functions-executepolicy-us-east-1", "PolicyArn": "arn:aws:iam::************:policy/serverless-step-functions-executepolicy-us-east-1" } ] } $ aws iam get-policy-version \ --policy-arn "$(aws iam list-attached-role-policies \ --role-name serverless-step-functions-executerole-us-east-1 \ --query 'AttachedPolicies[].PolicyArn' --output text)" \ --version-id v1 { "PolicyVersion": { "CreateDate": "2016-12-30T16:43:10Z", "VersionId": "v1", "Document": { "Version": "2012-10-17", "Statement": [ { "Action": [ "lambda:InvokeFunction" ], "Resource": "*", "Effect": "Allow" } ] }, "IsDefaultVersion": true } }
続いてステートマシンを実行してみましょう。
$ sls invoke stepf -t hellostepfunc -v Serverless: Start function hellostepfunc... . { executionArn: 'arn:aws:states:us-east-1:************:execution:hellostepfunc-dev:7324c3c7-5e36-4292-b745-8e2da3faae48', stateMachineArn: 'arn:aws:states:us-east-1:************:stateMachine:hellostepfunc-dev', name: '7324c3c7-5e36-4292-b745-8e2da3faae48', status: 'SUCCEEDED', startDate: 2016-12-30T17:09:11.418Z, stopDate: 2016-12-30T17:09:12.458Z, input: '{}', output: '{"body": "{\\"input\\": {}, \\"message\\": \\"Go Serverless v1.0! Your function executed successfully!\\"}", "statusCode": 200}' }
正常に実行されました!作成したステートマシーンを削除してみます。
# 削除 $ sls remove stepf -t hellostepfunc -v Serverless: Remove hellostepfunc # ステータスが削除中となっていることを確認 $ aws stepfunctions describe-state-machine \ --state-machine-arn "$(aws stepfunctions list-state-machines \ --query 'stateMachines[?name==`hellostepfunc-dev`].stateMachineArn' \ --output text)" { "status": "DELETING", "definition": "{\"Comment\":\"A Hello World example of the Amazon States Language using an AWS Lambda Function\",\"StartAt\":\"HelloWorld\",\"States\":{\"HelloWorld\":{\"Type\":\"Task\",\"Resource\":\"arn:aws:lambda:us-east-1:************:function:step-functions-plugin-test-dev-hellofunc\",\"End\":true}}}", "name": "hellostepfunc-dev", "roleArn": "arn:aws:iam::************:role/serverless-step-functions-executerole-us-east-1", "stateMachineArn": "arn:aws:states:us-east-1:************:stateMachine:hellostepfunc-dev", "creationDate": 1483116193.335 } # 削除されたことを確認 $ aws stepfunctions list-state-machines { "stateMachines": [] }
正常に削除されましたね。次はもう少し複雑なState Languageにしてみます。 Lambda関数を新規に作成してインプットの内容に応じて処理を分岐させるようにステートマシンを追加してみます。
choice.py
def hello(event, context): return {'statusCode': int(event['statusCode'])}
serverless.yml
service: step-functions-plugin-test provider: name: aws runtime: python2.7 region: us-east-1 stage: dev plugins: - serverless-step-functions functions: hellofunc: handler: handler.hello choicefunc: handler: choice.handler stepFunctions: hellostepfunc: Comment: "A Hello World example of the Amazon States Language using an AWS Lambda Function" StartAt: HelloWorld States: HelloWorld: Type: Task Resource: hellofunc End: true choicestepfunc: Comment: Choice Step Function StartAt: FirstState States: FirstState: Type: Task Resource: choicefunc Next: ChoiceState ChoiceState: Type: Choice Choices: - Variable: $.statusCode NumericEquals: 200 Next: FirstMatchState Default: DefaultState FirstMatchState: Type: Pass Next: EndState DefaultState: Type: Fail Cause: No Matches! EndState: Type: Pass End: true
両方共再度デプロイします。なお、作者の方も書かれていますが現状Step Functionsにアップデート用API自体が用意されていないので、一度作成したステートマシンの設定は編集できません。そのため、アップデートする場合はステートマシンを一度削除してから新規で再作成するというフローで擬似的に実装されています。つまり、 修正したステートの記述に文法間違いなどがありデプロイに失敗した場合ステートマシンが更新されずにただ削除される という点は注意した方が良いと思います。このあたり、今後改善されるとうれしいですね。
一応ワークアラウンドは用意されていて、例えば -s
または --stage
でデプロイする環境を切り替えるという方法があります。もしくはAWSが公式に提供しているState Languageのlintツールを利用するという手もあります。例えば以下のように使えます。ちょっとエグいですが。。。 Resource
に指定するのは本来ARNなのでその点はエラーとなってしまうようです。 ~/.aws/cli/alias
に引数を取る形で設定しておけば使い回しも可能です。
$ awk '/hellostepfunc/,/^$/' serverless.yml \ | ruby -ryaml -rjson -e 'puts JSON.pretty_generate(YAML.load(ARGF)["hellostepfunc"])' \ | statelint /dev/stdin One error: State Machine.States.HelloWorld.Resource is "hellofunc" but should be A URI
デプロイされた内容を確認してみましょう。
$ aws stepfunctions describe-state-machine \ --state-machine-arn "$(aws stepfunctions list-state-machines \ --query 'stateMachines[?name==`choicestepfunc-dev`].stateMachineArn' \ --output text)" \ --query 'definition' --output text \ | jq . { "Comment": "Choice Step Function", "StartAt": "FirstState", "States": { "FirstState": { "Type": "Task", "Resource": "arn:aws:lambda:us-east-1:************:function:step-functions-plugin-test-dev-choicefunc", "Next": "ChoiceState" }, "ChoiceState": { "Type": "Choice", "Choices": [ { "Variable": "$.statusCode", "NumericEquals": 200, "Next": "FirstMatchState" } ], "Default": "DefaultState" }, "FirstMatchState": { "Type": "Pass", "Next": "EndState" }, "DefaultState": { "Type": "Fail", "Cause": "No Matches!" }, "EndState": { "Type": "Pass", "End": true } } }
良さそうですね。Invokeしてみます。
$ sls invoke stepf -t choicestepfunc -d "$(jo Code=200)" Serverless: Start function choicestepfunc... { executionArn: 'arn:aws:states:us-east-1:************:execution:choicestepfunc-dev:a4ab03b0-d648-4785-9cad-d4120a1336c8', stateMachineArn: 'arn:aws:states:us-east-1:************:stateMachine:choicestepfunc-dev', name: 'a4ab03b0-d648-4785-9cad-d4120a1336c8', status: 'SUCCEEDED', startDate: 2016-12-30T18:00:54.456Z, stopDate: 2016-12-30T18:00:54.962Z, input: '{"Code":200}', output: '{"statusCode": 200}' } }
意図した動きをしてくれているようです。最高。
まとめ
いかがだったでしょうか。
Serverless FrameworkからStep Functionsを利用するプラグインをご紹介しました。かなり便利そうなのでどんどん使っていきたいと思います!最高!
本エントリがみなさんの参考になれば幸いに思います。