本題
皆さん、Step Functions使ってますか?
...僕は使ってません。(仕事では)
でも、プライベートでは使っておりまして「これは一連の処理(=ワークフロー)を実行するのに、すごく便利な機能だ!」と思ってます。(てか、業務に導入したい...)
で、今回はそのStep Functionsを、Serverless Frameworkで定義しちゃおう!という内容です。
※Serverless Framework公式ページ www.serverless.com
Step Functionsとは?
AWS公式サイトの説明では、下記のとおりです。
AWS Step Functions は、AWS Lambda 関数および AWS の複数のサービスを、ビジネスに不可欠なアプリケーション内に簡単に配列することができるサーバーレスの関数オーケストレーターです。アプリケーションの状態を維持する、チェックポイント化されたイベント駆動型の一連のワークフローを、視覚的なインターフェイスを使って作成および実行することができます。1 つのステップの結果が、次のステップへのインプットとして機能します。アプリケーション内の各ステップは、ユーザーが定義したビジネスロジックに基づいて、順番通り、計画通りに実行されます。
まとめると、ざっとこんな感じでしょうか?
- 複数のステップ(Lambda実行など)で構成された一連の処理(=フロー)を管理・制御するための仕組み
- イベントドリブンなフローを構成することができ、前ステップのアウトプットを次ステップのインプットにできる。
- 前の状態(ステート)により、次のステップの実行を変化させることができる
- 「ステート」とは、例えば「正常終了/異常発生」だったり、あるいはアウトプットの値...など
利点
個人的には、例えば下記のような点が利点なのかな、と思っています。
- あるステップの結果(正常終了/異常終了)により、処理を変化させられる
- 例えば「異常終了時、エラー種別によって次のステップの内容を変える」なんてことができる。
- CloudFormationのLambda.failrueと違い、細かい条件指定も可能
- 前ステップのアウトプットを次ステップのインプットにできる
- 引数など、必要な情報の受け渡しが簡単になる。
- アウトプットの内容により、次のステップの処理を変えることも可能
- Lambda関数の場合、各ステップのLambda間に依存関係が発生しない
- 上記2点は、やろうと思えばLambdaでできるが(Invokeやモジュール化)、そうすると依存関係が発生してしまう。
- Step Functionにするとそういう依存関係が発生しなくなるので、保守・改修がやりやすい
ちなみに個人的には、AzureのDurable Functionsに近い立ち位置なのかな?と思ってます。
今回の前提
Step Functionの詳細(定義の仕方、文法など)は、以下「Step Functions の詳細」を参照してください。
(「状態」及び「入力および出力処理」あたりが参考になると思います)
今回は、そのあたりについての詳細な解説は省きます。
docs.aws.amazon.com
また、今回使用するステートマシンの定義は、以下の通りになります。
※概要を説明すると、下記の通りです。
- State1~State7では、それぞれStepFunc1~StepFunc7というLambdaを実行します。
- Stete5、State6がないのは、諸事情で欠番になったからです。
- State1は戻り値として「Even」か「Odd」という値を「num_type」というキーで返却します。
- 「ChoiceStepFunc1ResponseEvenOrOdd」で「num_type」の値を判定し、下記の処理を行います。
- 「num_type」が「Even」の場合、State2を実施します。
- 「num_type」が「Odd」の場合、State3とState4を並列(Pararell)に実施します。
- どちらの場合も、最後に「State7」を実施して、処理を終了します。
そして、その定義(JSON)はこちらになります。(各プロパティの意味は、「Step Functions の詳細」を参照。)
{ "Comment": "test of creating StepFunctions from Serverless Framework.", "StartAt": "State1", "States": { "State1": { "Type": "Task", "Resource": "(StepFunc1のARN)", "Next": "ChoiceStepFunc1ResponseEvenOrOdd" }, "ChoiceStepFunc1ResponseEvenOrOdd": { "Type": "Choice", "Choices": [ { "Variable": "$.num_type", "StringEquals": "Even", "Next": "State2" }, { "Variable": "$.num_type", "StringEquals": "Odd", "Next": "OddParallelStepFunc3And4" } ], "Default": "State2" }, "State2": { "Type": "Task", "Resource": "(StepFunc2のARN)", "Next": "State7" }, "OddParallelStepFunc3And4": { "Type": "Parallel", "Next": "State7", "Branches": [ { "StartAt": "State3", "States": { "State3": { "Type": "Task", "Resource": "(StepFunc3のARN)", "End": true } } }, { "StartAt": "State4", "States": { "State4": { "Type": "Task", "Resource": "(StepFunc4のARN)", "End": true } } } ] }, "State7": { "Type": "Task", "Resource": "(StepFunc7のARN)", "End": true } } }
Step FunctionsをServerless Frameworkで定義する
で、本題の「Step FunctionsをServerless Frameworkで定義する」方法ですが、Step Functions自体はCloudFormationでサポートされてますので、serverless.ymlのresources.ResourcesにCloudFormation構文で定義を書けますし、デプロイすれば普通に作成されます。
が、Serverless Frameworkには、本題にある「Serverless Step Functions」という、Setp Functionsを定義するためのとても便利なプラグインがあるので、そちらを紹介します。
「Serverless Step Functions」プラグインを使う
「Serverless Step Functions」は、堀家 隆宏さんが開発した、Step Functions用のServerless Frameworkプラグインで、これを使うと、resources.ResourcesにCloudFormation構文を定義せずとも、独自にStepFunctionsの定義が行えます。
インストール&使用法
まずは、上記公式サイトにあるように、以下を実施します。
# npmコマンドで、Serverless Step Functionsのインストールを実施 > npm install --save-dev serverless-step-functions # serverless.ymlの「plugins」に、Serverless Step Functionsを追加する。 plugins: - serverless-step-functions
その後、serverless.ymlのルート階層(「service」や「functions」と同じ階層)に「stepFunctions.stateMachines」というキーを用意します。
実際のステートマシン定義は、この「stateMachines」の要素として書きます。
stepFunctions: stateMachines: # ここにStateMachineの定義を書く
実際に書いてみた
で、上記のステートマシンの定義は、下記の感じになります。
stepFunctions stateMachines: stateMachinetTest1: # retainをtrueに定義すると、スタックの更新・削除をしてもステートマシンを残せる。 retain: true events: - http: path: stepfunc method: GET # ステートマシンの名前を定義できる name: MyStateMachineTest1 definition: StartAt: State1 States: State1: Type: Task Resource: Fn::GetAtt: [StepFunc1, Arn] Next: ChoiceStepFunc1ResponseEvenOrOdd ChoiceStepFunc1ResponseEvenOrOdd: Type: Choice Choices: - Variable: "$.num_type" StringEquals: "Even" Next: State2 - Variable: "$.num_type" StringEquals: "Odd" Next: OddParallelStepFunc3And4 Default: State2 State2: Type: Task Resource: Fn::GetAtt: [StepFunc2, Arn] Next: State7 OddParallelStepFunc3And4: Type: Parallel Next: State7 Branches: - StartAt: State3 States: State3: Type: Task Resource: Fn::GetAtt: [StepFunc3, Arn] End: True - StartAt: State4 States: State4: Type: Task Resource: Fn::GetAtt: [StepFunc4, Arn] End: True State7: Type: Task Resource: Fn::GetAtt: [StepFunc7, Arn] End: True ## CloudFormation同様に「dependsOn」を使用することもできる。 dependsOn: - StepFunc1 - StepFunc2 - StepFunc3 - StepFunc4 - StepFunc7
上記の通り、先程のステートマシンのJSON定義そのままの感覚で、定義を書くことができます。
CloudFormation定義と違い、シンプルにステートマシンの定義さえすればOKというのが、このプラグインの魅力だと思います。
また、以下のようなステートマシンの制御を行うプロパティがそのまま使用できる...というのも分かると思います。(もちろん他のプロパティについても同様)
- StringEquals
- Branches
- Next/End
つまり、元のStepFunctionの構文を知っていれば、それをそのままServerless Frameworkで定義できる、ということです。
ファイルの分割
(公式サイトの説明が非常に詳しいので)簡単な説明は以上ですが、ステートマシンの定義はどうしても長くなりがちで、それに合わせてserverless.yml自体も大きくなってしまいがちです。
そういったこともあり(?)、ステートマシンの定義のみ別ファイルに分割するということが可能です。
ファイル分割する場合は、以下のように定義します。
# serverless,ymlでは「stepFunctions」キーのみ定義し、その値にステートマシンの定義ファイルのパスを記載する。 stepFunctions: ${file(./statemachines.yml)}
# statemachines.ymlファイルには「stateMachines」キー、及び実際のステートマシンの定義を記載する。 stateMachines: stateMachinetTest1: ... # ※以下、「stateMachinetTest1」以降の内容は「dependsOn」以外、上記のserverless.ymlと同じなので省略。 # dependsOnだけはstatemachines.ymlに定義がないので、コメントアウトなどするなどの対処が必要。
その他
その他にも、以下のようなことが可能です。
...が、さすがに全部は説明しきれないので、詳細は先述のServerless Step Functionsプラグイン公式ページを参照してください。
- Cloudwatch Alarmでの通知
- Cloudwatch Notificationでの別リソースへの通知
- blue-green deploymentへの対応 など
まとめ
というわけで、「Serverless Step Functions」プラグインでStep Functionsを定義する方法をざっと説明しました。
駆け足になってしまいましたが、なんとなくServerless Frameworkで比較的簡単にStep Functions定義を行える、ということを分かっていただけたかと思います。
というか、こういう便利なプラグインを利用して、どんどんAWSの機能を現場でも導入していきたいですね。
そして、サーバーレスアプリをもっと使いやすいものにできるよう、そいうった便利なを活用していきたいものです。
それでは、今回はこの辺で。