echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Serverless Framework】Serverless Step FunctionsプラグインでStep Functionsを定義する

本題

皆さん、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」を実施して、処理を終了します。

f:id:Makky12:20200810185742p:plain

そして、その定義(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構文で定義を書けますし、デプロイすれば普通に作成されます。

docs.aws.amazon.com

が、Serverless Frameworkには、本題にある「Serverless Step Functions」という、Setp Functionsを定義するためのとても便利なプラグインがあるので、そちらを紹介します。

「Serverless Step Functions」プラグインを使う

「Serverless Step Functions」は、堀家 隆宏さんが開発した、Step Functions用のServerless Frameworkプラグインで、これを使うと、resources.ResourcesにCloudFormation構文を定義せずとも、独自にStepFunctionsの定義が行えます。

www.serverless.com

インストール&使用法

まずは、上記公式サイトにあるように、以下を実施します。

# 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での別リソースへの通知
    • SNS, SQS, Kinesis, Firehose, Lambdaなど
    • 別のStep Functionsを起動することも可能
  • blue-green deploymentへの対応 など

まとめ

というわけで、「Serverless Step Functions」プラグインでStep Functionsを定義する方法をざっと説明しました。
駆け足になってしまいましたが、なんとなくServerless Frameworkで比較的簡単にStep Functions定義を行える、ということを分かっていただけたかと思います。

というか、こういう便利なプラグインを利用して、どんどんAWSの機能を現場でも導入していきたいですね。

そして、サーバーレスアプリをもっと使いやすいものにできるよう、そいうった便利なを活用していきたいものです。

それでは、今回はこの辺で。