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

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

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

이 가이드에서는 Stream Firestore to 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

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

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

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

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

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

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

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

작업 예시: Stream Firestore to BigQuery

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

extension.yaml 개수 / 값 이동 위치
params 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) Cloud Functions 매개변수 (섹션 5)
apis bigquery.googleapis.com requiresAPI(...) (섹션 7)
roles bigquery.dataEditor, datastore.user, bigquery.user requiresRole(...) (섹션 7)
resources 이벤트 트리거 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를 피어 종속 항목으로 선언합니다.

작업 예시: Stream Firestore to 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"
  }
}

이후. 게시 가능한 패키지: 범위 지정 이름, 내보내기 맵, 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);
  });

이후. 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에서 값을 읽거나 배포 중에 사용자에게 메시지를 표시합니다. 기존 설치의 값이 전달되도록 동일한 매개변수 이름을 유지합니다.

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

작업 예시: Stream Firestore to 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,

이후. 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에서 유형: 보안 비밀로 보안 비밀을 선언합니다. 확장 프로그램 런타임이 이를 저장하고 결합하므로 확장 프로그램 코드가 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,

이후. 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부터는 필수가 아니며 권장되지도 않습니다. 이제 태스크 큐 API는 기본적으로 동일한 컨텍스트(예: 확장 프로그램)의 태스크 큐를 타겟팅합니다. 확장 프로그램 및 독립형 함수로 코드에서 이 매개변수를 삭제하는 것이 안전하며 권장됩니다. 이 매개변수를 삭제하면 이동성과 전달 호환성이 보장됩니다.

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

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

이전. 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);

이후. 2세대 확장 프로그램

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

const queue = getFunctions().taskQueue(
  `locations/${process.env.FUNCTION_REGION}/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가 더 좁은 모델을 지원하지 않는 한 코드베이스의 모든 함수가 이러한 역할로 실행된다는 것을 사용자에게 문서화합니다.

작업 예시: Stream Firestore to BigQuery

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

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

이후. 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

작업 예시: Stream Firestore to BigQuery

이전. 확장 프로그램 런타임에 의해 결정되는 extension.yamllifecycleEvents:

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

이후. 코드에서 선언됨. 태스크는 첫 번째 배포에서 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

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

  • 결제 메모

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

작업 예시: Stream Firestore to BigQuery

패키지 README는 구체적인 '변경사항' 표를 제공합니다.

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

2세대 함수 테스트

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

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

firebase deploy --only functions

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

작업 예시: Stream Firestore to BigQuery

Cloud Firestore to BigQuery 동기화를 엔드 투 엔드로 확인합니다.

  1. Cloud Firestore 콘솔에서 COLLECTION_PATH (사용자)로 설정한 컬렉션이 아직 없는 경우 만듭니다.
  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를 사용하여 코드를 단위 테스트할 수도 있습니다.Cloud Functions
  • 이제 프로비저닝은 확장 프로그램 런타임에 의해 결정되지 않습니다. 배포 후 변경 로그 테이블이 누락된 경우 설정 태스크를 수동으로 다시 실행합니다. firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. 태스크는 멱등원이므로 다시 실행하면 데이터 세트, 테이블, 뷰가 조정됩니다.
  • 매개변수 값은 설치 양식이 아닌 .env에서 가져오므로 .env가 완료되면 firebase deploy를 다시 실행해도 상호작용이 없습니다.