AWSをローカル環境でエミュレートできるLocalstackを使ってみる

はじめに

AWSをローカルで動かすためのツールLocalstackを使ってみる。
https://github.com/localstack/localstack

環境

Windows 11 Professional
Docker Desktop 4.33.1 (161083)
WSL2 Ubuntu 24.04 LTS

構築

ローカル環境は、Dockerを使って構築する。

イメージは、localstack/localstackを使う。
https://hub.docker.com/r/localstack/localstack

aws-cliを使って、localstackにアクセスするので、下記も参照する。
https://hub.docker.com/r/amazon/aws-cli

環境変数は下記を参照する。
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html

compose.yml

services:
  localstack:
    container_name: localstack
    image: localstack/localstack:3.6.0
    ports:
      - "127.0.0.1:4566:4566"            # LocalStack Gateway
      - "127.0.0.1:4510-4559:4510-4559"  # external services port range
    environment:
      # LocalStack configuration: https://docs.localstack.cloud/references/configuration/
      - DEBUG=${DEBUG:-0}
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

  aws-cli:
    container_name: aws-cli
    image: amazon/aws-cli:2.17.37
    environment:
      - AWS_ACCESS_KEY_ID=dummy
      - AWS_SECRET_ACCESS_KEY=dummy
      - AWS_DEFAULT_REGION=us-east-1
      - AWS_DEFAULT_OUTPUT=json
      - AWS_ENDPOINT_URL=http://localstack:4566
    tty: true
    stdin_open: true
    entrypoint: ["sh"]
    depends_on:
      - localstack

上記を適当なディレクトリに保存する。

コンテナの起動

docker compose up -d

S3

S3バケットを作成する

aws-cliコンテナに入る。

docker compose exec aws-cli bash

S3バケットを作成する。

aws s3api create-bucket --bucket test

結果↓

{
    "Location": "/test"
}

下記のように出力される。

bash-4.2# aws s3 ls
2024-08-25 02:56:27 test
bash-4.2# 

S3バケットにファイルをアップロード

aws s3 cp /etc/hosts s3://test/hosts

結果↓

bash-4.2# aws s3 cp /etc/hosts s3://test/hosts
upload: ../etc/hosts to s3://test/hosts   

S3バケットの中身を確認する

aws s3 ls s3://test
bash-4.2# aws s3 ls s3://test
2024-08-25 04:49:31        174 hosts

アップロードした hosts ファイルをダウンロードする

aws s3 cp s3://test/hosts /tmp/hosts
bash-4.2# aws s3 cp s3://test/hosts /tmp/hosts
download: s3://test/hosts to ../tmp/hosts      

ダウンロードしたファイルの中身を確認する

cat /tmp/hosts

結果↓

bash-4.2# cat /tmp/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.23.0.3      ef72d46edfec

↑お~できていますね。

S3api コマンドで確認する

aws s3api list-objects --bucket test
{
    "Contents": [
        {
            "Key": "hosts",
            "LastModified": "2024-08-25T04:49:31+00:00",
            "ETag": "\"7248e9dd3e36e61a22956ba1306f2cf2\"",
            "Size": 174,
            "StorageClass": "STANDARD",
            "Owner": {
                "DisplayName": "webfile",
                "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
            }
        }
    ],
    "RequestCharged": null
}

hosts ファイルが表示されていますね。

S3バケットを削除する

aws s3 rb s3://test --force
bash-4.2# aws s3 rb s3://test --force
delete: s3://test/hosts
remove_bucket: test

確認

aws s3 ls

※何も返ってこなければ削除されている。

Lambda

pythonコードの作成

lambda_function.py を作成する。

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': 'Hello, World!'
    }

シンプルなHello WorldのLambda関数を作成する。

zipファイルの作成

zip lambda_function.zip lambda_function.py

zipファイルをaws-cliコンテナにアップロード

docker cp lambda_function.zip aws-cli:/tmp/lambda_function.zip

localstackにLambda関数をデプロイ

docker compose exec aws-cli bash
cd /tmp
aws lambda create-function --function-name hello-world --role arn:aws:iam::000000000000:role/role1 --runtime python3.12 --handler lambda_function.lambda_handler --zip-file fileb://lambda_function.zip

ランタイム
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html

結果↓

{
    "FunctionName": "hello-world",
    "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:hello-world",
    "Runtime": "python3.12",
    "Role": "arn:aws:iam::000000000000:role/role1",
    "Handler": "lambda_function.lambda_handler",
    "CodeSize": 281,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2024-08-25T05:24:39.443750+0000",
    "CodeSha256": "OjFvsqOXa1Du2sy+rSm4APYN9x6YsnuvDhiM1aYsIaQ=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "14a1140b-754d-48e2-833e-c0685b5b0903",
    "State": "Pending",
    "StateReason": "The function is being created.",
    "StateReasonCode": "Creating",
    "PackageType": "Zip",
    "Architectures": [
        "x86_64"
    ],
    "EphemeralStorage": {
        "Size": 512
    },
    "SnapStart": {
        "ApplyOn": "None",
        "OptimizationStatus": "Off"
    },
    "RuntimeVersionConfig": {
        "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:8eeff65f6809a3ce81507fe733fe09b835899b99481ba22fd75b5a7338290ec1"
    },
    "LoggingConfig": {
        "LogFormat": "Text",
        "LogGroup": "/aws/lambda/hello-world"
    }
}

Lambda関数の実行

aws lambda invoke --function-name hello-world /tmp/output.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

output.json に結果が出力される。

cat /tmp/output.json
{"statusCode": 200, "body": "Hello, World!"}

OK!

DynamoDB

テーブルを作る

aws dynamodb create-table \
    --table-name test \
    --key-schema AttributeName=id,KeyType=HASH \
    --attribute-definitions AttributeName=id,AttributeType=S \
    --billing-mode PAY_PER_REQUEST \
    --region ap-south-1

結果↓

{
    "TableDescription": {
        "AttributeDefinitions": [
            {
                "AttributeName": "id",
                "AttributeType": "S"
            }
        ],
        "TableName": "test",
        "KeySchema": [
            {
                "AttributeName": "id",
                "KeyType": "HASH"
            }
        ],
        "TableStatus": "ACTIVE",
        "CreationDateTime": "2024-08-25T06:07:20.435000+00:00",
        "ProvisionedThroughput": {
            "LastIncreaseDateTime": "1970-01-01T00:00:00+00:00",
            "LastDecreaseDateTime": "1970-01-01T00:00:00+00:00",
            "NumberOfDecreasesToday": 0,
            "ReadCapacityUnits": 0,
            "WriteCapacityUnits": 0
        },
        "TableSizeBytes": 0,
        "ItemCount": 0,
        "TableArn": "arn:aws:dynamodb:ap-south-1:000000000000:table/test",
        "TableId": "200004d2-5a7c-4bd7-a4ab-a5f7340baf4f",
        "BillingModeSummary": {
            "BillingMode": "PAY_PER_REQUEST",
            "LastUpdateToPayPerRequestDateTime": "2024-08-25T06:07:20.435000+00:00"
        },
        "DeletionProtectionEnabled": false
    }
}

テーブルの確認

aws dynamodb list-tables \
    --region ap-south-1

結果↓

{
    "TableNames": [
        "test"
    ]
}

データを入れる

aws dynamodb put-item \
    --table-name test \
    --item '{"id":{"S":"foo"}}' \
    --region ap-south-1

データの数を確認する

aws dynamodb describe-table \
    --table-name test \
    --query 'Table.ItemCount' \
    --region ap-south-1
1

データを確認する

aws dynamodb get-item \
    --key '{"id": {"S": "foo"}}' \
    --table-name test \
    --region ap-south-1

結果↓

{
    "Item": {
        "id": {
            "S": "foo"
        }
    }
}

おぉ!できてる。

参考

おわりに

この記事書いた後に知ったのだが、aws-cli-localというものがあるらしい。
aws-cliより、aws-cli-localの方が使いやすいかもしれない。
https://github.com/localstack/awscli-local

本番ではS3を使えるけど、ローカルの場合はどうするべきかと悩んでいたのでLocalstackという選択肢を知れてよかった。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。