🐡

TopicとSubscription作成を内包したCloud PubSub エミュレータのDocker Imageを作る

2022/05/25に公開

動機

GCPのCloud PubSubは、テスト用にlocal emulatorを提供しており、gcloud cliより以下のコマンドで起動することができます。

gcloud beta emulators pubsub start --project=PUBSUB_PROJECT_ID [options]

このままcloud sdkを用いてtopicとsubscriptionを作成してテストをすることもできますが、docker-composeを用いたintegration test等と組み合わせるとテストを実行する前にtopicとsubscriptionを用意したり、既に存在する場合は作成をskipしたりといったhelperを書く必要がでてきます。

こうした苦労をなくすために、image起動時にあらかじめ指定したtopicとsubscriptionを作成した状態でpubsub emulatorが起動するようなDockerfileを作成しました。

Dockerfile

FROM gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators

RUN apt-get update && \
    apt-get install -y git python3-pip netcat && \
    git clone https://github.com/googleapis/python-pubsub.git

ENV PROJECT_ID=emulator \
    TOPIC_ID=event-topic \
    SUBSCRIPTION_ID=event-subscription \
    PUSH_ENDPOINT=http://host.docker.internal:3000

WORKDIR /python-pubsub/samples/snippets
RUN pip3 install virtualenv && \
    virtualenv env && \
    . env/bin/activate && \
    pip3 install -r requirements.txt

COPY ./entrypoint.sh ./
EXPOSE 8085
ENTRYPOINT ["./entrypoint.sh"]

entrypoint.sh

#!/bin/bash

set -em

export PUBSUB_PROJECT_ID=$PROJECT_ID
export PUBSUB_EMULATOR_HOST=0.0.0.0:8085

gcloud beta emulators pubsub start --project=$PUBSUB_PROJECT_ID --host-port=$PUBSUB_EMULATOR_HOST --quiet &

while ! nc -z localhost 8085; do
  sleep 0.1
done

. env/bin/activate
python3 publisher.py $PUBSUB_PROJECT_ID create $TOPIC_ID
python3 subscriber.py $PUBSUB_PROJECT_ID create-push $TOPIC_ID $SUBSCRIPTION_ID $PUSH_ENDPOINT

fg %1

解説

Dockerfile

Dockerfileでは、cloud-sdk-dockerのemulatorが内包されたimageを使用しています。image tagの一覧はregistryから確認できます。

FROM gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators

image起動時にpubsubのtopicとsubscriptionの作成をcloud sdkを介して行う必要があるため、pythonのサンプルプロジェクトをcloneし、同時にpython3とnetcatをinstallしています。

RUN apt-get update && \
    apt-get install -y git python3-pip netcat && \
    git clone https://github.com/googleapis/python-pubsub.git

環境変数で作成するtopic idや、subscriptionのpush endpointを指定しています。

ENV PROJECT_ID=emulator \
    TOPIC_ID=event-topic \
    SUBSCRIPTION_ID=event-subscription \
    PUSH_ENDPOINT=http://host.docker.internal:3000

cloneしたpythonのサンプルプロジェクトの/python-pubsub/samples/snippetsをworking dirに設定し、依存関係のinstallを行っています。

WORKDIR /python-pubsub/samples/snippets
RUN pip3 install virtualenv && \
    virtualenv env && \
    . env/bin/activate && \
    pip3 install -r requirements.txt

ref: https://maku77.github.io/python/env/venv.html

後述するentrypoint.shファイルをcopyし、entrypointで実行しています。

COPY ./entrypoint.sh ./
EXPOSE 8085
ENTRYPOINT ["./entrypoint.sh"]

entrypoint.sh

shellにはbashを指定し、-mでjob controlを有効にしています。

#!/bin/bash

set -em

pubsub emulatorが読み込む環境変数をexportしています。

export PUBSUB_PROJECT_ID=$PROJECT_ID
export PUBSUB_EMULATOR_HOST=0.0.0.0:8085

後でtopicやsubscriptionの作成を行うため、pubsub emulatorをバックグラウンドで起動し、netcatを使用してlocalhost:8085がlistenするまで待機しています。末尾の&によりバックグラウンドで起動されます。

gcloud beta emulators pubsub start --project=$PUBSUB_PROJECT_ID --host-port=$PUBSUB_EMULATOR_HOST --quiet &

while ! nc -z localhost 8085; do
  sleep 0.1
done

pythonのサンプルプロジェクトで提供されているpublisher.pysubscriber.pyを実行し、topicとsubscriptionを作成しています。今回はpush subscriptionを作成してるので、push endpointも指定しています。

. env/bin/activate
python3 publisher.py $PUBSUB_PROJECT_ID create $TOPIC_ID
python3 subscriber.py $PUBSUB_PROJECT_ID create-push $TOPIC_ID $SUBSCRIPTION_ID $PUSH_ENDPOINT

最後にfgで先ほどbackgroundに移行させたemulatorの起動プロセスをforegroundに移行させます。これにより最終的にemulatorのプロセスがdocker image起動後に持続することになります。

fg %1

docker-compose.yaml

最後に、作成したpubsub emulatorのimageを使用したサンプルのdocker-compose.yamlを紹介します。

環境変数のPUSH_ENDPOINThost.docker.internalを指定することで、他にdocker-composeで起動しているサービスに対してhostのnetworkを通じてpush requestを送ることができます。
ref: https://qiita.com/skobaken/items/03a8b9d0e443745862ac

version: '3.8'
services:
  pubsub:
    image: {buildしたimage}:latest
    restart: always
    environment:
      - PROJECT_ID=emulator
      - TOPIC_ID=event-topic-local
      - SUBSCRIPTION_ID=event-subscription-local
      - PUSH_ENDPOINT=http://host.docker.internal:3000/example
    extra_hosts:
      - host.docker.internal:host-gateway
    ports:
      - 8085:8085

Discussion