您往往需要对您的函数进行额外配置,例如第三方 API 密钥或可调整的设置。Firebase SDK for Cloud Functions 提供了内置环境配置,让您可以轻松为项目存储和检索此类数据。
您可以从以下选项中进行选择:
- 参数化配置(推荐用于大多数情况)。这是一种强类型的环境配置方法,会在部署时对参数进行验证,这样可以防止一些错误的发生并简化调试过程。
- 基于文件的环境变量配置。如果使用此方法,您可以手动创建用于加载环境变量的 dotenv 文件。
对于大多数使用场景,建议使用参数化配置。此方法使得配置值在运行时和配置时都可使用;并且除非所有参数都具有有效值,否则系统将阻止部署。与之相反,使用环境变量方法进行的配置在部署时则无法使用。
参数化配置
Cloud Functions for Firebase 提供了一个用于在代码库中以声明方式定义配置参数的接口。这些参数的值可在函数部署期间(设置部署和运行时选项时)以及在执行过程中使用。这意味着,除非所有参数都具有有效值,否则 CLI 将阻止部署。
Node.js
const { onRequest } = require('firebase-functions/v2/https');
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 = onRequest(
{ minInstances: minInstancesConfig },
(req, res) => {
res.send(`${welcomeMessage.value()}! I am a function.`);
}
);
Python
from firebase_functions import https_fn
from firebase_functions.params import IntParam, StringParam
MIN_INSTANCES = IntParam("HELLO_WORLD_MIN_INSTANCES")
WELCOME_MESSAGE = StringParam("WELCOME_MESSAGE")
# To use configured parameters inside the config for a function, provide them
# directly. To use them at runtime, call .value() on them.
@https_fn.on_request(min_instances=MIN_INSTANCES)
def hello_world(req):
return https_fn.Response(f'{WELCOME_MESSAGE.value()}! I am a function!')
使用参数化配置变量部署函数时,Firebase CLI 会先尝试从本地 .env 文件加载其值。如果这些文件中不存在所需值,并且未设置 default
,CLI 将在部署过程中提示您输入值,然后自动将其值保存到您的 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()
。此回调会在任何函数在生产环境中运行之前运行,但不会在部署期间调用,因此是访问参数值的安全位置。
Node.js
const { GoogleGenerativeAI } = require('@google/generative-ai');
const { defineSecret } = require('firebase-functions/params');
const { onInit } = require('firebase-functions/v2/core');
const apiKey = defineSecret('GOOGLE_API_KEY');
let genAI;
onInit(() => {
genAI = new GoogleGenerativeAI(apiKey.value());
})
Python
from firebase_functions.core import init
from firebase_functions.params import StringParam, PROJECT_ID
import firebase_admin
import vertexai
location = StringParam("LOCATION")
x = "hello"
@init
def initialize():
# Note: to write back to a global, you'll need to use the "global" keyword
# to avoid creating a new local with the same name.
global x
x = "world"
firebase_admin.initialize_app()
vertexai.init(PROJECT_ID.value, location.value)
如果您使用的是 Secret
类型的参数,请注意,这些参数仅在已绑定 Secret 的函数进程中可用。如果某个 Secret 仅在某些函数中绑定,请在使用 secret.value()
之前检查其是否为 falsy 值。
配置 CLI 行为
您可以使用 Options
对象配置参数来控制 CLI 提示您输入值的方式。以下示例会设置选项来验证电话号码的格式、提供简单的选择选项,并会自动从 Firebase 项目填充选择选项:
Node.js
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: params.select(["odd", "even"])});
const memory = defineInt("MEMORY", {
description: "How much memory do you need?",
input: params.select({ "micro": 256, "chonky": 2048 }),
});
const extensions = defineList("EXTENSIONS", {
description: "Which file types should be processed?",
input: params.multiSelect(["jpg", "tiff", "png", "webp"]),
});
const storageBucket = defineString('BUCKET', {
description: "This will automatically
populate the selector field with the deploying Cloud Project’s
storage buckets",
input: params.PICK_STORAGE_BUCKET,
});
Python
from firebase_functions.params import (
StringParam,
ListParam,
TextInput,
SelectInput,
SelectOptions,
ResourceInput,
ResourceType,
)
MIN_INSTANCES = IntParam("HELLO_WORLD_MIN_INSTANCES")
WELCOME_MESSAGE = StringParam(
"WELCOME_MESSAGE",
default="Hello World",
description="The greeting that is returned to the caller of this function",
)
ONLY_PHONE_NUMBERS = StringParam(
"PHONE_NUMBER",
input=TextInput(
validation_regex="\d{3}-\d{3}-\d{4}",
validation_error_message="Please enter a phone number in the format XXX-YYY-XXX",
),
)
SELECT_OPTION = StringParam(
"PARITY",
input=SelectInput([SelectOptions(value="odd"), SelectOptions(value="even")]),
)
STORAGE_BUCKET = StringParam(
"BUCKET",
input=ResourceInput(type=ResourceType.STORAGE_BUCKET),
description="This will automatically populate the selector field with the deploying Cloud Project's storage buckets",
)
参数类型
参数化配置可提供强类型的参数值,并且还支持来自 Cloud Secret Manager 的 Secret。支持的类型包括:
- Secret
- 字符串
- 布尔值
- 整数
- 浮点数
- 列表 (Node.js)
参数值和表达式
Firebase 会在部署时和在函数执行期间评估参数。出于这种双重环境考虑,在比较参数值和使用它们来为函数设置运行时选项时,必须格外小心。
如需将参数作为运行时选项传递给函数,直接传递即可:
Node.js
const { onRequest } = require('firebase-functions/v2/https');
const { defineInt } = require('firebase-functions/params');
const minInstancesConfig = defineInt('HELLO\_WORLD\_MININSTANCES');
export const helloWorld = onRequest(
{ minInstances: minInstancesConfig },
(req, res) => {
//…
Python
from firebase_functions import https_fn
from firebase_functions.params import IntParam
MIN_INSTANCES = IntParam("HELLO_WORLD_MIN_INSTANCES")
@https_fn.on_request(min_instances=MIN_INSTANCES)
def hello_world(req):
...
但是,若您需要针对某个参数进行比较,以了解应该选择哪个选项,则需要使用内置比较器,而不仅仅是检查值:
Node.js
const { onRequest } = require('firebase-functions/v2/https');
const environment = params.defineString(‘ENVIRONMENT’, {default: 'dev'});
// use built-in comparators
const minInstancesConfig = environment.equals('PRODUCTION').thenElse(10, 1);
export const helloWorld = onRequest(
{ minInstances: minInstancesConfig },
(req, res) => {
//…
Python
from firebase_functions import https_fn
from firebase_functions.params import IntParam, StringParam
ENVIRONMENT = StringParam("ENVIRONMENT", default="dev")
MIN_INSTANCES = ENVIRONMENT.equals("PRODUCTION").then(10, 0)
@https_fn.on_request(min_instances=MIN_INSTANCES)
def hello_world(req):
...
只在运行时使用的参数和参数表达式可以通过其 value
函数访问:
Node.js
const { onRequest } = require('firebase-functions/v2/https');
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 = onRequest(
(req, res) => {
res.send(`${welcomeMessage.value()}! I am a function.`);
}
);
Python
from firebase_functions import https_fn
from firebase_functions.params import StringParam
WELCOME_MESSAGE = StringParam("WELCOME_MESSAGE")
@https_fn.on_request()
def hello_world(req):
return https_fn.Response(f'{WELCOME_MESSAGE.value()}! I am a function!')
内置参数
Cloud Functions SDK 提供三个预定义参数,可通过 firebase-functions/params
子软件包获取:
Node.js
projectID
- 运行函数的 Cloud 项目。databaseURL
- 与函数关联的 Realtime Database 实例的网址(如果已在 Firebase 项目中启用)。storageBucket
- 与函数关联的 Cloud Storage 存储桶(如果已在 Firebase 项目中启用)。
Python
PROJECT_ID
- 运行函数的 Cloud 项目。DATABASE_URL
- 与函数关联的 Realtime Database 实例的网址(如果已在 Firebase 项目中启用)。STORAGE_BUCKET
- 与函数关联的 Cloud Storage 存储桶(如果已在 Firebase 项目中启用)。
这些函数在各个方面都类似于用户定义的字符串参数,只是由于 Firebase CLI 始终知道它们的值,因此系统永远不会在部署时提示您输入其值,也不会将其值保存到 .env
文件中。
Secret 参数
使用 defineSecret()
定义的 Secret
类型的参数表示具有存储在 Cloud Secret Manager 中的值的字符串参数。Secret 参数会检查在 Cloud Secret Manager 中是否存在所需值,并在部署时以交互方式提示您输入新的 Secret 值,而不会针对本地 .env
文件进行检查并在值缺失时将新值写入文件。
以这种方式定义的 Secret 参数必须绑定到应该有权访问它们的各个函数:
Node.js
const { onRequest } = require('firebase-functions/v2/https');
const { defineSecret } = require('firebase-functions/params');
const discordApiKey = defineSecret('DISCORD_API_KEY');
export const postToDiscord = onRequest(
{ secrets: [discordApiKey] },
(req, res) => {
const apiKey = discordApiKey.value();
//…
Python
from firebase_functions import https_fn
from firebase_functions.params import SecretParam
DISCORD_API_KEY = SecretParam('DISCORD_API_KEY')
@https_fn.on_request(secrets=[DISCORD_API_KEY])
def post_to_discord(req):
api_key = DISCORD_API_KEY.value
由于 Secret 的值在函数执行之前会处于隐藏状态,因此您无法在配置函数时使用它们。
环境变量
Cloud Functions for Firebase 支持使用 dotenv 文件格式将 .env
文件中指定的环境变量加载到应用运行时。部署后,环境变量可通过 process.env
接口(在基于 Node.js 的项目中)或 os.environ
(在基于 Python 的项目中)读取。
如需以这种方式配置环境,请在项目中创建 .env
文件,添加所需的变量,然后进行部署:
在
functions/
目录中创建一个.env
文件:# Directory layout: # my-project/ # firebase.json # functions/ # .env # package.json # index.js
打开
.env
文件进行修改,然后添加所需的键。例如:PLANET=Earth AUDIENCE=Humans
部署函数并验证是否已加载环境变量:
firebase deploy --only functions # ... # i functions: Loaded environment variables from .env. # ...
部署自定义环境变量后,您的函数代码即可访问这些变量:
Node.js
// Responds with "Hello Earth and Humans"
exports.hello = onRequest((request, response) => {
response.send(`Hello ${process.env.PLANET} and ${process.env.AUDIENCE}`);
});
Python
import os
@https_fn.on_request()
def hello(req):
return https_fn.Response(
f"Hello {os.environ.get('PLANET')} and {os.environ.get('AUDIENCE')}"
)
部署多组环境变量
如果您的 Firebase 项目需要一组替代的环境变量(例如预演环境变量与生产环境变量),请创建 .env.<project or
alias>
文件并在其中写入项目特定的环境变量。.env
文件和项目专属 .env
文件(如果存在)中的环境变量将包含在所有已部署的函数中。
例如,一个项目可以包含以下三个文件,这些文件中包含的用于开发环境和生产环境的值略有不同:
.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
- PORT
- K_CONFIGURATION
存储和访问敏感的配置信息
存储在 .env
文件中的环境变量可用于函数配置,但您不应认为它们是存储数据库凭据或 API 密钥等敏感信息的安全方式。如果您将 .env
文件签入源代码控制系统,尤其需要注意这一点。
为了帮助您存储敏感的配置信息,Cloud Functions for Firebase 已与 Google Cloud Secret Manager 集成。此加密服务可以安全地存储配置值,同时仍可让您在需要时轻松从您的函数访问这些数据。
创建和使用 Secret
如需创建 Secret,请使用 Firebase CLI。
如需创建和使用 Secret,请执行以下操作:
从本地项目的根目录中,运行以下命令:
firebase functions:secrets:set SECRET_NAME
输入 SECRET_NAME 的值。
CLI 会回显成功消息并警告您必须部署函数,以使更改生效。
在部署之前,请确保您的函数代码允许函数使用
runWith
参数访问 Secret:Node.js
const { onRequest } = require('firebase-functions/v2/https'); exports.processPayment = onRequest( { secrets: ["SECRET_NAME"] }, (req, res) => { const myBillingService = initializeBillingService( // reference the secret value process.env.SECRET_NAME ); // Process the payment } );
Python
import os from firebase_functions import https_fn @https_fn.on_request(secrets=["SECRET_NAME"]) def process_payment(req): myBillingService = initialize_billing(key=os.environ.get('SECRET_NAME')) # Process the payment ...
部署 Cloud Functions:
firebase deploy --only functions
现在,您可以像访问任何其他环境变量一样对其进行访问。 相对地,如果另一个没有在
runWith
中指定 Secret 的函数试图访问该 Secret,则会收到一个未定义的值:Node.js
exports.anotherEndpoint = 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 });
Python
@https_fn.on_request() def another_endpoint(req): return https_fn.Response(f"The secret API key is {os.environ.get("SECRET_NAME")}") # Responds with "The secret API key is None" because the `secrets` parameter is missing.
函数部署后便可访问 Secret 值。只有在其 runWith
参数中明确包含 Secret 的函数才能以环境变量的形式访问该 Secret。这有助于确保系统仅在需要时才会提供 Secret 值,从而降低意外泄露 Secret 的风险。
管理密文
使用 Firebase CLI 管理您的 Secret。请注意,以这种方式管理 Secret 时,某些 CLI 更改需要您修改和/或重新部署关联的函数。具体而言:
- 每次为 Secret 设置新值时,您都必须重新部署所有引用该 Secret 的函数,使它们获取最新的值。
- 如果您删除 Secret,请确保已部署的所有函数均未引用该 Secret。使用已删除的 Secret 值的函数将会失败且无提示。
下面总结了用于 Secret 管理的 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 的结算方式
Secret Manager 允许您免费拥有 6 个有效的 Secret 版本。这意味着一个 Firebase 项目每月可以免费拥有 6 个 Secret。
默认情况下,Firebase CLI 会在适当的时候(例如,在使用新版本的 Secret 部署函数时)尝试自动销毁未使用的 Secret 版本。此外,您还可以使用 functions:secrets:destroy
和 functions:secrets:prune
主动清理未使用的 Secret。
Secret Manager 允许每月对一个 Secret 执行 10,000 次不计费的访问操作。函数实例每次冷启动时仅会读取在其 runWith
参数中指定的 Secret。如果您有许多函数实例都会读取大量 Secret,则您的项目可能会超出此限额,此时系统会按照每 10,000 次访问操作 $0.03 的费率向您收取费用。
如需了解详情,请参阅 Secret Manager 价格。
模拟器支持
使用 dotenv 的环境配置可与本地 Cloud Functions 模拟器进行互操作。
使用本地 Cloud Functions 模拟器时,您可以通过设置 .env.local
文件来替换项目的环境变量。.env.local
的内容优先于 .env
和项目特定的 .env
文件。
例如,一个项目可以包含以下三个文件,这些文件中包含的用于开发和本地测试的值会略有不同:
.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 模拟器中的 Secret 和凭据
Cloud Functions 模拟器支持使用 Secret 存储和访问敏感配置信息。默认情况下,模拟器将尝试使用应用默认凭据访问生产 Secret。在某些情况(例如 CI 环境)下,模拟器可能会由于权限限制而无法访问 Secret 值。
与 Cloud Functions 模拟器对环境变量的支持类似,您可以通过设置 .secret.local
文件来替换 Secret 值。这样,您就可以轻松地在本地测试函数,尤其是当您无法访问 Secret 值时。
从环境配置迁移
如果您一直将环境配置与 functions.config
搭配使用,则可以将现有配置作为环境变量(采用 dotenv 格式)迁移。Firebase CLI 提供了一条导出命令,用于输出您目录下 .firebaserc
文件中列出的每个别名或项目(下面示例中的 local
、dev
和 prod
)的配置作为 .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 中。
您还需要更新函数代码。任何使用 functions.config
的函数现在都需要改用 process.env
,如环境变量部分所示。