AWSを使い倒す(15.Step Functions)

lambdaとAPI Gatewayの組み合わせが動いたと思った矢先、

思わぬところで問題発生。

API Gatewayってタイムアウト29秒までしか設定できないのね。

lambdaで普通に1分ぐらいかかる処理仕込んでたので

API Gatewayが待ちきれない状態になってしまった・・・。

 

【API Gatewayタイムアウト対策】Step Functionsを組み合わせて非同期処理にしてみる | Developers.IO

ということでこのStep Functionsを使って解決を試みる。

記事を読むにStep Functions(のステートマシン)を介してlambdaを実行することで、

lambda実行中なら実行中、終わったら結果をステートマシンが代わりに「応答」してくれるようになるので、API Gatewayタイムアウトが回避できる。

代わりにAPI叩く人は結果返ってくるまで何回かAPIを叩かないといけないって感じ。

 

 

 

大変なのが

APIを叩いた時にユーザが渡してくるパラメータを

一度ステートマシンが受け取って

それをlambdaに渡す

ってしないといけないのでややこしい。

ここの理解にめちゃめちゃ時間がかかった。

 

 

まずは適当なlambda関数を作成する。

import json
def lambda_handler(event, context):
    msg = "bucket="+ event["s3bucket"]+",folder="+event["s3folder"]
    return msg

 eventとしてStep Functionsから引数を受け取れるか、の確認するだけなので

本当にシンプルな作り。

 

次にStep Functionのステートマシンの作成

f:id:Messerarche:20200710211005p:plain

 

作成画面

f:id:Messerarche:20200710211830p:plain

コードスニペットで作成、タイプは標準を選ぶ

 

定義を書く。

f:id:Messerarche:20200710211910p:plain

ここにどう書くか、を理解するのが大変だった

 

 

今回書いたのはずばりこのコード

{
  "StartAt": "Test",
  "States": {
    "Test": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:[中略]:function:test-lambda",
      "Parameters": {
     "s3bucket.$": "$.bucket",
     "s3folder.$": "$.folder"
    }, "End": true } } }

 大事なのは、ResourceとParameters

 

Resourceには、lambda関数のarnを記載する。

 

Parametersは記法に戸惑うが、API Gatewayから変数として受け取るのでこう書く。

具体例を書くと、以下のようなフォーマットでAPI Gatewayから情報が来た時に

"input": {
    "bucket: "nameofbucket",
    "folder": "nameoffolder"
}

 

"s3bucket.$": "$.bucket"の意味は

s3bucketは変数です、変数の中身は来た情報の中のbucketが指し示す値です

ということになる

上記のような解釈で今度はステートマシンが

"event": {
    "s3bucket": "nameofbucket",
    "s3folder": "nameoffolder"
}

と情報を再構築してlambdaに渡してくれる。

これを元にlambdaは

event["s3bucket"]

として値を受け取ることができる。

$.だの.$だのをつけないと、変数ではなく定数と解釈されて受け取れなくなる。

 この辺の何がどうなるはこちらで試されている

StepFunctionsの入力方法サンプルメモ集 - Qiita

 

 

作成すると、ARNが表示されるのでメモっておく。

f:id:Messerarche:20200711013205p:plain

 

 

API Gatewayの前にAPI Gateway用のIAMロールを作成しておく。

具体的には、StepFunctionsの実行権限を持ったロールが必要。

API Gateway を使用して Step Functions API を作成する - AWS Step Functions

 

 

ロールを作成したら、API Gatewayに移動

f:id:Messerarche:20200711014201p:plain

な形でメソッドとリソースを作成する。

executionがlambdaをキックするAPI、

statusがlambdaの進捗を確認するAPI。

 

メソッドの作成時に、StepFunctionsを選択する

f:id:Messerarche:20200711014610p:plain

重要なのはアクションのところ。

LambdaをキックするAPIにはStartExecution

進捗を確認するAPIにはDescribeExecution

と入力する。

実行ロールはさっき作ったIAMロールのarnを入力。

 

出来上がったら早速テストをする。

まずはlambdaの実行、つまりexecutionのPOSTから、テストの画面を開いた後

リクエスト本文に以下を入力

f:id:Messerarche:20200711015133p:plain

 

黒塗りのところにステートマシンのarnを記載する。

また、ステートマシンに渡したい変数を図のように記載する。

そしてテスト実行。

 

 

実行されたら

f:id:Messerarche:20200711015547p:plain

こんなレスポンスが返ってくる。

このexecutionArnを使って進捗確認を行うのでメモしておく。

 

次に進捗確認のほうのメソッド(/status)にうつる

リクエスト本文に、先ほどのexecutionArnを図のように記載する

f:id:Messerarche:20200711015918p:plain

これで実行すると、

先ほどキックしたlambdaが実行中であれば実行中、

実行完了していたら処理結果が返ってくる。

 

その進捗具合はステートマシンのログから確認ができる。

うまくいっているとこんな感じで

f:id:Messerarche:20200711020058p:plain

 

1.ExcecutionStartedにはAPIGatewayから送られてきた情報が

f:id:Messerarche:20200711020556p:plain

 

3でステートマシンがlamdaに渡した情報が

5でlambdaの実行結果が見える

f:id:Messerarche:20200711020947p:plain

 

一応この通りやれば動くハズ。

 

 

 

ここからは追加で

リクエスト本文にいちいちステートマシンのarn情報を記載しない方法と、

パスパラメータをステートマシン越しにlambdaへ渡す方法を記す。

 

まずは、実行したいステートマシンをあらかじめ記載しておく方法

f:id:Messerarche:20200711021539p:plain

この画面の統合リクエストのところをクリック

 

開いた画面の一番下、マッピングテンプレートを開く

ここにいろいろ書くことで、リクエスト本文の内容とかを書き換えることができる。

公式ドキュメントを参考に

API Gateway を使用して Step Functions API を作成する - AWS Step Functions

{
    "input": "$util.escapeJavaScript($input.json('$'))",
    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld"
} 

こんな感じで記載する。

stateMachineArnはもちろん自分のステートマシンに合わせて書き換える

右下の保存を押す。これでAPI実行の度にステートマシンの情報を書かなくてもよくなる。

 

 

 

次はパスパラメータ

f:id:Messerarche:20200711022354p:plain

パスパラメータを使うためのリソースとメソッドを作成する。

 

テストを押した時にここに

f:id:Messerarche:20200711022445p:plain

(めちゃ適当に)値を入れて、これがlambdaまで伝われば成功

 

こちらも統合リクエストでマッピングテンプレートの設定を行うことで実現できる

#set( $body = $util.escapeJavaScript($input.json('$')) )
{
  "input": "{\"input\":$body,\"InputParams\":{\"bucket\":\"$input.params('bucket')\",\"folder\":\"$input.params('folder')\"}}",
"stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld" }

ずばり書くとこんな感じ。

参考にしたのは以下

API GatewayでStepFunctionsと統合する時のTips - Qiita

API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス - Amazon API Gateway

 

 

これをテストすると、API Gatewayからの出力はこうなる

"input": {
    "input": {},
    "InputParams": {
        "bucket": "a",
        "folder": "b"
    }
},

 

パスパラメータで引数を渡すし、実行時のステートマシン設定も不要になったので

テスト時にリクエスト本文に何も書く必要がなくなったため、

"input": {}、つまり記事の前半で参照していたinputには何も入らない。

代わりに、InputParams配下に自分の指定したパラメータが入っている。

 

 

ステートマシンにも変更が必要。

今まで受け取っていたinputの中身が空っぽになって、

代わりにInputParams配下に中身が入るので、そのような宣言がいる。

{
  "StartAt": "Test",
  "States": {
    "Test": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:[中略]:function:test-lambda",
      "InputPath": "$.InputParams",
      "End": true
    }
  }
}

 

InputPathで宣言してやる。以下が参考

InputPath およびパラメータ - AWS Step Functions

 

lambdaも実態に合わせて変更("s3bucket"とかをただの"bucket"にしただけ)

import json
def lambda_handler(event, context):
    msg = "bucket="+ event["bucket"]+",folder="+event["folder"]
    return msg

 

これで、パスパラメータからステートマシンに、ステートマシンからlambdaに変数を渡していく仕組みが動く。

 

 

動きは確認できたので、あとは実装だ・・・!