環境を構成する


サードパーティの API キーや調整可能な設定など、関数に追加の構成が必要なことがよくあります。Firebase SDK for Cloud Functions では、プロジェクトのこのタイプのデータを簡単に保存および取得できるように、環境構成が組み込まれています。

次のオプションから選択できます。

  • パラメータ化された構成(ほとんどのシナリオで推奨)。このアプローチでは、デプロイ時に検証されるパラメータを使用して環境構成を厳密に型指定することで、エラーを防ぎ、デバッグを簡素化します。
  • ファイルベースの環境変数の構成。このアプローチでは、dotenv ファイルを手動で作成して環境変数を読み込みます。

ほとんどのユースケースでは、パラメータ化された構成が推奨されます。このアプローチでは、ランタイム時とデプロイ時の両方で構成値を使用できます。また、すべてのパラメータに有効な値が指定されていなければ、デプロイがブロックされます。一方、環境変数を使用した構成はデプロイ時に使用できません。

パラメータ化された構成

Cloud Functions for Firebase には、コードベース内で宣言的に構成パラメータを定義するインターフェースがあります。これらのパラメータの値は、関数のデプロイ時、デプロイ オプションとランタイム オプションの設定時、関数の実行時に使用できます。つまり、すべてのパラメータに有効な値が指定されていなければ、CLI でデプロイがブロックされます。

コード内でパラメータを定義するには、次のモデルを使用します。

const functions = require('firebase-functions/v1');
const { defineInt, defineString } = require('firebase-functions/params');

// Define some parameters
const minInstancesConfig = defineInt('HELLO_WORLD_MININSTANCES');
const welcomeMessage = defineString('WELCOME_MESSAGE');

// To use configured parameters inside the config for a function, provide them
// directly. To use them at runtime, call .value() on them.
export const helloWorld = functions.runWith({ minInstances: minInstancesConfig}).https.onRequest(
  (req, res) => {
    res.send(`${welcomeMessage.value()}! I am a function.`);
  }
);

パラメータ化された構成変数を使用して関数をデプロイすると、Firebase CLI は最初にローカルの .env ファイルから値を読み込もうとします。これらのファイル内に値が存在せず、default が設定されていない場合、値の入力を求めるプロンプトがデプロイ時に表示されます。入力された値は、functions/ ディレクトリの .env.<project_ID> という名前の .env ファイルに自動的に保存されます。

$ firebase deploy
i  functions: preparing codebase default for deployment
? Enter a string value for ENVIRONMENT: prod
i  functions: Writing new parameter values to disk: .env.projectId
…
$ firebase deploy
i  functions: Loaded environment variables from .env.projectId

開発ワークフローによっては、生成された .env.<project_ID> ファイルをバージョン管理に追加する方法が役立つことがあります。

グローバル スコープでのパラメータの使用

デプロイ中、パラメータに実際の値が設定される前に、関数コードが読み込まれて検査されます。つまり、グローバル スコープでパラメータ値を取得すると、デプロイが失敗します。パラメータを使用してグローバル値を初期化する場合は、初期化コールバック onInit() を使用します。このコールバックは、本番環境で関数が実行される前に実行されますが、デプロイ時に呼び出されないため、パラメータの値に安全にアクセスできます。

  const { GoogleGenerativeAI } = require('@google/generative-ai');
  const { defineSecret } = require('firebase-functions/params');
  const { onInit } = require('firebase-functions/v1');

  const apiKey = defineSecret('GOOGLE_API_KEY');

  let genAI;
  onInit(() => {
    genAI = new GoogleGenerativeAI(apiKey.value());
  })

CLI の動作を構成する

Options オブジェクトを使用してパラメータを構成し、値の入力を求める CLI の動作を制御することができます。次の例では、電話番号の形式を検証するオプション、簡単な選択オプションを提供するオプション、Firebase プロジェクトから選択オプションを自動的に入力するオプションを設定しています。

const { defineString } = require('firebase-functions/params');

const welcomeMessage = defineString('WELCOME_MESSAGE', {default: 'Hello World',
description: 'The greeting that is returned to the caller of this function'});

const onlyPhoneNumbers = defineString('PHONE_NUMBER', {input: {text:
{validationRegex: /\d{3}-\d{3}-\d{4}/, validationErrorMessage: "Please enter
a phone number in the format XXX-YYY-ZZZZ"}}});

const selectedOption = defineString('PARITY', {input: {select: {options:
[{value: "odd"}, {value: "even"}]}}})

const storageBucket = defineString('BUCKET', {input: {resource: {type:
"storage.googleapis.com/Bucket"}}, description: "This will automatically
populate the selector field with the deploying Cloud Project’s
storage buckets"})

パラメータ タイプ

パラメータ化された構成はパラメータ値を厳密に型指定し、Cloud Secret Manager のシークレットもサポートします。サポートされるタイプは次のとおりです。

  • シークレット
  • 文字列
  • ブール値
  • 整数
  • 浮動小数点数

パラメータ値と式

Firebase は、関数のデプロイ時と実行時にパラメータを評価します。こうしたデュアル環境では、パラメータ値を比較するときと、パラメータ値を使って関数のランタイム オプションを設定するときに特に注意が必要です。

ランタイム オプションとして関数にパラメータを渡すには、パラメータを直接渡します。

const functions = require('firebase-functions/v1');
const { defineInt} = require('firebase-functions/params');
const minInstancesConfig = defineInt('HELLO\_WORLD\_MININSTANCES');

export const helloWorld = functions.runWith({ minInstances: minInstancesConfig}).https.onRequest(
  (req, res) => {
    //…

また、選択するオプションを判断するためにパラメータと比較する必要がある場合は、値を確認する代わりに組み込みの比較演算子を使用する必要があります。

const functions = require('firebase-functions/v1');
const { defineBool } = require('firebase-functions/params');
const environment = params.defineString(ENVIRONMENT, {default: dev});

// use built-in comparators
const minInstancesConfig =environment.equals('PRODUCTION').thenElse(10, 1);
export const helloWorld = functions.runWith({ minInstances: minInstancesConfig}).https.onRequest(
  (req, res) => {
    //…

ランタイム時にのみ使用されるパラメータとパラメータ式には、value 関数でアクセスできます。

const functions = require('firebase-functions/v1');
const { defineString } = require('firebase-functions/params');
const welcomeMessage = defineString('WELCOME_MESSAGE');

// To use configured parameters inside the config for a function, provide them
// directly. To use them at runtime, call .value() on them.
export const helloWorld = functions.https.onRequest(
 (req, res) => {
    res.send(`${welcomeMessage.value()}! I am a function.`);
  }
);

組み込みパラメータ

Cloud Functions SDK の firebase-functions/params サブパッケージに、事前定義された 3 つのパラメータが用意されています。

  • projectID - 関数が実行されている Cloud プロジェクト。
  • databaseURL - 関数に関連付けられている Realtime Database インスタンスの URL(Firebase プロジェクトで有効な場合)。
  • storageBucket - 関数に関連付けられている Cloud Storage バケット(Firebase プロジェクトで有効な場合)。

これらは、あらゆる点でユーザー定義の文字列パラメータに類似しています。ただし、値は常に Firebase CLI に認識されているため、デプロイ時に値の入力を求められることも、.env ファイルに保存されることもありません。

シークレット パラメータ

defineSecret() を使用して定義される Secret タイプのパラメータで、Cloud Secret Manager に格納されている値を持つ文字列パラメータを表します。シークレット パラメータは、ローカルの .env ファイルを確認し、見つからない場合は新しい値をファイルに書き込むのではなく、Cloud Secret Manager 内に存在するかどうかを確認して、デプロイ時に新しいシークレット値の入力を求めるプロンプトをインタラクティブに出します。

この方法で定義するシークレット パラメータは、アクセスする必要のある関数ごとに個別にバインドする必要があります。

const functions = require('firebase-functions/v1');
const { defineSecret } = require('firebase-functions/params');
const discordApiKey = defineSecret('DISCORD_API_KEY');

export const postToDiscord = functions.runWith({ secrets: [discordApiKey] }).https.onRequest(
  (req, res) => {
    const apiKey = discordApiKey.value();
    //…

シークレットの値は関数の実行時まで非表示になるため、関数の構成中は使用できません。

環境変数

Cloud Functions for Firebase は、.env ファイルで指定した環境変数をアプリケーション ランタイムに読み込むために、dotenv ファイル形式をサポートしています。デプロイした後は、環境変数は process.env インターフェースを通じて読み取ることができます。

この方法で環境を構成するには、プロジェクトに .env ファイルを作成し、必要な変数を追加してデプロイします。

  1. functions/ ディレクトリに .env ファイルを作成します。

    # Directory layout:
    #   my-project/
    #     firebase.json
    #     functions/
    #       .env
    #       package.json
    #       index.js
    
  2. .env ファイルを開いて編集し、必要なキーを追加します。次に例を示します。

    PLANET=Earth
    AUDIENCE=Humans
    
  3. 関数をデプロイして、環境変数が読み込まれることを確認します。

    firebase deploy --only functions
    # ...
    # i functions: Loaded environment variables from .env.
    # ...
    

カスタム環境変数をデプロイした後、関数コードは process.env 構文を使用してカスタム環境変数にアクセスできます。

// Responds with "Hello Earth and Humans"
exports.hello = functions.https.onRequest((request, response) => {
  response.send(`Hello ${process.env.PLANET} and ${process.env.AUDIENCE}`);
});

環境変数の複数のセットのデプロイ

Firebase プロジェクトで環境変数の複数のセットが必要になる場合は(ステージング環境と本番環境など)、.env.<project or alias> ファイルを作成し、そこにプロジェクト固有の環境変数を記述します。.env の環境変数と、プロジェクト固有の .env ファイル(存在する場合)の環境変数が読み込まれ、デプロイされるすべての関数で利用できます。

たとえば、開発用と本番環境用でわずかに異なる値が含まれる 3 つのファイルを、1 つのプロジェクトに含めることができます。

.env .env.dev .env.prod
PLANET=Earth

AUDIENCE=Humans

AUDIENCE=Dev Humans AUDIENCE=Prod Humans

これらの個別のファイルに値があるため、関数とともにデプロイされる環境変数のセットは、ターゲット プロジェクトによって異なります。

$ firebase use dev
$ firebase deploy --only functions
i functions: Loaded environment variables from .env, .env.dev.
# Deploys functions with following user-defined environment variables:
#   PLANET=Earth
#   AUDIENCE=Dev Humans

$ firebase use prod
$ firebase deploy --only functions
i functions: Loaded environment variables from .env, .env.prod.
# Deploys functions with following user-defined environment variables:
#   PLANET=Earth
#   AUDIENCE=Prod Humans

予約済みの環境変数

一部の環境変数キーは内部使用のために予約されています。.env ファイル内で以下のキーを使用しないでください。

  • 先頭が X_GOOGLE_ であるすべてのキー
  • 先頭が EXT_ であるすべてのキー
  • 先頭が FIREBASE_ であるすべてのキー
  • 次のリストにあるキー:
  • CLOUD_RUNTIME_CONFIG
  • ENTRY_POINT
  • GCP_PROJECT
  • GCLOUD_PROJECT
  • GOOGLE_CLOUD_PROJECT
  • FUNCTION_TRIGGER_TYPE
  • FUNCTION_NAME
  • FUNCTION_MEMORY_MB
  • FUNCTION_TIMEOUT_SEC
  • FUNCTION_IDENTITY
  • FUNCTION_REGION
  • FUNCTION_TARGET
  • FUNCTION_SIGNATURE_TYPE
  • K_SERVICE
  • K_REVISION
  • ポート
  • K_CONFIGURATION

機密性の高い構成情報の保存とアクセス

.env ファイルに環境変数を保存してそれらを関数の構成に使用できますが、これはデータベースの認証情報や API キーなどの機密性の高い情報を安全に保存する方法ではありません。このことは、.env ファイルをソース管理にチェックインする場合に特に重要です。

機密性の高い構成情報を保存するため、Cloud Functions for FirebaseGoogle Cloud Secret Manager と統合されています。この暗号化されたサービスは、構成値を安全に保存すると同時に、必要に応じて関数から簡単にアクセスできるようにもしています。

シークレットの作成と使用

シークレットを作成するには、Firebase CLI を使用します。

シークレットを作成して使用するには:

  1. ローカル プロジェクト ディレクトリのルートから次のコマンドを実行します。

    firebase functions:secrets:set SECRET_NAME

  2. SECRET_NAME の値を入力します。

    CLI に成功を示すメッセージがエコーされ、変更を有効にするには関数をデプロイする必要があるという警告が表示されます。

  3. デプロイする前に、関数コードで runWith パラメータを使用してシークレットにアクセスできるようにしていることを確認してください。

    exports.processPayment = functions
      // Make the secret available to this function
      .runWith({ secrets: ["SECRET_NAME"] })
      .onCall((data, context) => {
        const myBillingService = initializeBillingService(
          // reference the secret value
          process.env.SECRET_NAME
        );
        // Process the payment
      });
  4. Cloud Functions をデプロイします。

    firebase deploy --only functions

これで、他の環境変数と同様にシークレットにアクセスできるようになります。runWith でシークレットを指定していない別の関数がシークレットにアクセスしようとすると、未定義の値が返されます。

  exports.anotherEndpoint = functions.https.onRequest((request, response) => {
    response.send(`The secret API key is ${process.env.SECRET_NAME}`);
    // responds with "The secret API key is undefined" because the `runWith` parameter is missing
  });

関数がデプロイされると、シークレット値にアクセスできるようになります。runWith パラメータにシークレットを明示的に含んでいる関数だけが、環境変数としてそのシークレットにアクセスできます。これにより、シークレット値は必要な場合のみ利用可能になるため、誤ってシークレットが漏洩するリスクを軽減できます。

シークレットの管理

シークレットの管理には Firebase CLI を使用します。この方法でシークレットを管理できますが、CLI で変更を行うと、場合によっては関連する関数の変更や再デプロイが必要になることに注意してください。特に、以下の点に注意してください。

  • シークレットに新しい値を設定した場合、関数で新しい値を取得するには、そのシークレットを参照するすべての関数を再デプロイする必要があります。
  • シークレットを削除する場合は、デプロイされている関数の中に、そのシークレットを参照しているものがないことを確認してください。削除されたシークレット値を使用する関数は正しく動作せず、その旨が通知されることもありません。

シークレットを管理するための Firebase CLI コマンドの概要は次のとおりです。

# Change the value of an existing secret
firebase functions:secrets:set SECRET_NAME

# View the value of a secret
functions:secrets:access SECRET_NAME

# Destroy a secret
functions:secrets:destroy SECRET_NAME

# View all secret versions and their state
functions:secrets:get SECRET_NAME

# Automatically clean up all secrets that aren't referenced by any of your functions
functions:secrets:prune

access コマンドと destroy コマンドでは、オプションのバージョン パラメータを指定することによって、特定のバージョンを管理できます。次に例を示します。

functions:secrets:access SECRET_NAME[@VERSION]

これらのオペレーションの詳細については、コマンドで -h を渡すと CLI のヘルプが表示されます。

シークレットの課金の仕組み

Secret Manager では、6 個のアクティブなシークレット バージョンを無料で使用できます。つまり、1 つの Firebase プロジェクトで 1 か月あたり 6 個のシークレットを無料で使用できます。

デフォルトでは、Firebase CLI は状況に応じて未使用のシークレット バージョンを自動的に破棄することを試みます。たとえば、新しいバージョンのシークレットを使用する関数をデプロイする場合などがこの状況に相当します。また、functions:secrets:destroyfunctions:secrets:prune を使用して、未使用のシークレットを自らクリーンアップすることもできます。

Secret Manager では、1 つのシークレットに対して毎月 10,000 回のアクセス オペレーションを請求なしで使用できます。関数インスタンスは、コールド スタートするたびに、runWith パラメータで指定されたシークレットのみを読み取ります。多くの関数インスタンスがあり、多くのシークレットを読み取る場合、プロジェクトでこの割り当て量を超過する可能性があります。超過した時点から、アクセス オペレーション 10,000 回あたり $0.03 が課金されます。

詳細は、Secret Manager の料金をご覧ください。

エミュレータのサポート

dotenv を使用する環境構成は、ローカルの Cloud Functions エミュレータと相互運用するように設計されています。

ローカルの Cloud Functions エミュレータを使用する場合は、.env.local ファイルを設定することで、プロジェクトの環境変数をオーバーライドできます。.env.local の内容は、.env ファイルおよびプロジェクト固有の .env ファイルよりも優先されます。

たとえば、開発用とローカルテスト用にわずかに異なる値が含まれる 3 つのファイルを 1 つのプロジェクトに含めたとします。

.env .env.dev .env.local
PLANET=Earth

AUDIENCE=Humans

AUDIENCE=Dev Humans AUDIENCE=Local Humans

ローカル コンテキストで起動された場合、エミュレータが読み込む環境変数は次のようになります。

  $ firebase emulators:start
  i  emulators: Starting emulators: functions
  # Starts emulator with following environment variables:
  #  PLANET=Earth
  #  AUDIENCE=Local Humans

Cloud Functions エミュレータ内のシークレットと認証情報

Cloud Functions エミュレータは、機密性の高い構成情報の保存とアクセスを可能にするために、シークレットの使用をサポートします。デフォルトでは、エミュレータはアプリケーションのデフォルト認証情報を使用して、本番環境のシークレットにアクセスしようと試みます。CI 環境などの特定の状況では、権限の制限により、エミュレータがシークレット値にアクセスできない場合があります。

Cloud Functions エミュレータによる環境変数のサポートと同様に、.secret.local ファイルを設定してシークレット値をオーバーライドできます。これにより、実際のシークレット値にアクセスできない場合であっても、関数をローカルで簡単にテストできます。

環境構成からの移行

functions.config で環境構成を使用している場合は、既存の構成を環境変数(dotenv 形式)として移行できます。Firebase CLI には、ディレクトリの .firebaserc ファイルに記載されている各エイリアスまたはプロジェクトの構成(次の例では localdevprod)を .env ファイルとして出力するエクスポート コマンドが用意されています。

移行するには、firebase functions:config:export コマンドを使用して既存の環境構成をエクスポートします。

firebase functions:config:export
i  Importing configs from projects: [project-0, project-1]
⚠  The following configs keys could not be exported as environment variables:

⚠  project-0 (dev):
    1foo.a => 1FOO\_A (Key 1FOO\_A must start with an uppercase ASCII letter or underscore, and then consist of uppercase ASCII letters, digits, and underscores.)

Enter a PREFIX to rename invalid environment variable keys: CONFIG\_
✔  Wrote functions/.env.prod
✔  Wrote functions/.env.dev
✔  Wrote functions/.env.local
✔  Wrote functions/.env

場合によっては、エクスポートする環境変数のキーの名前を変更するために、接頭辞の入力を求められることがあります。これは、構成が無効な場合や、予約済みの環境変数のキーを使用している場合などによって、すべての構成を自動で変換できるわけではないためです。

関数をデプロイしたり、.env ファイルをソース管理にチェックインしたりする前に、生成された .env ファイルの内容を十分に確認することをおすすめします。値が機密情報で漏洩することがあってはならない場合は、.env ファイルから削除し、代わりに Secret Manager で安全に保存してください。

また、関数コードを更新する必要もあります。第 2 世代にアップグレードするに示すように、functions.config を使用する関数はすべて process.env を使用する必要があります。

環境構成

CLI で環境構成を設定する

環境データを保存するには、Firebase CLIfirebase functions:config:set コマンドを使用します。関連する構成をグループ化するために、各キーにはピリオドを使用して名前空間を指定できます。キーには小文字しか使用できないことにご注意ください。大文字は使用できません。

たとえば、「Some Service」のクライアント ID と API キーを保存するには、以下を実行します。

firebase functions:config:set someservice.key="THE API KEY" someservice.id="THE CLIENT ID"

現在の環境構成を取得する

プロジェクトの環境構成に現在保存されているものを調べるには、firebase functions:config:get を使用します。以下のような JSON が出力されます。

{
  "someservice": {
    "key":"THE API KEY",
    "id":"THE CLIENT ID"
  }
}

この機能は Google Cloud ランタイム構成 API に基づいています。

functions.config を使用して関数で環境構成にアクセスする

一部の構成は、予約された firebase 名前空間の下で自動的に提供されます。環境構成は functions.config() を介して実行中の関数内で使用可能になります。上記の構成を使用する場合、コードは以下のようになります。

const functions = require('firebase-functions/v1');
const request = require('request-promise');

exports.userCreated = functions.database.ref('/users/{id}').onWrite(event => {
  let email = event.data.child('email').val();

  return request({
    url: 'https://someservice.com/api/some/call',
    headers: {
      'X-Client-ID': functions.config().someservice.id,
      'Authorization': `Bearer ${functions.config().someservice.key}`
    },
    body: {email: email}
  });
});

環境構成を使用してモジュールを初期化する

一部の Node モジュールは、構成しなくても準備ができています。バックエンドに接続し、正しく初期化するために追加の構成が必要なモジュールもあります。この構成を、ハードコード化するのではなく、環境変数に保存することをおすすめします。これにより、コードを移植性の高い状態に保ち、アプリケーションをオープンソースにしたり、本番バージョンとステージング バージョンを簡単に切り替えたりすることができます。

たとえば、Slack Node SDK モジュールを使用するには、次のように記述します。

const functions = require('firebase-functions/v1');
const IncomingWebhook = require('@slack/client').IncomingWebhook;
const webhook = new IncomingWebhook(functions.config().slack.url);

デプロイする前に、以下の slack.url 環境構成変数を設定します。

firebase functions:config:set slack.url=https://hooks.slack.com/services/XXX

その他の環境コマンド

  • firebase functions:config:unset key1 key2 は、指定されたキーを構成から削除します。
  • firebase functions:config:clone --from <fromProject> は、別のプロジェクトの環境を現在アクティブなプロジェクトにクローンします。

自動的に設定される環境変数

関数のランタイムやローカルでエミュレートされる関数で自動的に設定される環境変数があります。これには、Google Cloud によって設定される環境変数と、次の Firebase 固有の環境変数があります。

process.env.FIREBASE_CONFIG: 次のような Firebase プロジェクトの構成情報を提供します。

{
  databaseURL: 'https://databaseName.firebaseio.com',
  storageBucket: 'projectId.appspot.com',
  projectId: 'projectId'
}

引数を指定せずに Firebase Admin SDK を初期化すると、この構成が自動的に適用されます。関数を JavaScript で記述する場合は、次のように初期化します。

const admin = require('firebase-admin');
admin.initializeApp();

関数を TypeScript で作成する場合は、次のように初期化します。

import * as functions from 'firebase-functions/v1';
import * as admin from 'firebase-admin';
import 'firebase-functions/v1';
admin.initializeApp();

サービス アカウントの認証情報を使用して、デフォルトのプロジェクト構成の Admin SDK を初期化する必要がある場合は、次のようにファイルから認証情報を読み込み、FIREBASE_CONFIG に追加します。

serviceAccount = require('./serviceAccount.json');

const adminConfig = JSON.parse(process.env.FIREBASE_CONFIG);
adminConfig.credential = admin.credential.cert(serviceAccount);
admin.initializeApp(adminConfig);