Use Cases (更新: 2026/6/3)

用 Claude Code 构建 GCP Cloud Functions:HTTP、Secret Manager、Cloud Logging

用 Claude Code 构建 Cloud Run functions:HTTP、密钥、日志、部署验证与常见坑。

用 Claude Code 构建 GCP Cloud Functions:HTTP、Secret Manager、Cloud Logging

Google Cloud 的当前文档经常把 Cloud Functions 放在 Cloud Run functions 这条路径下说明。实际理解可以很简单:你部署源代码,Google Cloud 把它构建成容器,然后它作为一个 Cloud Run 服务运行,由 HTTP 请求或事件触发。

这篇文章整理如何用 Claude Code 构建 GCP Cloud Functions:一个 Node.js HTTP 函数,一个通过 Eventarc 接收 Cloud Storage 事件的函数,Secret Manager、Cloud Logging,以及部署后的确认步骤。Eventarc 是 Google Cloud 的事件投递层。Functions Framework 是让同一种函数形态可以在本地和云端运行的适配层。

在 ClaudeCodeLab,我会把 Cloud Run functions 用在小而清晰的工作单元上:接收 webhook、发送轻量通知、触发 CSV 导入、作为定时任务入口。如果需要多路由、完整 API、Next.js 或 Express、WebSocket、自定义 Dockerfile、长时间任务或更细的容器控制,我会选择 Cloud Run。相关判断可以看内部文章 GCP Cloud Run 指南

Claude Code 很适合生成重复性内容:package.json、Functions Framework 注册、curl 命令、gcloud run deploy、IAM 备注和 review prompt。但认证、Secret Manager、重试、幂等性和日志仍然需要人工审查。幂等性指的是同一个请求或事件来两次,也不会破坏结果。


适合的用例

用例入口需要审查的点
Stripe、GitHub 或内部工具 webhookHTTP 函数签名校验、Bearer token、防重放、Secret Manager
Cloud Storage 上传后导入 CSVEventarc + CloudEvent 函数重复事件、bucket 区域、文件命名规则
联系表单通知HTTP 函数或 Pub/Sub 转交快速返回 200、队列转交、速率限制
夜间同步或报表触发Cloud Scheduler + HTTP 函数OIDC 认证、时区、timeout

这些场景适合 Claude Code,因为输入、校验、日志和失败处理都容易变成 checklist。如果一个函数承担太多职责、执行很长的循环,或者被当作完整公开 API,设计会很快变得难维护。

团队流程中,可以搭配 Claude Code 代码审查Claude Code 密钥管理


最小项目

示例使用 CommonJS Node.js,这样不需要 build 步骤。官方文档通过 Functions Framework 注册函数,同一个 framework 也能在本地运行。

functions-demo/
  index.js
  package.json
{
  "name": "claude-code-gcp-functions-demo",
  "version": "1.0.0",
  "private": true,
  "main": "index.js",
  "scripts": {
    "start:http": "functions-framework --target=handleAction --port=8080",
    "start:event": "functions-framework --target=handleStorageObject --signature-type=cloudevent --port=8081"
  },
  "dependencies": {
    "@google-cloud/firestore": "^7.11.0",
    "@google-cloud/functions-framework": "^3.4.6"
  }
}
npm install

函数代码

把下面内容保存为 index.js。HTTP 函数检查 Authorization: Bearer ...,并使用 Idempotency-Key 作为防重复键。Storage 函数把 CloudEvent ID 保存到 Firestore,避免重试事件创建两次同一个 job。

const functions = require("@google-cloud/functions-framework");
const { Firestore } = require("@google-cloud/firestore");
const crypto = require("node:crypto");

const db = new Firestore();

function jsonLog(severity, message, extra = {}) {
  console.log(JSON.stringify({ severity, message, ...extra }));
}

function requireBearerToken(req) {
  const expected = process.env.API_TOKEN;
  const header = req.get("Authorization") || "";
  return Boolean(expected && header === `Bearer ${expected}`);
}

function stableHash(value) {
  return crypto.createHash("sha256").update(value).digest("hex");
}

functions.http("handleAction", async (req, res) => {
  if (req.method !== "POST") {
    res.status(405).json({ ok: false, error: "POST only" });
    return;
  }

  if (!requireBearerToken(req)) {
    res.status(401).json({ ok: false, error: "invalid token" });
    return;
  }

  const body = req.body || {};
  if (typeof body.userId !== "string" || typeof body.action !== "string") {
    res.status(400).json({ ok: false, error: "userId and action are required" });
    return;
  }

  const idempotencyKey =
    req.get("Idempotency-Key") ||
    stableHash(`${body.userId}:${body.action}:${body.requestedAt || ""}`);

  const requestRef = db.collection("function_requests").doc(idempotencyKey);
  const logRef = db.collection("action_logs").doc(idempotencyKey);

  try {
    let duplicate = false;
    await db.runTransaction(async (tx) => {
      const existing = await tx.get(requestRef);
      if (existing.exists) {
        duplicate = true;
        return;
      }

      tx.create(requestRef, {
        userId: body.userId,
        action: body.action,
        createdAt: new Date(),
        source: "handleAction"
      });
      tx.set(logRef, {
        userId: body.userId,
        action: body.action,
        createdAt: new Date()
      });
    });

    jsonLog("INFO", "action accepted", { userId: body.userId, duplicate });
    res.status(200).json({ ok: true, duplicate, idempotencyKey });
  } catch (error) {
    jsonLog("ERROR", "action failed", { error: String(error) });
    res.status(500).json({ ok: false, error: "internal error" });
  }
});

functions.cloudEvent("handleStorageObject", async (cloudEvent) => {
  const data = cloudEvent.data || {};
  const bucket = data.bucket;
  const name = data.name;

  if (!bucket || !name) {
    jsonLog("WARNING", "storage event missing bucket or name", { eventId: cloudEvent.id });
    return;
  }

  const eventRef = db.collection("processed_storage_events").doc(cloudEvent.id);
  const jobRef = db.collection("storage_import_jobs").doc(stableHash(`${bucket}/${name}`));

  await db.runTransaction(async (tx) => {
    const existing = await tx.get(eventRef);
    if (existing.exists) {
      jsonLog("INFO", "duplicate storage event ignored", { eventId: cloudEvent.id });
      return;
    }

    tx.create(eventRef, {
      bucket,
      name,
      eventType: cloudEvent.type,
      createdAt: new Date()
    });
    tx.set(jobRef, {
      bucket,
      name,
      status: "queued",
      updatedAt: new Date()
    }, { merge: true });
  });

  jsonLog("INFO", "storage import job queued", { bucket, name, eventId: cloudEvent.id });
});

结构化日志能让 Cloud Logging 在故障排查时真正可用。Cloud Run 会自动把 stdout 和 stderr 发送到 Cloud Logging。带有 severitymessageeventIduserId 的 JSON 日志,比普通字符串更容易过滤。


本地测试

启动 HTTP 函数:

export API_TOKEN="local-token"
npm run start:http

在另一个终端发送请求:

curl -X POST http://localhost:8080 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer local-token" \
  -H "Idempotency-Key: local-001" \
  -d '{"userId":"user-123","action":"login","requestedAt":"2026-06-03T00:00:00Z"}'

启动 CloudEvent 函数:

npm run start:event
curl -X POST http://localhost:8081 \
  -H "Content-Type: application/json" \
  -H "ce-id: local-event-001" \
  -H "ce-specversion: 1.0" \
  -H "ce-type: google.cloud.storage.object.v1.finalized" \
  -H "ce-source: //storage.googleapis.com/projects/_/buckets/demo-bucket" \
  -d '{"bucket":"demo-bucket","name":"inbox/sample.csv","metageneration":"1"}'

如果本地代码要连接 Firestore,请使用 gcloud auth application-default login 或单独的测试项目。不要把 smoke test 指向生产数据。


Secret Manager 和 IAM

不要把 token 放进源代码,也不要放进会进入 review 的 .env 文件。把它存到 Secret Manager,并只给运行时 service account 访问权限。

PROJECT_ID="$(gcloud config get-value project)"
REGION="asia-northeast1"
RUNTIME_SA="functions-runtime@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud iam service-accounts create functions-runtime \
  --display-name="Functions runtime service account"

printf "replace-with-real-token" | gcloud secrets create api-token \
  --replication-policy="automatic" \
  --data-file=-

gcloud secrets add-iam-policy-binding api-token \
  --member="serviceAccount:${RUNTIME_SA}" \
  --role="roles/secretmanager.secretAccessor"

gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
  --member="serviceAccount:${RUNTIME_SA}" \
  --role="roles/datastore.user"

要区分两个身份:部署者需要创建 Cloud Run functions 并使用 service account 的权限;运行时 service account 只需要代码实际用到的最小权限。


用 gcloud run 部署

当前流程使用 gcloud run deploy。如果 HTTP endpoint 不是公开 webhook,先部署为需要认证的服务。

gcloud run deploy handle-action \
  --source . \
  --function handleAction \
  --base-image nodejs24 \
  --region "${REGION}" \
  --service-account "${RUNTIME_SA}" \
  --no-allow-unauthenticated \
  --memory 512Mi \
  --timeout 60s \
  --max-instances 20

gcloud run services update handle-action \
  --region "${REGION}" \
  --update-secrets=API_TOKEN=api-token:latest

Storage 事件函数需要先部署服务,再创建 Eventarc trigger:

BUCKET="your-import-bucket"
EVENTARC_SA="eventarc-invoker@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud iam service-accounts create eventarc-invoker \
  --display-name="Eventarc trigger invoker"

gcloud run deploy storage-import \
  --source . \
  --function handleStorageObject \
  --base-image nodejs24 \
  --region "${REGION}" \
  --service-account "${RUNTIME_SA}" \
  --no-allow-unauthenticated \
  --memory 512Mi \
  --timeout 120s \
  --max-instances 10

gcloud run services add-iam-policy-binding storage-import \
  --region "${REGION}" \
  --member="serviceAccount:${EVENTARC_SA}" \
  --role="roles/run.invoker"

gcloud eventarc triggers create storage-finalized-to-function \
  --location="${REGION}" \
  --destination-run-service=storage-import \
  --destination-run-region="${REGION}" \
  --event-filters="type=google.cloud.storage.object.v1.finalized" \
  --event-filters="bucket=${BUCKET}" \
  --service-account="${EVENTARC_SA}"

Eventarc trigger 可能需要几分钟才会生效。bucket 区域、trigger 位置和 Cloud Run 区域要一起设计,因为它们会影响延迟、数据驻留和价格。


日志与验证

部署后不要只看 URL。先测试 endpoint,再读取日志。

SERVICE_URL="$(gcloud run services describe handle-action \
  --region "${REGION}" \
  --format='value(status.url)')"

curl -X POST "${SERVICE_URL}" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer replace-with-real-token" \
  -H "Idempotency-Key: prod-smoke-001" \
  -d '{"userId":"smoke-user","action":"deploy-check","requestedAt":"2026-06-03T00:00:00Z"}'

gcloud run services logs read handle-action \
  --region "${REGION}" \
  --limit 20

gcloud logging read \
  'resource.type="cloud_run_revision" AND resource.labels.service_name="handle-action" AND jsonPayload.message="action accepted"' \
  --limit 20 \
  --format json

日志要足够支持排查:event ID、文件名、是否重复、外部 API 状态、安全的业务 ID。不要记录 token、完整消息体或个人数据。


常见坑

第一,只有重试没有幂等性会造成重复副作用。计费、邮件、库存和导入任务都需要持久化 event ID 或业务 key。

第二,Secret Manager 权限必须给运行时 service account。部署可以成功,但生产请求仍可能因为 Permission denied 失败。

第三,公开 HTTP 函数不能只依赖 --allow-unauthenticated。公开 webhook 需要签名校验、速率限制,必要时加 API Gateway 或 Cloud Armor。内部 job 应优先使用认证调用。

第四,不要把 Cloud Run functions 当成完整应用托管。多路由、长任务、系统包、GPU、细粒度容器控制更适合 Cloud Run、Cloud Run jobs 或 Workflows。

第五,把成本和清理写进 prompt。即使 Cloud Run 可以 scale to zero,Artifact Registry、Cloud Build、Storage、Eventarc 和 Cloud Logging 仍可能产生费用。


审查提示词

Review this Cloud Run functions implementation.
Check:
- Functions Framework registration, gcloud run deploy --function, and package.json target match
- HTTP authentication, input validation, and error responses are safe
- Eventarc retries cannot create duplicate side effects
- Secret Manager values are not logged or returned in exceptions
- The runtime service account has only the minimum IAM roles
- Cloud Logging entries are structured enough for incident review
- Any workload that should be Cloud Run is not forced into a function

If there are issues, return severity, reason, corrected code, and verification commands.

ClaudeCodeLab 会把这类运维 checklist 做成 产品与模板团队培训。当你希望 serverless review 可重复,而不是依赖某一位资深工程师的记忆时,这很有用。


官方文档


结果

这个验证形态很小,但接近生产:Node.js 24 Cloud Run functions、本地 Functions Framework 命令、用 curl 检查 HTTP 和 CloudEvent、Secret Manager 注入、Firestore 幂等性、Cloud Logging 查询。真正接入流量前,最重要的是检查身份、日志、重试、区域和成本。

#claude-code #gcp #cloud-functions #typescript #serverless #pubsub
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。