Use Cases (Actualizado: 3/6/2026)

GCP Cloud Functions con Claude Code: HTTP, Secret Manager y Cloud Logging

Crea Cloud Run functions con Claude Code: HTTP, secretos, logs, verificación de despliegue y errores comunes.

GCP Cloud Functions con Claude Code: HTTP, Secret Manager y Cloud Logging

La documentación actual de Google Cloud presenta muchas veces Cloud Functions dentro de Cloud Run functions. El modelo práctico es directo: despliegas código fuente, Google Cloud lo construye como contenedor y el resultado se ejecuta como un servicio de Cloud Run invocado por HTTP o por eventos.

Esta guía muestra cómo crear GCP Cloud Functions con Claude Code: una función HTTP en Node.js, una función de evento de Cloud Storage con Eventarc, Secret Manager, Cloud Logging y verificación después del despliegue. Eventarc es la capa que entrega eventos dentro de Google Cloud. Functions Framework es el adaptador que permite ejecutar la misma forma de función localmente y en la nube.

En ClaudeCodeLab uso Cloud Run functions para trabajos pequeños y bien delimitados: recibir webhooks, enviar notificaciones ligeras, iniciar importaciones CSV o disparar un job programado. Uso Cloud Run cuando necesito muchas rutas, una API completa, Next.js o Express, WebSockets, un Dockerfile propio, procesos largos o control fino del contenedor. Para esa decisión, consulta también la guía interna de GCP Cloud Run.

Claude Code ayuda mucho con las partes repetibles: package.json, registro en Functions Framework, comandos curl, gcloud run deploy, notas de IAM y prompts de revisión. La revisión humana sigue siendo necesaria para autenticación, Secret Manager, reintentos, idempotencia y logs. Idempotencia significa que recibir la misma solicitud o el mismo evento dos veces no rompe el resultado.


Casos de uso adecuados

Caso de usoEntradaQué revisar
Webhooks de Stripe, GitHub o herramientas internasFunción HTTPFirma, Bearer token, protección contra reenvíos, Secret Manager
Importación CSV tras subir a Cloud StorageEventarc + función CloudEventEventos duplicados, región del bucket, reglas de nombre
Notificación de formulario de contactoFunción HTTP o Pub/SubRespuesta 200 rápida, traspaso a cola, rate limits
Sincronización nocturna o reporteCloud Scheduler + función HTTPAutenticación OIDC, zona horaria, timeout

Estos casos encajan bien con Claude Code porque entrada, validación, logging y manejo de fallos se convierten fácilmente en una lista de revisión. El diseño se vuelve frágil cuando una sola función concentra tareas distintas, ejecuta bucles largos o se usa como toda una API pública.

Para procesos de equipo, conviene enlazar con revisión de código con Claude Code y gestión de secretos con Claude Code.


Proyecto mínimo

El ejemplo usa Node.js CommonJS para que no haya paso de build. La documentación oficial registra funciones con Functions Framework, y el mismo framework funciona en local.

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

Código de la función

Guarda esto como index.js. La función HTTP valida Authorization: Bearer ... y usa Idempotency-Key para evitar duplicados. La función de Storage guarda el ID del CloudEvent en Firestore para que un evento reintentado no cree dos veces el mismo 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 });
});

Los logs estructurados hacen que Cloud Logging sea útil durante una incidencia. Cloud Run envía stdout y stderr automáticamente a Cloud Logging, así que líneas JSON con severity, message, eventId y userId se filtran mucho mejor que textos sueltos.


Pruebas locales

Arranca la función HTTP:

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

Envía una solicitud desde otra terminal:

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"}'

Arranca la función 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"}'

Si el código local conecta con Firestore, usa gcloud auth application-default login o un proyecto de prueba. No apuntes las pruebas rápidas a datos de producción.


Secretos e IAM

No guardes tokens en el código ni en archivos .env que terminan en revisiones. Guárdalos en Secret Manager y concede acceso solo a la cuenta de servicio de ejecución.

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"

Separa dos identidades. Quien despliega necesita permisos para crear Cloud Run functions y usar la cuenta de servicio. La cuenta de ejecución solo debe tener los permisos mínimos que el código necesita.


Despliegue con gcloud run

La ruta actual usa gcloud run deploy. Para un endpoint HTTP privado, empieza autenticado:

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

Para eventos de Storage, despliega primero el servicio y después crea el trigger de Eventarc:

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}"

Los triggers de Eventarc pueden tardar unos minutos en activarse. Planifica juntos la región del bucket, la ubicación del trigger y la región de Cloud Run, porque afectan latencia, residencia de datos y costes.


Logs y verificación

Después del despliegue, prueba el endpoint y revisa logs antes de darlo por listo.

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

Registra lo necesario para diagnosticar: ID de evento, nombre de archivo, marca de duplicado, estado de API externa e IDs de negocio seguros. No registres tokens, cuerpos completos de mensajes ni datos personales.


Errores comunes

Primero, reintentos sin idempotencia producen efectos duplicados. Facturación, emails, inventario e importaciones necesitan un ID de evento o clave de negocio guardada.

Segundo, el acceso a Secret Manager debe estar en la cuenta de ejecución. El despliegue puede pasar y las solicitudes fallar con Permission denied.

Tercero, una función HTTP pública necesita más que --allow-unauthenticated: firmas, rate limits y, si aplica, API Gateway o Cloud Armor. Para trabajos internos, prefiere llamadas autenticadas.

Cuarto, Cloud Run functions no debería convertirse en un host de aplicación completo. Muchas rutas, jobs largos, paquetes de sistema, GPU o control fino del contenedor encajan mejor en Cloud Run, Cloud Run jobs o Workflows.

Quinto, incluye coste y limpieza en el prompt. Aunque Cloud Run escale a cero, Artifact Registry, Cloud Build, Storage, Eventarc y Cloud Logging pueden generar costes.


Prompt de revisión

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 convierte estas listas operativas en productos y plantillas y formación para equipos. Son útiles cuando quieres que las revisiones serverless sean repetibles y no dependan de la memoria de una sola persona senior.


Documentación oficial


Resultado

La forma probada es pequeña, pero cercana a producción: Cloud Run functions con Node.js 24, comandos locales de Functions Framework, pruebas HTTP y CloudEvent con curl, inyección desde Secret Manager, idempotencia con Firestore y consultas de Cloud Logging. Lo importante es revisar identidad, logs, reintentos, región y costes antes de recibir tráfico real.

#claude-code #gcp #cloud-functions #typescript #serverless #pubsub
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.