Quantcast
Viewing all articles
Browse latest Browse all 8697

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

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*asAWSfrom'aws-sdk';AWS.config.update({region:'us-west-2',endpoint:'http://localhost:8000'});constdynamodb=newAWS.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*asAWSfrom'aws-sdk';import{ServiceConfigurationOptions}from'aws-sdk/lib/service';constserviceConfigOptions:ServiceConfigurationOptions={region:'us-west-2',endpoint:'http://localhost:8000'};AWS.config.update(serviceConfigOptions);constdynamodb=newAWS.DynamoDB.DocumentClient();

その2 : Missing credentials in config

import*asAWSfrom'aws-sdk';import{ServiceConfigurationOptions}from'aws-sdk/lib/service';constserviceConfigOptions:ServiceConfigurationOptions={region:'us-west-2',endpoint:'http://localhost:8000'};AWS.config.update(serviceConfigOptions);constdynamodb=newAWS.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*asAWSfrom'aws-sdk';import{ServiceConfigurationOptions}from'aws-sdk/lib/service';constserviceConfigOptions:ServiceConfigurationOptions={region:'us-west-2',endpoint:'http://localhost:8000',accessKeyId:'fakeAccessKeyId',secretAccessKey:'fakeSecretAccessKey'};AWS.config.update(serviceConfigOptions);constdynamodb=newAWS.DynamoDB.DocumentClient();

その3 : connect ECONNREFUSED 127.0.0.1:8000

import*asAWSfrom'aws-sdk';import{ServiceConfigurationOptions}from'aws-sdk/lib/service';constserviceConfigOptions:ServiceConfigurationOptions={region:'us-west-2',endpoint:'http://localhost:8000',accessKeyId:'fakeAccessKeyId',secretAccessKey:'fakeSecretAccessKey'};AWS.config.update(serviceConfigOptions);constdynamodb=newAWS.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*asAWSfrom'aws-sdk';import{ServiceConfigurationOptions}from'aws-sdk/lib/service';constserviceConfigOptions:ServiceConfigurationOptions={region:'us-west-2',endpoint:'http://docker.for.mac.localhost:8000',accessKeyId:'fakeAccessKeyId',secretAccessKey:'fakeSecretAccessKey'};AWS.config.update(serviceConfigOptions);constdynamodb=newAWS.DynamoDB.DocumentClient();

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

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

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

http://localhost:8000/shell/

操作方法: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を立ち上げる際に環境変数で指定することも可能?)

Image may be NSFW.
Clik here to view.
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*asAWSfrom'aws-sdk';import{ServiceConfigurationOptions}from'aws-sdk/lib/service';letserviceConfigOptions:ServiceConfigurationOptions={region:'us-west-2',endpoint:'http://docker.for.mac.localhost:8000',accessKeyId:'fakeMyKeyId',secretAccessKey:'fakeSecretAccessKey'};AWS.config.update(serviceConfigOptions);constdynamodb=newAWS.DynamoDB.DocumentClient();

起動

$ cd dynamodblocal/
$ docker-compose up -d

停止

$ docker-compose down

Viewing all articles
Browse latest Browse all 8697

Trending Articles