LoginSignup
8
2

More than 3 years have passed since last update.

DynamoDB Localトラブルシューティング(Node.js + TypeScript)

Last updated at Posted at 2020-08-25

Node.js + TypeScript(Dockerコンテナ)からDynamoDB Localへ接続、操作をする際に発生したトラブルの備忘録です。

DynamoDB Localとは?

AWS上のDynamoDBにアクセスすることなく、DynamoDBを利用するアプリケーションの開発・テストをすることが可能になります。

DynamoDB Localの設定(ダウンロード版)

背景

前提としてNode.js + TypeScriptのプログラムはECS on Fargateのタスクスケジューラで定期実行するプログラムです。

今回DynamoDB Localを導入するのは、開発時にAWSのDynamoDBを利用せず、ローカル環境で全て完結させたいというのが理由です。

忙しい人のために

トラブル対応後のファイル構成、操作手順が下記になります。

まとめ📖 (ファイル一覧/操作手順)

事前準備

本実装に入る前にDynamoDB LocalをDockerで立ち上げて、AWS公式のNode.jsとDynamoDBのチュートリアルを参考に実装を進めることに。

ファイル構成

$ tree
.
├── Dockerfile
├── package.json
├── src
│   └── index.ts
└── tsconfig.json

DynamoDB Local立ち上げ

最初はdocker-composeを利用せず、下記コマンドでDynamoDB Localを立ち上げていました。(なるべく本番環境に不要なファイルを作りたくなかった)

$ docker run --name dynamodb -p 8000:8000 amazon/dynamodb-local

トラブル(発生順)

その1 : ~ is not assignable to parameter of type 'ConfigurationOptions & ConfigurationServicePlaceholders & APIVersions'

import * as AWS from 'aws-sdk';

AWS.config.update({
  region: 'us-west-2',
  endpoint: 'http://localhost:8000'
});

const dynamodb = new AWS.DynamoDB.DocumentClient();
TSError: ⨯ Unable to compile TypeScript:
src/index.ts(14,3): error TS2345: Argument of type '{ region: string; endpoint: string; accessKeyId: string; secretAccessKey: string; }' is not assignable to parameter of type 'ConfigurationOptions & ConfigurationServicePlaceholders & APIVersions'.
  Object literal may only specify known properties, and 'endpoint' does not exist in type 'ConfigurationOptions & ConfigurationServicePlaceholders & APIVersions'.

解決法

TypeScriptで実装しているため、型定義によるエラーが発生。詳細は省きますが、下記のようにプログラムを書き換えることで解消されました。

修正ポイント : ServiceConfigurationOptionsを指定する

import * as AWS from 'aws-sdk';
import { ServiceConfigurationOptions } from 'aws-sdk/lib/service';

const serviceConfigOptions: ServiceConfigurationOptions = {
  region: 'us-west-2',
  endpoint: 'http://localhost:8000'
};
AWS.config.update(serviceConfigOptions);

const dynamodb = new AWS.DynamoDB.DocumentClient();

その2 : Missing credentials in config

import * as AWS from 'aws-sdk';
import { ServiceConfigurationOptions } from 'aws-sdk/lib/service';

const serviceConfigOptions: ServiceConfigurationOptions = {
  region: 'us-west-2',
  endpoint: 'http://localhost:8000'
};
AWS.config.update(serviceConfigOptions);

const dynamodb = new AWS.DynamoDB.DocumentClient();
Error Error: connect ECONNREFUSED 169.254.169.254:80
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1141:16) {
  message: 'Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1',
  errno: 'ECONNREFUSED',
  code: 'CredentialsError',
  syscall: 'connect',
  address: '169.254.169.254',
  port: 80,
  time: 2020-08-23T23:30:29.548Z,
  originalError: {
    message: 'Could not load credentials from any providers',
    errno: 'ECONNREFUSED',
    code: 'CredentialsError',
    syscall: 'connect',
    address: '169.254.169.254',
    port: 80,
    time: 2020-08-23T23:30:29.547Z,
    originalError: {
      message: 'EC2 Metadata roleName request returned error',
      errno: 'ECONNREFUSED',
      code: 'ECONNREFUSED',
      syscall: 'connect',
      address: '169.254.169.254',
      port: 80,
      time: 2020-08-23T23:30:29.547Z,
      originalError: [Object]
    }
  }
}

AWS公式のNode.jsとDynamoDBのチュートリアルをTypeScriptに置き換えただけですが、動作せず・・・。

解決法

設定値にAccess KeySecret Access Keyがないとエラーになるようです。

修正ポイント : キーにaccessKeyIdsecretAccessKeyを追加、値は任意の値でOK

import * as AWS from 'aws-sdk';
import { ServiceConfigurationOptions } from 'aws-sdk/lib/service';

const serviceConfigOptions: ServiceConfigurationOptions = {
  region: 'us-west-2',
  endpoint: 'http://localhost:8000',
  accessKeyId: 'fakeAccessKeyId',
  secretAccessKey: 'fakeSecretAccessKey'
};
AWS.config.update(serviceConfigOptions);

const dynamodb = new AWS.DynamoDB.DocumentClient();

その3 : connect ECONNREFUSED 127.0.0.1:8000

import * as AWS from 'aws-sdk';
import { ServiceConfigurationOptions } from 'aws-sdk/lib/service';

const serviceConfigOptions: ServiceConfigurationOptions = {
  region: 'us-west-2',
  endpoint: 'http://localhost:8000',
  accessKeyId: 'fakeAccessKeyId',
  secretAccessKey: 'fakeSecretAccessKey'
};
AWS.config.update(serviceConfigOptions);

const dynamodb = new AWS.DynamoDB.DocumentClient();
Error Error: connect ECONNREFUSED 127.0.0.1:8000
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1141:16) {
  errno: 'ECONNREFUSED',
  code: 'NetworkingError',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 8000,
  region: 'us-west-2',
  hostname: 'localhost',
  retryable: true,
  time: 2020-08-24T23:03:41.246Z
}

localhostではDockerホストにアクセスできずエラーとなります(そりゃそうだ)

解決法

解決方法としてはコンテナ間の通信を可能とする、もしくはDockerホスト(Mac)を経由する2通りが考えられますが、今回は後者で解決できました。

Docker for Mac上のコンテナから、Mac上のアプリケーションに簡単に接続する方法

修正ポイント : endpointの値を http://docker.for.mac.localhost:8000 に変更

import * as AWS from 'aws-sdk';
import { ServiceConfigurationOptions } from 'aws-sdk/lib/service';

const serviceConfigOptions: ServiceConfigurationOptions = {
  region: 'us-west-2',
  endpoint: 'http://docker.for.mac.localhost:8000',
  accessKeyId: 'fakeAccessKeyId',
  secretAccessKey: 'fakeSecretAccessKey'
};
AWS.config.update(serviceConfigOptions);

const dynamodb = new AWS.DynamoDB.DocumentClient();

その4 : ResourceNotFoundException: Cannot do operations on a non-existent table

DynamoDB Localへの接続で上手くいったので、テストデータを追加してプログラムを作成していきます。

追加は下記にアクセスして操作します。

操作方法:DynamoDBローカルをDockerコンテナとして動かす

ブラウザ上で追加したデータをNode.js + TypeScript(Dockerコンテナ)から取得してみましたが掲題のエラーに・・・。

テーブル一覧を取得しても、ブラウザ上で追加したテーブルがない状態・・・。

🤔 🤔 🤔 🤔 🤔

解決法

DynamoDB Localのデータファイルが設定されているアクセスキー(Access Key)リージョン(Region)によって決まってくるようです。

[Access Key]_[Region].db

DynamoDB LocalでgetItemしたら、ResourceNotFoundException: Cannot do operations on a non-existent table が返ってきた

要するにアクセスキーリージョンの値をDynamoDB Localの設定値とプログラムで指定する値を揃える必要があります。

DynamoDB Localのアクセスキーはブラウザ上のオプションから変更が可能です。(おそらくDynamoDB Localを立ち上げる際に環境変数で指定することも可能?)

dynamodb-local.jpg

ちゃんと書いてありますね。 the DB file is based upon the Access Key ID

プログラム上でアクセスキーをfakeAccessKeyIdと指定しているので、同じ値をブラウザ上で設定すると今度は上手くいきました。

ただ、今のままではDynamoDB Localを立ち上げるたびにAccess Keyを指定する必要があるので、データの永続化も含めdocker-composeで対応します。

ルートディレクトリにdynamodblocalというDynamoDB Local用のディレクトリを新規に作成し、その中にdocker-compose.ymlとdataディレクトリを追加します。

$ tree
.
├── Dockerfile
├── dynamodblocal
│   ├── data/
│   └── docker-compose.yml
├── package.json
├── src
│   └── index.ts
└── tsconfig.json

./dynamodblocal/data/ディレクトリはDynamoDB Localを起動する前に作成しておく

dynamodblocal/docker-compose.yml
version: "3"

services:
  dynamodb:
    image: amazon/dynamodb-local
    volumes:
      - ./data:/home/dynamodblocal/data
    ports:
      - "8000:8000"
    command: -jar DynamoDBLocal.jar -dbPath ./data -sharedDb

DynamoDB Localを起動時にsharedDBオプションを指定することで、アクセスキーやリージョンに関係なくアクセスすることが可能になります。

まとめ

$ tree
.
├── Dockerfile
├── dynamodblocal
│   ├── data
│   │   └── shared-local-instance.db
│   └── docker-compose.yml
├── package.json
├── src
│   └── index.ts
└── tsconfig.json

./data/shared-local-instance.dbはコンテナ起動後、自動的に生成される(dataディレクトリだけ用意しておけばおk)

dynamodblocal/docker-compose.yml
version: "3"

services:
  dynamodb:
    image: amazon/dynamodb-local
    volumes:
      - ./data:/home/dynamodblocal/data
    ports:
      - "8000:8000"
    command: -jar DynamoDBLocal.jar -dbPath ./data -sharedDb
src/index.ts
import * as AWS from 'aws-sdk';
import { ServiceConfigurationOptions } from 'aws-sdk/lib/service';

let serviceConfigOptions: ServiceConfigurationOptions = {
  region: 'us-west-2',
  endpoint: 'http://docker.for.mac.localhost:8000',
  accessKeyId: 'fakeMyKeyId',
  secretAccessKey: 'fakeSecretAccessKey'
};
AWS.config.update(serviceConfigOptions);

const dynamodb = new AWS.DynamoDB.DocumentClient();

起動

$ cd dynamodblocal/
$ docker-compose up -d

停止

$ docker-compose down
8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2