背景
サービスでdatastoreを使う事になり、開発環境やCIでのテストをどうするか試行錯誤した。
AWSと比較して情報が少ないので、まとめた。
要点
- 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
ちょっと適当すぎる気がするが、データを保存して取り出すだけのエンドポイントを作成。
認証情報は、ダミーの認証をかませる。
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
参考
下記のサイトを参考にさせていただきました。