Cloud Functions로 마이그레이션할 Firebase Extensions 준비

이 가이드에서는 지원 중단된 Firebase Extensions 환경에서 사용자가 Firebase (2세대) 코드베이스를 위해 자체 Cloud Functions에 설치하고 배포하는 함수로 확장 프로그램을 이전하는 방법을 보여줍니다.

권장되는 이전 경로입니다. Firebase에서는 공식 npm에 상응하는 확장 프로그램 목록을 유지합니다. 이 가이드에서는 확장 프로그램을 만드는 방법을 안내합니다.

이 가이드에서는 Firestore를 BigQuery로 스트리밍 확장 프로그램 (firestore-bigquery-export)을 예로 사용합니다. 각 섹션은 이전 후의 확장 프로그램을 @firebase/firestore-bigquery-export 패키지로 보여주는 작업 예시로 끝납니다.

확장 프로그램 이전에 관한 자세한 정보와 도움을 받으려면 가입하세요.

Firebase Extensions에서 마이그레이션하는 방법에 관해 궁금한 점이 있으면 firebase-extensions-migrator-support-external@google.com으로 문의해 주세요. 또한 2세대 함수 패키징, 테스트, 배포에 관한 자세한 정보로 가이드를 업데이트할 때 이 그룹에 이메일을 보내드릴 예정입니다.

이 그룹에 참여하려면 firebase-extensions-migrator-support-external+subscribe@google.com에 메시지를 보내세요. 그러면 회원 가입 요청 이메일이 전송됩니다. '이 그룹에 참여' 버튼을 클릭하지 말고 이메일에 답장해야 합니다.

시작하기 전에

이 마이그레이션을 완료하려면 Cloud Functions의 다음 기능을 사용합니다.

  • 매개변수화된 구성. extension.yaml에서 선언한 각 매개변수는 패키지 코드에서 정의된 매개변수가 됩니다.

  • 선언적 IAM 역할 및 필수 API extension.yaml에서 선언한 각 역할은 requiresRole(...) 호출이 되고 각 API는 함수 코드에서 requiresAPI(...) 호출이 됩니다. 배포 시 Firebase CLI는 선언된 역할을 관리형 런타임 서비스 계정에 부여하고 선언된 API를 대신 사용 설정합니다.

  • Cloud Functions 코드베이스의 수명 주기 이벤트 이제 Cloud Functions 코드베이스가 Firebase Extensions와 유사한 수명 주기 이벤트를 지원합니다. 수명 주기 후크 afterFirstDeploy(...)afterRedeploy(...)를 사용하여 설치 시간 및 업데이트 시간 설정을 선언합니다. 이러한 항목은 extension.yaml에서 선언한 lifecycleEvents을 대체합니다.

확장 프로그램 인벤토리

먼저 확장 프로그램의 인벤토리를 작성합니다. 확장 프로그램이 선언하고, 제공하고, 문서화하는 모든 항목의 전체 목록으로, 모든 동작이 2세대 함수에서 정의된 대상을 갖도록 하고 이전 중에 손실되는 항목이 없도록 합니다.

다음을 각각 검토하고 확인한 내용을 기록합니다.

  • extension.yaml: 매개변수, 함수, 이벤트, IAM 역할, 필수 API, 보안 비밀, 수명 주기 후크를 선언합니다.

  • 함수 코드, 종속 항목, 빌드 구성, 트리거, 태스크 큐 함수가 포함된 functions/

  • 설정 단계, 경고, 결제 참고사항이 포함된 README.md, PREINSTALL.md, POSTINSTALL.md

  • scripts/: 가져오기, 백필, IAM, 복구 또는 이전 유틸리티와 확장 프로그램과 함께 제공되는 기타 도구가 포함됩니다.

그런 다음 extension.yaml의 각 항목에 대해 npm 패키지에서 항목이 어디로 이동하는지 결정합니다.

  • 사용자 구성을 Cloud Functions 매개변수 (섹션 5)로 변환합니다.

  • 보안 비밀을 Cloud Functions 보안 비밀로 변환합니다 (섹션 6).

  • IAM 역할을 requiresRole(...) 선언으로 변환합니다 (섹션 8).

  • 필수 Google API를 적절한 경우 requiresAPI(...) 선언으로 변환합니다 (섹션 8).

  • 설치 및 업데이트 후크를 afterFirstDeploy(...)afterRedeploy(...) 선언으로 변환합니다 (섹션 8).

작업 예시: Firestore를 BigQuery로 스트리밍

firestore-bigquery-export/extension.yaml 및 functions/ 를 읽으면 다음과 같은 인벤토리가 생성됩니다.

extension.yaml 개수 / 값 데이터가 이동하는 위치
params 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) Cloud Functions 매개변수 (섹션 5)
API bigquery.googleapis.com requiresAPI(...) (섹션 7)
역할 bigquery.dataEditor, datastore.user, bigquery.user requiresRole(...) (섹션 7)
리소스 이벤트 트리거 1개 (fsexportbigquery) + 태스크 큐 함수 (initBigQuerySync, setupBigQuerySync) 내보낸 패키지 함수 (섹션 3)
lifecycleEvents onInstall → initBigQuerySync, onUpdate / onConfigure → setupBigQuerySync afterFirstDeploy / afterRedeploy (섹션 9)
scripts/ import/ (백필), gen-schema-view/ 스크립트로 유지 (여기서는 범위 외)

확장 프로그램은 유형을 선언하지 않습니다(비밀 매개변수). 따라서 이 가이드의 섹션 6에서 이전할 항목이 없습니다. 이벤트 트리거는 이미 2세대입니다. 작업 대기열 함수만 아직 1세대입니다 (3절 참고).

package.json 업데이트

확장 프로그램의 package.json 파일을 업데이트합니다. 확장 프로그램 하나를 이전하는 경우 루트 package.json일 수 있습니다. 하나의 저장소에서 여러 확장 프로그램을 이전하는 경우 각 확장 프로그램에 자체 패키지를 부여하세요.

최소 SDK 버전입니다. firebase-functions >= 7.3 및 firebase-admin >= 14.2.0을 종속 항목으로 선언합니다. firebase-functions 버전을 피어 종속 항목으로도 선언합니다.

{
  "name": "<package-name>",
  "version": "1.0.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".": {
      "types": "./lib/index.d.ts",
      "default": "./lib/index.js"
    }
  },
  "engines": {
    "node": ">=22"
  },
  "peerDependencies": {
    "firebase-functions": "^7.3.0"
  },
  "dependencies": {
    "firebase-functions": "^7.3.0",
    "firebase-admin": "^14.2.0"
  }
}

사용자의 Cloud Functions 프로젝트에 라이브러리가 작성된 SDK와 동일한 버전이 있도록 일반 종속 항목 외에 firebase-functions를 피어 종속 항목으로 선언합니다.

작업 예시: Firestore를 BigQuery로 스트리밍

이전. 확장 프로그램의 functions/package.json은 비공개이며, 확장 프로그램 ID를 지정하고 firebase-functions를 직접 종속 항목으로 선언합니다.

{
  "name": "firestore-bigquery-export",
  "main": "lib/index.js",
  "private": true,
  "dependencies": {
    "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
    "firebase-admin": "^14.2.0",
    "firebase-functions": "^6.3.2"
  }
}

After 게시 가능한 패키지: 범위가 지정된 이름, 내보내기 맵, firebase-functionspeerDependencies로 이동됨:

{
  "name": "@firebase/firestore-bigquery-export",
  "version": "0.1.0",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".":     { "types": "./lib/index.d.ts", "default": "./lib/index.js" },  },
  "engines": { "node": ">=22" },
  "peerDependencies": { "firebase-functions": "^7.3.0" },
  "dependencies": {
      "@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
      "firebase-admin": "^14.2.0",
      "firebase-functions": "^7.3.0"
    }
}

1세대에서 2세대로 함수 업그레이드

확장 프로그램이 여전히 1세대 함수를 내보내는 경우 각 함수를 2세대 함수로 변환합니다. firebase-functions/... 모듈에서 가져오고 함수 옵션에서 런타임 설정을 전달합니다.

2세대 패치 이벤트 구조 분해를 사용하면 재작성 노력을 최소화할 수 있습니다. 2세대 SDK는 이제 V1 매개변수를 이벤트 객체의 필드로 노출하므로 구조 분해된/명명된 매개변수를 사용하고 비즈니스 로직을 변경하지 않아도 됩니다. 따라서 함수 로직을 재작성하지 않아도 됩니다.

이전. 1세대:

import * as functions from "firebase-functions/v1";

export const sync = functions.firestore
  .document("{collectionId}/{documentId}")
  .onWrite(async (change, context) => {
    await handleWrite(change.before, change.after, context.params);
  });

After 2세대:

import { onDocumentWritten } from "firebase-functions/firestore";

export const syncV2 = onDocumentWritten(
  { document: "{collectionId}/{documentId}" },
  async ({change,context}) =>
    await handleWrite(change.before, change.after, context.params);
);

1세대와 2세대 함수 간의 전체 차이점 목록은 Cloud Functions 버전 비교를 참고하세요.

확장 프로그램 매개변수 및 보안 비밀 변환

변환 매개변수

extension.yaml에서 선언한 각 매개변수는 Cloud Functions 매개변수가 됩니다.

직접 환경 읽기 변환:

const collectionPath = process.env.COLLECTION_PATH;

Cloud Functions 매개변수:

import { defineString } from "firebase-functions/params";
import { onDocumentWritten} from "firebase-functions/firestore";

const collectionPath = defineString("COLLECTION_PATH");

// Pass the param directly when used as a placeholder (e.g. trigger path)
export const sync = onDocumentWritten(
  { document: collectionPath },
  async (event) => {
    // Call .value() to read the string inside a handler
    const path = collectionPath.value();
    await handleWrite(path, event);
  }
);

핸들러 내의 문자열을 읽으려면 collectionPath.value()를 사용하고 함수 트리거 경로와 같이 자리표시자가 필요한 곳에서는 collectionPath를 직접 사용하세요.

Firebase CLI는 매개변수를 검색하고 .env, .env.projectId에서 값을 읽거나 배포 중에 사용자에게 메시지를 표시합니다. 기존 설치의 값이 유지되도록 동일한 매개변수 이름을 유지합니다.

코드에 선언된 매개변수 이름을 절대 변경하지 않는 것이 중요합니다. 확장 프로그램 이전은 기존 최종 사용자 매개변수 값을 자동으로 유지하지만 이름이 변경되지 않은 경우에만 가능합니다.

작동하는 예: Firestore를 BigQuery로 스트리밍 이전. extension.yaml에 선언되고 config.ts에서 원시 환경 변수로 읽히는 매개변수:

# extension.yaml
-   param: COLLECTION_PATH
  label: Collection path
  type: string
  required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,

After defineString 하나. CLI가 이를 검색하고 .env에서 읽습니다.

// src/config.ts
import { defineString } from "firebase-functions/params";

collectionPath: defineString("COLLECTION_PATH", {
  label: "Collection path",
  // We now support "nonEmpty: true" to ensure a value other than the empty string
  // is entered, analagous to "required: true" in extensions.yaml
  input: { text: { nonEmpty: true} }
}),

매개변수 이름은 변경되지 않으므로 기존 .env가 계속 작동합니다.

보안 비밀 변환

extension.yaml에서 유형이 secret인 보안 비밀을 선언합니다. 확장 프로그램 런타임은 이를 저장하고 바인딩하므로 확장 프로그램 코드가 process.env.PARAM_NAME을 직접 읽을 수 있습니다. 일반적인 Cloud Functions 코드베이스에서는 각 비밀을 명시적으로 선언하고 바인딩합니다.

import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";

const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);

확장 프로그램이 npm 패키지/키트로 이전되면 비밀 참조가 최종 사용자의 .env 파일에서 관리됩니다. 코드에 선언된 비밀 이름을 절대 변경하지 않는 것이 중요합니다. 이전하는 동안 최종 사용자 비밀번호가 적절하게 이전됩니다.

작동 예: Cloud Firestore에서 이메일 트리거

이전. config.ts에서 원시 환경 변수로 읽는 MAIL_COLLECTION 및 SMTP_PASSWORD:

# extension.yaml
-   param: MAIL_COLLECTION
  label: Email documents collection
  type: string
  default: mail
  required: true

-   param: SMTP_PASSWORD
  label: SMTP password
  type: secret
// functions/src/config.ts
mailCollection: process.env.MAIL_COLLECTION,
smtpPassword: process.env.SMTP_PASSWORD,

After defineString 하나와 defineSecret 하나. CLI가 둘 다 검색하고 .env에서 읽습니다.

import { defineString, defineSecret } from "firebase-functions/params";
import { onDocumentWritten } from "firebase-functions/firestore";

const mailCollection = defineString("MAIL_COLLECTION",{ label: "Email documents collection",
 default: "mail"
});

const smtpPassword = defineSecret("SMTP_PASSWORD", { label: "SMTP password" });

export const processQueue = onDocumentWritten(
  { document: `${mailCollection}/{documentId}`, secrets: [smtpPassword] },
  async (event) => {
    const collection = mailCollection.value();
    const password = smtpPassword.value();
    // ...
  }
);

내부 작업 대기열 호출 마이그레이션

일부 확장 프로그램은 Firebase Admin SDK를 사용하여 함수 코드 내부에서 자체 작업 대기열에 작업을 대기열에 추가합니다. 이는 디스패치된 작업을 수신하는 것과는 다릅니다 (함수 업그레이드수명 주기 후크 변환 섹션 참고). 여기서 코드는 queue.enqueue(...)를 호출하는 생산자입니다.

이전 버전의 Admin SDK에서는 동일한 확장 프로그램의 태스크 큐 함수를 타겟팅하기 위해 확장 프로그램이 자체 확장 프로그램 인스턴스 ID를 두 번째 매개변수로 전달해야 했습니다. `firebase-admin` 14.2.0부터는 필요하지도 않고 권장되지도 않습니다. 이제 Task Queue API는 기본적으로 동일한 컨텍스트(예: 확장 프로그램)의 태스크 큐를 타겟팅합니다. 확장 프로그램과 독립형 함수 모두에서 코드의 이 매개변수를 삭제해도 안전하며 삭제하는 것이 좋습니다. 이 매개변수를 삭제하면 이식성과 향후 호환성이 보장됩니다.

대기열에 추가 호출에 관한 다른 모든 항목(위치/region/함수/name 리소스 경로, 작업 페이로드, 재시도 로직)은 동일하게 유지됩니다.

Cloud Tasks로 함수를 대기열에 추가하는 방법에 관한 자세한 내용은 /docs/functions/task-functions를 참고하세요.

이전. 1세대 확장 프로그램

import { getFunctions } from "firebase-admin/functions";

const queue = getFunctions().taskQueue(
  `locations/${config.location}/functions/syncBigQuery`,
  process.env.EXT_INSTANCE_ID, // extension instance ID, injected by the runtime
);
await queue.enqueue(taskData);

After 2세대 확장 프로그램

import { getFunctions } from "firebase-admin/functions";
import { region } from "firebase-functions/params";

const queue = getFunctions().taskQueue(
  `locations/${region.value()}/functions/syncBigQuery`);
await queue.enqueue(taskData);

대기열에 추가 호출이 접두사가 지정된 코드베이스를 타겟팅하는 경우 검색된 함수 이름에도 접두사가 지정됩니다 (예: orders-syncBigQuery).

필수 API 및 IAM 역할 선언

확장 프로그램의 IAM 및 API 요구사항을 extension.yaml에서 코드로 이동합니다.

import { requiresAPI, requiresRole } from "firebase-functions"

requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");

선언적 보안을 사용하면 Firebase CLI가 코드베이스의 관리 런타임 서비스 계정을 만들거나 업데이트하고 선언된 모든 역할의 합집합을 부여합니다. 최종 API가 더 좁은 모델을 지원하지 않는 한 코드베이스의 모든 함수가 이러한 역할로 실행된다고 사용자에게 설명합니다.

작업 예시: Firestore를 BigQuery로 스트리밍

이전. extension.yaml에 선언됩니다. 확장 프로그램 런타임이 API를 사용 설정하고 관리 계정에 역할을 부여했습니다.

apis:
  -   apiName: bigquery.googleapis.com
roles:
  -   role: bigquery.dataEditor
  -   role: datastore.user
  -   role: bigquery.user

After requiresAPI 및 requiresRole을 사용하여 코드에서 선언됩니다.

import { requiresAPI, requiresRole } from "firebase-functions/";
requiresAPI("bigquery.googleapis.com",
  "Needed to write changelog rows and views");
requiresRole("roles/biguqery.dataEditor");
requiresRole("roles/datastore.user");
requiresRole("roles/bigquery.user");

수명 주기 후크 변환

확장 프로그램이 getExtensions().runtime()(예: setProcessingState 또는 setFatalError)을 호출하는 경우 이러한 호출을 삭제하세요. 일반적으로 배포된 2세대 함수에서 호출되면 오류가 발생합니다. 이제 수명 주기 상태는 이 상태 추적이 사용되지 않는 afterFirstDeployafterRedeploy에 의해 결정됩니다.

Firebase Extensions는 사용자가 확장 프로그램을 설치, 업데이트 또는 재구성할 때 설정을 실행할 수 있습니다. npm 패키지에서 코드에 상응하는 수명 주기 작업을 선언합니다.

일회성 설정의 경우:

import { afterFirstDeploy } from "firebase-functions/lifecycle";
import { onTaskDispatched } from "firebase-functions/tasks";

export const runInitialSetup = onTaskDispatched(async (request) => {
  await initializeResources(request.data);
});

afterFirstDeploy({
  task: {
    function: "runInitialSetup",
    body: {}
  }
});

구성 또는 코드 업데이트의 경우:

import { afterRedeploy } from "firebase-functions/lifecycle";

afterRedeploy({
  task: {
    function: "runInitialSetup",
    body: { reconcile: true }
  }
});

수명 주기 작업을 멱등성으로 만드세요. 전송 또는 실행이 실패하면 사용자가 수동으로 다시 실행해야 할 수 있습니다.

firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME

작업 예시: Firestore를 BigQuery로 스트리밍

이전. 확장 프로그램 런타임에 의해 extension.yaml에서 lifecycleEvents이 실행됩니다.

lifecycleEvents:
  onInstall:
    function: initBigQuerySync
    processingMessage: Configuring BigQuery Sync.
  onUpdate:
    function: setupBigQuerySync
    processingMessage: Configuring BigQuery Sync
  onConfigure:
    function: setupBigQuerySync
    processingMessage: Configuring BigQuery Sync

After 코드에 선언됩니다. 작업은 첫 번째 배포 시 BigQuery를 프로비저닝합니다.

import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";

afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });

프로비저닝은 동일한 결과를 반환하므로 다시 실행하면 데이터 세트, 테이블, 뷰가 조정됩니다. 사용자는 firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME을 사용하여 수동으로 다시 실행할 수 있습니다.

사용자를 위한 문서 설정

  • 최소한 다음을 설명하는 패키지 README를 작성합니다.

  • 패키지에 필요한 .env 값입니다.

  • 패키지에 필요한 보안 비밀과 기존 보안 비밀 값을 이전하는 방법

  • 패키지가 requiresRole(...)로 선언하는 IAM 역할입니다.

  • 패키지에서 사용 설정하거나 요구하는 Google API입니다.

  • 패키지가 선언하는 수명 주기 후크와 이를 수동으로 다시 실행하는 방법

  • 결제 메모

  • 원래 확장 프로그램과 비교하여 변경된 사항입니다.

작업 예시: Firestore를 BigQuery로 스트리밍

패키지 README에는 구체적인 '변경사항' 표가 포함됩니다.

문제 확장 프로그램으로 @firebase/firestore-bigquery-export
구성 확장 프로그램 매개변수 .env를 통한 함수 매개변수
IAM 확장 프로그램에서 부여 배포 시 적용되는 requiresRole(...)
프로비저닝 확장 프로그램별 수명 주기 작업 afterFirstDeploy / afterRedeploy 작업
함수 이름 ext-instanceId-fsexportbigquery fsexportbigquery (선택적으로 접두사 지정)

2세대 함수 테스트

이제 배포 시 확장 프로그램의 새 설치와 동일하게 동작하는 2세대 함수가 있습니다. 마지막 단계는 실수로 도입된 문제를 확인하고 수정하는 것입니다.

firebase-tools >= 15.24.0을 사용하고 변환된 2세대 함수를 동작을 테스트하기 위한 적절한 리소스가 있는 테스트 프로젝트에 배포합니다. 확장 프로그램 테스트에서 이미 테스트 프로젝트를 설정한 경우 다음 명령어를 사용하세요.

firebase deploy --only functions

이 명령어를 입력한 후 매개변수 값을 묻는 결과 마법사를 확장 프로그램의 Firebase 콘솔에서 설치 양식을 작성한 것과 동일한 방식으로 작성합니다.

작업 예시: Firestore를 BigQuery로 스트리밍

Google에서는 Cloud Firestore~BigQuery 동기화를 엔드 투 엔드로 확인합니다.

  1. Cloud Firestore 콘솔에서 COLLECTION_PATH(users)로 설정한 컬렉션을 만듭니다(아직 없는 경우).
  2. 원하는 값이 있는 필드가 포함된 bigquery-mirror-test라는 문서를 만듭니다.
  3. BigQuery 콘솔에서 원시 변경 로그 테이블을 쿼리합니다. 문서 생성을 로깅하는 단일 행이 포함되어야 합니다.
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
  1. 최신 뷰를 쿼리합니다. 그러면 유일한 문서인 bigquery-mirror-test에 대한 최신 변경 이벤트가 반환됩니다.
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
  1. Cloud Firestore에서 bigquery-mirror-test 문서를 삭제합니다. 최신 뷰에서 사라지고 DELETE 이벤트가 원시 변경 로그 표에 추가됩니다.

다음 명령어를 사용하여 단일 문서의 전체 기록을 검사할 수 있습니다.

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

확장 프로그램 테스트와의 차이점:

  • 트리거는 ext-&lt;instanceId&gt;-fsexportbigquery이 아닌 fsexportbigquery (선택적으로 코드베이스 접두사)로 배포됩니다. Cloud Functions 대시보드와 로그에서 해당 이름을 찾습니다.
  • 이제 코드가 Firebase Local Emulator Suite에서 일반 함수로 실행됩니다. .env.local를 사용하여 에뮬레이터에서 사용할 매개변수의 값을 설정할 수 있습니다. Cloud Functions 단위 테스트에 설명된 대로 firebase-functions-test SDK를 사용하여 코드를 단위 테스트할 수도 있습니다.
  • 프로비저닝은 더 이상 확장 프로그램 런타임에 의해 실행되지 않습니다. 배포 후 변경사항 로그 표가 누락된 경우 설정 작업을 수동으로 다시 실행합니다(firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME). 이 작업은 멱등성이 있으므로 다시 실행하면 데이터 세트, 테이블, 뷰가 조정됩니다.
  • 매개변수 값은 설치 양식이 아닌 .env에서 가져오므로 .env가 완료되면 firebase deploy의 재실행은 비대화형입니다.