LocalStackにAWS SAMでデプロイしたLambdaにAndroidからアクセスしてみた

## はじめに

- AWS SAM CLI for LocalStack を使ってアプリをLocalStackにデプロイできる
- Dockerでhttpdを起動した端末とスマホが同じネットワークにいれば、スマホのブラウザからWebページを閲覧できる

…ということから、「Dockerで動いているLocalStackにデプロイしたLambda関数へ、同じネットワークのAndroidアプリからアクセスできるのでは？」などと思いつきました。試してみたところ、一応できたのでここに残しておきます
AWSとスマホアプリを連携させるケースを、不具合の混入や費用などの心配なくローカルで好き勝手に試したい、という方の参考になれば幸いです

ちなみに当方はAndroid専門なので、iOSについては言及しておりません。申し訳ありませんが、ご了承ください

### 追記

私のGitHubに以下の3点を含むレポジトリを共有しております。よければご参考ください

- LocalStackコンテナを起動するための docker-compose.yaml
- 簡単なSAMプロジェクト
- Lambda関数を呼び出せす簡単なAndroidアプリ

https://github.com/outerlet/sample-sam-app

## 実行環境について

このエントリを書くにあたり、以下の環境で作業を行いました

- MacとAndroid端末を用意し、両者を同じネットワークに接続
- MacにDockerをインストールし、コンテナでLocalStackを起動
    - AWS SAM CLI for LocalStackを使ってアプリをビルド、デプロイ
    - アプリに含まれるLambda関数は、REST APIとして呼び出せるよう設定
- APIにリクエストをかけるAndroidアプリを用意して、Lambdaからのレスポンスが得られるかを確認

### ソフトウェアのバージョン

#### AWS関連

```sh
% aws --version
aws-cli/2.17.13 Python/3.11.9 Darwin/23.6.0 exe/x86_64

% sam --version
SAM CLI, version 1.121.0
```

#### LocalStack関連

```sh
% docker compose up
[+] Running 1/0
 ✔ Container localstack-main   Created   0.0s 
Attaching to localstack-main
localstack-main  | LocalStack version: 3.5.1.dev
localstack-main  | LocalStack build date: 2024-07-19
localstack-main  | LocalStack build git hash: 9d37d8285
localstack-main  | Ready.
```

#### Android関連

- Android Studio Koala | 2024.1.1
- Pixel5 (Android 14)

#### その他

- macOS sonoma
- Docker Desktop 27.1.1, build 6312585
- Python 3.12.5

## SAMを使ったアプリのビルドとデプロイ

### 前提条件

PythonやDocker Desktop以外に、AWS CLIとSAM CLIがインストール済みとします

- [AWS CLI](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html)
- [AWS SAM CLI](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/install-sam-cli.html)

### 手順

基本的には下記を実施すれば、アプリをLocalStackにデプロイできます（場合によっては不要な作業もあります）

1. LocalStack AWS CLI のセットアップ
2. AWS SAM CLI for LocalStack のセットアップ
3. docker-compose.yaml で `SERVICES` 環境変数をセット
4. `AWS_ENDPOINT_URL` 環境変数をセット（必要に応じて）
5. `samlocal` コマンドを使って、アプリをビルド・デプロイ

#### LocalStack AWS CLI のセットアップ

https://github.com/localstack/awscli-local

```
% pip install awscli-local

% awslocal --version
aws-cli/2.17.13 Python/3.11.9 Darwin/23.6.0 exe/x86_64
```

#### AWS SAM CLI for LocalStack のセットアップ

https://github.com/localstack/aws-sam-cli-local

```
% pip install aws-sam-cli-local

% samlocal --version
SAM CLI, version 1.122.0
```

#### docker-compose.yaml で SERVICES 環境変数をセット

https://docs.localstack.cloud/references/configuration/

- LocalStackでエミュレーションするAWSサービスの一覧を `SERVICES` という環境変数に列挙します
- SAMでのデプロイにCloudFormationが必要だったり、ロール付与のためにIAMが必要なので、以下を指定しておくのが無難だと思います
    - `logs`
    - `cloudformation`
    - `iam` 
- テンプレートに定義されているサービスが `SERVICES` に無いと `samlocal deploy` でエラーになります
    - 例えばREST APIとしてLambdaを起動する場合は `apigateway` が必要です
- 私が実際に使用した docker-compose.yaml は [こちら](https://github.com/outerlet/sample-sam-app/blob/main/docker/docker-compose.yaml) でご確認ください

##### （例） 必要な SERVICES を指定しないとどうなるか

`cloudformation` を `SERVICES` に指定しないで `samlocal deploy` を実行した場合です。下記のエラーが発生してデプロイに失敗します

```sh
% samlocal deploy --config-env local        
	Creating the required resources...
Error: Failed to create managed resources: An error occurred (InternalFailure) when calling the CreateChangeSet operation: Service 'cloudformation' is not enabled. Please check your 'SERVICES' configuration variable.
```

#### AWS_ENDPOINT_URL 環境変数をセット（必要に応じて）

https://docs.localstack.cloud/user-guide/integrations/aws-sam/#configuration

- こちらは docker-compose.yaml でなく、 `awslocal` コマンドや `samlocal` コマンドを発行する側にセットします。このエントリの場合、Macのターミナルで `export` することになります
- `AWS_ENDPOINT_URL` 環境変数によって、DockerコンテナにセットアップしたLocalStackが `samlocal` コマンドの相手として設定されます
    - `aws` コマンド発行時に `endpoint-url` を指定するのと同様の効果があります
    - `AWS_ENDPOINT_URL` のデフォルトは http://localhost.localstack.cloud:4566 です。そのためコンテナの設定でポート番号などを変更していなければ、 `AWS_ENDPOINT_URL` を設定しなくても `samlocal` は期待通りに機能します。状況に応じて設定して下さい

#### アプリのビルド・デプロイ

- 実環境に対して `sam` コマンドを発行するのと同様、 `samlocal` コマンドを使ってLocalStackにアプリをビルド・デプロイします
    - 下記の例では何もオプションを指定していませんが、必要に応じて `--guided` などを指定してください
- ビルドには `samlocal` でなく `sam` コマンドでも構いませんが、 `deploy` には必ず `samlocal` を使うようにしてください

```sh
samlocal build
samlocal deploy
```

## Lambda関数をAPIとして起動

同じネットワークに繋がっており、エンドポイントが分かれば、基本的にはcURLでもAndroidアプリでも、同じようにLambda関数に対するリクエストをかけることができます

### Lambdaの設定

`AWS::Serverless::Function` の `Events` は以下のように設定されているとします
（必要な箇所だけ抜粋）

```yaml
Type: AWS::Serverless::Function
Properties:
  Events:
    SimpleResponseApiEvent:
      Type: Api
      Properties:
        Method: get
        Path: /sample
```

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

1. [awslocal apigateway get-rest-apis](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/apigateway/get-rest-apis.html") コマンドでREST API IDを取得
    - 実行結果の “items” > “id” から取得できます（実行例を後掲してあります）
2. [LocalStack のリファレンス](https://docs.localstack.cloud/user-guide/aws/apigateway/#create-a-deployment")を参考に、Lambda関数を起動するためのURLを割り出します

#### REST API IDからエンドポイントを割り出す

エンドポイントのルールは以下の通りです
ここではホスト名が localhost になっていますが、他の端末からアクセスする場合は、LocalStackの稼働している端末のIPアドレスに置き換えてください

```
http://localhost:4566/restapis/[REST API ID]/[パス]/_user_request_/[パス]?[クエリ]
```

`apigateway get-rest-apis` で確認したREST API IDが “abcde12345” だった場合、Lambda関数にアクセスするためのエンドポイントは以下のようになります

```
# クエリを必要としない場合
http://localhost:4566/restapis/abcde12345/sample/_user_request_/sample

# param というキーでクエリを利用する場合
http://localhost:4566/restapis/abcde12345/sample/_user_request_/sample?param=XXXXXX
```

ここまでで、cURLなりAndroid端末なりで目的の関数にリクエストをかけることができます

```sh
curl http://localhost:4566/restapis/abcde12345/sample/_user_request_/sample
```

#### （参考） awslocal apigateway get-rest-apis の実行結果

```
% awslocal apigateway get-rest-apis
{
    "items": [
        {
            "id": "8g99upccg5",
            "name": "sample-sam-app-local-ServerlessRestApi-9a7a3dda",
            "createdDate": "2024-08-28T13:57:18+09:00",
            "version": "1.0",
            "apiKeySource": "HEADER",
            "endpointConfiguration": { "types": [ "EDGE" ] },
            "tags": {
                "aws:cloudformation:logical-id": "ServerlessRestApi",
                "aws:cloudformation:stack-name": "sample-sam-app-local",
                "aws:cloudformation:stack-id": "arn:aws:cloudformation:ap-northeast-3:000000000000:stack/sample-sam-app-local/ac48696f"
            },
            "disableExecuteApiEndpoint": false,
            "rootResourceId": "8pv2rh0na7"
        }
    ]
}
```