【DynamoDB Local】快適なDynamoDBのローカル開発環境を構築する

DynamoDBのアイコンイメージ

DynamoDBをデータベースとして用いる開発を、完全にローカル環境のみで完結させることができるよう環境構築したので紹介します。

システムの開発フェーズにおいては、個人が好き勝手に作って壊せるデータベース環境が欲しくなることも多いかと思います。RDS等のRDBであれば、ローカルにデータベースを立てればそれで終わりですが、DynamoDBでも同様にローカルにデータベースを立てることが可能です。

この記事では、ただ単に「ローカルにDynamoDBを立てて終わり」ではなく、コマンド一発ですぐに開発が始められるような、快適な開発環境を構築することをゴールとします。

前提とする環境

Docker Composeが利用可能である、インターネットアクセス可能なWindows もしくは Linux環境

これから解説する環境構築の前提は、これだけです。MacOS環境でも動作確認こそしていませんが、使うのはDockerなのでおそらく動作するかと思います。

環境構築方法

使用ツール

まず、DynamoDBのローカル開発環境で使用するツールについて簡単に説明します。

DynamoDB Local

AWS公式がリリースしている、ローカル端末で動作可能なDynamoDB互換のデータベースです。JavaもしくはDocker上で動作します。

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html

https://hub.docker.com/r/amazon/dynamodb-local

 

AWS環境のDynamoDBのAPIと同じAPIで操作可能であるため、本物を触るのと同じ感覚で開発を進めることができます。(なんとDynamoDB Streamsまで再現されています)もちろん利用料金は一切かかりません。

ただし、AWS上のDynamoDBとは一部異なる動作をしますので、注意する必要があります。

  • プロビジョニングされたキャパシティー(Provisioned Read/Write Capacity)に設定した値は無視されます。
  • 単一のローカルマシンで実行されるので、副次的にほとんどの読み取り操作で強力な整合性が取れているように見えます。(全く同じテーブルに対し、全く同じ読み取り操作を行っても、結果がAWS上で得られるものとは異なるケースがあります)
  • トランザクションの競合によって発生する、TransactionConflictExceptionsはスローされません
  • DynamoDB Localではテーブル名の大文字小文字を区別しませんが、AWS環境ではこれらは区別されます。

より詳細な差異については、下記を参照のこと。

Differences Between Downloadable DynamoDB and the DynamoDB Web Service

 

DynamoDB Localのソースは非公開なので、詳細な内部実装は分かりませんが、内部的にSQLiteを使用しているらしいです。すごい…

dynamodb-admin

dynamodb-adminのスクリーンショット

ローカルで動作するDynamoDBを、GUIで操作可能なWebベースのツールです。node.jsもしくはDocker上で動作します。

localstackなど、DynamoDB Local以外の環境でも動作するようです。

https://github.com/aaronshaf/dynamodb-admin

 

ちなみに、DynamoDB Local自体にもJavaScriptのAWS SDKを実行できるWebインターフェースが用意されているのですが、テーブルの中身を一覧表示したり、少しレコードを追加したりする程度の操作を実行するには面倒なのでdynamodb-localを使用しています。

DynamoDB Local JavaScript shellのスクリーンショット

DynamoDB Localに組み込まれているJavaScript Shell

ベースとするdocker-compose.yml ファイル

上記のツールは、それぞれDockerイメージが配布されているので、Docker Composeで連携させてコマンド一発で起動するようにします。

基本となるdocker-compose.yml ファイルは下記です。

version: '3'

services:
  dynamodb-local:
    container_name: test_dynamodb-local
    image: amazon/dynamodb-local:latest
    user: root
    command: -jar DynamoDBLocal.jar -sharedDb -dbPath /data
    volumes:
      - dynamodb-local-data:/data
    ports:
      - 8000:8000
    networks:
      - dynamodb-local-network

  dynamodb-admin:
    container_name: test_dynamodb-admin
    image: aaronshaf/dynamodb-admin:latest
    environment:
      - DYNAMO_ENDPOINT=dynamodb-local:8000
    ports:
      - 8001:8001
    depends_on:
      - dynamodb-local
    networks:
      - dynamodb-local-network

volumes:
  dynamodb-local-data:

networks:
  dynamodb-local-network:
    driver: bridge

Dockerfileは使わずにすべてDocker Composeのみで完結させることで、イメージのビルド作業を削減し素早い環境構築を可能にしています。

実装のポイント(DynamoDB Local)

# 一部のみ抜粋
    user: root
    command: -jar DynamoDBLocal.jar -sharedDb -dbPath /data
    volumes:
      - dynamodb-local-data:/data

user オプションを付けることで、rootユーザーで起動しています。

DynamoDB LocalのDockerイメージは、デフォルトでは一般ユーザーで実行されるようになっています。そのままだとLinux環境で名前付きボリュームを使えなかったため、rootユーザーで起動しています。名前付きボリュームを使わない場合はデフォルトの一般ユーザーで実行しても問題ありません。

 

command オプションには、コンテナ起動時にJavaのランタイムに渡すコマンドを指定します。

-sharedDb オプションを指定することで、DynamoDB LocalへのAPIコール時のリージョン指定を区別しないように設定しています。(デフォルトではリージョン指定が区別され、異なるリージョンを指定するとそれぞれ別々のDBに対する操作となります)

-dbPath オプションには、永続化するデータのファイルパスを指定しています。名前付きボリューム(volumes オプション)をここで指定したパスに展開すると、コンテナを停止・削除してもDBに保存したデータが永続化されます。これとは反対に、-inMemory オプションを指定すると、データはメモリ内に展開され、コンテナを停止・削除するとデータも破棄されます。(これはcommand オプションに何も指定しない場合のデフォルトの動作です。)ここはユースケースに応じて設定を変えると良いでしょう。

実装のポイント(dynamodb-admin)

# 一部のみ抜粋
    environment:
      - DYNAMO_ENDPOINT=dynamodb-local:8000
    ports:
      - 8001:8001

特に捻りはありません。環境変数DYNAMODB_ENDPOINTにDynamoDB Localで設定したポート番号を指定することで、接続先を設定します。

こちらは8001番ポートにバインドしてあるので、http://localhost:8001 にアクセスするとdynamodb-adminのwebインターフェースにアクセスできます。

コンテナを起動する

何の変哲もなく、docker-composeコマンドを叩くだけです。

$ docker-compose up -d
Creating network "dynamodb_dynamodb-local-network" with driver "bridge"
Creating test_dynamodb-local ... done
Creating test_dynamodb-admin     ... done

これで、AWS SDK、AWS CLIやDynamoDB LocalのWebインターフェースからテーブルを作成してデータを投入できる状態になります。

Terraformを利用して自動でDynamoDB Localをセットアップする

上記のdocker-compose.ymlでもローカル開発を始めることはできますが、AWS環境でのDynamoDBの作成にTerraformを使っている場合、設定(.tfファイル)はほぼそのままでDynamoDB Localのテーブルやデータの作成も行うことができます

先ほどの構成をTerraformと連携させることで、DynamoDB Localのコンテナが起動した時点でAWS上の開発/本番環境と同等の状態にセットアップすることができ、さらに開発の効率を向上させることが可能です。

ここからはその方法を紹介します。

Terraform 側の設定

既にTerraformでDynamoDBを構築している場合は、providerの設定を変えるだけです。

provider "aws" {
  region                      = "ap-northeast-1"
  skip_credentials_validation = true
  skip_requesting_account_id  = true
  endpoints {
    dynamodb = "http://dynamodb-local:8000"
  }
}

skip_credential_validationskip_requesting_account_id を有効にすることで、TerraformによるAWSの認証情報とアカウント情報の検証を無効化しています。こうすることで、後述するTerraformコンテナにAWS認証情報を設定する必要がなくなります

そして、endpoints の設定に、DynamoDB Localのコンテナを指定することで、実行の向き先をローカルに切り替えることができます。(Dockerコンテナ同士の通信になるので、ホスト名にはlocalhostではなく、サービス名を指定します)

環境別にフォルダを切ってTerraformを使用している場合は、比較的簡単にローカル開発用の設定を追加することができるかと思います。下記にサンプル構成を載せておきます。

.infra
├── environment
│   ├── prod # 本番環境用の設定
│   │   ├── backend.tf
│   │   ├── main.tf
│   │   └── providers.tf
│   └── stg # ステージング環境用の設定
│   │   └── ... 略 ...
│   └── local # ★ローカル開発(DynamoDB Local)用の設定★
│       ├── main.tf
│       └── providers.tf # endpointをローカルに変更
└── modules
    └── dynamodb
        ├── main.tf
        └── variables.tf

モジュールを利用している場合のごく普通の構成かと思います。.tfstateファイルは共有したくないので、backendを指定しないことでローカル環境内に作成されるようにしています。

もちろんモジュールを利用していない場合やワークスペースを利用している場合でも、うまく設定はできるでしょう。

docker-compose.yml ファイル

Terraform以外の設定は、先ほどのベースとするdocker-compose.yml ファイルと同じなので割愛します。

terraform:
    container_name: test_dynamodb-terraform
    image: hashicorp/terraform:0.13.4
    command: >
      /bin/sh -c
      '
      echo "plugin_cache_dir = \"/root/.terraform.d/plugin-cache\"" > ~/.terraformrc &&
      terraform init &&
      terraform apply -auto-approve;
      /bin/sh
      '
    entrypoint: ''
    tty: true
    environment:
      - AWS_ACCESS_KEY_ID=dummy
      - AWS_SECRET_ACCESS_KEY=dummy
    working_dir: /terraform/environment/local
    volumes:
      - ../../.infra:/terraform
    depends_on:
      - dynamodb-local
    networks:
      - dynamodb-local-network

実装のポイント(docker-compose.yml)

image: hashicorp/terraform:0.13.4

Terraform公式のDockerイメージを使用しています。バージョンを固定していますが、必須ではありません。※1Terraformは、バージョンアップを挟むと比較的頻繁に構文が変わったり後方互換性がなくなったりするので個人的にはできる限りバージョンは固定にしておき、必要に応じて手動でアップデートしながら使いたいと思っています。

 

    command: >
      /bin/sh -c
      '
      echo "plugin_cache_dir = \"/root/.terraform.d/plugin-cache\"" > ~/.terraformrc && # AWSプラグインをキャッシュして使いまわす
      terraform init &&
      terraform apply -auto-approve;
      /bin/sh
      '
    entrypoint: ''
    tty: true
    environment:
      - AWS_ACCESS_KEY_ID=dummy
      - AWS_SECRET_ACCESS_KEY=dummy

まず、公式のDockerイメージにはENTRYPOINTが設定されており、起動と同時にコマンドを実行して終了するようになっているので、entrypoint に空文字を指定することでこの挙動を無効化しています。

command オプションの最後に/bin/sh を指定し、tty オプションを有効化しておくことで、コンテナ起動時にDynamoDB Localの初期化処理を実行した後もコンテナを起動したままにしています。これにより、開発中にTerraformの設定を変え、すぐにTerraformコンテナに入って変更を適用することも可能です。

先ほどTerraformの設定でAWSの認証情報とアカウント情報の検証を無効化したので、正しいAWS認証情報を設定する必要はないのですが、設定自体がないとエラーになるので、でたらめな認証情報を環境変数に設定しています。

 

# 一部のみ抜粋
    working_dir: /terraform/env/local
    volumes:
      - ../../.infra:/terraform

volumes オプションでTerraformの設定ファイル群をコンテナにマウントし、このディレクトリをコンテナ起動時のカレントディレクトリに設定します。

 

以上でTerraformとの連携設定は完了です。

まとめ

ここまでの設定を済ませることで、コマンド一発で本番環境同様のDynamoDBをローカルに立てて開発を進めることが可能になります。

コンテナで開発環境を構築しておくと、環境差異によってうまく動作しなくなることも少なくなり、誰でも構築/破棄がすぐにできるのがとてもうれしいですね。

脚注   [ + ]

1. Terraformは、バージョンアップを挟むと比較的頻繁に構文が変わったり後方互換性がなくなったりするので個人的にはできる限りバージョンは固定にしておき、必要に応じて手動でアップデートしながら使いたいと思っています。

あわせて読みたい

コメントを残す

質問・感想などお気軽にどうぞ。
*が付いている項目は入力必須です。メールアドレス以外の項目が公開されます。
スパム防止のため、コメント反映まで少々時間がかかります。