Node.js + TypeScript(Dockerコンテナ)
からDynamoDB Local
へ接続、操作をする際に発生したトラブルの備忘録です。
DynamoDB Localとは?
AWS上のDynamoDBにアクセスすることなく、DynamoDBを利用するアプリケーションの開発・テストをすることが可能になります。
背景
前提として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 Key、Secret Access Keyがないとエラーになるようです。
修正ポイント : キーにaccessKeyId
、secretAccessKey
を追加、値は任意の値で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の設定値とプログラムで指定する値を揃える必要があります。
DynamoDB Localのアクセスキーはブラウザ上のオプションから変更が可能です。(おそらくDynamoDB Localを立ち上げる際に環境変数で指定することも可能?)
ちゃんと書いてありますね。 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を起動する前に作成しておく
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)
version: "3"
services:
dynamodb:
image: amazon/dynamodb-local
volumes:
- ./data:/home/dynamodblocal/data
ports:
- "8000:8000"
command: -jar DynamoDBLocal.jar -dbPath ./data -sharedDb
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