LoginSignup
6

More than 3 years have passed since last update.

Organization

docker-compose で Cloud Datastore Emulatorを立ち上げpythonアプリと連携する

背景

サービスでdatastoreを使う事になり、開発環境やCIでのテストをどうするか試行錯誤した。 :thinking:
AWSと比較して情報が少ないので、まとめた。 :grinning:

要点

  • flaskアプリとCloud Datastore Emulatorをコンテナを作る
  • docker-composeでサービス連携を行う
  • datastoreの初期データ投入
  • flaskアプリから、Cloud Datastore Emulatorを操作する

ディレクトリ構成

.
├── app
│   ├── Dockerfile
│   └── src
│       ├── main.py
│       └── requirements.txt
├── datastore
│   ├── Dockerfile
│   ├── entrypoint
│   └── import
│       ├── 2020-01-21.overall_export_metadata
│       ├── default_namespace
│       │   └── kind_test_data
│       │       ├── default_namespace_kind_test_data.export_metadata
│       │       └── output-0
│       └── run.sh
└──  docker-compose.yaml

Cloud Datastore Emulator コンテナ構築

datastore/Dockerfile

公式のSDKイメージから最小限で作成。
エミュレータ起動用shell、データ投入用shellに実行権限付与。

FROM google/cloud-sdk:alpine

RUN apk add --update --no-cache openjdk8-jre \
  && gcloud components install cloud-datastore-emulator beta --quiet

COPY . /datastore/

WORKDIR /datastore

RUN chmod +x ./entrypoint
RUN chmod +x ./import/run.sh

ENTRYPOINT ["./entrypoint"]

datastore/entrypoint

docker-compose downした際にも、データを維持するために、/datastore/.data/ディレクトリにデータをためる。

オプションなしで起動すると、コンテナ内でしかアクセスできないので、--host-port=0.0.0.0:8081 として起動する。
環境変数から、プロジェクト名と一緒に流し込む。

#!/usr/bin/env bash

gcloud config set project ${DATASTORE_PROJECT_ID}

gcloud beta emulators datastore start \
  --data-dir=/datastore/.data \
  --host-port=${DATASTORE_LISTEN_ADDRESS}

データ投入のdatastore/import/run.sh

サーバー起動後、下記のエンドポイントにdump済みのデータの保存パスを投げるとデータがインポートできる。

export DATASTORE_PROJECT_ID

curl -X POST localhost:8081/v1/projects/${DATASTORE_PROJECT_ID}:import \
    -H 'Content-Type: application/json' \
    -d '{"input_url":"/datastore/import/2020-01-21.overall_export_metadata"}'

datastore/importのメタデータ

今回は、gcp のコンソールから、gcsにダンプしたデータをディレクトリ丸ごとdatastore/import配下に持ってきた。
sdkデータを直接生成してもよい。

python アプリ コンテナ構築

サンプル用にペラペラのアプリ構築する。

app/Dockerfile

FROM python:3.7-slim-buster

ENV HOME /api/

ADD ./ ${HOME}

WORKDIR ${HOME}

RUN pip install --upgrade pip \
    && pip install --no-cache-dir -r ${HOME}src/requirements.txt

ENTRYPOINT ["python", "src/main.py"]

app/main.py

ちょっと適当すぎる気がするが、データを保存して取り出すだけのエンドポイントを作成。 :thinking:

認証情報は、ダミーの認証をかませる。

from flask import Flask, jsonify
from google.auth.credentials import AnonymousCredentials
from google.cloud import datastore
from os import getenv

client = datastore.Client(
    credentials=AnonymousCredentials(),
    project=getenv('PROJECT_ID')
)

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False


@app.route('/')
def index():
    key = client.key('EntityKind', 1234)
    entity = datastore.Entity(key=key)
    entity.update({
        'foo': u'bar'
    })
    client.put(entity)
    result = client.get(key)
    return jsonify(result)


if __name__ == '__main__':
    app.run(host='0.0.0.0')

app/requirements.txt

Flask==1.1.1
google-auth==1.6.2
google-cloud-datastore==1.8.0

docker-compose.yaml

datastoreのポートは、ホスト側にも開けておくと、GUIツールで見ることができて便利

version: '3.7'

x-custom:
  gcp:
    - &gcp_project_id "dummy"

services:
  app:
    build: "./app/"
    volumes:
      - "./app/:/app/"
    environment:
      FLASK_APP: dev
      DATASTORE_HOST: "http://datastore:8081"
      DATASTORE_EMULATOR_HOST: "datastore:8081"
      PROJECT_ID: *gcp_project_id
      TZ: Asia/Tokyo
    ports:
      - "5000:5000"
    depends_on:
      - datastore
  datastore:
    build: "./datastore"
    volumes:
      - "./datastore/.data:/datastore/.data"
    environment:
      DATASTORE_PROJECT_ID: *gcp_project_id
      DATASTORE_LISTEN_ADDRESS: 0.0.0.0:8081
    ports:
      - "18081:8081"

起動

docker-compose up 

ブラウザやcurlで、http://localhost:5000にアクセスすると{"foo": "bar"}と表示されるはず。

ログ

下記にデータが貯められてるのがわかる

/datastore/.data/WEB-INF/appengine-generated/local_db.bin

datastore_1  | Updated property [core/project].
datastore_1  | WARNING: Reusing existing data in [/datastore/.data].
datastore_1  | Executing: /google-cloud-sdk/platform/cloud-datastore-emulator/cloud_datastore_emulator start --host=0.0.0.0 --port=8081 --store_on_disk=True --consistency=0.9 --allow_remote_shutdown /datastore/.data
app_1        |  * Serving Flask app "main" (lazy loading)
app_1        |  * Environment: production
app_1        |    WARNING: This is a development server. Do not use it in a production deployment.
app_1        |    Use a production WSGI server instead.
app_1        |  * Debug mode: off
app_1        |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
datastore_1  | [datastore] Jan 22, 2020 6:22:00 AM com.google.cloud.datastore.emulator.CloudDatastore$FakeDatastoreAction$9 apply
datastore_1  | [datastore] INFO: Provided --allow_remote_shutdown to start command which is no longer necessary.
datastore_1  | [datastore] Jan 22, 2020 6:22:01 AM com.google.cloud.datastore.emulator.impl.LocalDatastoreFileStub <init>
datastore_1  | [datastore] INFO: Local Datastore initialized:
datastore_1  | [datastore]      Type: High Replication
datastore_1  | [datastore]      Storage: /datastore/.data/WEB-INF/appengine-generated/local_db.bin
datastore_1  | [datastore] Jan 22, 2020 6:22:02 AM com.google.cloud.datastore.emulator.impl.LocalDatastoreFileStub load
datastore_1  | [datastore] INFO: Time to load datastore: 218 ms
datastore_1  | [datastore] API endpoint: http://0.0.0.0:8081
datastore_1  | [datastore] If you are using a library that supports the DATASTORE_EMULATOR_HOST environment variable, run:
datastore_1  | [datastore] 
datastore_1  | [datastore]   export DATASTORE_EMULATOR_HOST=0.0.0.0:8081
datastore_1  | [datastore] 
datastore_1  | [datastore] Dev App Server is now running.
datastore_1  | [datastore] 
datastore_1  | [datastore] The previous line was printed for backwards compatibility only.
datastore_1  | [datastore] If your tests rely on it to confirm emulator startup,
datastore_1  | [datastore] please migrate to the emulator health check endpoint (/). Thank you!
datastore_1  | [datastore] The health check endpoint for this emulator instance is http://0.0.0.0:8081/Jan 22, 2020 6:22:11 AM io.gapi.emulators.grpc.GrpcServer$3 operationComplete
datastore_1  | [datastore] INFO: Adding handler(s) to newly registered Channel.
datastore_1  | [datastore] Jan 22, 2020 6:22:11 AM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
datastore_1  | [datastore] INFO: Detected HTTP/2 connection.

データ投入

エミュレータのエンドポイントに、リクエストを投げてあげれば、ダンプデータも取り込める

docker-compose exec datastore bash ./import/run.sh

参考

下記のサイトを参考にさせていただきました。

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
What you can do with signing up
6