なになれ

IT系のことを記録していきます。

DynamoDBにおけるCRUD操作の色々なやり方をまとめた

DynamoDBをよく触るようになったもののDynamoDBヘのCRUD操作は色々なやり方があって、
毎度ググっては実行してというのを繰り返してました。
そのやり方をまとめていちいちググらないようにしたいと思います。

この記事について

色々なやり方があるDynamoDBへのCRUD操作を紹介します。
また、それぞれのやり方のメリット・デメリットを自分なりに説明します。

目次

AWS CLI

AWS CLIを使うやり方です。

CRUD

Create
put-itemでデータを1つ作成できます。

$ aws dynamodb put-item --table-name Sample --item '{"partitionKey": {"S": "posts"}, "sortKey": {"S": "post-1"}}'

Read(query)
queryでkeyを条件にしてデータを取得できます。

$ aws dynamodb query --table-name Sample --key-condition-expression "partitionKey = :value" --expression-attribute-values  '{":value":{"S":"posts"}}'

Update
update-itemで既存のデータを更新できます。

$ aws dynamodb update-item --table-name Sample --key '{ "partitionKey": {"S": "posts"}, "sortKey": {"S": "post-1"}}' --update-expression "SET title = :newval" --expression-attribute-values '{":newval":{"S":"title-1"}}'

データに含まれる値を削除する場合など更新式の書き方のパターンがいくつかあります。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html

Delete
delete-itemで既存のデータを削除できます。

$ aws dynamodb delete-item --table-name Sample --key '{ "partitionKey": {"S": "posts"}, "sortKey": {"S": "post-1"}}' 

メリット&デメリット

AWS CLIがインストールされていないのはあまりないかと思うので、すぐに利用できるのがメリットだと思います。
keyの指定にデータ型が必要だったり、expressionとvalueを分けて書かないといけなかったりと書き方が複雑です。

DQL

非公式ですが、DQLというSQLライクな構文でDynamoDBにアクセスできるツールがあります。

github.com

CRUD

事前に以下のコマンドでREPLを起動します。

$ dql

Create

> INSERT INTO Sample (partitionKey, sortKey) VALUES ('posts', 'post-1');

Read

> SELECT * FROM Sample WHERE partitionKey = 'posts';

Update

> UPDATE Sample SET title = 'title-1' WHERE partitionKey = 'posts' AND sortKey = 'post-1';

Delete

> DELETE FROM Sample WHERE partitionKey = 'posts' AND sortKey = 'post-1';

メリット&デメリット

SQLに慣れていると直感的にDynamoDBの操作ができます。
SELECTの出力形式が扱いづらいのでとりあえずデータの内容を確認したい用途向けだと思います。

AWS SDK

プログラミング言語から操作する方法です。ここではNode.jsを使う方法になります。

CRUD

Create

const AWS = require("aws-sdk");

AWS.config.update({
  region: "ap-northeast-1",
  endpoint: "http://localhost:8000"
});

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

const params = {
  TableName: "Sample",
  Item: {
    partitionKey: "posts",
    sortKey: "post-1"
  }
};

docClient.put(params).promise();

Read

const AWS = require("aws-sdk");

AWS.config.update({
  region: "ap-northeast-1",
  endpoint: "http://localhost:8000"
});

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

const params = {
  TableName: "Sample",
  KeyConditionExpression: "partitionKey = :value",
  ExpressionAttributeValues: {
    ":value": "posts"
  }
};

docClient
  .query(params)
  .promise()
  .then(d => {
    console.log(d);
  });

Update

const AWS = require("aws-sdk");

AWS.config.update({
  region: "ap-northeast-1",
  endpoint: "http://localhost:8000"
});

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

const params = {
  TableName: "Sample",
  Key: {
    partitionKey: "posts",
    sortKey: "post-1"
  },
  UpdateExpression: "set title = :newval",
  ExpressionAttributeValues: {
    ":newval": "title-1"
  }
};

docClient.update(params).promise();

Delete

const AWS = require("aws-sdk");

AWS.config.update({
  region: "ap-northeast-1",
  endpoint: "http://localhost:8000"
});

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

const params = {
  TableName: "Sample",
  Key: {
    partitionKey: "posts",
    sortKey: "post-1"
  }
};

docClient.delete(params).promise();

メリット&デメリット

DynamoDBは1度に取得できるデータ量に制限があるために大量データを扱う場合には繰り返し実行する必要があります。
SDKを使うとこのような大量データを扱うのがやりやすいです。
ちょっとデータを確認したいというような用途には向かないと思います。

NoSQL Workbench

GUIでDynamoDBを操作するツールです。

インストールは以下からできます。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/workbench.settingup.html

CRUD

NoSQL Workbenchを起動します。
f:id:hi1280:20200327225102p:plain:w500

Create
[Operation builder] -> [Build operations] -> [Put Item] を選択します。

f:id:hi1280:20200327230707p:plain:w500

keyを指定して、Executeで実行します。

Read
[Operation builder] -> [Build operations] -> [Query] を選択します。

f:id:hi1280:20200327230728p:plain:w500

keyを指定して、Executeで実行します。

Update
[Operation builder] -> [Build operations] -> [Update Item] を選択します。

f:id:hi1280:20200327230744p:plain:w500

keyとUpdate expressionを指定して、Executeで実行します。

Delete
[Operation builder] -> [Build operations] -> [Delete Item] を選択します。

f:id:hi1280:20200327230759p:plain:w500

keyを指定して、Executeで実行します。

チェックボックスで選択して消す方法もあります。
f:id:hi1280:20200328003731p:plain:w500

メリット&デメリット

CRUD毎に何のパラメータを指定すればいいか指示されているので分かりやすいです。
複数のテーブルを包括したデータモデリングの機能もあります。
GUIに抵抗がなければこれを利用するのが良さそうです。

AWSマネジメントコンソール

ブラウザでAWSマネジメントコンソールから操作する方法です。

CRUD

[DynamoDB] -> [Tables] で特定のテーブルを選択し、[Items]タブを選択します。

f:id:hi1280:20200328111423p:plain:w500

Create
[Create Item] を選択します。

f:id:hi1280:20200328002103p:plain:w500

keyを指定して、Saveで実行します。

Read
[Query] を選択します。
keyを指定して、[Start search] を選択します。

f:id:hi1280:20200328002342p:plain:w500

Update
Itemを選択し、[Actions] -> [Edit] を選択します。

f:id:hi1280:20200328002456p:plain:w500

attributeを指定して、Saveで実行します。

Delete
Itemを選択し、[Actions] -> [Delete] を選択します。

f:id:hi1280:20200328010607p:plain:w500

メリット&デメリット

インストールが不要なのでとにかく簡単に使えます。
ちょっとデータの内容を確認したい場合に良く使います。
マネジメントコンソールの事情ですが、セッションタイムアウトが発生してその度に画面の内容がクリアされるのが欠点かなと思います。

まとめ

DynamoDBへCRUD操作をする場合の色々なやり方を紹介しました。
自分の場合、なんだかんだでAWSマネジメントコンソールを使ってしまいます。

KubernetesでLoad BalancerとしてEnvoyを試した

こちらでEnvoyを一通り学習しました。
www.katacoda.com

理解できているかの確認のために、KubernetesでEnvoyを使ってみます。
KubernetesでEnvoyを使う場合、EnvoyをSidecarプロキシにすることで各Podの通信をコントロールする構成が知られています。
サービスメッシュを実現するIstioはそのような構成になっています。
このような最新のシステム構成を理解するためにもEnvoyの理解を深めることが求められていると感じています。
今回はPodへの通信の前段にEnvoyを配置する単純な構成を試しました。

前提

Docker for MacKubernetes環境で動作確認しています。

構成について

バージョン2とバージョン3のアプリケーションが存在します。
2つのアプリケーションのロードバランサーとしてEnvoyが前段に存在します。
一部の通信をバージョン3に向ける処理をEnvoyが行います。

f:id:hi1280:20200304230435p:plain

デプロイするアプリケーションについて

HTTPの通信を受け付けるNode.jsのアプリケーションです。
KubernetesとEnvoyを組み合わせるために、KubernetesのHeadless Serviceを利用します。
Headless Serviceを使うことでEnvoyでService Discoveryが可能になります。

デプロイを行うためのKubernetesのManifestを示します。
下記ではバージョン2とバージョン3のアプリケーションをデプロイします。

nodejs-http-server.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodejs-http-server-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nodejs-http-server-v2
  template:
    metadata:
      labels:
        app: nodejs-http-server-v2
    spec:
      containers:
        - name: nodejs-http-server-v2
          image: hi1280/nodejs-http-server:0.0.2
          ports:
            - containerPort: 3000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodejs-http-server-v3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nodejs-http-server-v3
  template:
    metadata:
      labels:
        app: nodejs-http-server-v3
    spec:
      containers:
        - name: nodejs-http-server-v3
          image: hi1280/nodejs-http-server:0.0.3
          ports:
            - containerPort: 3000

アプリケーションに繋がるHeadless Serviceを作ります。
clusterIPにNoneを指定します。

nodejs-http-server.yaml

apiVersion: v1
kind: Service
metadata:
  name: nodejs-http-server-v2
spec:
  clusterIP: None
  ports:
    - protocol: TCP
      port: 3000
      targetPort: 3000
  selector:
    app: nodejs-http-server-v2
---
  apiVersion: v1
  kind: Service
  metadata:
    name: nodejs-http-server-v3
  spec:
    clusterIP: None
    ports:
      - protocol: TCP
        port: 3000
        targetPort: 3000
    selector:
      app: nodejs-http-server-v3

Envoyをデプロイする

Envoyをデプロイします。
envoyの公式のDockerイメージを使ってenvoyを起動します。

my-envoy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-envoy
  labels:
    app: my-envoy
spec:
  selector:
    matchLabels:
      app: my-envoy
  template:
    metadata:
      labels:
        app: my-envoy
    spec:
      containers:
      - name: my-envoy
        image: envoyproxy/envoy:latest
        volumeMounts:
          - name: config-volume
            mountPath: /etc/envoy
        ports:
        - name: http
          containerPort: 8080
        - name: envoy-admin
          containerPort: 9901
      volumes:
        - name: config-volume
          configMap:
            name: my-envoy

/etc/envoyenvoy.yamlを配置すると、そのファイルに定義されたenvoyの設定が読み込まれます。
envoy.yamlを作成するのにConfigMapを使用しています。

ConfigMapでenvoy.yamlを作成します。
my-envoy.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-envoy
data:
  envoy.yaml: |
    admin:
      access_log_path: /tmp/admin_access.log
      address:
        socket_address: { address: 0.0.0.0, port_value: 9901 }
    static_resources:
      listeners:
      - name: listener_0
        address:
          socket_address: { address: 0.0.0.0, port_value: 8080 }
        filter_chains:
          - filters:
            - name: envoy.http_connection_manager
              config:
                codec_type: auto
                stat_prefix: ingress_http
                route_config:
                  name: local_route
                  virtual_hosts:
                  - name: backend
                    domains: ["*"]
                    routes:
                    - match:
                        prefix: "/"
                      route:
                        weighted_clusters:
                          clusters:
                          - name: v2
                            weight: 80
                          - name: v3
                            weight: 20
                http_filters:
                - name: envoy.router
      clusters:
      - name: v2
        connect_timeout: 0.25s
        type: STRICT_DNS
        dns_lookup_family: V4_ONLY
        lb_policy: ROUND_ROBIN
        hosts: [{ socket_address: { address: nodejs-http-server-v2, port_value: 3000 }}]
      - name: v3
        connect_timeout: 0.25s
        type: STRICT_DNS
        dns_lookup_family: V4_ONLY
        lb_policy: ROUND_ROBIN
        hosts: [{ socket_address: { address: nodejs-http-server-v3, port_value: 3000 }}]

route_configの設定でHTTPのルーティングを決めています。
アプリケーションのバージョン2への通信を8割、バージョン3への通信を2割にするようにトラフィックの制御を行なっています。
clustersの設定でアプリケーションの宛先を決めています。
nodejs-http-server-v2nodejs-http-server-v3のHost名になっています。
これはHeadless Serviceによって解決されます。

最後にNodePortでenvoyを外部に公開します。

my-envoy.yaml

apiVersion: v1
kind: Service
metadata:
  name: my-envoy
  labels:
    app: my-envoy
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: 8080
    protocol: TCP
  selector:
    app: my-envoy

動作確認する

curlで動作確認します。
公開されるポート番号はランダムに決められますので、kubectl get svcで確認します。

$ for i in {1..10}; do curl -s http://localhost:30392; done
Now v2. This request was processed by host: nodejs-http-server-v2-855b645dcd-x68x6
Now v2. This request was processed by host: nodejs-http-server-v2-855b645dcd-x68x6
Now v2. This request was processed by host: nodejs-http-server-v2-855b645dcd-x68x6
Now v2. This request was processed by host: nodejs-http-server-v2-855b645dcd-x68x6
Now v2. This request was processed by host: nodejs-http-server-v2-855b645dcd-x68x6
Now v2. This request was processed by host: nodejs-http-server-v2-855b645dcd-x68x6
Now v2. This request was processed by host: nodejs-http-server-v2-855b645dcd-x68x6
Now v3. This request was processed by host: nodejs-http-server-v3-7d66cbd7c7-fcq98
Now v2. This request was processed by host: nodejs-http-server-v2-855b645dcd-x68x6
Now v2. This request was processed by host: nodejs-http-server-v2-855b645dcd-x68x6

おおよそ決めた割合で通信が分けられていることが確認できます。

まとめ

今回はEnvoyをLoad Balancer的に使うやり方を理解できました。
これを踏まえるとEnvoyをSidecarプロキシとして利用する構成が理解しやすいのではないでしょうか。

Kubernetes環境での開発のTips

Kubernetes環境で開発する場合のTipsを紹介します。

Kubernetes環境での開発について

Tipsを紹介する前にKubernetes環境での開発について説明します。
Kubernetes環境で開発したサービスを動かすには、実際に動かすプログラムの他にマニフェストファイルが必要になり、その成果物が正しいかを確認する必要があります。
正しいかを確認するためのポイントは以下になると思います。

  • サービスが動作するか(プログラムを確認する)
  • サービスがデプロイできるか(マニフェストファイルを確認する)

サービスが動作するかという観点について、Kubernetesの環境では複数のサービスが稼働していたり、ネットワーク環境が特別であったり、Kubernetes上で動作確認しないと気づけないことがあるので注意が必要です。
サービスがデプロイできるかという観点について、Kubernetesではデプロイするのにマニフェストファイルを記述することになります。
このマニフェストが正しくないとデプロイがうまく行われません。

Kubernetes上でプログラムを動かすにはコンテナイメージをビルドして、コンテナレジストリにPushして、Kubernetesマニフェストを更新してというように手順が多いです。
このような面倒な手順を行わずになるべく効率的に確認するためのTIpsです。

Tips

動作確認のTips

既に存在するKubernetes環境を使って、ローカルで開発したプログラムの動作確認を行います。
それを実現するのがTelepresenceというツールです。
www.telepresence.io

TelepresenceではKubernetes環境とローカルの環境をネットワーク的に接続して、一時的にローカルのプログラムをKubernetes上のリソースとして動作させることが可能になります。

以下のコマンドでローカルの環境とKubernetes環境とをつなぐDeploymentリソースが作られます。

$ telepresence --new-deployment helloworld --expose 3000

ローカルでプログラムを動かせば、Kubernetes環境上で動作していることと同等になります。
新しくリソースを作るだけでなく、既存のリソースをローカルのプログラムに置き換えることも可能です。
動作確認のためにデプロイする手間が省けます。

マニフェストを作るためのTips

kubectlを使う方法

なるべく間違いがないようにマニフェストをするために雛形を利用します。
kubectlを使うことでKubernetesのリソースの雛形を作成することができます。
-o yaml--dry-runを指定することがポイントです。

Deploymentリソースの雛形は以下のようにして確認できます。

$ kubectl run mydeploy --image nginx -o yaml --dry-run
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    run: mydeploy
  name: mydeploy
spec:
  replicas: 1
  selector:
    matchLabels:
      run: mydeploy
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: mydeploy
    spec:
      containers:
      - image: nginx
        name: mydeploy
        resources: {}
status: {}

Serviceリソースは以下のようにして確認できます。

$ kubectl create svc clusterip myapp --tcp 80 -o yaml --dry-run

また、kubectl explainで設定値を確認できます。

$ kubectl explain deployment.spec.template.spec

このようなコマンドを活用すれば、マニフェスト作成が楽になります。

komposeを使う方法

既にdocker-composeを使っているなら、komposeが使えます。

kompose.io

kompose convertコマンドを使用するとdocker-composeのyamlkubernetesyamlに変換できます。

docker-compose.yaml

version: '3.7'
services:
  webapp:
    image: hi1280/node-helloworld:0.0.1
    ports:
      - 3000:3000
    environment:
      - NODE_ENV=production

docker-compose.yamlが存在するディレクトリ上で以下のようにコマンドを実行します。

$ kompose convert
INFO Kubernetes file "webapp-service.yaml" created
INFO Kubernetes file "webapp-deployment.yaml" created

Kubernetes用のマニフェストファイルが作成されます。

webapp-deployment.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.20.0 ()
  creationTimestamp: null
  labels:
    io.kompose.service: webapp
  name: webapp
spec:
  replicas: 1
  strategy: {}
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.20.0 ()
      creationTimestamp: null
      labels:
        io.kompose.service: webapp
    spec:
      containers:
      - env:
        - name: NODE_ENV
          value: production
        image: hi1280/node-helloworld:0.0.1
        name: webapp
        ports:
        - containerPort: 3000
        resources: {}
      restartPolicy: Always
status: {}

webapp-service.yaml

apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.20.0 ()
  creationTimestamp: null
  labels:
    io.kompose.service: webapp
  name: webapp
spec:
  ports:
  - name: "3000"
    port: 3000
    targetPort: 3000
  selector:
    io.kompose.service: webapp
status:
  loadBalancer: {}

マニフェストを確認するためのTips

--dry-runオプションでマニフェストを適用することなく、コマンドを仮に実行することでマニフェストファイルに問題がないか確認できます。

$ kubectl apply -f k8s.yaml --dry-run

問題があればエラーで問題箇所を教えてくれます。

error: error parsing k8s.yaml: error converting YAML to JSON: yaml: line 17: mapping values are not allowed in this context

まとめ

Kubernetesでの開発は手間がかかるので、出来るだけ効率化する必要があります。
Telepresenceや今回紹介したマニフェストの作り方、確認方法が役立つと思います。