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.
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 uso | Entrada | Qué revisar |
|---|---|---|
| Webhooks de Stripe, GitHub o herramientas internas | Función HTTP | Firma, Bearer token, protección contra reenvíos, Secret Manager |
| Importación CSV tras subir a Cloud Storage | Eventarc + función CloudEvent | Eventos duplicados, región del bucket, reglas de nombre |
| Notificación de formulario de contacto | Función HTTP o Pub/Sub | Respuesta 200 rápida, traspaso a cola, rate limits |
| Sincronización nocturna o reporte | Cloud Scheduler + función HTTP | Autenticació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
- Cloud Run functions documentation
- Deploy a Cloud Run function
- Deploy a Cloud Run function using the gcloud CLI
- Write Cloud Run functions
- Local functions development
- Trigger functions from Cloud Storage using Eventarc
- Configure secrets for Cloud Run services
- Logging and viewing logs in Cloud Run
- Compare Cloud Run functions
- Cloud Run locations
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.
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.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.