名前空間を使用したマルチテナンシーの実装

Namespaces API では、appengine_config.pyの各テナントに対する名前空間文字列を選択するだけで、アプリケーションでマルチテナンシーを簡単に有効にできます。この操作には、 namespace_manager パッケージを使用します。

現在の名前空間の設定

名前空間の取得、設定、検証には、 namespace_manager パッケージを使用します。名前空間マネージャーを使用すると、名前空間に対応した API に現在の名前空間を設定できます。appengine_config.pyに現在の名前空間を事前に設定することで、Datastore と memcache によりその名前空間が自動的に使用されます。

ほとんどの App Engine デベロッパーは、Google Workspace(旧 G Suite)ドメインを現在の名前空間として使用します。Google Workspace では、所有している任意のドメインにアプリをデプロイできるため、この仕組みを使用してドメインごとに異なる名前空間を簡単に構成できます。その後で、それらの個別の名前空間を使用して、データをドメイン間で分離できます。詳細については、カスタム ドメインのマッピングをご覧ください。

次のコードサンプルは、URL のマッピングに使用した Google Workspace ドメインを現在の名前空間として設定する方法を示しています。同じ Google Workspace ドメインでマッピングされるすべての URL に対して同じ文字列が使用されることに注意してください。

Python で名前空間を設定するには、アプリケーションのルートディレクトリにある App Engine 構成システム appengine_config.py を使用します。次の簡単な例は、Google Workspace ドメインを現在の名前空間として使用する方法を示しています。

from google.appengine.api import namespace_manager

# Called only if the current namespace is not set.
def namespace_manager_default_namespace_for_request():
    # The returned string will be used as the Google Apps domain.
    return namespace_manager.google_apps_namespace()

namespace の値を指定しない場合、名前空間が空の文字列に設定されます。namespace には任意の文字列を指定できますが、文字数は 100 文字までに制限されます。また、使用できる文字は英数字、ピリオド、アンダースコア、ハイフンだけです。より明示的に設定するには、名前空間文字列は正規表現 [0-9A-Za-z._-]{0,100} と同じ形式で設定する必要があります。

慣例として、「_」(アンダースコア)で始まるすべての名前空間はシステムで使用するために予約されています。システムの名前空間に関するこのルールは強制的なものではありませんが、従わないと、不安定で好ましくない結果を引き起こす原因になることがあります。

appengine_config.py の構成に関する一般的な情報については、Python モジュールの構成をご覧ください。

データ漏洩の防止

マルチテナント アプリに関連する一般的なリスクの 1 つに名前空間でのデータ漏洩があります。次のようなさまざまな状況で、意図しないデータ漏洩が発生する可能性があります。

  • 名前空間をまだサポートしていない App Engine API で名前空間を使用した場合。たとえば、Blobstore では名前空間がサポートされていません。Blobstore で名前空間を使用する場合は、エンドユーザーのリクエストに対して Blobstore クエリを使用しないようにするか、信頼できないソースから取得した Blobstore キーを使用しないようにする必要があります。
  • URL Fetch やその他のメカニズムで、名前空間の区分化スキームを指定せずに外部ストレージ メディアを使用した場合(Memcache と Datastore は使用しない)。
  • ユーザーのメールドメインに基づいて名前空間を設定した場合。ほとんどの場合、特定のドメインのすべてのメールアドレスが特定の名前空間にアクセスするのは適切ではありません。また、メールドメインを使用すると、ユーザーがログインするまでアプリケーションで名前空間を使用できません。

名前空間のデプロイ

以降のセクションでは、その他の App Engine ツールや API を使用して名前空間をデプロイする方法について説明します。

ユーザー単位での名前空間の作成

一部のアプリケーションでは、名前空間をユーザー単位で作成する必要があります。ログインしているユーザーのユーザーレベルでデータを区分けする場合は、ユーザーを一意に識別する永続的な ID を返すUser.user_id()の使用を検討してください。次のコードサンプルは、この目的のために Users API を使用する方法を示したものです。

from google.appengine.api import users

def namespace_manager_default_namespace_for_request():
    # assumes the user is logged in.
    return users.get_current_user().user_id()

通常、名前空間をユーザー単位で作成するアプリでは、ユーザーごとに固有のランディング ページも用意します。その場合は、ユーザーに表示するランディング ページを決定する URL スキームをアプリケーションで指定する必要があります。

データストアでの名前空間の使用

Datastore のデフォルトでは、Datastore リクエストに 名前空間マネージャーで設定された 現在の名前空間を使用します。Key または Queryオブジェクトの作成時に、API により現在の名前空間がこのオブジェクトに適用されます。したがって、アプリケーションで Key または Query オブジェクトをシリアル化した形式で保持する場合は、シリアル化したそれらに名前空間が保持されるので、注意が必要です。

シリアル化解除されたオブジェクト(KeyQuery )を使用している場合は、意図したとおりに動作することを確認してください。他のストレージ メカニズムを使用せずに Datastore(put / query / get)を使用するシンプルなアプリケーションの多くは、Datastore API を呼び出す前に現在の名前空間を設定すれば、想定どおりに動作します。

QueryKey オブジェクトは、名前空間に関して次のような独特の動作をします。

  • Query オブジェクトと Key オブジェクトは、明示的な名前空間を設定しない限り、現在の名前空間を継承します。
  • アプリケーションが祖先から新しい Key を作成すると、新しい Key は祖先の名前空間を継承します。

Memcache での名前空間の使用

デフォルトで、Memcache のリクエストには名前空間マネージャーで設定された現在の名前空間が使用されます。ほとんどの場合、Memcache で名前空間を明示的に設定する必要はなく、設定すると予期しないバグの原因になることもあります。

ただし、特定のまれな状況では、Memcache で名前空間を明示的に設定した方がよい場合もあります。たとえば、すべての名前空間で共有される共通のデータ(国コードが格納されたテーブルなど)をアプリケーションで使用する場合などです。

Memcache 用の Python API を使用すると、現在の名前空間を名前空間マネージャーから取得したり、Memcache サービスの作成時に名前空間を明示的に設定できます。次の例では、Memcache に値を保存するときに名前空間を明示的に設定しています。

  // Store an entry to the memcache explicitly
memcache.add("key", data, namespace='abc')

タスクキューでの名前空間の使用

デフォルトで、push キューはタスクの作成時に名前空間マネージャーで設定された現在の名前空間を使用します。ほとんどの場合、タスクキューで名前空間を明示的に設定する必要はなく、設定すると予期しないバグの原因になることもあります。

タスク名はすべての名前空間で共有されます。使用する名前空間が異なる場合でも、同じ名前のタスクを複数作成することはできません。同じタスク名を複数の名前空間で使用したい場合は、タスク名にそれぞれの名前空間を付加できます。

新しいタスクでタスクキューadd()メソッドを呼び出すと、現在の名前空間と Google Workspace ドメイン(該当する場合)が名前空間マネージャーからコピーされます。タスクの実行時に、現在の名前空間と Google Workspace の名前空間が復元されます。

現在の名前空間が元のリクエストに設定されていない場合(つまり、get_namespace()'' を返した場合)、set_namespace() を使用してタスクの現在の名前空間を設定できます。

すべての名前空間に関連するタスクの名前空間を明示的に設定した方がよいような、まれな状況もあります。たとえば、すべての名前空間の使用統計情報を集約するタスクを作成する場合が挙げられます。この場合は、タスクの名前空間を明示的に設定できます。

Blobstore での名前空間の使用

Blobstore は名前空間で分割されません。Blobstore で名前空間を保持するには、名前空間に対応したストレージ メディア(現在は Memcache、Datastore、タスクキューのみ)から Blobstore にアクセスする必要があります。たとえば、blob の Key が Datastore エンティティに格納されている場合、名前空間に対応した Datastore の Key または Query を使用してアクセスできます。

名前空間対応のストレージに格納されたキーを使用してアプリケーションから Blobstore にアクセスする場合、Blobstore 自体を名前空間で分割する必要はありません。名前空間での blob のデータ漏洩を防ぐために、次の点に注意してください。

  • エンドユーザー リクエストにBlobInfo.gql()を使用しないでください。管理者のリクエスト(アプリケーションのすべての blob に関するレポートの生成など)には BlobInfo クエリを使用してもかまいませんが、エンドユーザーのリクエストに使用すると、すべての BlobInfo レコードが名前空間で区分されないためデータ漏洩が発生する可能性があります。
  • 信頼できないソースから取得した Blobstore キーは使用しないでください。

データストア クエリの名前空間の設定

Google Cloud コンソールでは、データストア クエリの名前空間を設定できます。

デフォルト以外の名前空間を使用する場合は、使用する名前空間をプルダウンから選択します。

一括ローダーでの名前空間の使用

一括ローダーは、使用する名前空間を指定できる --namespace=NAMESPACE フラグをサポートしています。それぞれの名前空間が別々に処理されるため、すべての名前空間にアクセスする場合は反復処理が必要になります。

Index の新しいインスタンスを作成すると、デフォルトでは現在の名前空間に割り当てられます。

# set the current namespace
namespace_manager.set_namespace("aSpace")
index = search.Index(name="myIndex")
# index namespace is now fixed to "aSpace"

名前空間をコンストラクタで明示的に割り当てることもできます。

index = search.Index(name="myIndex", namespace="aSpace")

インデックス仕様の作成後は、その名前空間を変更できません。

# change the current namespace
namespace_manager.set_namespace("anotherSpace")
# the namespaceof 'index' is still "aSpace" because it was bound at create time
index.search('hello')