カテゴリー
[DynamoDB Local] DynamoDB LocalをDockerコンテナサービスとして起動し、別のDockerコンテナから呼び出す方法
※ 当ページには【広告/PR】を含む場合があります。
2020/03/29
Dynamodb Local
docker-compose
serverless-offline
Dynamodb Localを使う環境の準備
DynamoDBについて
docker-compose.ymlの実装例
docker
docker-compose
dynamodb_local_docker
docker-compose.yml
$ mkdir dynamodb_local_docker && cd dynamodb_local_docker
$ touch docker-compose.yml
docker-compose.yml
version: "3"
services:
dynamodb:
image: amazon/dynamodb-local
container_name: dynamodb-local-docker-storage
volumes:
- "./data:/home/dynamodblocal/data"
ports:
- "5984:5984"
command: "-jar DynamoDBLocal.jar -port 5984 -dbPath ./data -sharedDb"
data
-sharedDb
shared-local-instance.db
$ tree
.
├── data
│ └── shared-local-instance.db #👈この時点ではまだ生成していない
└── docker-compose.yml
8000
5984
-sharedDb
[access_key_id]_[region_name].db
-sharedDb
.aws/credentials
Dynamodb Localコンテナを起動してみる
$ docker-compose up
Starting dynamodb-local-docker-storage ... done
Attaching to dynamodb-local-docker-storage
dynamodb-local-docker-storage | Initializing DynamoDB Local with the following configuration:
dynamodb-local-docker-storage | Port: 5984
dynamodb-local-docker-storage | InMemory: false
dynamodb-local-docker-storage | DbPath: ./data
dynamodb-local-docker-storage | SharedDb: true
dynamodb-local-docker-storage | shouldDelayTransientStatuses: false
dynamodb-local-docker-storage | CorsParams: *
dynamodb-local-docker-storage |
docker-compose
http://localhost:5984/shell/
参考書籍 〜 Nosql・Dynamodbをもっと学びたい方向け
DynamoDB Localの基本的な操作方法
DocumentClientクラス
DocumentClient
N
B
DynamoDB
javascript 型 | DynamoDB アトリビュート型 |
---|---|
string | S |
number | N |
boolean | BOOL |
null | NULL |
Array | L |
Object | M |
Buffer, File, Blob, ArrayBuffer, DataView, プリミティブ型以外の要素配列 | B |
createTable (テーブルの新規作成)
createTable
AWS.DynamoDB
DocumentClient
string
challenger
number
timestamp
// 👇のようにわざわざ新しくAWS.DynamoDBインスタンスをnewしなくても
// DynamoDB JavaScript Shell 起動時に、'dynamodb'という名の
// インスタンスが存在している. (今回は練習も兼ね, 新しいインスタンス'myDynamo'を生成)
const myDynamo = new AWS.DynamoDB({
endpoint: "http://localhost:5984"
});
const params = {
TableName: 'hand-game',
AttributeDefinitions: [
// 少なくともHASH属性に指定する代表変数が一つ必要.
// RANGEも追加するなら1項目のみ記述できる.
{
AttributeName: 'challenger', // HASH用のフィールド
AttributeType: 'S' // String型
},
{
AttributeName: 'timestamp', // RANGE用のフィールド
AttributeType: 'N' // Number型
}
],
KeySchema: [ // 先程のフィールドにスキーマを指定する.
{
AttributeName: 'challenger',
KeyType: 'HASH'
},
{
AttributeName: 'timestamp',
KeyType: 'RANGE'
}
],
ProvisionedThroughput: {
ReadCapacityUnits: 1,
WriteCapacityUnits: 1
}
}
myDynamo.createTable(params, (err, data) => {
if (err) {
console.log(err, err.stack);
}
else {
console.log(data);
}
})
BillingMode: 'PAY_PER_REQUEST'
1
▶️ボタン
{"TableDescription":{"AttributeDefinitions":[{"AttributeName":"challenger","AttributeType":"S"},{"AttributeName":"timestamp","AttributeType":"N"}],"TableName":"hand-game","KeySchema":[{"AttributeName":"challenger","KeyType":"HASH"},{"AttributeName":"timestamp","KeyType":"RANGE"}],"TableStatus":"ACTIVE","CreationDateTime":"2019-10-02T14:50:32.561Z","ProvisionedThroughput":{"LastIncreaseDateTime":"1970-01-01T00:00:00.000Z","LastDecreaseDateTime":"1970-01-01T00:00:00.000Z","NumberOfDecreasesToday":0,"ReadCapacityUnits":1,"WriteCapacityUnits":1},"TableSizeBytes":0,"ItemCount":0,"TableArn":"arn:aws:dynamodb:ddblocal:000000000000:table/hand-game"}}
listTables (テーブル一覧)
DynamoDB
const myDynamo = new AWS.DynamoDB({
endpoint: "http://localhost:5984"
});
myDynamo.listTables({}, (err, data) => {
if (err) { console.log(err, err.stack); }
else { console.log(data); }
});
describeTable (テーブル定義の確認)
DynamoDB
describe***
const myDynamo = new AWS.DynamoDB({
endpoint: "http://localhost:5984"
});
myDynamo.describeTable({ TableName: 'hand-game' }, (err, data) => {
if (err) { console.log(err, err.stack); }
else { console.log(data); }
});
put (要素追加)
DocumentClient
const docClient = new AWS.DynamoDB.DocumentClient({
endpoint: "http://localhost:5984"
});
const date = new Date();
const epochTime = date.getTime();
const params = {
TableName: 'hand-game',
Item: {
challenger: 'taro', // Hash key, mandatory
timestamp: epochTime, // Range key, required
challengerSign: '3',
foeSign: '5',
issue: 'draw'
}
};
docClient.put(params, (err, data) => {
if (err) {
console.log(err);
}
else {
console.log(data);
}
});
{}
scan (リスト全検索)
const docClient = new AWS.DynamoDB.DocumentClient({
endpoint: "http://localhost:5984"
});
docClient.scan({ TableName: 'hand-game' },
(err, data) => {
if (err) {
console.log(err, err.stack);
}
else {
console.log(data);
}
}
);
{"Items":[{"challenger":"taro","challengerSign":"3","issue":"draw","foeSign":"5","timestamp":1570029168460}],"Count":2,"ScannedCount":2}
get (テーブルから先程追加した要素を取得)
DocumentClient
const docClient = new AWS.DynamoDB.DocumentClient({
endpoint: "http://localhost:5984"
});
docClient.get({
TableName: 'hand-game',
Key: { // 取得したいHASH値とRANGE値をもつ要素を指定
challenger: 'taro',
timestamp: 1570029168460
}
}, (err, data) => {
if (err) { console.log(err); }
else { console.log(data); }
});
その他の操作
実践編: DynamoDB Localをローカルで利用するためのSAMモデル
本番
Api Gateway + Lambda
Api Gateway + Lambda
Serverless Offline
ローカルSAMプロジェクトの構造
tree
$ tree -I "node_modules"
.
├── Dockerfile
├── docker-compose.yml
└── package.json
node_modules
tree
-I "node_modules"
Serverless Offlineコンテナ作成
Serverless Offline
Dockerfile
docker-compose.yml
Dockerfile
FROM node:10-alpine
#Install dependent packages
RUN apk update && apk upgrade && apk add --no-cache \
bash git openssh \
python \
curl \
groff \
jq
#awscli on dokcer alpine
ARG pip_installer="https://bootstrap.pypa.io/get-pip.py"
ARG awscli_version="1.16.248"
#Install awscli
RUN curl ${pip_installer} | python && \
pip install awscli==${awscli_version}
#Completing input
RUN bash -c 'echo complete -C '/usr/bin/aws_completer' aws >> $HOME/.bashrc'
#Setting envirnment for development
WORKDIR /usr/src/app
ENV PS1="[\u@\h:\w]$"
#Installing npm package manager
RUN npm i -g yarn
node:10-alpine
nodejs
node:10-alpine
aws cli
docker-compose.yml
docker-compose.yml
docker-compose
version: '3'
services:
app: # appというサービス名で構築
image: my/sls-offline-test # コンテナー名
build: .
environment:
NODE_ENV: development
AWS_ACCESS_KEY_ID: "ABCDEFGHIJKLMNOPQRST"
AWS_SECRET_ACCESS_KEY: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AWS_DEFAULT_REGION: "us-east-1"
LOCAL_SLS_SERVICE_NAME: "sls-offline-test" # Serverless Offlineのアプリ名
ports:
- "5985:5985" # Serverless Offlineのサービス用に5985番を指定
# - "3000:3000" # デフォルトのServerless Offileのサービスポート
volumes:
- ./:/usr/src/app
tty: true
http://localhost:5985
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION
aws-cli
コンテナのビルドとインタラクティブモードへの移行
$ docker-compose build
bash
$ docker-compose up -d
Creating your_awesome_project_app_1 ... done
$ docker-compose exec app bash
[root@xxxxxxxxxx:/usr/src/app]$
Serverlessアプリケーションの作成 (前準備)
package.json
{
"name": "sls-offline-test",
"version": "0.0.1",
"scripts": {}
}
serverless
devDependancies
$ yarn add serverless -D
package.json
app
serverless
$ sls create --template aws-nodejs-ecma-script \
--name $LOCAL_SLS_SERVICE_NAME \
--path app
aws-nodejs-ecma-script
aws-nodejs
package.json
webpack.config.js
sls create
serverless
app
$ tree -I 'node_modules'
.
├── Dockerfile
├── docker-compose.yml
├── package.json
├── app
│ ├── first.js
│ ├── package.json
│ ├── second.js
│ ├── serverless.yml
│ └── webpack.config.js
└── yarn.lock
serverless
cd ./app && yarn install
app
aws-sdk
serverless-offline
#'app'フォルダー内で実行
$ yarn add aws-sdk serverless-offline -S
serverless
app
first.js
second.js
handler.js
$ tree -I 'node_modules'
.
├── Dockerfile
├── docker-compose.yml
├── package.json
├── app
│ ├── handler.js
│ ├── package.json
│ ├── serverless.yml
│ └── webpack.config.js
└── yarn.lock
実践編: Serverless-OfflineコンテナからDynamodb Localサービスを利用する方法
serverless.yml
serverless.ymlを実装
service:
name: sls-offline-test
plugins:
- serverless-webpack
- serverless-offline # ここにServerless Offlineプラグインを追加
custom:
serverless-offline:
host: 0.0.0.0 # 重要: 既定値のlocalhostではdocker内部から呼び出せません
port: 5985 # サービスポートを5985に変更
provider:
name: aws
runtime: nodejs8.10
functions:
play:
handler: handler.playGame # handler.js内の playGame 関数を呼び出す
events:
- http:
path: handgame
method: post
list:
handler: handler.showHistory # handler.js内の showHistory 関数を呼び出す
events:
- http:
path: handgame
method: get
sls-offline-test
play
POST
list
GET
play
handler.js
playGame
list
showHistory
#👇POSTメソッドのイメージ
curl 'http://localhost:5985/handgame?{何かのクエリ文字列}' -X POST
#👇GETメソッドのイメージ
curl 'http://localhost:5985/handgame'
ハンドラ関数を定義
handler.js
POST
"use strict";
// オフライン(isOffline)を判定し、ローカル利用なら DynamoDB Localに、
// そうでないなら通常のAWSへサービスのターゲットを割り振ります。
const aws = require("aws-sdk");
const getDynamoClient = (event) => {
let dynamodb = null;
if ("isOffline" in event && event.isOffline) {
dynamodb = new aws.DynamoDB.DocumentClient({
region: "localhost",
// Dockerコンテナの環境変数 OUTER_DYNAMODB_IP には別のyamlファイルを用意する (後述)
endpoint: `http://${process.env.OUTER_DYNAMODB_IP}:5984`
});
} else {
dynamodb = new aws.DynamoDB.DocumentClient();
}
return dynamodb;
}
// とあるローカルな遊びのルール...
const judgeGame = (challenger, foe) => {
const c = Math.abs(challenger - foe);
const c_sgn = Math.sign(challenger - foe);
if (c === 1 && c_sgn > 0) {
return "won";
} else if (c === 1 && c_sgn < 0) {
return "lose";
} else {
return "draw";
}
}
// コンピュータと対戦させて、試合結果のインスタンスを吐き出す。
const createGameInstance = (challenger, challengerHand) => {
const handRule = ["zero", "one", "two", "three", "four", "five"];
const date = new Date();
const foeHandIndex = Math.floor( Math.random() * 6);
return {
timestamp: date.getTime(),
challenger: challenger,
challengerSign: challengerHand,
foeSign: handRule[foeHandIndex],
issue: judgeGame(handRule.indexOf(challengerHand), foeHandIndex)
};
}
// POSTメソッドの実体関数
module.exports.playGame = (event, context, callback) => {
const gameResult = createGameInstance(
event.queryStringParameters.name,
event.queryStringParameters.hand
);
const params = {
TableName: "hand-game", // 今回は'hand-game'というテーブル名を使います。
Item: gameResult
};
const docClient = getDynamoClient(event);
docClient.put(params, (error) => {
const response = {statusCode: null, body: null};
if (error) {
console.log(error);
response.statusCode = 500;
response.body = {
code: 500,
message: "Doesn't show the list due to internal error!"
};
} else {
response.statusCode = 200;
response.body = JSON.stringify(gameResult);
}
callback(null, response);
});
};
// GETメソッドの実体関数
module.exports.showHistory = (event, context, callback) => {
const docClient = getDynamoClient(event);
docClient.scan({
TableName: "hand-game" // 今回は'hand-game'というテーブル名を使います。
}, (error, data) => {
const response = {
statusCode: null,
body: null
};
if (error) {
console.log(error);
response.statusCode = 500;
response.body = {
code: 500,
message: "Doesn't show the list due to internal error!"
};
} else if ("Items" in data) {
response.statusCode = 200;
response.body = JSON.stringify({handGame: data["Items"]});
}
callback(null, response);
});
};
kubernetes
docker
package.jsonへスクリプトの追加
serverless offline
scripts
{
"name": "sls-offline-test",
"version": "0.0.1",
"scripts": {
"start": "cd ./app && sls offline" // 👈 追加
},
"devDependencies": {
"serverless": "^1.53.0"
}
}
複数のdocker-compose.ymlの合わせ技
exit
docker-compose down
env-local.yml
$ tree -I 'node_modules'
.
├── Dockerfile
├── app
│ ├── handler.js
│ ├── package.json
│ ├── serverless.yml
│ ├── webpack.config.js
│ └── yarn.lock
├── docker-compose.yml
├── env-local.yml
├── package.json
└── yarn.lock
docker-compose
docker-compose config
$ docker-compose config
services:
app:
build:
context: /{現在のプロジェクトのフォルダまでのパス}
environment:
AWS_ACCESS_KEY_ID: ABCDEFGHIJKLMNOPQRST
AWS_DEFAULT_REGION: us-east-1
AWS_SECRET_ACCESS_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
LOCAL_SLS_SERVICE_NAME: sls-offline-test
NODE_ENV: development
image: my/sls-offline-test
ports:
- 5985:5985/tcp
tty: true
volumes:
- /{現在のプロジェクトのフォルダまでのパス}:/usr/src/app:rw
version: '3.0'
docker-compose.yml
env-local.yml
version: '3'
services:
app:
entrypoint: yarn start
environment:
OUTER_DYNAMODB_IP: "172.0.0.1" # 適当なIP値を与えておきます
docker-compose
$ docker-compose -f docker-compose.yml -f env-local.yml config
services:
app:
build:
context: /{現在のプロジェクトのフォルダまでのパス}
entrypoint: yarn start # 👈この項目が追加
environment:
AWS_ACCESS_KEY_ID: ABCDEFGHIJKLMNOPQRST
AWS_DEFAULT_REGION: us-east-1
AWS_SECRET_ACCESS_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
LOCAL_SLS_SERVICE_NAME: sls-offline-test
NODE_ENV: development
OUTER_DYNAMODB_IP: 172.0.0.1 # 👈この項目が追加
image: my/sls-offline-test
ports:
- 5985:5985/tcp
tty: true
volumes:
- /{現在のプロジェクトのフォルダまでのパス}:/usr/src/app:rw
version: '3.0'
DynamoDB LocalのDockerコンテナのIPの取得
DynamoDB Local
#👇DynamoDB Local のコンテナIDをチェック
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b7209695b7e1 amazon/dynamodb-local "java -jar DynamoDBL…" 6 hours ago Up 6 hours 0.0.0.0:5984->5984/tcp, 8000/tcp dynamodb-local-docker-storage
#👇コンテナIDからIPを取得
$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.Gateway}}{{end}}' b72
172.19.0.1
172.19.0.1
env-local.yml
version: '3'
services:
app:
entrypoint: yarn start
environment:
OUTER_DYNAMODB_IP: "172.19.0.1" # DynamoDB Localコンテナの(デフォルトゲートウェイ)IP
Serverless Offlineコンテナの起動
$ docker-compose -f docker-compose.yml -f env-local.yml up
$ cd ./app && sls offline
app_1 | Serverless: Bundling with Webpack...
app_1 | Time: 6313ms
app_1 | Built at: 10/06/2019 9:47:16 AM
app_1 | Asset Size Chunks Chunk Names
app_1 | handler.js 6.4 MiB handler [emitted] handler
app_1 | Entrypoint handler = handler.js
#...中略
app_1 | Serverless: Starting Offline: dev/us-east-1.
app_1 |
app_1 | Serverless: Routes for play:
app_1 | Serverless: POST /handgame
app_1 | Serverless: POST /{apiVersion}/functions/sls-offline-test-dev-play/invocations
app_1 |
app_1 | Serverless: Routes for list:
app_1 | Serverless: GET /handgame
app_1 | Serverless: POST /{apiVersion}/functions/sls-offline-test-dev-list/invocations
app_1 |
app_1 | Serverless: Offline [HTTP] listening on http://0.0.0.0:5985
app_1 | Serverless: Enter "rp" to replay the last request
動作確認〜じゃんけんゲームをさせてみる
#👇チャレンジャー名=tarao が 零の手=zero を出して勝負
$ curl 'http://localhost:5985/handgame?hand=zero&name=tarao' -X POST | jq '.'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 104 100 104 0 0 298 0 --:--:-- --:--:-- --:--:-- 298
{
"timestamp": 1570355702256,
"challenger": "tarao",
"challengerSign": "zero",
"foeSign": "four",
"issue": "draw"
}
#👇チャレンジャー名=ikura が 四の手=four を出して勝負
$ curl 'http://localhost:5985/handgame?hand=four&name=ikura' -X POST | jq '.'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 104 100 104 0 0 515 0 --:--:-- --:--:-- --:--:-- 517
{
"timestamp": 1570355971259,
"challenger": "ikura",
"challengerSign": "four",
"foeSign": "five",
"issue": "lose"
}
$ curl 'http://localhost:5985/handgame' | jq '.'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1360 100 1360 0 0 8545 0 --:--:-- --:--:-- --:--:-- 8553
{
"handGame": [
{
"challenger": "ikura",
"challengerSign": "four",
"issue": "lose",
"foeSign": "five",
"timestamp": 1570355971259
},
{
"challenger": "tarao",
"challengerSign": "zero",
"issue": "draw",
"foeSign": "four",
"timestamp": 1570355702256
}
]
}
まとめ
DynamoDB Local
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー