將 Firebase Extensions 遷移至自行建立的函式套件

選取遷移路徑: 遷移至 npm 上的函式套件 遷移至自行建立的函式套件

如果發布商尚未建立透過 npm 發布的正式替代套件,本指南會逐步說明如何將擴充功能分叉,並設定為本機函式套件。

查看已知的遷移限制

開始遷移擴充功能執行個體前,請先檢查設定是否使用下列任何功能,這些功能需要解決方法,或函式套件尚未支援:

  • 自訂 Docker 存放區和 KMS 金鑰需要手動解決 Cloud Functions for Firebase 不支援 用於設定自訂 Docker 存放區或客戶自行管理的加密金鑰 (KMS 金鑰) 的替代系統參數。如果擴充功能設定了其中一個參數,請參閱常見問題因應措施。

事前準備

您需要設定 Firebase CLI,並初始化 Firebase 專案。使用 CLI 時,請務必使用 firebase-tools 版本 >= 15.32.0,其中包含新的遷移和函式套件指令。

所需帳戶權限和角色

視 Firebase CLI 在遷移期間需要建立及設定的項目而定,您用來向 Firebase 和 Google Cloud 驗證的帳戶必須具備下列角色:

  • roles/firebaseextensions.editor
  • roles/cloudbuild.builds.editor
  • roles/artifactregistry.writer
  • roles/run.developer
  • roles/iam.serviceAccountUser
  • roles/iam.serviceAccountCreator
  • roles/cloudfunctions.admin (如需對公開端點執行 setIamPermissions)
  • roles/secretmanager.admin (如果使用密鑰)
  • roles/serviceusage.serviceUsageAdmin (如需啟用新的 API)

建議您使用先前已安裝擴充功能及部署函式的帳戶,因為大部分的權限都已授予。如果遷移帳戶需要更多角色,請按照 Google Cloud IAM 指示新增角色。

將擴充功能執行個體升級至最新版本

您必須將擴充功能更新至最新版本,盡量縮小擴充功能執行個體與替代套件之間的差異。如果擴充功能未升級,擴充功能執行個體與其套件替代項目之間,可能存在重大且破壞性的變更。由於各版本之間的參數有所變更,匯出的設定可能與套件預期的設定不符。

請根據擴充功能的安裝位置,使用下列其中一種方式更新:

  • 從 Firebase 控制台
  • 透過 Firebase CLI 使用:
    • firebase ext:update <extension-instance-id> --project <project-id> firebase deploy --only extensions --project <project-id>

如果略過這個步驟,當擴充功能不是最新版本時,CLI 會在匯出設定時提示您升級。

將擴充功能分叉到本機函式套件

開始將擴充功能轉換為本機函式套件之前,請確認擴充功能原始碼位於 Firebase 專案中。如要這麼做,請從 GitHub 複製擴充功能存放區,在 Firebase 專案根目錄中建立目錄,然後將擴充功能的 functions/ 資料夾和 extension.yaml 複製到該目錄:

mkdir -p path/to/kit
cp -r /path/to/extension-source/functions/* path/to/kit/
cp /path/to/extension-source/extension.yaml path/to/kit/.

請按照發布商遷移指南的步驟 1 至 8,將擴充功能原始碼遷移至第 2 代函式。然後繼續執行下列步驟。

讓本機套件支援匯出的函式區域和進階參數

在函式套件中,Firebase CLI 不會產生 index.ts 檔案,設定套件並將其設定為使用遷移的系統參數。如要使用為擴充功能設定的函式區域和進階參數,請設定 index.ts 檔案,將 firebase ext:export --mode functions 匯出的格式讀取至環境變數檔案。

具體來說,在匯出函式的頂層 index.ts 檔案中,定義 FUNCTION_DEFAULT_REGION 的參數,並使用 EXT_MIGRATED_SYSTEM_<GLOBAL_OPTION> 形式的環境變數呼叫 setGlobalOptions,類似於 CLI 使用的 index-kit-migration.ts 範本:

import { setGlobalOptions } from "firebase-functions";
import { MemoryOption, VpcEgressSetting, IngressSetting } from "firebase-functions/v2/options";
import { defineString } from "firebase-functions/params";

export const regionParam = defineString("FUNCTION_DEFAULT_REGION", {
  input: { text: { nonEmpty: true } },
  description: "Global default region where functions should be deployed. Can be overridden per-function.",
});

setGlobalOptions({
  region: regionParam,
  memory: (process.env.EXT_MIGRATED_SYSTEM_MEMORY as MemoryOption) ?? undefined,
  timeoutSeconds: process.env.EXT_MIGRATED_SYSTEM_TIMEOUTSECONDS
    ? Number(process.env.EXT_MIGRATED_SYSTEM_TIMEOUTSECONDS)
    : undefined,
  vpcConnectorEgressSettings:
    process.env.EXT_MIGRATED_SYSTEM_VPCCONNECTOREGRESSSETTINGS &&
    process.env.EXT_MIGRATED_SYSTEM_VPCCONNECTOREGRESSSETTINGS !== "VPC_CONNECTOR_EGRESS_SETTINGS_UNSPECIFIED"
      ? (process.env.EXT_MIGRATED_SYSTEM_VPCCONNECTOREGRESSSETTINGS as VpcEgressSetting)
      : undefined,
  vpcConnector: process.env.EXT_MIGRATED_SYSTEM_VPCCONNECTOR ?? undefined,
  maxInstances: process.env.EXT_MIGRATED_SYSTEM_MAXINSTANCES
    ? Number(process.env.EXT_MIGRATED_SYSTEM_MAXINSTANCES)
    : undefined,
  minInstances: process.env.EXT_MIGRATED_SYSTEM_MININSTANCES
    ? Number(process.env.EXT_MIGRATED_SYSTEM_MININSTANCES)
    : undefined,
  ingressSettings: (process.env.EXT_MIGRATED_SYSTEM_INGRESSSETTINGS as IngressSetting) ?? undefined,
  // Parses a comma-separated string of key:value pairs into a key-value object
  // (for example, "key1:value1,key2:value2" -> { key1: "value1", key2: "value2" }).
  labels: process.env.EXT_MIGRATED_SYSTEM_LABELS
    ? process.env.EXT_MIGRATED_SYSTEM_LABELS.split(",").reduce<Record<string, string> | undefined>(
        (acc, curr) => {
          const [key, value] = curr.split(":");
          const trimmedKey = key?.trim();
          const trimmedValue = value?.trim();
          if (!trimmedKey || !trimmedValue) {
            return acc;
          }
          acc = acc ?? {};
          acc[trimmedKey] = trimmedValue;
          return acc;
        },
        undefined,
      )
    : undefined,
});

// Re-export all functions so the Firebase CLI can deploy them
export * from "./your-functions";

先測試套件再進行遷移

您現在擁有本機函式套件,部署後行為與擴充功能的新安裝項目完全相同。下一個步驟是驗證並修正過程中意外發生的任何問題,然後將正式版擴充功能例項遷移至該版本。

首先,請將 Fork 新增為本機套件、設定並部署至測試專案。本機函式套件必須位於 Firebase 專案中,因此如果複製的擴充功能存放區位於 Firebase 專案外部,請將其移至專案目錄中。然後執行下列套件安裝指令,將其安裝為本機套件:

firebase functions:kits:install --directory <path-to-your-fork> --project <test-project-id>

這項指令會引導您為第一個測試執行個體選擇套件 ID、執行個體 ID 和設定。接著,這個指令會修改 firebase.json 檔案,註冊指向您分支目錄的本機套件,並將每個執行個體的設定儲存在 function-kits/<kit-id>/config-<instance-id> 的 .env 檔案中。

將本機套件部署至測試專案,並提供適當資源來測試套件行為。如果您已設定測試專案來測試擴充功能,請執行下列指令:

firebase deploy --only functions:<kit-instance-id> --project <test-project-id>

範例:將串流 Cloud Firestore 轉移至 BigQuery (firestore-bigquery-export)

驗證 Cloud Firestore 到 BigQuery 的端對端同步:

  1. 在 Firebase 控制台的 Cloud Firestore 頁面中,建立您設為 COLLECTION_PATH (users) 的集合 (如果還沒有的話)。
  2. 建立名為 bigquery-mirror-test 的文件,其中包含任何欄位和值。
  3. 在 Google Cloud 控制台的 BigQuery 頁面中,查詢原始變更記錄表。其中應包含記錄文件建立作業的單一資料列:

    SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
    
  4. 查詢最新檢視畫面,系統應會傳回唯一存在的文件 (bigquery-mirror-test) 的最新變更事件:

    SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
    
  5. 刪除 Cloud Firestore 中的 bigquery-mirror-test 文件。該項目會從最新檢視畫面中消失,且 DELETE 事件會附加至原始變更記錄表。

    您可以使用下列指令檢查單一文件的完整記錄:

    SELECT *
       FROM `PROJECT_ID.analytics.users_raw_changelog`
       WHERE document_name = "bigquery-mirror-test"
       ORDER BY timestamp ASC
    

與測試擴充功能的不同之處:

  • 觸發條件會以 kit-<kit-instance-id>-fsexportbigquery 形式部署,而非 ext-<instanceId>-fsexportbigquery。在 Cloud Functions 資訊主頁和記錄中尋找該名稱。
  • 您的程式碼會在 Firebase Local Emulator Suite 中以標準函式形式執行。您可以使用 .env.local 設定要在模擬器中使用的參數值。您也可以使用 firebase-functions-test SDK 進行程式碼單元測試,詳情請參閱「Cloud Functions 的單元測試」。
  • 佈建作業不再由 Extensions 執行階段驅動。如果部署後缺少變更記錄表,請手動重新執行設定工作:firebase functions:lifecycle:run afterFirstDeploy <kit-instance-id>。這項工作是等冪的,因此重新執行工作會協調資料集、資料表和檢視區塊。
  • 參數值來自 .env,而非安裝表單,因此 .env 完成後,firebase deploy 的重新執行作業不會有互動。

(選用) 清理測試資源

如要在測試後移除這個測試執行個體,請解除安裝:

firebase functions:kits:uninstall --instance <kit-instance-id> --project <test-project-id>

這會刪除部署套件所建立的所有雲端資源,並移除執行個體設定。如果只有一個套件例項,這也會從 firebase.json 中移除套件項目。但不會刪除本機原始碼目錄。安裝生產環境遷移套件時,可以再次選擇套件 ID。

從擴充功能遷移至本機套件

測試完本機套件後,您就可以遷移已部署的擴充功能執行個體。

1. 安裝替換用函式套件執行個體

安裝本機函式套件,並傳遞 --no-configure 來略過手動設定,以便在下一個步驟中,將現有擴充功能設定直接匯出至這個套件例項:

firebase functions:kits:install --no-configure --directory <path-to-your-fork> --project <project-id>

2. 設定函式套件執行個體,與擴充功能完全相同

您必須自訂這個套件執行個體,並使用與要取代的擴充功能相同的設定。您可以將擴充功能執行個體設定匯出至 .env 檔案,其中會儲存所有 Cloud Functions (包括套件) 的參數、環境變數和密鑰參照設定資料。如要直接匯出至套件的設定檔,請執行下列指令:

firebase ext:export --mode functions --instance <extension-instance-id> --kit-instance <kit-instance-id> --project <project-id>

完成這個步驟後,這個執行個體的設定資訊會儲存在執行個體設定目錄中,專案專屬的 .env 檔案中,例如: function-kits/<kit-name>/config-<instance-id>/.env.<project-id>

3. 部署及驗證套件更換作業

現在套件已安裝完畢,並可做為一組函式使用,您可以部署套件替代項目。函式套件的運作方式與標準函式類似,每個套件例項都會做為獨立的程式碼集,用於整理函式。您可以選擇部署所有函式,或只部署特定套件例項。遷移單一擴充功能例項時,請只部署該套件例項。

如果套件使用任何新的參數,而這些參數並未出現在您遷移的擴充功能例項中,Firebase CLI 會在部署程序開始時提示您輸入這些參數。在最新版 firestore-bigquery-export 擴充功能的這個範例中,這並非預期行為,但許多套件會針對套件使用的任何事件觸發來源,提示輸入新參數。在這次遷移作業中,更新後的套件會使用第 2 代函式,而擴充功能先前使用的是第 1 代函式。在第 2 代中,函式位於事件來源附近,並以額外參數的形式新增。在日後的更新中,如果新增參數,CLI 會在下次部署時提示您。

範例:

firebase deploy --only functions:firestore-bigquery-export --project my-project

輸出內容:

=== Deploying to 'my-project'...
i  deploying functions
i  functions: Loaded environment variables from function-kits/firestore-bigquery-export/config-firestore-bigquery-export/.env.my-project
i  functions: ensuring required API bigquery.googleapis.com is enabled...
i  functions: ensuring required API cloudtasks.googleapis.com is enabled...
✔  functions: required APIs are enabled
i  functions: granting declarative IAM roles to managed service account:
   - BigQuery Data Editor
   - BigQuery User
   - Cloud Datastore User
   - Eventarc Event Receiver
   - roles/run.invoker
✔  functions: successfully granted IAM roles
i  functions: creating Node.js 22 (2nd Gen) function kit-firestore-bigquery-export-fsexportbigquery(us-central1)...
i  functions: creating Node.js 22 (2nd Gen) function kit-firestore-bigquery-export-initBigQuerySync(us-central1)...
i  functions: creating Node.js 22 (2nd Gen) function kit-firestore-bigquery-export-setupBigQuerySync(us-central1)...
✔  functions[kit-firestore-bigquery-export-fsexportbigquery(us-central1)] Successful create operation.
✔  functions[kit-firestore-bigquery-export-initBigQuerySync(us-central1)] Successful create operation.
✔  functions[kit-firestore-bigquery-export-setupBigQuerySync(us-central1)] Successful create operation.
i  functions: Executing afterFirstDeploy lifecycle hook targeting: kit-firestore-bigquery-export-initBigQuerySync...
✔  functions: Successfully queued task for lifecycle hook kit-firestore-bigquery-export-initBigQuerySync in queue projects/my-project/locations/us-central1/queues/kit-firestore-bigquery-export-initBigQuerySync.
✔  Deploy complete!

如要確認套件的 firebase deploy 沒有錯誤,請檢查部署記錄,看看是否觸發任何生命週期掛鉤。Stream Cloud Firestore to BigQuery 等熱門擴充功能會使用生命週期掛鉤。以下範例顯示觸發生命週期掛鉤時的樣子:

i  functions: Executing afterFirstDeploy lifecycle hook targeting: kit-firestore-bigquery-export-initBigQuerySync...
✔  functions: Successfully queued task for lifecycle hook kit-firestore-bigquery-export-initBigQuerySync in queue projects/my-project/locations/europe-west1/queues/kit-firestore-bigquery-export-initBigQuerySync.
i  functions: View logs for afterFirstDeploy at: https://console.cloud.google.com/logs/query;query=resource.type%3D%22cloud_run_revision%22%0Aresource.labels.service_name%3D%22kit-firestore-bigquery-export--initbigquerysync%22%0Aresource.labels.location%3D%22europe-west1%22;project=my-project

這些記錄訊息可確認下列事項:

  • 系統找到並執行生命週期掛鉤。
  • 生命週期掛鉤相關聯的工作佇列中已排定任務。
  • 系統會提供 Cloud Logging 的連結,方便您確認工作是否順利完成。

點選記錄連結前往 Google Cloud 控制台,確認記錄中沒有錯誤,且工作佇列事件已成功處理。如果生命週期事件未順利執行,您可以執行下列程式碼重新觸發:

firebase functions:lifecycle:run <hook-name> <codebase>

如果是首次部署函式套件執行個體,請執行下列指令:

firebase functions:lifecycle:run afterFirstDeploy <kit-instance-id>

如果在驗證期間決定要停止或復原這項遷移作業,可以按照「解除安裝擴充功能」一文中的說明解除安裝套件。

4. 解除安裝擴充功能

確認已部署函式套件後,即可解除安裝擴充功能,避免套件和擴充功能重複執行相同行為。無論擴充功能是透過何種方式安裝,只要傳遞 --immediate 旗標,即可透過 Firebase CLI 解除安裝所有擴充功能:

firebase ext:uninstall <extension-instance-id> --project <project-id> --immediate

範例:

firebase ext:uninstall firestore-bigquery-export --project my-project --immediate

輸出內容:

i  extensions: uninstalling firestore-bigquery-export...
i  extensions: deleting extension instance resources in project my-project...
✔  extensions: successfully uninstalled firestore-bigquery-export

進階遷移作業

您可以在多個 Firebase 專案中加入擴充功能,並透過單一程式碼集進行管理。舉例來說,如果您將相同基礎架構部署到 testing 環境和 production 環境,而這兩個環境各有您匯出至 BigQuery 的 documents Cloud Firestore 執行個體,則您可能會安裝兩個 firestore-bigquery-export 擴充功能執行個體:

  • export-documents-testing
  • export-documents-production

如果您使用 Firebase CLI,並透過 firebase deploy --project testing 和 firebase deploy --project production 部署,將這兩個擴充功能例項遷移至單一程式碼集中的兩個函式套件例項,則每次部署都會在 testing 和 production 環境中建立兩個例項。

請改為使用部署至多個專案的 firestore-bigquery-export 函式套件執行個體,取代兩個擴充功能執行個體,每個專案都有自己的設定。執行個體的設定目錄應如下所示:

  • config-export-documents/
    • .env.testing
    • .env.production

每次部署至 testing 和 production 時,都會建立一個套件執行個體,並使用對應的設定。只要在每次叫用 ext:migrate 或 functions:kits:install 時傳遞 --project 標記,現有的 CLI 指令就會建立這項設定。

範例:

firebase functions:kits:install --package @firebase-function-kits/firestore-bigquery-export --project testing --no-configure --template migration
✔ What would you like to name this kit? firestore-bigquery-export
✔ What would you like to name this instance? export-documents
✔  Wrote function-kits/firestore-bigquery-export/source/package.json
✔  Wrote function-kits/firestore-bigquery-export/source/tsconfig.json
✔  Wrote function-kits/firestore-bigquery-export/source/.gitignore
✔  Wrote function-kits/firestore-bigquery-export/source/src/index.ts
i  functions: Running npm install
✔  Wrote configuration info to firebase.json
✔  functions: Function kit firestore-bigquery-export successfully installed.
# This creates the export-documents instance with an empty .env.testing file
# for the testing project. Now populate it via export:
firebase ext:export --mode functions --instance export-documents-testing \
  --kit-instance export-documents --project testing

# Repeat the export for production into the same kit instance to create
# .env.production from the export-documents-prod instance:
firebase ext:export --mode functions --instance export-documents-prod \
  --kit-instance export-documents --project production

您現在已設定單一套件執行個體,可使用各自的設定部署至 testing 和 production 專案。如果您在 testing 專案中建立執行個體,並在 production 專案中為相同套件執行 functions:kits:install 指令,系統會提示您選擇重複使用為 testing 設定的執行個體,或安裝第二個執行個體。