Use Cases (Diperbarui: 3/6/2026)

GCP Cloud Functions dengan Claude Code: HTTP, Secret Manager, Cloud Logging

Bangun Cloud Run functions dengan Claude Code: HTTP, secrets, logging, cek deploy, dan jebakan umum.

GCP Cloud Functions dengan Claude Code: HTTP, Secret Manager, Cloud Logging

Dokumentasi Google Cloud sekarang sering menjelaskan Cloud Functions lewat jalur Cloud Run functions. Model praktisnya sederhana: Anda mengirim source code, Google Cloud membangunnya menjadi container, lalu hasilnya berjalan sebagai layanan Cloud Run yang dipanggil lewat HTTP atau event.

Panduan ini membahas cara membuat GCP Cloud Functions dengan Claude Code: satu fungsi HTTP Node.js, satu fungsi event Cloud Storage melalui Eventarc, Secret Manager, Cloud Logging, dan pengecekan setelah deploy. Eventarc adalah lapisan pengiriman event di Google Cloud. Functions Framework adalah adapter yang membuat bentuk fungsi yang sama bisa berjalan lokal maupun di cloud.

Di ClaudeCodeLab, saya memakai Cloud Run functions untuk pekerjaan kecil dan jelas: penerima webhook, notifikasi ringan, pemicu impor CSV, atau pintu masuk job terjadwal. Saya memilih Cloud Run jika butuh banyak route, API penuh, Next.js atau Express, WebSocket, Dockerfile sendiri, proses lama, atau kontrol container yang detail. Untuk sisi itu, lihat panduan GCP Cloud Run.

Claude Code sangat membantu untuk bagian yang berulang: package.json, registrasi Functions Framework, perintah curl, gcloud run deploy, catatan IAM, dan prompt review. Namun review manusia tetap penting untuk autentikasi, Secret Manager, retry, idempotensi, dan log. Idempotensi berarti request atau event yang sama bisa datang dua kali tanpa membuat hasil menjadi rusak.


Use case yang cocok

Use casePintu masukYang perlu direview
Webhook Stripe, GitHub, atau tool internalFungsi HTTPValidasi signature, Bearer token, replay handling, Secret Manager
Impor CSV setelah upload Cloud StorageEventarc + fungsi CloudEventEvent duplikat, region bucket, aturan nama file
Notifikasi formulir kontakFungsi HTTP atau Pub/SubRespons 200 cepat, handoff ke queue, rate limit
Sinkronisasi malam atau pemicu laporanCloud Scheduler + fungsi HTTPAutentikasi OIDC, zona waktu, timeout

Kasus ini cocok dengan Claude Code karena input, validasi, logging, dan perilaku gagal mudah dijadikan checklist. Desain menjadi rapuh jika satu fungsi menampung terlalu banyak tanggung jawab, menjalankan loop panjang, atau dipaksa menjadi seluruh API publik.

Untuk proses tim, hubungkan juga dengan review kode Claude Code dan pengelolaan secret dengan Claude Code.


Proyek minimal

Contoh ini memakai Node.js CommonJS agar tidak perlu langkah build. Dokumentasi resmi mendaftarkan fungsi dengan Functions Framework, dan framework yang sama bisa berjalan lokal.

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

Kode fungsi

Simpan sebagai index.js. Fungsi HTTP memeriksa Authorization: Bearer ... dan memakai Idempotency-Key sebagai kunci pencegah duplikasi. Fungsi Storage menyimpan ID CloudEvent di Firestore agar event yang dikirim ulang tidak membuat job yang sama dua kali.

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 });
});

Log terstruktur membuat Cloud Logging berguna saat insiden. Cloud Run otomatis mengirim stdout dan stderr ke Cloud Logging, sehingga baris JSON dengan severity, message, eventId, dan userId jauh lebih mudah difilter daripada teks bebas.


Tes lokal

Jalankan fungsi HTTP:

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

Kirim request dari terminal lain:

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

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

Jika kode lokal terhubung ke Firestore, gunakan gcloud auth application-default login atau proyek test khusus. Jangan arahkan smoke test ke data produksi.


Secret dan IAM

Jangan simpan token di source code atau file .env yang ikut direview. Simpan di Secret Manager dan beri akses hanya ke runtime 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"

Pisahkan dua identitas. Orang yang deploy perlu izin membuat Cloud Run functions dan memakai service account. Runtime service account hanya perlu izin minimum yang dipakai kode.


Deploy dengan gcloud run

Alur saat ini memakai gcloud run deploy. Untuk endpoint HTTP privat, mulai dengan autentikasi wajib:

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

Untuk event Storage, deploy service dahulu lalu buat trigger 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}"

Trigger Eventarc bisa butuh beberapa menit sampai aktif. Rencanakan region bucket, lokasi trigger, dan region Cloud Run bersama-sama karena memengaruhi latensi, lokasi data, dan harga.


Log dan verifikasi

Setelah deploy, uji endpoint dan baca log sebelum menganggap fungsi siap.

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

Log hal yang membantu investigasi: ID event, nama file, flag duplikat, status API eksternal, dan ID bisnis yang aman. Jangan log token, body pesan lengkap, atau data pribadi.


Jebakan umum

Pertama, retry tanpa idempotensi akan membuat efek samping ganda. Penagihan, email, stok, dan impor membutuhkan ID event atau kunci bisnis yang disimpan.

Kedua, akses Secret Manager harus diberikan ke runtime service account. Deploy bisa sukses, tetapi request produksi gagal dengan Permission denied.

Ketiga, fungsi HTTP publik perlu lebih dari --allow-unauthenticated: validasi signature, rate limit, dan bila perlu API Gateway atau Cloud Armor. Untuk job internal, pilih panggilan terautentikasi.

Keempat, Cloud Run functions bukan host aplikasi lengkap. Banyak route, job panjang, paket sistem, GPU, atau kontrol container detail lebih cocok di Cloud Run, Cloud Run jobs, atau Workflows.

Kelima, masukkan biaya dan cleanup ke prompt. Walau Cloud Run bisa scale to zero, Artifact Registry, Cloud Build, Storage, Eventarc, dan Cloud Logging tetap bisa menimbulkan biaya.


Prompt review

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 mengubah checklist operasional seperti ini menjadi produk dan template serta pelatihan tim. Ini berguna saat Anda ingin review serverless yang berulang, bukan bergantung pada ingatan satu engineer senior.


Dokumentasi resmi


Hasil

Bentuk yang diuji kecil tetapi mirip produksi: Cloud Run functions Node.js 24, perintah lokal Functions Framework, pengecekan HTTP dan CloudEvent dengan curl, injeksi Secret Manager, idempotensi dengan Firestore, dan query Cloud Logging. Kebiasaan terpenting adalah meninjau identitas, log, retry, region, dan biaya sebelum menerima traffic nyata.

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

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.