转到控制台

安排数据导出

本页介绍了如何安排 Cloud Firestore 数据的导出。要根据预定时间导出,我们建议您部署 App Engine 服务来调用 Cloud Firestore 托管式导出功能。部署后,您可以安排使用 App Engine Cron 服务调用此服务。

准备工作

在使用托管式导出功能安排数据导出之前,您必须完成以下任务:

  1. 为 Google Cloud Platform 项目启用结算功能。 只有启用了结算功能的 Google Cloud Platform (GCP) 项目才能使用导出和导入功能。
  2. Cloud Firestore 数据库位置附近为您的项目创建 Cloud Storage 存储分区。您不能使用“请求者付款”存储分区执行导出和导入操作。
  3. 安装 Google Cloud SDK 以授予访问权限并部署应用。

配置访问权限

应用会使用 App Engine 默认服务帐号对其导出操作进行身份验证和授权。当您创建项目时,系统会使用以下格式为您创建默认服务帐号:

YOUR_PROJECT_ID@appspot.gserviceaccount.com

该服务帐号需要具备启动导出操作和写入 Cloud Storage 存储分区的权限。要授予这些权限,请将以下 IAM 角色分配给默认服务帐号:

  • Cloud Datastore Import Export Admin
  • 存储分区上的 OwnerStorage Admin 角色

您可以使用 Google Cloud SDK 中的 gcloudgsutil 命令行工具分配以下角色:

  1. 分配 Cloud Datastore Import Export Admin 角色:

    gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
        --member serviceAccount:YOUR_PROJECT_ID@appspot.gserviceaccount.com \
        --role roles/datastore.importExportAdmin
    
  2. 分配存储分区上的 Storage Admin 角色:

    gsutil iam ch serviceAccount:YOUR_PROJECT_ID@appspot.gserviceaccount.com:storage.admin \
        gs://BUCKET_NAME
    

应用文件

在新文件夹中,使用以下代码创建相应的应用文件:

app.yaml
配置 App Engine 运行时。应用会使用标准环境 Node.js 运行时。
app.js
主应用代码。此应用会在 https://YOUR_PROJECT_ID.appspot.com 设置一项启动导出操作的 Web 服务。
package.json
包括有关应用及其依赖项的信息。
cron.yaml
配置调用网络服务的 Cron 作业。

app.yaml

runtime: nodejs8

上面的代码假设该应用是默认应用。如果它不是默认应用,请添加以下行:

target: cloud-firestore-admin

app.js

const axios = require('axios');
const dateformat = require('dateformat');
const express = require('express');
const { google } = require('googleapis');

const app = express();

// Trigger a backup
app.get('/cloud-firestore-export', async (req, res) => {
  const auth = await google.auth.getClient({
    scopes: ['https://www.googleapis.com/auth/datastore']
  });

  const accessTokenResponse = await auth.getAccessToken();
  const accessToken = accessTokenResponse.token;

  const headers = {
    'Content-Type': 'application/json',
    Authorization: 'Bearer ' + accessToken
  };

  const outputUriPrefix = req.param('outputUriPrefix');
  if (!(outputUriPrefix && outputUriPrefix.indexOf('gs://') == 0)) {
    res.status(500).send(`Malformed outputUriPrefix: ${outputUriPrefix}`);
  }

  // Construct a backup path folder based on the timestamp
  const timestamp = dateformat(Date.now(), 'yyyy-mm-dd-HH-MM-ss');
  let path = outputUriPrefix;
  if (path.endsWith('/')) {
    path += timestamp;
  } else {
    path += '/' + timestamp;
  }

  const body = {
    outputUriPrefix: path
  };

  // If specified, mark specific collections for backup
  const collectionParam = req.param('collections');
  if (collectionParam) {
    body.collectionIds = collectionParam.split(',');
  }

  const projectId = process.env.GOOGLE_CLOUD_PROJECT;
  const url = `https://firestore.googleapis.com/v1beta1/projects/${projectId}/databases/(default):exportDocuments`;

  try {
    const response = await axios.post(url, body, { headers: headers });
    res
      .status(200)
      .send(response.data)
      .end();
  } catch (e) {
    if (e.response) {
      console.warn(e.response.data);
    }

    res
      .status(500)
      .send('Could not start backup: ' + e)
      .end();
  }
});

// Index page, just to make it easy to see if the app is working.
app.get('/', (req, res) => {
  res
    .status(200)
    .send('[scheduled-backups]: Hello, world!')
    .end();
});

// Start the server
const PORT = process.env.PORT || 6060;
app.listen(PORT, () => {
  console.log(`App listening on port ${PORT}`);
  console.log('Press Ctrl+C to quit.');
});

package.json

{
  "name": "solution-scheduled-backups",
  "version": "1.0.0",
  "description": "Scheduled Cloud Firestore backups via AppEngine cron",
  "main": "app.js",
  "engines": {
    "node": "8.x.x"
  },
  "scripts": {
    "deploy": "gcloud app deploy --quiet app.yaml cron.yaml",
    "start": "node app.js"
  },
  "author": "Google, Inc.",
  "license": "Apache-2.0",
  "dependencies": {
    "axios": "^0.18.0",
    "dateformat": "^3.0.3",
    "express": "^4.16.4",
    "googleapis": "^38.0.0"
  },
  "devDependencies": {
    "prettier": "^1.16.4"
  }
}

cron.yaml

cron:
- description: "Daily Cloud Firestore Export"
  url: /cloud-firestore-export?outputUriPrefix=gs://BUCKET_NAME[/PATH]&collections=test1,test2
  target: cloud-firestore-admin
  schedule: every 24 hours

修改 url 行以配置导出操作。应用会在 https://YOUR_PROJECT_ID.appspot.com/cloud-firestore-export 设置一项接受以下网址参数的服务:

outputUriPrefix
格式为 gs://BUCKET_NAME 的 Cloud Storage 存储分区的位置。
collections
要导出的集合 ID 的逗号分隔列表。如果未指定,则此操作会导出所有集合。

例如,要导出集合 ID 为 SongsAlbums 的所有集合,您可以使用以下代码:

url: /cloud-firestore-export?outputUriPrefix=gs://BUCKET_NAME&collections=Songs,Albums

示例 cron.yaml 每 24 小时执行一次导出。如需了解不同的时间安排选项,请参阅时间安排格式

部署应用和 Cron 作业

使用 gcloud 部署应用和 Cron 作业:

gcloud app deploy app.yaml cron.yaml

测试 Cron 作业

您可以通过在 Google Cloud Platform Console 的“Cron 作业”页面中启动已部署的 Cron 作业,对其进行测试。

  1. 在 GCP Console 中打开 Cron 作业页面。
    打开“Cron 作业”页面

  2. 对于说明为每日 Cloud Firestore 导出的 Cron 作业,请点击立即运行

  3. 作业运行完成后,您可以在状态中看到状态消息。点击查看即可查看作业日志。状态消息和作业日志将提供有关作业是否运行成功的信息。

查看导出

导出操作完成后,您可以在 Cloud Storage 存储分区中查看导出内容:

在 GCP 控制台中打开 Cloud Storage 浏览器。
打开 Cloud Storage 浏览器