Firestoreのプロジェクト間移行をやってみて困ったところのメモ。タイトルのままだけど。。
基本
公式Docがあるので、基本的にはこれに沿ってやっていった。
が、ここに明記されてないポイントで詰まった部分などがあったので、ここにまとめる。
このDocを読めばわかる通り、非常にざっくり言えば以下の流れで作業することになっている。
- 移行「元」のプロジェクト側にCloud Storage(バケット)を用意し、移行「元」データをそのバケットに吐き出す
- 移行「先」からそのバケットにアクセスして、移行「先」のFirestoreにimportする
前提
- 移行「元」と移行「先」のプロジェクトと、そのプロジェクトにアクセスするService Account、及びそのService Accountを使ったconfigがそれぞれ作成済みであること
- 移行「元」のほうにExportするべきコレクションが既に作成済みであること(移行「先」のほうにはコレクションがなくてもいい。importで勝手に作られる)
- 移行「先」のほうで[firestore.googleapis.com]が有効化されていること。ただこれは、仮に無効化のまま進めても、移行「先」でimportを実行した際に、「有効化します?」と聞いてくるので、最悪それまで放置でも良い。
- 移行「先」のほうでDatastoreではなくFirestoreが使える状態になっている(Firestoreへの切り替えが済んでいる)こと。これは作業前に私がそういう状況だったからという理由で書いており、実際前提条件になってるかはわからない。なんとなく前提条件な気がしている。
1.Cloud Storageの用意
- 移行「元」プロジェクトのほうに切り替える
gcloud config configurations activate [移行元プロジェクトconfig名]
-
管理コンソールでCloud Storageを1個作る。
-
公式Docにあるとおり、
[SOURCE_PROJECT_ID]@appspot.gserviceaccount.com
にStorage AdminのPermissionを与える。公式DocだとCLIで設定してるが私はコンソール上で設定しました
2. Export
- Exportする。以下は移行元バケット名=
src-bucket
、コレクション名はTest
の例
gcloud firestore export gs://src-bucket --collection-ids=Test --async
- 以下のような標準出力が出るので公式Docに記載のあるように
outputUriPrefix
の値を目盛る(下記の例だとgs://src-bucket/2022-06-06T09:38:10_9672
)
metadata:
'@type': type.googleapis.com/google.firestore.admin.v1.ExportDocumentsMetadata
collectionIds:
- Test
operationState: PROCESSING
outputUriPrefix: gs://src-bucket/2022-06-06T09:38:10_9672
startTime: '2022-06-06T09:38:10.886670Z'
name: projects/src-project/databases/(default)/operations/ASAwODYwNTQxODMJGnRsdWFmZWQHEjF0c2FlaHRyb24tYWlzYS1zYm9qLW5pbWRhGgoyEg
- この例だとステータスが「PROCESSING」になっているのでまだ出力が完了していないが、公式Docにもあるように、
gcloud firestore operations list
を使えば状況の確認はできる
> gcloud firestore operations list
---
done: true
metadata:
'@type': type.googleapis.com/google.firestore.admin.v1.ExportDocumentsMetadata
collectionIds:
- Test
endTime: '2022-06-06T09:38:18.301258Z'
operationState: SUCCESSFUL
outputUriPrefix: gs://src-bucket/2022-06-06T09:38:10_9672
progressBytes:
completedWork: '546'
estimatedWork: '444'
progressDocuments:
completedWork: '3'
estimatedWork: '3'
startTime: '2022-06-06T09:38:10.886670Z'
name: projects/src-project/databases/(default)/operations/ASAwODYwNTQxODMJGnRsdWFmZWQHEjF0c2FlaHRyb24tYWlzYS1zYm9qLW5pbWRhGgoyEg
response:
'@type': type.googleapis.com/google.firestore.admin.v1.ExportDocumentsResponse
outputUriPrefix: gs://src-bucket/2022-06-06T09:38:10_9672
3. Import(試行錯誤)
- 移行「先」のほうのプロジェクトに切り替える。
gcloud config configurations activate [移行先プロジェクトconfig名]
-
公式Docにもある通り、移行「先」のほうのプロジェクト設定で使用するService Accountに、移行「元」のほうのバケットに対して「Storage オブジェクト管理者」のロールを付与する。設定方法は1.と同様。移行「先」のほうのバケットで「プリンシパルの追加」を行った後、対象プリンシパルの検索ワードに移行「先」のほうのService Account名を入力して検索し、ロールを付与する。
-
Importする。この際2. の標準出力の
outputUriPrefix
を引数に指定する。
> gcloud firestore import gs://src-bucket/2022-06-06T09:38:10_9672
ERROR: (gcloud.firestore.import) PERMISSION_DENIED: The caller does not have permission
- ★失敗した。これは、この
gcloud firestore import
コマンドを実行するユーザー(=Service Account)に権限がないのだとエラーメッセージから推察。
移行「先」のプロジェクトのIAM管理にて、このコマンドを実行するService Accountに「Firestoreサービスエージェント」というロールを付与。
- 再実行。
> gcloud firestore import gs://src-bucket/2022-06-06T09:38:10_9672 --async
ERROR: (gcloud.firestore.import) PERMISSION_DENIED: Service account does not have access to Google Cloud Storage file: /src-bucket/2022-06-06T09:38:10_9672/2022-06-06T09:38:10_9672.overall_export_metadata. See https://cloud.google.com/datastore/docs/export-import-entities#permissions for a list of permissions needed. Error details: service-123456789012@gcp-sa-firestore.iam.gserviceaccount.com does not have storage.buckets.get access to the Google Cloud Storage bucket.
- ★また失敗した。今度はまた違うエラーメッセージが出ている。公式Docには書かれていないが、どうやら
service-123456789012@gcp-sa-firestore.iam.gserviceaccount.com
(※123456789012
の部分は例です。実際には移行「先」のProject ID値が入ると思う)というService Accountにこのバケットに対するアクセス権が必要なようだ。これ、ロール名がわかりづらく、「storage レガシーバケット読み取り」というロールが正解らしい。1.で[SOURCE_PROJECT_ID]@appspot.gserviceaccount.com
にStorage AdminのPermission与えたのと同様に、バケットの権限画面でservice-123456789012@gcp-sa-firestore.iam.gserviceaccount.com
を追加して、「storage レガシーバケット読み取り」というロールを与える。
- 再実行。
> gcloud firestore import gs://src-bucket/2022-06-06T09:38:10_9672 --async
ERROR: (gcloud.firestore.import) PERMISSION_DENIED: Service account does not have access to Google Cloud Storage file: /src-bucket/2022-06-06T09:38:10_9672/2022-06-06T09:38:10_9672.overall_export_metadata. See https://cloud.google.com/datastore/docs/export-import-entities#permissions for a list of permissions needed.
- ★また失敗した。どうやら「storage レガシーバケット読み取り」だけでは足りないらしい。エラーメッセージを読む限りではオブジェクトをリストする(閲覧する)権限があれば事足りるように見える。とりあえず「Storage オブジェクト閲覧者」をつけてみる。
- 再実行。
> gcloud firestore import gs://src-bucket/2022-06-06T09:38:10_9672 --async
metadata:
'@type': type.googleapis.com/google.firestore.admin.v1.ImportDocumentsMetadata
collectionIds:
- Test
inputUriPrefix: gs://src-bucket/2022-06-06T09:38:10_9672
operationState: PROCESSING
startTime: '2022-06-06T10:03:56.622775Z'
name: projects/dst-project/databases/(default)/operations/AiA3NTEwMzE2NzMJGnRsdWFmZWQHEjF0c2FlaHRyb24tYWlzYS1zYm9qLW5pbWRhGgoyEg
- ようやく正常に動作した。。長い戦いだった。。。
おわりに
- 上で「★」をつけた部分は公式Docには書かれていなかった(と私が思ってる)部分である。特に後半のImport周りは謎のServiceAccountが登場したりロールが色々必要だったり大変だった。Service Accountに関しては、「CLIを実行するユーザー(Service Account)」と移行元・先両方のプロジェクトの
[PROJECT_ID]@appspot.gserviceaccount.com
と、2つが登場人物として出てくるあたりが混乱の要因になってる気はする。そもそもCLIも[PROJECT_ID]@appspot.gserviceaccount.com
で実行しろってことだったのかな??この辺は確かめられていない。 - まあでもPermission周りに関しては、都度エラーメッセージを見ていけば割となんとかなる部分があったので、基本はエラーメッセージの指摘通りにやっていけばよいのだと思う。「レガシー閲覧」みたいな、いかにも怪しいロール名もあるし、そのうち変わりそうな気はプンプンするし、今後同じことをやるときも、冒頭の公式Docを「参考」にして、ちょいちょいやっていけば、あとはエラーメッセージが導いてくれると信じている。