跳到主要内容

云函数 / 云托管的密钥与环境变量分层管理

一句话定义:把云函数 / 云托管 / 前端三层的环境变量用 dev / staging / prod 三套配置隔开,本地走 .env + .gitignore、CI 走 tcb login --apiKeyId/--apiKey 注入永久密钥 + 流水线变量、生产走控制台环境变量,确保任何一层 secret 都不会进入 git 历史或前端 bundle。

预计耗时:30 分钟 | 难度:中级

适用场景

  • 已经部署了云函数 / 云托管服务,里面有 LLM API key、数据库密码、第三方服务凭证
  • 想把团队从"本地 .env 散乱、生产环境依赖人工维护控制台"升级到一套规范流程
  • 准备接 CI 流水线(工蜂 / 腾讯 CODING / GitHub Actions),需要让流水线能拿到密钥但又不让密钥进仓库

不适用:

  • 完全个人项目,团队就一个人,且没有合规要求——那 .env + .gitignore 就够了,不用上 CI
  • 用了第三方专门的 secret manager(HashiCorp Vault、AWS Secrets Manager),那有自己的整套规范

不该做的事

先列清楚什么不能做,比怎么做更重要:

反模式为什么不行
把 secret 写进代码里git 历史里永远删不掉,rotate 一次也救不回;commit 内网仓库一样有风险
把 secret 写进 package.jsonpackage-lock.json同上,且这些文件通常都会被提交
.env.example 里留真实值当"参考"经常有人 cp 完忘了改,真值进了 git
微信小程序 / Web 前端代码里放 API key前端代码用户本地都能拿到,反编译/F12 一看就漏;前端任何变量都视作公开
console.log(process.env.OPENAI_KEY) 调试,提交时没删日志会进监控系统,谁有日志权限谁有 key
给整个团队共用同一个生产 key一旦某人离职或离场,整套 key 都要 rotate
在 commit message / PR description 里贴 key 说"修了下这个"review 一搜 sk- 就能挖出来
.env 提交到私有仓库觉得"反正没公开"团队成员变动、仓库迁移、备份外泄都是入口;私有不等于安全

三层环境的分工

┌─────────────────────┐
│ 本地开发(dev) │ .env(本地) + .gitignore + .env.example(模板)
└─────────────────────┘

┌─────────────────────┐
│ CI 流水线 │ 流水线变量(工蜂/CODING) + tcb login --apiKeyId/--apiKey
└─────────────────────┘

┌─────────────────────┐
│ 生产(prod) │ CloudBase 控制台「环境变量」
└─────────────────────┘

下面分层讲。

第一层:本地开发

.env + .gitignore

项目根目录建:

# 本地开发配置(永远不提交)
touch .env
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo "*.env" >> .gitignore

.env 内容(具体 key 名按你的项目):

# 本地开发用的 LLM 网关代理 token
PROXY_ACCESS_TOKEN=local-dev-token-xxxxxxxxxxxxxxxx

# 上游 LLM 真 key
UPSTREAM_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx

# 本地开发的 PostgreSQL 连接(本机 docker 起的实例)
PG_HOST=127.0.0.1
PG_PASSWORD=local-pg-password

提交一份模板给团队成员复用,不写真值

touch .env.example

.env.example

# 复制本文件为 .env 并填入真实值
PROXY_ACCESS_TOKEN=
UPSTREAM_API_KEY=

PG_HOST=
PG_PASSWORD=

.env.example 是允许提交的——它只是文件骨架,不含值。新成员 clone 仓库后第一件事是 cp .env.example .env,然后去找内部文档填值。

Node.js 代码里读 .env

云函数和云托管的 Node 项目里装 dotenv

npm install --save-dev dotenv

入口文件最顶部:

// 仅本地开发加载 .env;部署到云函数后,环境变量由平台注入,不需要 dotenv
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}

const apiKey = process.env.UPSTREAM_API_KEY;

部署到 CloudBase 后 NODE_ENV=production 是平台默认,不会再尝试读 .env——这一点重要,避免误把 .env 一起打包上去后被加载。

.gitignore 检查清单

提交前一定看一眼 .gitignore 至少包含这些(按需增减):

# 环境变量
.env
.env.*
!.env.example
*.env

# CloudBase CLI 缓存
.cloudbase/
cloudbaserc.json.local

# 各种自动生成的密钥文件
tcb_custom_login.json
*.pem
*.key
firebase-adminsdk-*.json
service-account*.json

!.env.example 是允许 example 模板进 git 的例外。

第二层:CI 流水线

CI 不能拿本地 .env,因为 CI 跑在另一台机器上,且仓库里没有 .env。要走流水线变量。

配置流水线变量

工蜂(CODING / GitLab 同理)为例:

  1. 仓库 → 「设置 → 流水线 → 流水线变量」
  2. 加变量,例如 TCB_DEPLOY_TOKENPROD_UPSTREAM_API_KEYPROD_PG_PASSWORD
  3. 勾「保护」+「掩码」(前者限制只在受保护分支生效,后者让日志里这些值显示成 ***

CloudBase CLI 在 CI 里的非交互登录

CloudBase CLI 在 CI 里不能用浏览器登录(没人按确认按钮)。改用 tcb login --apiKeyId/--apiKey 显式注入腾讯云永久密钥(区别于本地 tcb login 的浏览器登录):

# 1. 用永久密钥非交互登录
tcb login --apiKeyId $TENCENT_SECRET_ID --apiKey $TENCENT_SECRET_KEY

# 2. 部署
tcb fn deploy llm-proxy --httpFn -e your-env-id

TENCENT_SECRET_ID / TENCENT_SECRET_KEY 在腾讯云访问管理 CAM 控制台 创建,仅用于 CI 部署,不要在开发者本地使用(开发者本地用 tcb login 浏览器登录拿临时密钥,遵循最小权限)。

CI 步骤里绝对不要 echo $TENCENT_SECRET_KEY——一旦日志被外人看到等于 key 公开。流水线掩码只是兜底。

在 CI 里同时下发应用层 secret 到云函数

部署的同时把 UPSTREAM_API_KEY 等业务密钥灌到云函数环境变量,可以分两步:

# 1. 用永久密钥登录 CLI
tcb login --apiKeyId $TENCENT_SECRET_ID --apiKey $TENCENT_SECRET_KEY

# 2. 部署函数代码
tcb fn deploy llm-proxy --httpFn -e your-env-id

# 3. 通过云开发 OpenAPI / @cloudbase/manager-node 更新环境变量
# (具体接口请参考 manager-node 的 functions.updateFunctionConfig)

或者把环境变量写到 cloudbaserc.jsonfunctions.envVariables 里,部署时一并更新。不要把 cloudbaserc.json 里的 secret 字段提交到 git,可以用 cloudbaserc.local.json + .gitignore,或在 CI 里用 envsubst / sed 把占位符替换成真值再部署。

具体命令以 @cloudbase/cli 的当前版本为准——CLI 的环境变量管理子命令各版本略有差异,参考 tcb fn --help 实际选项。

第三层:生产环境

生产 secret 的最终落点是 CloudBase 控制台环境变量

资源配置入口
云函数(事件 / Web 函数)控制台 → 云函数 → 函数详情 → 函数配置 → 环境变量
云托管服务控制台 → 云托管 → 服务详情 → 服务设置 → 版本管理 → 新版本时配置

云托管和云函数的环境变量机制略有差异:

维度云函数云托管
改完是否立即生效重新调用即生效(实例热更新)需要发布新版本,并切流量
是否绑定版本不绑定(环境变量是函数级)绑定服务版本,每个版本独立
Dockerfile 默认值无此概念ENV KEY=VALUE 设置默认,控制台同名变量优先生效

跨环境(dev / staging / prod)命名约定

如果生产、预发、开发各占一个 CloudBase 环境,每个环境里同名变量直接区分即可(同一个 key 名 UPSTREAM_API_KEY 在 prod 和 staging 是两个不同的值)。

如果三套环境塞在同一个 CloudBase 环境里(不推荐但常见),用前缀区分:

PROD_UPSTREAM_API_KEY=...
STAGING_UPSTREAM_API_KEY=...
DEV_UPSTREAM_API_KEY=...

代码里按 process.env.STAGE 选:

const stage = process.env.STAGE || 'dev';
const apiKey = process.env[`${stage.toUpperCase()}_UPSTREAM_API_KEY`];

建议直接用三套环境,资源隔离永远比命名约定可靠

前端 secret 的特别提醒

任何前端代码里出现的环境变量都不是 secret,包括:

  • Next.js 的 NEXT_PUBLIC_*
  • Vite 的 VITE_*
  • 微信小程序里的任何配置项

它们 build 时被静态嵌入到 JS bundle,浏览器或微信开发者工具能直接看到。

正确做法:让前端只持有一个公开 endpoint URL,secret 留在云函数 / 云托管后端。例如:

✗ 错误:小程序代码里 const OPENAI_KEY = 'sk-xxx';
✓ 正确:小程序代码里 const PROXY_URL = 'https://your-env.service.tcloudbase.com/llm-proxy';
OPENAI_KEY 留在云函数环境变量里

如果你已经在前端硬编码过 key,那个 key 就视为已泄露,无论之后怎么改代码都不安全——必须 rotate。

key 已经泄露怎么办

每个服务 rotate 流程不一样,先列最常用的:

OpenAI

  1. 登录 platform.openai.com → API Keys
  2. 找到泄露的 key,点 Revoke(立即作废,所有调用 401)
  3. Create new secret key 生成新值
  4. 更新 CloudBase 控制台所有用到该 key 的云函数 / 云托管环境变量
  5. 检查账单页面,看泄露期间是否有异常调用(异常用量是关键线索)

腾讯云 CAM 永久密钥

  1. CAM 控制台 → 访问密钥
  2. 找到泄露的 SecretId,点「禁用」,再删除
  3. 新建一对 SecretId / SecretKey
  4. 更新 CI 流水线变量
  5. 立刻去操作记录查泄露期间是否有异常 API 调用

微信小程序 AppSecret

  1. 微信公众平台 → 开发设置 → 重置 AppSecret
  2. 重置后旧 AppSecret 立即失效(不像 OpenAI 有过渡期,这里是硬切)
  3. 立即更新所有用到的云函数环境变量并重新部署,否则线上立刻挂

数据库密码

  1. DMC 工具登录 → 账号管理 → 重置密码
  2. 更新所有用到的云函数 / 云托管环境变量
  3. 旧 session 不会立即断,但新连接拿不到了

每次 rotate 后做一件事:把"泄露的 key 怎么进 git 的"复盘下来。是 commit 漏检查?是某人本地 alias 把 .env 错当成 .env.example?是 CI 日志没打码?根因比 rotate 更重要,否则下次还会发生。

落地清单

部署到生产前过一遍:

  • .gitignore 包含 .env.env.*(除 .env.example)、tcb_custom_login.json*.pem
  • 仓库历史里搜过 sk-AKID-----BEGIN PRIVATE,确认无 secret 残留(git log -S "sk-" -- '*.js'
  • CI 用永久密钥 + 掩码,开发本地用 tcb login Web 登录
  • 前端代码里没有任何 API key(grep 一遍 sk- Bearer apiKey:
  • 所有云函数 / 云托管环境变量都从控制台或 CI 注入,不在代码里
  • 团队有共享文档说明每个变量在哪个 CAM 子账号下、谁负责 rotate
  • 关键 key(数据库 / 支付)开启了告警,异常调用量能触发通知

常见错误

现象原因修复
部署后 process.env.Xundefined控制台环境变量没加,或函数没重新部署控制台加变量后重新触发部署(云函数会拉新值,但保险起见重新部署)
改了 NEXT_PUBLIC_* 重启服务后前端拿到的还是旧值这类变量是 build 期注入,重启不会更新重新 npm run build 并部署
.env 不小心进了 git.gitignore 漏配置git rm --cached .env,加 .gitignore,然后rotate 所有泄露的 key(git 历史里仍然能看到,加 ignore 不能洗白)
CI 里日志显示 *** 但部署后函数报 401CI 把变量名拼错,掩码后看不出来CI 步骤里加 `printenv
cloudbaserc.json 里写了 secret 占位符忘了替换部署时占位符直接进了云函数环境变量部署前在 CI 里加 grep 检查 \${.*} 这种未替换标记
重置 AppSecret 后线上小程序登录全挂没同步更新云函数环境变量流程里硬性要求"先更新环境变量再重置 AppSecret"
同一个 key 在 staging 和 prod 混用没做环境隔离严肃做的话开三个 CloudBase 环境,不同 key

相关文档

下一步

把这套基线立起来后,建议接着做:

  • connect-openai-api-cloud-function — LLM key 隔离的最佳实践范本,本篇里讲的所有规则都能套用
  • deploy-nextjs-to-cloudbase-run — Next.js 应用的环境变量分层(NEXT_PUBLIC_* vs 服务端)
  • add-rag-with-pgvector-cloudbase — 数据库密码 + LLM key 同时管理的复合场景