【Firebase】Firestore の自動バックアップを24行で実現する


なるべく firebase プロダクト内で完結する Firestore の自動バックアップを目指してみました
役者が揃ってきたこともあり、今ならだいぶ簡潔に実装できます

結論

ADC と functions の pubsub.schedule 機能を使うのがポイントです

import { region } from 'firebase-functions'
import { credential } from 'firebase-admin'
import axios from 'axios' // optional

export const backupFirestoreToStorage = region('asia-northeast1')
  .pubsub.schedule('0 3 * * *')
  .timeZone('Asia/Tokyo')
  .onRun(async () => {
    try {
      const accessToken = await credential
        .applicationDefault()
        .getAccessToken()
        .then(result => result.access_token)
      const projectID = process.env.GCLOUD_PROJECT
      const response = await axios.post(
        `https://firestore.googleapis.com/v1/projects/${projectID}/databases/(default):exportDocuments`,
        { outputUriPrefix: `gs://${projectID}-firestore-backups` },
        { headers: { Authorization: `Bearer ${accessToken}` } }
      )
      console.log(response)
    } catch (err) {
      console.error(err)
    }
  })

手順

2019年6月時点での方法です

(どうしても GCP の操作が必要になる部分はありますが、その際は gcloud コマンドは使わずにコンソールで行うようにしています)

課金を有効にする

まずは Blaze プランにして課金を有効にします

バックアップ用の Storage バケットを作成

GCP のコンソールから「Storage」を開き、一意のバケット名を指定して作成します
今回の例では ${projectId}-firestore-backup としています

Firestore のリージョンと同じリージョンを指定しましょう

参考:https://speakerdeck.com/vexus2/alu-firestore-number-aru?slide=14

サービスアカウントに役割を付与

GCP のコンソールから「IAM と管理」を開き、
[project-id]@appspot.gserviceaccount.com に以下の2つの役割を付与します

  • ストレージのオブジェクト管理者
  • Cloud Datastore インポート / エクスポート管理者

functions をデプロイ

$ firebase deploy --only functions:backupFirestoreToStorage

これで終わりです
デプロイの際に Cloud Pub/Sub と Could Scheduler を自動で設定してくれます(便利)

今回は毎日午前3時にバックアップを実行する cron (Cloud Scheduler) を設定しました
動作確認をする場合は Cloud Scheduler を実行すれば OK です

解説

ADC について

Cloud Firestore のバックアップは REST API 経由で実行しているため OAuth2 のアクセストークンが必要です
そのためにサービスアカウントキーの json ファイルを渡したり googleapis ライブラリを使ったりしてトークンを発行するサンプルコードがありましたが、実はそれらを使わなくても実現することができます

サーバー間での本番環境アプリケーションの認証の設定

ADC (= Application Default Credentials) の詳しい説明は上記の公式ドキュメントに任せますが、
今回の流れで説明すると

  • Cloud Functions に設定されているデフォルトのサービスアカウント([project-id]@appspot.gserviceaccount.com)がある
  • firebase-admin が ADC を使ってそのサービスアカウントを検出している
    (今回のサンプルの credential.applicationDefault() に対応)
  • ついでに OAuth2 のアクセストークンを取得 (getAccessToken() に対応)
  • 検出したサービスアカウントに firestore バックアップ用の権限を持たせておいたので API が実行できる

となります

サービスアカウントキーを管理しなくて済みますし、追加のライブラリが必要ないのでよりセキュアな実装といえるのではないでしょうか

他にいい方法があればぜひ教えてくださいm(_ _)m

余談ですが、
[project-id]@appspot.gserviceaccount.com は IAM 上では App Engine default service account と説明されているのに Cloud Functions のデフォルトのサービスアカウントでもあるのややこしいですよね。。。