AWS公式10分チュートリアルから始めるアウトプット 〜AWS Step Functions編〜

StepFunctions+LambdaのAWS公式10分チュートリアルを通じて、ハンズオン→インプット→アウトプットを実践しました。
2021.09.28

みなさん、こんにちは、あしさんです。

突然ですが、皆さんはこんな経験をしたことはありませんか?

  • あのサービス触ってみたいんだよなーと思っていながらも、どこからどうやって触ったらいいかわからん!
  • 使ったことのないAWSサービスのハンズオンに参加したけど、その後実務でも使わないししばらく経つとあんまり覚えてないなー

そんな経験ありませんか?

私はあります。

私も含め、そんな経験のあるすべての皆様に向けて今日はブログを書いています。

みんな、AWS公式チュートリアルをやってアウトプットしませんか!

3行まとめ

  • StepFunctionsのAWS公式10分チュートリアルをやってみました。
  • アップデートによるWorkFlow Studioの追加で、より使いやすくなりました
  • アウトプットは決して楽なものではない。だがとても勉強になる。

対象読者

  • マネジメントコンソールからのAWS操作にある程度慣れている人
  • AWS Step Functionsをまだ触ったことがない人
  • ハンズオン学習をアウトプットしたい人

はじめに

本ブログを書いたきっかけ

Step Functionsを触ってみたいけど、どこから触れたらいいのかわからないなーと思っていた時に下記のブログを見つけました。

これまでAWSのハンズオンやチュートリアルは、JAWS-UGなどの非公式のイベントでの参加経験がありましたが、AWS公式のハンズオン・チュートリアルを利用したことはありませんでした。

実際にサービスに触れてみてブログにしてみようと思ったことが本ブログを書くことになった直接のきっかけです。

ハンズオン学習時に意識すべきこと

突然ですが、導入としてこんな章を入れてみました。

よく言われることでもありますが、ハンズオン学習はその場でやるだけにしてしまうと学びは浅いものになってしまいます。

私も何度も自分に言い聞かせていますが、何かと言い訳をしながらやらないことが多かったなと振り返ると思います。

ハンズオン学習についての考え方について、JAWS-UG CLI専門支部の波多野さん作の資料がとても良かったので、ここで紹介させてください。

この資料はCLI専門支部で行われているハンズオンに参加する際の心構えやポイントについて説明しているものなのですが、資料の中の一節にある「ハンズオン学習において優先すべきこと」に書いてある文が含蓄のある文で、私は個人的に何度も読み返しています。

主旨をまとめると、下記3点です。

  • 「完了すること」を最優先すること。 (全体把握)
  • 必ず復習すること。(インプット)
  • 自分なりにカスタマイズしてオリジナル手順にしてみること。 (アウトプット)

すべて意識すべき点で大事なことですが、個人的にいいなと思っているところが、3点目のアウトプットを「自分なりにカスタマイズして」としているところです。

自分なりのカスタマイズ」がないと、アウトプットしているつもりでも、個人的なインプットの延長にしかなっていない場合があリます。 ハンズオン学習で得られた知識をよりよい学びに昇華させるためにも、自分なりにやってみるの精神が必要になってきます。

(注:自戒です)

今回やること

ここからが主題です。

本記事では、以前から個人的に触れてみたいと思っていたAWS Step Functionsを公式10分チュートリアルを利用して学んでみます。

StepFuncitonsを利用した公式チュートリアルは複数ありますが、今回はStep Functionsを使ってLambdaのエラー処理をサーバレスでやってみる、という内容のチュートリアルを選びました。

理由は

  • 登場するAWSサービスが少なく構成が単純である点
  • エラー処理を学ぶコンテンツなので正常系だけでなく異常系の処理を含めたワークフロー作成を学べそうだと思った点

です。

本記事のこれからのが流れについて、先述した「ハンズオン学習時に意識すべきこと」のフレームワークを使用して、実際のハンズオン〜アウトプットの流れを実演してみようと思います。

  1. 手順通りやってみた(全体把握)
  2. チュートリアルの内容を復習してみる(インプット)
  3. 手順をカスタマイズしたオリジナル手順でやってみた(アウトプット)

事前インプット

これから本題に入る前に、最低限今回使うAWS Step Functionsがどんなサービスなのかざっくりと理解しましょう。

AWS Step Functionsとは?

分散アプリケーション、マイクロサービスのコンポーネントの疎結合化を可能にするAWSのマネージドサービス。 各コンポーネントが独立するため、アプリケーションのスケール及び変更を容易にすることができます。 一つの Step Functions の定義全体をステートマシンと呼び、これらはASL(Amazon States Language)と呼ばれる独自言語を用いて記述されます。 また、ASLを用いて定義されたワークフローはマネジメントコンソール上でビジュアライズされます。

公式としての説明は上記です、押さえておきましょう。

もう少し詳しく知りたい方は、下記ブログが参考になりましたので一読してみてください。概要を理解するのに役に立ちました。

1. 手順通りやってみた(全体把握)

今回実施する公式チュートリアルを再掲します。

まずは手順通りにやってみます。

手順の流れとしては、以下4ステップで構成されていて、すべてAWSマネジメントコンソールから作業します。

  • a. Lambda関数を作成する
  • b. IAMロールを作成する
  • c. Step Functionsのステートマシンを作成する
  • d. ステートマシンのテスト

a.Lambda関数作成

マネジメントコンソールからLambda関数を作成します。

Lambdaマネジメントコンソールの関数を作成画面から、関数名、ランタイム、アクセス権限を指定し、関数を作成をクリックします。

無事関数が作成できたら、手順にあるソースで上書きし、コードソースを変更します。

この画面で表示されるLambda関数のARNは後の手順で使用するのでどこかにコピーして取っておきましょう。

コードソースを変更できたら、Deployをクリックして変更を反映してください。

b.IAMロール作成

マネジメントコンソールからIAMロールを作成します。

IAMマネジメントコンソールのロール画面からロールの作成をクリックします。

一般的なユースケースのLambdaを選択。

次のステップをクリックします。

次のステップをクリックします。

今回はタグは設定しません。

次のステップをクリックします。

ロールが一覧に表示されていればOKです。

c.StepFunctionsステートマシン作成

ステートマシンの作成は、Amazonステートメント言語(Amazon States Language, 以下ASLと略)というJSONベースの独自の構造化言語で記述されたテンプレートを使用して行います。

今回実際に使ったコードはこちらです。

{
  "Comment": "An example of using retry and catch to handle API responses",
  "StartAt": "Call API",
  "States": {
    "Call API": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
      "Next" : "OK",
      "Comment": "Catch a 429 (Too many requests) API exception, and resubmit the failed request in a rate-limiting fashion.",
      "Retry" : [ {
        "ErrorEquals": [ "TooManyRequestsException" ],
        "IntervalSeconds": 1,
        "MaxAttempts": 2
      } ],
      "Catch": [ 
        {
          "ErrorEquals": ["TooManyRequestsException"],
          "Next": "Wait and Try Later"
        }, {
          "ErrorEquals": ["ServerUnavailableException"],
          "Next": "Server Unavailable"
        }, {
          "ErrorEquals": ["States.ALL"],
          "Next": "Catch All"
        }
      ]
    },
    "Wait and Try Later": {
      "Type": "Wait",
      "Seconds" : 1,
      "Next" : "Change to 200"
    },
    "Server Unavailable": {
      "Type": "Fail",
      "Error":"ServerUnavailable",
      "Cause": "The server is currently unable to handle the request."
    },
    "Catch All": {
      "Type": "Fail",
      "Cause": "Unknown error!",
      "Error": "An error of unknown type occurred"
    },
    "Change to 200": {
      "Type": "Pass",
      "Result": {"statuscode" :"200"} ,
      "Next": "Call API"
    },
    "OK": {
      "Type": "Pass",
      "Result": "The request has succeeded.",
      "End": true
    }
  }
}

マネジメントコンソールからStepFunctionsステートマシンを作成します。

StepFunctionsマネジメントコンソールのステートマシンの作成から、コードでワークフローを記述を選択し、タイプは標準を選択します。

定義画面で、手順からコピーしたASLのコードを貼り付けます。

この時、7行目のResourcesに先述した手順で作成したLambda関数のARNを記載しましょう。

次へをクリックします。

ステートマシン名、ロールを指定します。

ロールは先述した手順で作成したものを選びたいため、既存のロールを選択から指定します。

ステートマシンの作成をクリックします。

ステートマシンが正常に作成されたことを見届けてください。

d. ステートマシンのテスト

ここからは、作成したStepFunctionsステートマシンのテストを行います。

StepFunctiuonsマネジメントコンソールのステートマシンから、対象のステートマシン名をクリックします。

実行の開始をクリックします。

正常系(StatusCode=200)

JSON形式の入力値(今回はStatusCode=200)を入力欄に記載し、実行の開始をクリック。

実行ステータスが成功となり、グラフインスペクターの状態がすべて緑色で成功となっていることを確認します。

グラフインスペクターは各ステップ毎に入出力の値を確認することができます。

初めのCall APIでは入力がStatusCode=200であること

出力が200 OKであることが確認できます。

続いてのOKでは、入力が200 OK

出力がThe request has succeeded.であることが確認できます。

実行イベント履歴からはグラフインスペクターで確認できた各処理のログを確認することができます。

正常系②(StatusCode=429)

StatusCode=429の場合も同様に実施してみます。

実行ステータスが成功となり、グラフインスペクターも緑色になっています。

実行イベント履歴を確認すると、ID=3〜11の間でCall APIを数回リトライしていることがわかります。

ID=15,16でStatusCode=200に変更され、その後は正常終了しています。

異常系①(StatusCode=503)

StatusCode=503の場合も同様に実施してみます。

今度は実行ステータスが失敗となり、グラフインスペクターもオレンジ色や赤色になっています。

Call APIの詳細を確認してみると、キャッチされたエラーとなっていて、事前に設定されたキャッチャーが起動されていることがわかります。

ServerUnavailableの詳細を見ると、ステータスが失敗となっています。

異常系②(StatusCode=999)

最後に、上記全てのパターンに当てはまらないパターンをやってみます。

今度も実行ステータスが失敗となり、グラフインスペクターもオレンジ色や赤色になっています。

以上でマネジメントコンソールからのステートマシンのテストは完了です。

テストの実行履歴はステートマシン名の画面からアクセスできるため、後からでも見返せます。

2. チュートリアルの内容を復習してみる(インプット)

インプットでは、チュートリアルで作成した手順の内容を確認します。

コードの流れを学ぶ

チュートリアルの手順では、準備されたコードをマネジメントコンソールへ貼り付けるだけで進めることができてしまうので、どのような処理が期待されているかを調べてみます。

そのままコードを読んでいく、でもできるのですがStepFunctionsで利用されるACLは目視だと少し構造がわかりづらいので、コードをフローチャートに起こしてわかりやすくしてみました。

右の図がStepFunctionsのステートマシンで、左の図がLambda関数を表すフローチャートです。

全部で4つのパターンがあります。

  • ① 正常系(StatusCode=200の場合は正常終了)
  • ② 正常系(StatusCode=429の場合は2回リトライした後、StatusCode=200に変更して再実行)
  • ③ 異常系(StatusCode=503の場合は異常終了)
  • ④ 異常系(StatusCode=上記以外の場合は異常終了)

1つずつ処理をみてみましょう。

①は、APIがコールされて戻り値が200だった場合、正常終了するという内容です

②は、APIがコールされて戻り値が429だった場合TooManyRequestsExeptionエラーとなり、2回リトライした後、StatusCode=200に変更して再実行します。

200で再実行されるため、正常終了します。

③は、APIがコールされて戻り値が503だった場合ServerUnavailableExeptionエラーとなり、異常終了します。

④は、APIがコールされて戻り値が上記以外だった場合States.ALLエラーとなり、異常終了します。

Amazonステートメント言語(ASL)について学ぶ

処理内容について復習できたので、ここまでよく分からず使っていたASLについて少しだけ掘り下げてみましょう。

今回使用したASLのState(状態)は4つです。

  • Task
  • Pass
  • Wait
  • Fail

Task

Task 状態 ("Type": "Task") は、ステートマシンによって実行される単一の作業単位を表します。 ステートマシンのすべての作業は、タスクにより処理されます。タスクの作業を実行するには、アクティビティ、または AWS Lambda 関数を使用するか、パラメータをそのサービスの API アクションに渡します。

今回のステートマシンの中では、Lambda関数を実行させるときに使用しています。

Lambda関数以外にも様々なAWSサービスを登録することが可能です。

実際使っていて、Task状態の中でエラー処理を実装できるところがスゴい!と思いました。

Pass

Pass 状態 ("Type": "Pass") は、何も作業せずに入力を出力に渡します。Pass 状態は、ステートマシンを構築およびデバッグする際に便利です。

今回のステートマシンの中では、正常系の処理と、異常系①の処理でAPIを繰り返し実行した後にStateCode=200に変更する箇所で使用しています。

何もしない時や入力出力だけを変更したい時に使用できる状態です。

他にはどんなことができるのか?については下記ブログがわかりやすかったです。

Wait

Wait 状態 ("Type": "Wait") は、ステートマシンの続行を指定された時間遅延させます。相対時間 (状態が開始してからの秒数で指定) と絶対時間 (タイムスタンプで指定) のいずれかを選択できます。

今回のステートマシンの中では、異常系①の処理でAPIの繰り返し実行中に数秒待機させたいときに使用しています。

使用していませんが相対時間だけでなく、待機する時間を絶対時間でも指定できるところがポイントですね。

Fail

Failステータス ("Type": "Fail") は、ステートマシンの実行を停止し、失敗としてマークします。

今回のステートマシンの中では、異常系の処理で強制的に異常終了させるときに使用しています。

対になる状態にSucceedという状態もあります。

3. 手順をカスタマイズしたオリジナル手順でやってみた(アウトプット)

続いてこれまでの学んだ内容のアウトプットとして、少し手順を工夫して同じステートマシンを作ってみます。

ここからの流れはこんな感じでやっていきます。

a-2. Lambda関数を作成する

  • 既存の関数を使用(先述の手順で作成したもの)

b-2. IAMロールを作成する

  • 既存のロールを使用(先述の手順で作成したもの)

c-3. Step Functionsのステートマシンを作成する

  • マネジメントコンソールからWorkFlow Studioで作成してみる

d-4. ステートマシンのテスト

  • AWS CLIでやってみる

a-2およびb-2の手順は概要把握でステートマシンを作成した時に作成したリソースが残っているので、同じものを流用します。再作成することもできますが今回のテーマとズレてしまうため割愛としました。

c-3のステートマシン作成では、2021年6月のアップデートで追加されたWorkFlow Studioを使ったステートマシンの作成をやってみます。

a-2.Lambda関数作成

割愛

b-2.IAMロール作成

割愛

c-2.StepFunctionsステートマシン作成

マネジメントコンソールからWorkFlow Studioを使ってStepFunctionsステートマシンを作成します。

StepFunctionsのステートマシン作成画面から「ワークフローを視覚的に設計」を選択し、次へをクリックします。

「ワークフローを設計」画面からステートマシンの作成が始まります。

正常系処理の実装

左ペインの状態ブラウザからアクションの「Lambda:Invoke」をここに状態をドロップとなっているところにドラッグ&ドロップします。

状態名を変更し、追加の設定で「新しい状態を追加」を選択すると、新しいここに状態をドロップ欄が現れます。

続いて、フローの「Pass」をここに状態をドロップとなっているところにドラッグ&ドロップします。

状態名を変更し、追加の設定で「最後に移動」を選択してください。

ここまでで、APIを呼び出してそのまま正常終了するフローの完成です。

エラー処理の実装

ここからチュートリアルの主目的であるエラー処理のフローを作成し、実装していきます。

「Lambda:Invoke」を選択し、エラー処理タブのキャッチャーの編集ボタンをクリックします。

Errors欄に処理したいエラー名を入力し、Failback stateに新しい状態を追加を選択すると、空の条件分岐が作成されます。

同様に別のエラー名で、3つ目4つ目の条件分岐を作成していきます。

キャッチャーを増やすときは、新しいキャッチャーを追加をクリックしてください。

エラー処理①

1つ目のエラー処理を実装していきます。

キャッチャーの設定でエラー名TooMany Requests Exectionを設定したところに処理を追加します。

フローの「Wait」をここに状態をドロップとなっているところにドラッグ&ドロップします。

状態名を変更し、次の状態を新しい状態を追加に設定します。

続けてフローの「Pass」を先ほど配置した「Wait」の下のここに状態をドロップとなっているところにドラッグ&ドロップします。

状態名を変更し。次の状態に「Call API」を指定します。

出力タブの結果欄にJSON形式でStatusCode=200になる内容を貼り付けます。

ここまで設定したエラー処理のリトライ設定を追加していきます。

「Lambda:Invoke」を選択しエラー処理のタブで、エラー時に再試行の編集をクリックします。

Errors欄に処理したいエラー名を入力し、実行間隔、最大リトライ回数を設定します。

これで繰り返し処理が含まれたエラー処理が実装できました。

エラー処理②

キャッチャーの設定でエラー名Server Unavailable Exeptionを設定したところに処理を追加します。

フローの「Fail」をここに状態をドロップとなっているところにドラッグ&ドロップします。

状態名、Errorオプション、Causeオプションを変更します。

Fail状態だと異常終了し即時に処理が終了するので、「次の状態」が出てこないところがポイントですね。

エラー処理③

キャッチャーの設定でエラー名States.ALLを設定したところに処理を追加します。

フローの「Fail」をここに状態をドロップとなっているところにドラッグ&ドロップします。

状態名、Errorオプション、Causeオプションを変更します。

以上で、WorkFlow Studioを使ったステートマシンの作成が完了です。

最後にステートマシン名や実行ロールの選択を行なってステートマシンの作成をクリックします。

無事ステートマシンが正常に作成できることを確認しましょう。

d-2. ステートマシンのテスト

作成したステートマシンのテストをAWS CLIを使って実施します。

  • ステートマシン名とLambda関数に渡すステータスコードを指定する。
  • c-2の手順で作成したステートマシンのARNを取得する
  • ステートマシンのARNと入力するJSONファイルを指定して、ステートマシンを実行する
  • ステートマシンの実行結果を確認する
  • (必要な場合)ステートマシン実行ログを確認する

下記のコードをAWS CloudShellから実行します。

AWS CloudShellはCLIコマンド実行環境の準備が爆速ででき、利用料金も無料なのでよく使っています。

### StatusCodeのみ実施するテストの内容によって変更する
StatusCode="429" 

### ここから下は常に同じコードでOK
StateMachineName="MyAPIStateMachine-WorkFlowStudio"

InputJson=$(cat << EOF
{"statuscode": "${StatusCode}"}
EOF
)

StateMachineARN=$(aws stepfunctions list-state-machines \
  --query "stateMachines[].[stateMachineArn]" \
  --output text \
  |grep ${StateMachineName}) \
&& echo ${StateMachineARN}

StateExecutionARN=$(aws stepfunctions start-execution \
  --state-machine-arn ${StateMachineARN} \
  --input "${InputJson}" \
  --query "executionArn" \
  --output text) \
&& echo ${StateExecutionARN}

aws stepfunctions describe-execution \
  --execution-arn ${StateExecutionARN}
テストの実行結果はこちら。
[cloudshell-user@ip-10-0-26-125 ~]$ ### StatusCodeのみ実施するテストの内容によって変更する
[cloudshell-user@ip-10-0-26-125 ~]$ StatusCode="200" 
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ ### ここから下は常に同じコードでOK
[cloudshell-user@ip-10-0-26-125 ~]$ StateMachineName="MyAPIStateMachine-WorkFlowStudio"
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ InputJson=$(cat << EOF
> {"statuscode": "${StatusCode}"}
> EOF
> )
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ StateMachineARN=$(aws stepfunctions list-state-machines \
>   --query "stateMachines[].[stateMachineArn]" \
>   --output text \
>   |grep ${StateMachineName}) \
> && echo ${StateMachineARN}
arn:aws:states:ap-northeast-1:851655284847:stateMachine:MyAPIStateMachine-WorkFlowStudio
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ StateExecutionARN=$(aws stepfunctions start-execution \
>   --state-machine-arn ${StateMachineARN} \
>   --input "${InputJson}" \
>   --query "executionArn" \
>   --output text) \
> && echo ${StateExecutionARN}
arn:aws:states:ap-northeast-1:851655284847:execution:MyAPIStateMachine-WorkFlowStudio:19a8bc29-a959-4afb-bdc3-c555b48e4deb
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ aws stepfunctions describe-execution \
>   --execution-arn ${StateExecutionARN}
{
    "executionArn": "arn:aws:states:ap-northeast-1:851655284847:execution:MyAPIStateMachine-WorkFlowStudio:19a8bc29-a959-4afb-bdc3-c555b48e4deb",
    "stateMachineArn": "arn:aws:states:ap-northeast-1:851655284847:stateMachine:MyAPIStateMachine-WorkFlowStudio",
    "name": "19a8bc29-a959-4afb-bdc3-c555b48e4deb",
    "status": "SUCCEEDED",
    "startDate": "2021-09-28T06:20:25.806000+00:00",
    "stopDate": "2021-09-28T06:20:26.385000+00:00",
    "input": "{\"statuscode\": \"200\"}",
    "inputDetails": {
        "included": true
    },
    "output": "\"200 OK\"",
    "outputDetails": {
        "included": true
    }
}
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ ### StatusCodeのみ実施するテストの内容によって変更する
[cloudshell-user@ip-10-0-26-125 ~]$ StatusCode="429" 
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ ### ここから下は常に同じコードでOK
[cloudshell-user@ip-10-0-26-125 ~]$ StateMachineName="MyAPIStateMachine-WorkFlowStudio"
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ InputJson=$(cat << EOF
> {"statuscode": "${StatusCode}"}
> EOF
> )
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ StateMachineARN=$(aws stepfunctions list-state-machines \
>   --query "stateMachines[].[stateMachineArn]" \
>   --output text \
>   |grep ${StateMachineName}) \
> && echo ${StateMachineARN}
arn:aws:states:ap-northeast-1:851655284847:stateMachine:MyAPIStateMachine-WorkFlowStudio
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ StateExecutionARN=$(aws stepfunctions start-execution \
>   --state-machine-arn ${StateMachineARN} \
>   --input "${InputJson}" \
>   --query "executionArn" \
>   --output text) \
> && echo ${StateExecutionARN}
arn:aws:states:ap-northeast-1:851655284847:execution:MyAPIStateMachine-WorkFlowStudio:41f0eebe-f402-4502-9c91-5e9952c10616
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ aws stepfunctions describe-execution \
>   --execution-arn ${StateExecutionARN}
{
    "executionArn": "arn:aws:states:ap-northeast-1:851655284847:execution:MyAPIStateMachine-WorkFlowStudio:41f0eebe-f402-4502-9c91-5e9952c10616",
    "stateMachineArn": "arn:aws:states:ap-northeast-1:851655284847:stateMachine:MyAPIStateMachine-WorkFlowStudio",
    "name": "41f0eebe-f402-4502-9c91-5e9952c10616",
    "status": "SUCCEEDED",
    "startDate": "2021-09-28T06:22:08.550000+00:00",
    "stopDate": "2021-09-28T06:22:13.692000+00:00",
    "input": "{\"statuscode\": \"429\"}",
    "inputDetails": {
        "included": true
    },
    "output": "\"200 OK\"",
    "outputDetails": {
        "included": true
    }
}
[cloudshell-user@ip-10-0-26-125 ~]$ ### StatusCodeのみ実施するテストの内容によって変更する
[cloudshell-user@ip-10-0-26-125 ~]$ StatusCode="503" 
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ ### ここから下は常に同じコードでOK
[cloudshell-user@ip-10-0-26-125 ~]$ StateMachineName="MyAPIStateMachine-WorkFlowStudio"
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ InputJson=$(cat << EOF
> {"statuscode": "${StatusCode}"}
> EOF
> )
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ StateMachineARN=$(aws stepfunctions list-state-machines \
>   --query "stateMachines[].[stateMachineArn]" \
>   --output text \
>   |grep ${StateMachineName}) \
> && echo ${StateMachineARN}
arn:aws:states:ap-northeast-1:851655284847:stateMachine:MyAPIStateMachine-WorkFlowStudio
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ StateExecutionARN=$(aws stepfunctions start-execution \
>   --state-machine-arn ${StateMachineARN} \
>   --input "${InputJson}" \
>   --query "executionArn" \
>   --output text) \
> && echo ${StateExecutionARN}
arn:aws:states:ap-northeast-1:851655284847:execution:MyAPIStateMachine-WorkFlowStudio:c49aab06-1bf3-4760-ade8-ef4d33ed7105
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ aws stepfunctions describe-execution \
>   --execution-arn ${StateExecutionARN}
{
    "executionArn": "arn:aws:states:ap-northeast-1:851655284847:execution:MyAPIStateMachine-WorkFlowStudio:c49aab06-1bf3-4760-ade8-ef4d33ed7105",
    "stateMachineArn": "arn:aws:states:ap-northeast-1:851655284847:stateMachine:MyAPIStateMachine-WorkFlowStudio",
    "name": "c49aab06-1bf3-4760-ade8-ef4d33ed7105",
    "status": "FAILED",
    "startDate": "2021-09-28T06:21:35.433000+00:00",
    "stopDate": "2021-09-28T06:21:35.745000+00:00",
    "input": "{\"statuscode\": \"503\"}",
    "inputDetails": {
        "included": true
    }
}
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ ### StatusCodeのみ実施するテストの内容によって変更する
[cloudshell-user@ip-10-0-26-125 ~]$ StatusCode="999" 
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ ### ここから下は常に同じコードでOK
[cloudshell-user@ip-10-0-26-125 ~]$ StateMachineName="MyAPIStateMachine-WorkFlowStudio"
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ InputJson=$(cat << EOF
> {"statuscode": "${StatusCode}"}
> EOF
> )
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ StateMachineARN=$(aws stepfunctions list-state-machines \
>   --query "stateMachines[].[stateMachineArn]" \
>   --output text \
>   |grep ${StateMachineName}) \
> && echo ${StateMachineARN}
arn:aws:states:ap-northeast-1:851655284847:stateMachine:MyAPIStateMachine-WorkFlowStudio
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ StateExecutionARN=$(aws stepfunctions start-execution \
>   --state-machine-arn ${StateMachineARN} \
>   --input "${InputJson}" \
>   --query "executionArn" \
>   --output text) \
> && echo ${StateExecutionARN}
arn:aws:states:ap-northeast-1:851655284847:execution:MyAPIStateMachine-WorkFlowStudio:aadbc0ff-6115-4e08-a468-b7a52db730b5
[cloudshell-user@ip-10-0-26-125 ~]$ 
[cloudshell-user@ip-10-0-26-125 ~]$ aws stepfunctions describe-execution \
>   --execution-arn ${StateExecutionARN}
{
    "executionArn": "arn:aws:states:ap-northeast-1:851655284847:execution:MyAPIStateMachine-WorkFlowStudio:aadbc0ff-6115-4e08-a468-b7a52db730b5",
    "stateMachineArn": "arn:aws:states:ap-northeast-1:851655284847:stateMachine:MyAPIStateMachine-WorkFlowStudio",
    "name": "aadbc0ff-6115-4e08-a468-b7a52db730b5",
    "status": "FAILED",
    "startDate": "2021-09-28T06:21:52.514000+00:00",
    "stopDate": "2021-09-28T06:21:52.803000+00:00",
    "input": "{\"statuscode\": \"999\"}",
    "inputDetails": {
        "included": true
    }
}

おまけ

上記のテストコマンドでは細かい実行ログが確認できないため、より詳細のログをみたい方はこちらのコマンドを使用してみてください。

## ステートマシンの実行ログを確認したい場合のみ実行
aws stepfunctions get-execution-history \
  --execution-arn ${StateExecutionARN}

StatusCode=429の場合の実行ログのみ参考として載せておきます。

StatusCode=429の実行ログはこちら。
[cloudshell-user@ip-10-0-112-17 ~]$ aws stepfunctions get-execution-history \
>   --execution-arn ${StateExecutionARN}
{
    "events": [
        {
            "timestamp": "2021-09-26T13:25:38.830000+00:00",
            "type": "ExecutionStarted",
            "id": 1,
            "previousEventId": 0,
            "executionStartedEventDetails": {
                "input": "{\"statuscode\": \"429\"}",
                "inputDetails": {
                    "truncated": false
                },
                "roleArn": "arn:aws:iam::851655284847:role/step_functions_basic_execution"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:38.901000+00:00",
            "type": "TaskStateEntered",
            "id": 2,
            "previousEventId": 0,
            "stateEnteredEventDetails": {
                "name": "Call API",
                "input": "{\"statuscode\": \"429\"}",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:38.901000+00:00",
            "type": "TaskScheduled",
            "id": 3,
            "previousEventId": 2,
            "taskScheduledEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke",
                "region": "ap-northeast-1",
                "parameters": "{\"FunctionName\":\"arn:aws:lambda:ap-northeast-1:851655284847:function:MockAPIFunction:$LATEST\",\"Payload\":{\"statuscode\":\"429\"}}"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:39.023000+00:00",
            "type": "TaskStarted",
            "id": 4,
            "previousEventId": 3,
            "taskStartedEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:39.364000+00:00",
            "type": "TaskFailed",
            "id": 5,
            "previousEventId": 4,
            "taskFailedEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke",
                "error": "TooManyRequestsException",
                "cause": "{\"errorMessage\":\"429 Too Many Requests\",\"errorType\":\"TooManyRequestsException\",\"stackTrace\":[[\"/var/task/lambda_function.py\",8,\"lambda_handler\",\"raise TooManyRequestsException('429 Too Many Requests')\"]]}"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:40.464000+00:00",
            "type": "TaskScheduled",
            "id": 6,
            "previousEventId": 5,
            "taskScheduledEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke",
                "region": "ap-northeast-1",
                "parameters": "{\"FunctionName\":\"arn:aws:lambda:ap-northeast-1:851655284847:function:MockAPIFunction:$LATEST\",\"Payload\":{\"statuscode\":\"429\"}}"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:40.564000+00:00",
            "type": "TaskStarted",
            "id": 7,
            "previousEventId": 6,
            "taskStartedEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:40.656000+00:00",
            "type": "TaskFailed",
            "id": 8,
            "previousEventId": 7,
            "taskFailedEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke",
                "error": "TooManyRequestsException",
                "cause": "{\"errorMessage\":\"429 Too Many Requests\",\"errorType\":\"TooManyRequestsException\",\"stackTrace\":[[\"/var/task/lambda_function.py\",8,\"lambda_handler\",\"raise TooManyRequestsException('429 Too Many Requests')\"]]}"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:42.792000+00:00",
            "type": "TaskScheduled",
            "id": 9,
            "previousEventId": 8,
            "taskScheduledEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke",
                "region": "ap-northeast-1",
                "parameters": "{\"FunctionName\":\"arn:aws:lambda:ap-northeast-1:851655284847:function:MockAPIFunction:$LATEST\",\"Payload\":{\"statuscode\":\"429\"}}"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:42.892000+00:00",
            "type": "TaskStarted",
            "id": 10,
            "previousEventId": 9,
            "taskStartedEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:42.982000+00:00",
            "type": "TaskFailed",
            "id": 11,
            "previousEventId": 10,
            "taskFailedEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke",
                "error": "TooManyRequestsException",
                "cause": "{\"errorMessage\":\"429 Too Many Requests\",\"errorType\":\"TooManyRequestsException\",\"stackTrace\":[[\"/var/task/lambda_function.py\",8,\"lambda_handler\",\"raise TooManyRequestsException('429 Too Many Requests')\"]]}"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:43.014000+00:00",
            "type": "TaskStateExited",
            "id": 12,
            "previousEventId": 11,
            "stateExitedEventDetails": {
                "name": "Call API",
                "output": "{\"Error\":\"TooManyRequestsException\",\"Cause\":\"{\\\"errorMessage\\\":\\\"429 Too Many Requests\\\",\\\"errorType\\\":\\\"TooManyRequestsException\\\",\\\"stackTrace\\\":[[\\\"/var/task/lambda_function.py\\\",8,\\\"lambda_handler\\\",\\\"raise TooManyRequestsException('429 Too Many Requests')\\\"]]}\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:43.014000+00:00",
            "type": "WaitStateEntered",
            "id": 13,
            "previousEventId": 12,
            "stateEnteredEventDetails": {
                "name": "Wait and Try Later",
                "input": "{\"Error\":\"TooManyRequestsException\",\"Cause\":\"{\\\"errorMessage\\\":\\\"429 Too Many Requests\\\",\\\"errorType\\\":\\\"TooManyRequestsException\\\",\\\"stackTrace\\\":[[\\\"/var/task/lambda_function.py\\\",8,\\\"lambda_handler\\\",\\\"raise TooManyRequestsException('429 Too Many Requests')\\\"]]}\"}",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.100000+00:00",
            "type": "WaitStateExited",
            "id": 14,
            "previousEventId": 13,
            "stateExitedEventDetails": {
                "name": "Wait and Try Later",
                "output": "{\"Error\":\"TooManyRequestsException\",\"Cause\":\"{\\\"errorMessage\\\":\\\"429 Too Many Requests\\\",\\\"errorType\\\":\\\"TooManyRequestsException\\\",\\\"stackTrace\\\":[[\\\"/var/task/lambda_function.py\\\",8,\\\"lambda_handler\\\",\\\"raise TooManyRequestsException('429 Too Many Requests')\\\"]]}\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.100000+00:00",
            "type": "PassStateEntered",
            "id": 15,
            "previousEventId": 14,
            "stateEnteredEventDetails": {
                "name": "Change to 200",
                "input": "{\"Error\":\"TooManyRequestsException\",\"Cause\":\"{\\\"errorMessage\\\":\\\"429 Too Many Requests\\\",\\\"errorType\\\":\\\"TooManyRequestsException\\\",\\\"stackTrace\\\":[[\\\"/var/task/lambda_function.py\\\",8,\\\"lambda_handler\\\",\\\"raise TooManyRequestsException('429 Too Many Requests')\\\"]]}\"}",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.100000+00:00",
            "type": "PassStateExited",
            "id": 16,
            "previousEventId": 15,
            "stateExitedEventDetails": {
                "name": "Change to 200",
                "output": "{\"statuscode\":\"200\"}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.100000+00:00",
            "type": "TaskStateEntered",
            "id": 17,
            "previousEventId": 16,
            "stateEnteredEventDetails": {
                "name": "Call API",
                "input": "{\"statuscode\":\"200\"}",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.100000+00:00",
            "type": "TaskScheduled",
            "id": 18,
            "previousEventId": 17,
            "taskScheduledEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke",
                "region": "ap-northeast-1",
                "parameters": "{\"FunctionName\":\"arn:aws:lambda:ap-northeast-1:851655284847:function:MockAPIFunction:$LATEST\",\"Payload\":{\"statuscode\":\"200\"}}"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.192000+00:00",
            "type": "TaskStarted",
            "id": 19,
            "previousEventId": 18,
            "taskStartedEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke"
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.255000+00:00",
            "type": "TaskSucceeded",
            "id": 20,
            "previousEventId": 19,
            "taskSucceededEventDetails": {
                "resourceType": "lambda",
                "resource": "invoke",
                "output": "{\"ExecutedVersion\":\"$LATEST\",\"Payload\":\"200 OK\",\"SdkHttpMetadata\":{\"AllHttpHeaders\":{\"X-Amz-Executed-Version\":[\"$LATEST\"],\"x-amzn-Remapped-Content-Length\":[\"0\"],\"Connection\":[\"keep-alive\"],\"x-amzn-RequestId\":[\"d9490256-c5f9-4a3d-a392-b0fdd92f12ef\"],\"Content-Length\":[\"8\"],\"Date\":[\"Sun, 26 Sep 2021 13:25:44 GMT\"],\"X-Amzn-Trace-Id\":[\"root=1-615074d8-15846b977cbf4ecb32c867b8;sampled=0\"],\"Content-Type\":[\"application/json\"]},\"HttpHeaders\":{\"Connection\":\"keep-alive\",\"Content-Length\":\"8\",\"Content-Type\":\"application/json\",\"Date\":\"Sun, 26 Sep 2021 13:25:44 GMT\",\"X-Amz-Executed-Version\":\"$LATEST\",\"x-amzn-Remapped-Content-Length\":\"0\",\"x-amzn-RequestId\":\"d9490256-c5f9-4a3d-a392-b0fdd92f12ef\",\"X-Amzn-Trace-Id\":\"root=1-615074d8-15846b977cbf4ecb32c867b8;sampled=0\"},\"HttpStatusCode\":200},\"SdkResponseMetadata\":{\"RequestId\":\"d9490256-c5f9-4a3d-a392-b0fdd92f12ef\"},\"StatusCode\":200}",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.289000+00:00",
            "type": "TaskStateExited",
            "id": 21,
            "previousEventId": 20,
            "stateExitedEventDetails": {
                "name": "Call API",
                "output": "\"200 OK\"",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.289000+00:00",
            "type": "PassStateEntered",
            "id": 22,
            "previousEventId": 21,
            "stateEnteredEventDetails": {
                "name": "OK",
                "input": "\"200 OK\"",
                "inputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.289000+00:00",
            "type": "PassStateExited",
            "id": 23,
            "previousEventId": 22,
            "stateExitedEventDetails": {
                "name": "OK",
                "output": "\"200 OK\"",
                "outputDetails": {
                    "truncated": false
                }
            }
        },
        {
            "timestamp": "2021-09-26T13:25:44.343000+00:00",
            "type": "ExecutionSucceeded",
            "id": 24,
            "previousEventId": 23,
            "executionSucceededEventDetails": {
                "output": "\"200 OK\"",
                "outputDetails": {
                    "truncated": false
                }
            }
        }
    ]
}

長いですが以下のことが確認できます(チュートリアルの時と同様の挙動になっています)

- id=4〜14の間で関数実行を3回繰り返す - id=15で「statuscode=200」へ変更する - id=16以降で再度関数を実行し正常終了。

感想

StepFunctionsのチュートリアルの実施、そしてアップデートで追加されたWorkFlow Studioを使用してみての感想をまとめました。

  • StepFunctionsはAWSサービスを絡めた処理の疎結合化に役に立ちそう。
  • 0から作ることを考えると、ACLの学習コストは多少必要そう。
  • WorkFlow StudioはACLが分からなくてもステートマシンの全体像がわかってとても良い。
  • WorkFlow Studioで構築する際でもACLが理解できるとと色々小回りが効く。
  • WorkFlow Studioでの構築は感覚的で分かりやすいが、手動構築のためコードの物量によっては大変な作業になる。
  • 初回検証時の構築はWorkFlow Studioで行って、細かいところは生成されたACLを確認しながら調整、本番構築はACLで行うといいかもしれない。
  • ACLで作成したステートマシンを後からWorkFlow Studioで編集といった使い方も便利。

そして、今回はハンズオン実施→インプット→アウトプットの流れを1つのブログでやってみましたのでその感想も書いてみます。

今回下記の一連の流れを1つの記事にしました。

  • 「完了すること」を最優先すること。 (全体把握)
  • 必ず復習すること。(インプット)
  • 自分なりにカスタマイズしてオリジナル手順にしてみること。 (アウトプット)

より労力がかかってきますが、忘れずにアウトプットまで辿り着けるので学べることは大きいと思います。

機会があれば、またチュートリアル記事を書いてみたいです。

まとめ

  • StepFunctionsのAWS公式10分チュートリアルをやってみました
  • アップデートによるWorkFlow Studioの追加により、より使いやすくなった
  • アウトプットは決して楽なものではない。だがとても勉強になる。

後始末

今回作成したリソースに放置しておいて料金がかかるものはありませんが、不要なリソースは忘れずに削除しておきましょう。

  • StepFunctiuonsワークフロー
  • Lambda関数
  • CloudWatch Logsストリーム(Lambda関数)
  • IAMロール

終わりに

ここまで読んでくださった方、お疲れ様でした。そしてありがとうございます。

軽い気持ちで書き始めたブログでしたが、結構ボリューミーな内容になってしまいました。

でもその分学べたことも多かったですし、伝わる情報もより価値のあるものになったんじゃないかなと思います。

この記事を読んで、アウトプットしてみようかな、AWS勉強してみようかな、Step Functions学んでみたいな、と思ってくれる方が1人でも生まれることを願っています。

以上、東京オフィスのあしさんでした。