Setup DynamoDB with LocalStack

6 분 소요


RECOMMEND POSTS BEFORE THIS

0. 들어가면서

로컬 환경에서 개발, 테스트를 위해 DynamoDB 로컬 컨테이너를 사용하고 있다. 팀원이 LocalStack을 사용해보자는 의견을 내서 사용하기 전에 LocalStack 컨셉과 이를 사용해 어떻게 DynamoDB를 셋업하는지 정리했다.

1. What is LocalStack?

LocalStack은 AWS 리소스들을 로컬 환경에서 간단하게 실행할 수 있는 에뮬레이터(emulator)다. LocalStack을 사용하면 내부 환경에 애플리케이션 개발에 필요한 AWS 리소스(e.g. DynamoDB, Lambda, S3, Kinesis, SQS 등)를 손쉽게 모두 준비할 수 있다. AWS 클라우드 리소스를 대체하기 위해 사용하는 다른 서비스들을 LocalStack 하나로 모두 커버할 수 있다.

예를 들어 다음과 같은 스택을 사용했다.

  • S3 대체 서비스 - MinIO 컨테이너
  • DynamoDB 대체 서비스 - DynamoDB Local 컨테이너
  • ElastiCache 대체 서비스 - Redis 컨테이너

LocalStack은 위 AWS 리소스들을 하나의 컨테이너를 통해 제공할 수 있다. 무료로 사용할 수 있지만, 특정 기능들은 유료이기 때문에 결제가 필요할 수도 있다. One for All이기 때문에 엄청 편리할 것 같지만, 내가 개발하는 애플리케이션들이 필요한 AWS 리소스들을 로컬 환경에 구축할 때 사용하는 도커 컴포즈(docker compose) YAML 파일의 복잡도가 낮아지는 장점 외에 특별한 점을 느끼긴 어려웠다.

2. Setup DynamoDB table on LocalStack container

이제 LocalStack 컨테이너를 실행한 후 내부 DynamoDB에 개발에 필요한 테이블을 준비해보자. 커맨드로 LocalStack 컨테이너를 쉽게 실행할 수 있지만, 이 글에선 도커 컴포즈를 사용한다. 다음과 같은 도커 컴포즈 YAML 파일을 작성한다.

version: "3.8"
services:
  localstack:
    image: localstack/localstack
    ports:
      - "127.0.0.1:4566:4566" # LocalStack 내부 서비스를 외부에서 접근할 때 사용하는 포트, LocalStack Gateway
      - "127.0.0.1:4510-4559:4510-4559" # 외부 서비스들의 포트 범위
    environment:
      - DYNAMODB_SHARE_DB=1 # DynamoDB를 단일 데이터베이스로 사용
    volumes:
      - "./init:/etc/localstack/init/ready.d" # 초기화 훅(hook) 스크립트
      - "./volume:/var/lib/localstack"
      - "${DOCKER_SOCK:-/var/run/docker.sock}:/var/run/docker.sock" # 로컬 호스트 도커 연결

DYNAMODB_SHARE_DB 환경 변수를 1로 설정하여 DynamoDB가 지역마다 별도 데이터베이스를 사용하지 않도록 한다. 단일 데이터베이스로 사용하지 않는 경우 각 지역마다 다른 데이터베이스를 사용하기 때문에 지역(region) 설정이 불가능한 NoSQL워크벤치에선 DynamoDB에 생성된 테이블을 확인할 수 없다. DynamoDB에 생성한 테이블을 NoSQL워크벤치(workbench) 같은 도구를 통해 확인하기 위해선 이 설정이 필요하다.

나는 LocalStack 컨테이너를 실행함과 동시에 DyanmoDB에 테이블을 준비하고 싶었다. LocalStack 컨테이너의 초기화 훅을 사용하면 테이블 초기화가 가능하다. LocalStack 컨테이너는 다음과 같은 라이프사이클을 갖는다.

  • BOOT - the container is running but the LocalStack runtime has not been started
  • START - the Python process is running and the LocalStack runtime is starting
  • READY - LocalStack is ready to serve requests
  • SHUTDOWN - LocalStack is shutting down

각 라이프사이클에 동작하는 훅 스크립트는 아래 디렉토리들에 존재한다.

/etc
└── localstack
    └── init
        ├── boot.d           <-- executed in the container before localstack starts
        ├── ready.d          <-- executed when localstack becomes ready
        ├── shutdown.d       <-- executed when localstack shuts down
        └── start.d          <-- executed when localstack starts up

/etc/localstack/init/ready.d 경로에 가져다 놓은 쉘 스크립트는 LocalStack 컨테이너 준비가 완료된 후 실행된다. 이 곳에 DynamoDB 테이블을 생성하는 쉘 스크립트를 볼륨을 통해 공유한다. 볼륨으로 공유된 프로젝트 ./init 경로에 아래와 같은 쉘 스크립트를 준비한다.

#!/bin/bash
awslocal dynamodb create-table \
    --table-name TodoTable \
    --key-schema AttributeName=PK,KeyType=HASH \
    --attribute-definitions AttributeName=PK,AttributeType=S \
    --billing-mode PAY_PER_REQUEST \
    --region ap-northeast-1;

도커 컴포즈를 실행하면 컨테이너 실행과 동시에 DynamoDB에 TodoTable 테이블이 생성되는 로그를 확인할 수 있다.

$ docker compose up

...

localstack-1  | 2025-01-24T17:24:24.030  INFO --- [et.reactor-0] localstack.utils.bootstrap : Execution of "require" took 2801.54ms
localstack-1  | 2025-01-24T17:24:24.444  INFO --- [et.reactor-0] localstack.request.aws     : AWS dynamodb.CreateTable => 200
localstack-1  | {
localstack-1  |     "TableDescription": {
localstack-1  |         "AttributeDefinitions": [
localstack-1  |             {
localstack-1  |                 "AttributeName": "PK",
localstack-1  |                 "AttributeType": "S"
localstack-1  |             }
localstack-1  |         ],
localstack-1  |         "TableName": "TodoTable",
localstack-1  |         "KeySchema": [
localstack-1  |             {
localstack-1  |                 "AttributeName": "PK",
localstack-1  |                 "KeyType": "HASH"
localstack-1  |             }
localstack-1  |         ],
localstack-1  |         "TableStatus": "ACTIVE",
localstack-1  |         "CreationDateTime": 1737739464.222,
localstack-1  |         "ProvisionedThroughput": {
localstack-1  |             "LastIncreaseDateTime": 0.0,
localstack-1  |             "LastDecreaseDateTime": 0.0,
localstack-1  |             "NumberOfDecreasesToday": 0,
localstack-1  |             "ReadCapacityUnits": 0,
localstack-1  |             "WriteCapacityUnits": 0
localstack-1  |         },
localstack-1  |         "TableSizeBytes": 0,
localstack-1  |         "ItemCount": 0,
localstack-1  |         "TableArn": "arn:aws:dynamodb:ap-northeast-1:000000000000:table/TodoTable",
localstack-1  |         "TableId": "4f87df05-3ab0-48e2-95cf-5ab3b9348cb3",
localstack-1  |         "BillingModeSummary": {
localstack-1  |             "BillingMode": "PAY_PER_REQUEST",
localstack-1  |             "LastUpdateToPayPerRequestDateTime": 1737739464.222
localstack-1  |         },
localstack-1  |         "DeletionProtectionEnabled": false
localstack-1  |     }
localstack-1  | }
localstack-1  | Ready.

이제 NoSQL워크벤치에서 LocalStack 컨테이너로 연결하면 해당 테이블 정보를 확인할 수 있다. NoSQL워크벤치에서 4566 포트번호를 사용해 연결을 만든다.


“TodoTable” 이름을 갖는 테이블 정보를 확인할 수 있다.


3. Trouble shooting

다음과 같은 에러들이 있었다.

3.1. M4 Apple chip issue

나는 M4 맥북(macbook)을 사용 중이다. M4 맥북을 사용하는 경우 localstack/localstack:latest 이미지를 사용하면 에러가 발생한다. M3 맥북에선 문제가 없는 것을 확인헀다.

  • DynamoDBLocal.jar 파일을 계속 재시작한다. 무한 루프처럼 재시작이 빠르게 반복된다.
...

localstack-1  | 2025-01-24T17:18:57.063  INFO --- [-functhread5] localstack.utils.run       : Restarting process (received exit code -6): ['java', '-Xmx256m', '-javaagent:/usr/lib/localstack/dynamodb-local/2/ddb-local-loader-0.1.jar', '-Djava.library.path=/usr/lib/localstack/dynamodb-local/2/DynamoDBLocal_lib', '-jar', '/usr/lib/localstack/dynamodb-local/2/DynamoDBLocal.jar', '-port', '40083', '-dbPath', '/tmp/localstack/state/dynamodb', '-sharedDb']
localstack-1  | 2025-01-24T17:18:57.080  INFO --- [-functhread5] localstack.utils.run       : Restarting process (received exit code -6): ['java', '-Xmx256m', '-javaagent:/usr/lib/localstack/dynamodb-local/2/ddb-local-loader-0.1.jar', '-Djava.library.path=/usr/lib/localstack/dynamodb-local/2/DynamoDBLocal_lib', '-jar', '/usr/lib/localstack/dynamodb-local/2/DynamoDBLocal.jar', '-port', '40083', '-dbPath', '/tmp/localstack/state/dynamodb', '-sharedDb']
localstack-1  | 2025-01-24T17:18:57.098  INFO --- [-functhread5] localstack.utils.run       : Restarting process (received exit code -6): ['java', '-Xmx256m', '-javaagent:/usr/lib/localstack/dynamodb-local/2/ddb-local-loader-0.1.jar', '-Djava.library.path=/usr/lib/localstack/dynamodb-local/2/DynamoDBLocal_lib', '-jar', '/usr/lib/localstack/dynamodb-local/2/DynamoDBLocal.jar', '-port', '40083', '-dbPath', '/tmp/localstack/state/dynamodb', '-sharedDb']

...

M4 칩에선 localstack/localstack:latest-amd64 이미지를 사용하면 호스트 플랫폼과 맞지 않는다는 경고 메시지와 함께 DynamoDB에 정상적으로 테이블이 생성되는 것을 확인할 수 있다.

$ docker compose up

WARN[0000] /Users/junhyunny/Desktop/todoApp/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion 
[+] Running 3/0
 ✔ Network todoapp_default                                                                                                                                   Created                                                                                                  0.0s 
 ✔ Container todoapp-localstack-1                                                                                                                            Created                                                                                                  0.1s 
 ! localstack The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested                                                                                                          0.0s 
Attaching to localstack-1
localstack-1  | 
localstack-1  | LocalStack version: 4.0.4.dev117
localstack-1  | LocalStack build date: 2025-01-22
localstack-1  | LocalStack build git hash: b4b0ab9f9
localstack-1  | 
localstack-1  | 2025-01-24T17:24:24.030  INFO --- [et.reactor-0] localstack.utils.bootstrap : Execution of "require" took 2801.54ms
localstack-1  | 2025-01-24T17:24:24.444  INFO --- [et.reactor-0] localstack.request.aws     : AWS dynamodb.CreateTable => 200
localstack-1  | {
localstack-1  |     "TableDescription": {
localstack-1  |         "AttributeDefinitions": [
localstack-1  |             {
localstack-1  |                 "AttributeName": "PK",
localstack-1  |                 "AttributeType": "S"
localstack-1  |             }
localstack-1  |         ],
localstack-1  |         "TableName": "TodoTable",
localstack-1  |         "KeySchema": [
localstack-1  |             {
localstack-1  |                 "AttributeName": "PK",
localstack-1  |                 "KeyType": "HASH"
localstack-1  |             }
localstack-1  |         ],
localstack-1  |         "TableStatus": "ACTIVE",
localstack-1  |         "CreationDateTime": 1737739464.222,
localstack-1  |         "ProvisionedThroughput": {
localstack-1  |             "LastIncreaseDateTime": 0.0,
localstack-1  |             "LastDecreaseDateTime": 0.0,
localstack-1  |             "NumberOfDecreasesToday": 0,
localstack-1  |             "ReadCapacityUnits": 0,
localstack-1  |             "WriteCapacityUnits": 0
localstack-1  |         },
localstack-1  |         "TableSizeBytes": 0,
localstack-1  |         "ItemCount": 0,
localstack-1  |         "TableArn": "arn:aws:dynamodb:ap-northeast-1:000000000000:table/TodoTable",
localstack-1  |         "TableId": "4f87df05-3ab0-48e2-95cf-5ab3b9348cb3",
localstack-1  |         "BillingModeSummary": {
localstack-1  |             "BillingMode": "PAY_PER_REQUEST",
localstack-1  |             "LastUpdateToPayPerRequestDateTime": 1737739464.222
localstack-1  |         },
localstack-1  |         "DeletionProtectionEnabled": false
localstack-1  |     }
localstack-1  | }
localstack-1  | Ready.

해당 에러는 이미 논-이슈(known-issue)이지만, 아직 해결되진 않은 것 같다.

3.2. Permission denied to run initialization shell

초기화 훅 폴더에 담긴 쉘 스크립트의 실행 권한이 없는 경우 다음과 같은 에러를 만난다.

$ ls -la init/dynamodb.sh
-rw-r--r--@ 1 junhyunny  staff  251 Jan 23 14:28 init/dynamodb.sh

$ docker compose up

...

localstack-1  | 2025-01-24T17:34:43.063 ERROR --- [ady_monitor)] localstack.runtime.init    : Error while running script Script(path='/etc/localstack/init/ready.d/dynamodb.sh', stage=READY, state=ERROR): [Errno 13] Permission denied: '/etc/localstack/init/ready.d/dynamodb.sh'

초기화 스크립트에 실행 가능 여부를 설정하면 해결된다.

$ chmod +x init/dynamodb.sh

$ ls -la init/dynamodb.sh  
-rwxr-xr-x@ 1 junhyunny  staff  251 Jan 23 14:28 init/dynamodb.sh

3.3 Exec format error

초기화 스크립트를 실행할 때 스크립트 위에 쉘(shell)에 대한 힌트가 없는 경우 에러가 발생한다. 예를 들어 아래 스크립트를 사용하면 초기화가 실패한다.

awslocal dynamodb create-table \
    --table-name TodoTable \
    --key-schema AttributeName=PK,KeyType=HASH \
    --attribute-definitions AttributeName=PK,AttributeType=S \
    --billing-mode PAY_PER_REQUEST \
    --region ap-northeast-1;

다음과 같은 에러를 확인할 수 있다.

$ docker compose up

...

localstack-1  | 2025-01-24T17:37:23.369 ERROR --- [ady_monitor)] localstack.runtime.init    : Error while running script Script(path='/etc/localstack/init/ready.d/dynamodb.sh', stage=READY, state=ERROR): [Errno 8] Exec format error: '/etc/localstack/init/ready.d/dynamodb.sh'

반면, 초기화 쉘 스크립트 파일 최상단에 어떤 쉘을 사용할 것인지 힌트를 작성해주면 문제 없이 실행된다.

#!/bin/bash
awslocal dynamodb create-table \
    --table-name TodoTable \
    --key-schema AttributeName=PK,KeyType=HASH \
    --attribute-definitions AttributeName=PK,AttributeType=S \
    --billing-mode PAY_PER_REQUEST \
    --region ap-northeast-1;

REFERENCE

댓글남기기