💨

AWS Step Functionsでできること・できないこと

2024/10/19に公開

はじめに

みなさん、ジョブ運用どうされてますか?
一口にジョブと言ってもいろんな定義があるかと思いますが、今回はいわゆるシステムジョブです。JP1やJobCenter、Senjuなどジョブ管理ツールは多数存在しますし、これまでも長く使われてきているかと思います。
しかし、最近AWSをベースにシステム開発をする際に、EC2の運用を減らしたい等の理由でジョブマネージャーサーバーを使わないジョブ運用を考えることが多くなってきました。さらに、新規導入案件などで初期フェーズにバッチ処理自体も少なく先述した製品ライセンスに見合わないシステム規模の場合、AWSでマネージドに利用できるAWS Step Functionsは第一候補に上がってくることが多いかと思います。

そこで、今回とある業務で実際にAWS Step Functionsを利用して分かった、できること・できないこと・ここが便利・ここが不便等を素直に記録しておきたいと思います。

AWS Step Functionsとは

まずはAWS Step Functionsについて簡単におさらいです。
AWS Step Functionsとは、AWSが提供する分散型アプリケーションワークフローサービスです。AWSサービスと統合されており、サービスのAPIを簡単に実行することが可能です。
GUIからAWSの各種サービスAPIを並べ替えて、業務に必要なワークフローを構成することができ、定義はJSONで記載することもできますし、GUIで作成したものをJSONやYAMLとしてエクスポートすることも可能です。これによってワークフローをコード管理することも嬉しい部分です。
最近ではCDKやTerraformなどでIaC化している方も多いと思います。それぞれコンストラクトやモジュールがありますので、IaCでの構築ももちろん可能となっています。

AWSだけでなく、3rdパーティのAPIを実行することも可能になり、LambdaレスでHTTP APIを実行することでよりLambdaのランタイム管理などの運用負荷低減につながることにもなります。

基本的な構築方法については、こちらのワークショップを試してみてください。

変数操作

変数操作については、Passステートを利用します。

Passステート内でParametersに変数化したい文字列 or JSON or 数値(後述する注意あり)を設定します。この例では、Itemという変数にTestが入っている状態です。
このItemという変数を次のステートで利用することができます。変数を利用する場合は、JSONを操作する形になります。

{
    "Item": "Test"
}

JSONを利用する際は、Keyの最後に.$、Valueの頭に$.をつけなければいけません。
これを実行すると以下のとおり、実行結果出力には先頭に設定したTestが出力されていることがわかります。

{
    "InputValue.$": "$.Item"
}

ここまでで変数の基本的な使い方は分かったと思います。
では、これをさらに次のステートで利用してみましょう。同じ定義のステートを追加してみます。

これで実行してみると以下のとおり失敗します。

これは、アウトプットを設定しないと、常に全てを上書きしてしまうからです。
例えば、先述したとおり直前のステートの出力は直後で利用できるので、以下はうまく動作します。

{
    "InputValue.$": "$.InputValue"
}

この形で受け渡すことで要件を満たせれば良いですが、さらに後続でも利用したい場面があるかと思います。そんな時には、ステートごとに設定できる 出力(アウトプット) を利用します。
以下の例では、ResultPath$.SetEnvValueと設定しています。

こうすることで、SetEnvValueがJSONキーとなって後続に受け渡すことができます。ただし、後続の他のステートでも注意することがあります。

まずは、ParametersでJSONキーを指定する必要があります。

{
    "InputValue.$": "$.SetEnvValue.Item"
}

さらに、次のステートでもアウトプットを設定しないといけません。これをしないと先ほど同様上書きされて後続で一つ目の値を利用できません。
ResultPath$.Output1と設定します。

最後のステートで両方の値を出力してみます。

{
    "InputValue.$": "$.SetEnvValue.Item",
    "Output1Value.$": "$.Output1.InputValue"
}

これによって、複数のステートを跨いで変数を利用することができるようになりました。
ただし、これだとAPIを実行した際にレスポンスが全てパラメータに入ってきて、出力が追いにくくなることもあるので、OutputPath やAPIのステートにある ResultSelector を利用することをオススメします。この辺りの変数の位置付けについては公式ガイドクラメソさんブログを参照して理解を深めていただければと思います。

日付操作

基本的にStepFunctionsで日付の加減算や比較判定などの日付操作をすることはできません。唯一利用できる時間として、コンテキストオブジェクトから得られる実行開始時間となります。
コンテキストオブジェクトを利用するためには以下のようにPassステートを利用します。

実行結果は以下のようになり、実行に関する各種情報が取れているのがわかります。

ここから、Execution.StartTimeを取得すれば実行開始時間が利用できます。
さらに、組み込み関数を利用すると、日付と時間それぞれで利用するということもできます。
例えば、以下をPassステートに渡すと、yyyy-mm-dd形式の日付が取得できます。

{
    "Date.$":"States.ArrayGetItem(States.StringSplit($$.Execution.StartTime, 'T'),0)"
}

簡単に説明すると、$$.Execution.StartTimeで実行時間を取得し、States.StringSplitTを基準に文字列を分割して配列化します。最後に、States.ArrayGetItemで0番目の要素を取得して日付を抽出しています。
これを応用すると、アカウントIDやリージョンなども取得できます。

アカウントIDの取得

{
    "AccountId.$":"States.ArrayGetItem(States.StringSplit($$.Execution.Id, ':'),4)"
}

リージョンの取得

{
    "Region.$":"States.ArrayGetItem(States.StringSplit($$.Execution.Id, ':'),3)"
}

実行時のInput Value

実行時に任意の文字列をインプットとして与えることが出来ます。これによって、同じステートマシンの中でインプットに応じた処理を実装することが可能です。
例えば、Input ValueにECS RunTaskのOverrideをセットして、RunTaskのみを実行する子ステートマシンを呼び出すことで、コンポーネントのように使いまわすといった使い方が想定できます。

Input Valueについても、先頭Passステートでの利用だけでなく、コンテキストオブジェクトから利用できるので用途に応じて取得方法を使い分けるのが良いかと思います。

先頭Passステートでの変数化

記事冒頭のPassステートにTestという文言をセットしている部分を実行時に指定できるようにしてみます。

まずは、Passステートの中で以下のように設定します。

{
  "Input.$": "$$.Execution.Input.InputValue"
}

この状態で実行時に以下のJSONを渡してみると、実行時の値がステートマシン内で利用できていることがわかります。

{
  "InputValue":"InputTest" 
}

ここで読み込んだ値は上述したとおり後続での利用もできますが、複数ステートで利用する場合はアウトプットをうまく活用するようにしてください。

先頭以外でのInputValueの利用

この仕組みはステートマシン内のどのステートでも利用できるので、実行時の呼び出しによって処理を変える場合はこちらの利用方法をとることをオススメします。
先ほどのステートをコピーして、それぞれ以下のようにParameterを設定します。この時、Outputには何も指定していません。

{
  "Input1.$": "$$.Execution.Input.InputValue1"
}
{
  "Input2.$": "$$.Execution.Input.InputValue2"
}

先ほどと同様にInputを渡して実行してみます。

{
  "InputValue1":"InputTest1",
  "InputValue2":"InputTest2"
}

2つ目のステートでもInputをうまく出力できていることがわかります。

追記(変数操作)

そのステートの出力結果を必要とせず、単純にその前の状態維持したい場合は、Outputの選択でDiscard result and keep original inputを選択してください。

ステートを一つ足して、2つ目のステートで上記の設定をしてみます。

先ほどと同様にインプットを与えて実行してみます。

きちんと2番目のステートでアウトプットを設定していない状態でも、与えられたインプットをそのままアウトプットに渡して、3番目のステートで1番目のアウトプット結果を利用できています。

さいごに

ここまでで、StepFunctions自体の状態を利用してできることとTipsを合わせて記載してきました。

もちろんこれ以外の方法もありますし、Lambdaを書くのが得意であればLambdaを作る方が細かな制御を直感的にできるので有用かもしれません。ただし、冒頭でも述べたとおり、Lambdaのランタイム管理すらしたくないような方には、こういったTipsをもとにStepFunctionsに入門してみるのもアリではないでしょうか。

まだまだTipsやテンプレートとして使えるものがたたありますので、その辺りはまたの機会にまとめていきたいと思います。

GitHubで編集を提案

Discussion