Step Functions と Lambda をローカルで実行するときのハマりどころ

## はじめに

StepFunctions を local で実行するための Docker Image `amazon/aws-stepfunctions-local` が公開されました。
公式チュートリアルにしたがって StepFunctions と Lambda Function をローカルで実行してみたときの手順メモ。
Docker Container を起動するときの環境変数周りでハマりました。。

## 手順

### SAM アプリケーションの作成

[公式チュートリアル][1]にしたがって、HellorWorldFunction のSAMアプリケーションを作成します。

### API のエンドポイントの確認

```
$ sam local start-api
```

エンドポイントにアクセスする

```
$ curl http://127.0.0.1:3000/hello
{"message":"hello world"}
```

### Lambda Function をローカルで起動する

```
$ sam local start-lambda
2019-02-11 00:19:31 Found credentials in environment variables.
2019-02-11 00:19:31 Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint.
2019-02-11 00:19:31  * Running on http://127.0.0.1:3001/ (Press CTRL+C to quit)
```

### Docker コンテナの環境変数を指定する

[資料][2]にあるとおり、Dockerイメージに環境変数を与える必要があります。

```:aws-credentials.txt
AWS_ACCOUNT_ID=123456789012
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=dummy
AWS_SECRET_ACCESS_KEY=dummy
LAMBDA_ENDPOINT=http://host.docker.internal:3001
```

### ローカル環境変数を指定

```:.envrc
export AWS_DEFAULT_REGION=us-east-1
export AWS_REGION=us-east-1
export AWS_ACCOUNT_ID=123456789012
export AWS_ACCESS_KEY_ID=dummy1
export AWS_SECRET_ACCESS_KEY=dummy
```

### StepFunctions をローカルで起動

```bash
$ docker run -p 8083:8083 --env-file aws-credentials.txt amazon/aws-stepfunctions-local
```

### ローカルの StepFunctions に state machine を作成する

```bash
$ aws stepfunctions --endpoint http://localhost:8083 create-state-machine --definition "{\
    \"Comment\": \"A Hello World example of the Amazon States Language using an AWS Lambda Local function\",\
    \"StartAt\": \"HelloWorld\",\
    \"States\": {\
      \"HelloWorld\": {\
        \"Type\": \"Task\",\
        \"Resource\": \"arn:aws:lambda:us-east-1:123456789012:function:HelloWorldFunction\",\
        \"End\": true\
      }\
    }\
  }\
  }}" --name "HelloWorld" --role-arn "arn:aws:iam::012345678901:role/DummyRole"
```

### StepFunction の実行

```bash
$ aws stepfunctions --endpoint http://localhost:8083 start-execution --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld --name test
{
    "executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test",
    "startDate": 1549812335.58
}
```

### StepFunctin の実行状態の確認

```
$ aws stepfunctions --endpoint http://localhost:8083 describe-execution --execution-arn arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test
{
    "executionArn": "arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test",
    "stateMachineArn": "arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld",
    "name": "test",
    "status": "SUCCEEDED",
    "startDate": 1549812335.58,
    "stopDate": 1549812339.711,
    "input": "{}",
    "output": "{\"statusCode\":200,\"body\":\"{\\\"message\\\":\\\"hello world\\\"}\"}"
}
```

## この手順のハマりどころ

ポイントは、Dockerコンテナの起動時の環境変数 **`LAMBDA_ENDPOINT` を `host.docker.internal` に指定すること**です。
`localhost` に指定すると、**Docker コンテナからLocalに接続できない**ので `Connection Error` になります。
なお、 `host.docker.internal` というのは Docker コンテナからローカルPCのポートに接続するために用意されたエンドポイントらしいです[[4]]
。この方法は Docker form Mac/Windows のみ有効のようです。

```:LAMBDA_ENDPOINTをlocalhostにした場合のエラー
2019-02-10 14:27:56.514: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test : {"Type":"LambdaFunctionFailed","PreviousEventId":4,"LambdaFunctionFailedEventDetails":{"Error":"Lambda.SdkClientException","Cause":"Unable to execute HTTP request: Connect to localhost:3001 [localhost/127.0.0.1] failed: Connection refused (Connection refused)"}}
2019-02-10 14:27:56.520: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test : {"Type":"ExecutionFailed","PreviousEventId":5,"ExecutionFailedEventDetails":{"Error":"Lambda.SdkClientException","Cause":"Unable to execute HTTP request: Connect to localhost:3001 [localhost/127.0.0.1] failed: Connection refused (Connection refused)"}}
```

なお、 `LAMBDA_ENDPOINT` の設定をせずに起動した場合は `The security token included in the request is invalid.` という認証系のエラーメッセージとなり、さらに混乱させられました。

```:LAMBDA_ENDPOINT設定されていない場合のエラー
2019-02-10 14:25:36.380: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test1 : {"Type":"LambdaFunctionFailed","PreviousEventId":4,"LambdaFunctionFailedEventDetails":{"Error":"Lambda.AWSLambdaException","Cause":"The security token included in the request is invalid. (Service: AWSLambda; Status Code: 403; Error Code: UnrecognizedClientException; Request ID: bb917ab7-8309-447a-a830-28cbedef8aa1)"}}
2019-02-10 14:25:36.381: arn:aws:states:us-east-1:123456789012:execution:HelloWorld:test1 : {"Type":"ExecutionFailed","PreviousEventId":5,"ExecutionFailedEventDetails":{"Error":"Lambda.AWSLambdaException","Cause":"The security token included in the request is invalid. (Service: AWSLambda; Status Code: 403; Error Code: UnrecognizedClientException; Request ID: bb917ab7-8309-447a-a830-28cbedef8aa1)"}}
```

## 終わりに

環境変数の設定について、公式チュートリアルには明確な設定内容が書かれてなかったのでけっこうハマりました。。
とにかくこれで、StepFunctionsをローカルから実行することができるようになりました。

ただ、やはりテストの指針としては以下の記事で書いたような、ローカルでテスト可能なライブラリを増やしていって、

https://qiita.com/billthelizard/items/be87a7662c34fd5c634e

## 参考

1. [Step Functions and Lambda Local][1]
2. [Step Functions Local Configuration Options][2]
3. [DockerHub - amazon/aws-stepfunctions-local][3]
4. [Networking features in Docker Desktop for Mac][4]

[1]: https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/sfn-local-lambda.html#install-sam
[2]: https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/sfn-local-config-options.html
[3]: https://hub.docker.com/r/amazon/aws-stepfunctions-local
[4]: https://docs.docker.com/docker-for-mac/networking/#there-is-no-docker0-bridge-on-macos#i-want-to-connect-from-a-container-to-a-service-on-the-host