LocalStack と Docker Compose でローカル AWS S3 環境を構築してみた - セットアップから主要な操作まで

## はじめに

LocalStack は知っていたけど、実際に触ったことが無かったので、LocakStack で S3 を起動して、主要な機能を触ってみました。

## セットアップ

LocalStack を管理する方法としては、[LocalStack CLI](https://docs.localstack.cloud/getting-started/installation/#localstack-cli), [LocalStack Desktop](https://docs.localstack.cloud/user-guide/tools/localstack-desktop/) などの専用のツールもありますが、今回はプロジェクトの扱いやすさを考えて、[Docker-Compose](https://docs.localstack.cloud/getting-started/installation/#docker-compose) を利用します。

### Docker-Compose で起動する

docker-compose.yml を作成して、S3 を利用できるように LocakStack を起動します。

```yaml:docker-compose.yml
services:
  localstack:
    container_name: localstack-poc
    image: localstack/localstack # Community イメージ
    ports:
      - "4566:4566"  # LocalStack メインポート
    environment:
      - SERVICES=s3  # 必要な AWS サービスを指定
      - DEBUG=1      # デバッグログの出力
```

起動時の設定については、環境変数で指定することができます。利用可能な環境変数一覧は、こちらで確認することができます。Legacy パラメータも多くありますね。

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

### LocalStack に接続する

AWS CLI や AWS SDK を利用して LocalStack に接続する方法はいくつかありますが、[エンドポイント URL](https://docs.localstack.cloud/references/network-troubleshooting/) を使ってアクセスする方法がシンプルに接続できそうです。エンドポイント URL の設定は、コマンドや関数のオプションに指定するか、環境変数(`AWS_ENDPOINT_URL`)を使って指定することができそうです。また、config ファイルで指定することもできそうです。詳細は AWS 公式ドキュメント（[CLI](https://docs.aws.amazon.com/ja_jp/cli/v1/userguide/cli-configure-endpoints.html) or [SDK](https://docs.aws.amazon.com/ja_jp/sdkref/latest/guide/feature-ss-endpoints.html)）を確認してください。

AWS CLI で接続する場合は、

```sh
aws --endpoint-url=http://localhost:4566 s3 ls
```

または

```sh
export AWS_ENDPOINT_URL=http://localhost:4566
aws s3 ls
```

AWS SDK(boto3) で接続する場合は、

```python
# 環境変数に AWS_ENDPOINT_URL が設定されている場合は endpoint_url を指定しなくてよい
# AWS に接続する場合は endpoint_url に None を設定する
s3_client = boto3.client("s3", endpoint_url="http://localhost:4566")
response = s3_client.list_buckets()
```

このように接続することができます。

また、config ファイルを設定して、プロファイルで切り替えたい場合は、以下のようなプロファイルを作成しておくと接続できました。継続的に AWS CLI を使って接続したい際は、プロファイルを作成するのが楽そうですね。

```text:.aws/credentials
[localstack]
aws_access_key_id = dummy
aws_secret_access_key = dummy
```

```text:.aws/config
[profile localstack]
region = dummy
endpoint_url = http://localhost:4566
```

実行時のオプションや環境変数で設定することができるので、接続先を LocalStack か AWS かを柔軟に切り替えることができそうですね。

### 起動時にコマンドを実行する

LocalStack 起動時になんらかの処理を実行したい場合は、[Initialization Hooks](https://docs.localstack.cloud/references/init-hooks/)を使うことができます。データを永続化([Persistence](https://docs.localstack.cloud/user-guide/state-management/persistence/)は Pro Image の機能)していない場合は、コンテナを停止または破棄したタイミングでデータが無くなります。起動のたびに作成したいデータなどあれば、Initialization Hooks で作成するのが楽かもしれないですね。

```shell: ready.sh
#!/bin/bash

# 起動時にバケットを作成する
awslocal s3 mb s3://sample-bucket
```

```yaml:docker-compose.yml
# ...
volumes:
  - "/path/to/ready.sh:/etc/localstack/init/ready.d/ready.sh"  
# ...
```

ローカルに作成した `ready.sh` ファイルには、実行権限を付与する必要があります。

## 各種コマンドを実行する

セットアップで起動した LocalStack の S3 に対して、AWS CLI を使って基本的なコマンドを実行してみます。
コマンド実行の際は、config ファイルに LocalStack に接続できるように設定してから、実行していきます。

:::note info
LocalStack で起動した S3 でサポートされている機能一覧は、[公式ページ](https://docs.localstack.cloud/references/coverage/coverage_s3/)で確認することができます。
:::

### バケットを作成する

```shell
aws s3api create-bucket --bucket sample-bucket
```

```shell
{
    "Location": "/sample-bucket"
}
```

### バケット一覧を表示する

```shell
aws s3api list-buckets
```

```shell
{
    "Buckets": [
        {
            "Name": "sample-bucket",
            "CreationDate": "2024-11-02T01:44:21+00:00"
        }
    ],
    "Owner": {
        "DisplayName": "webfile",
        "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
    }
}
```

### オブジェクトをアップロードする

```shell
aws s3api put-object \ 
--bucket sample-bucket \
--key upload/sample.pdf \
--body [ローカルファイルパス]sample.pdf
```

```shell
{
    "ETag": "\"f15d8a573ef37ccbeeb5da27d09046d7\"",
    "ServerSideEncryption": "AES256"
}
```

### オブジェクト一覧を表示する

```shell
aws s3api list-objects --bucket sample-bucket
```

```shell
{
    "Contents": [
        {
            "Key": "upload/sample.pdf",
            "LastModified": "2024-11-02T01:58:40+00:00",
            "ETag": "\"f15d8a573ef37ccbeeb5da27d09046d7\"",
            "Size": 341595,
            "StorageClass": "STANDARD",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        }
    ],
    "RequestCharged": null
}
```

### オブジェクトをダウンロードする

```shell
aws s3api get-object --bucket sample-bucket --key upload/sample.pdf ./download.pdf
```

### 署名付き URL を生成する

署名付き URL を生成して、生成された URL にアクセスすると実際にオブジェクトをダウンロードすることができます。

```shell
aws s3 presign s3://sample-bucket/upload/sample.pdf
```

```shell
http://localhost:4566/sample-bucket/upload/sample.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=dummy%2F20241103%2Fdummy%2Fs3%2Faws4_request&X-Amz-Date=20241103T234527Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=865d24edcbffc1bf62bd2d3949579c0ee10808abb055d66c9a0f33b248735c5a
```

### バージョニング機能を使う

#### バケットのバージョニングを有効にする

```shell
aws s3api put-bucket-versioning --bucket sample-bucket --versioning-configuration Status=Enabled
```

同じキーのオブジェクトをアップロードすると `VersionId` が付与されるようになる。

```shell
aws s3api put-object \ 
--bucket sample-bucket \
--key upload/sample.pdf \
--body [ローカルファイルパス]sample-version.pdf
{
    "ETag": "\"f15d8a573ef37ccbeeb5da27d09046d7\"",
    "ServerSideEncryption": "AES256",
    "VersionId": "Hu4vBqQ1Xm__2dxOL3I--A"
}
```

#### バージョン情報を確認する

```shell
aws s3api list-object-versions --bucket sample-bucket --prefix upload/sample-version.pdf
{
    "Versions": [
        {
            "ETag": "\"f15d8a573ef37ccbeeb5da27d09046d7\"",
            "Size": 341595,
            "StorageClass": "STANDARD",
            "Key": "upload/sample-version.pdf",
            "VersionId": "Hu4vBqQ1Xm__2dxOL3I--A",
            "IsLatest": true,
            "LastModified": "2024-11-04T00:15:20+00:00",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        },
        {
            "ETag": "\"f15d8a573ef37ccbeeb5da27d09046d7\"",
            "Size": 341595,
            "StorageClass": "STANDARD",
            "Key": "upload/sample-version.pdf",
            "VersionId": "QrHWHuSeXqenDImNRvM_Og",
            "IsLatest": false,
            "LastModified": "2024-11-04T00:15:15+00:00",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        }
    ],
    "RequestCharged": null
}
```

#### オブジェクトを削除する

```shell
aws s3api delete-object --bucket sample-bucket --key upload/sample-version.pdf
{
    "DeleteMarker": true,
    "VersionId": "iuuscmhfJmTyV3SOEUaIig"
}
```

バージョン情報を確認すると、削除情報（`DeleteMarkers`）も確認することができる。

```shell
aws s3api list-object-versions --bucket sample-bucket --prefix upload/sample-version.pdf
{
    "Versions": [
        {
            "ETag": "\"f15d8a573ef37ccbeeb5da27d09046d7\"",
            "Size": 341595,
            "StorageClass": "STANDARD",
            "Key": "upload/sample-version.pdf",
            "VersionId": "Hu4vBqQ1Xm__2dxOL3I--A",
            "IsLatest": false,
            "LastModified": "2024-11-04T00:15:20+00:00",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        },
        {
            "ETag": "\"f15d8a573ef37ccbeeb5da27d09046d7\"",
            "Size": 341595,
            "StorageClass": "STANDARD",
            "Key": "upload/sample-version.pdf",
            "VersionId": "QrHWHuSeXqenDImNRvM_Og",
            "IsLatest": false,
            "LastModified": "2024-11-04T00:15:15+00:00",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        }
    ],
    "DeleteMarkers": [
        {
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            },
            "Key": "upload/sample-version.pdf",
            "VersionId": "iuuscmhfJmTyV3SOEUaIig",
            "IsLatest": true,
            "LastModified": "2024-11-04T00:21:00+00:00"
        }
    ],
    "RequestCharged": null
}
```

## さいごに

Docker ですぐに起動できて、S3 の主要な機能はほとんど網羅されているように感じました。ローカル開発時やテスト実行時などの S3 接続先としては、十分に使えるなぁと思いました。LocalStack は S3 以外のサービスも多くあるので、いろいろな使い方を試してみたいと思います。

## 参考

https://docs.localstack.cloud/overview/

https://docs.localstack.cloud/user-guide/aws/feature-coverage/

https://docs.localstack.cloud/user-guide/aws/s3/

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