Konfigurasi Cloud · pegangan Peran C

Setelan setiap layanan Google Cloud

Semua perintah memakai variabel di bagian 01. Jalankan berurutan di Cloud Shell. Nilai ukuran (CPU, memori, instance) adalah titik awal untuk skala satu provinsi, dan bisa diubah setelah uji beban.

01 · Variabel dan wilayah

Jakarta untuk data dan layanan, global untuk Gemini

Cloud Run, Firestore, dan bucket di asia-southeast2 (Jakarta) supaya dekat dengan pengguna. Gemini dipanggil lewat lokasi global. Kalau model yang dipilih belum tersedia di sana, pakai us-central1.

Jalankan di awal setiap sesi Cloud Shellinfra/env.sh
export PROJECT_ID="asapify-hackathon"           # ganti dengan project tim
export PROJECT_NUMBER="$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')"
export REGION="asia-southeast2"
export GEMINI_LOCATION="global"
export GEMINI_MODEL="<nama model Gemini Flash terbaru>"   # cek di Model Garden
export PUBLIC_BUCKET="asapify-public-$PROJECT_ID"
export DATA_BUCKET="asapify-data-$PROJECT_ID"
export PROVINCE="KT"                              # KT = Kalteng, KB = Kalbar
gcloud config set project $PROJECT_ID
02 · API yang diaktifkan

Satu perintah untuk semua API

Aktifkan APIbash
gcloud services enable \
  run.googleapis.com cloudscheduler.googleapis.com cloudbuild.googleapis.com \
  artifactregistry.googleapis.com firestore.googleapis.com storage.googleapis.com \
  secretmanager.googleapis.com aiplatform.googleapis.com earthengine.googleapis.com \
  iamcredentials.googleapis.com logging.googleapis.com monitoring.googleapis.com \
  firebase.googleapis.com firebasehosting.googleapis.com identitytoolkit.googleapis.com
03 · Service account dan IAM

Tiga akun, hak akses seminimal mungkin

Service accountDipakai olehPeran
sa-apiservice asapify-apiroles/datastore.user, roles/aiplatform.user, roles/storage.objectViewer (bucket publik)
sa-jobsjob ingest dan dailyroles/datastore.user, roles/storage.objectAdmin (kedua bucket), roles/earthengine.writer, roles/serviceusage.serviceUsageConsumer, roles/secretmanager.secretAccessor (secret FIRMS), roles/run.invoker (pada asapify-api)
sa-schedulerCloud Schedulerroles/run.invoker untuk menjalankan kedua job
Earth Engine mensyaratkan Service Usage Consumer agar ee.Initialize(project=...) berhasil. Coba turunkan ke roles/earthengine.viewer bila cukup untuk komputasi ToPeCAl.
Buat akun dan beri peranbash
for SA in sa-api sa-jobs sa-scheduler; do
  gcloud iam service-accounts create $SA --display-name "$SA"
done
API_SA="sa-api@$PROJECT_ID.iam.gserviceaccount.com"
JOBS_SA="sa-jobs@$PROJECT_ID.iam.gserviceaccount.com"
SCHED_SA="sa-scheduler@$PROJECT_ID.iam.gserviceaccount.com"

for ROLE in roles/datastore.user roles/aiplatform.user; do
  gcloud projects add-iam-policy-binding $PROJECT_ID --member "serviceAccount:$API_SA" --role $ROLE
done
for ROLE in roles/datastore.user roles/earthengine.writer roles/serviceusage.serviceUsageConsumer; do
  gcloud projects add-iam-policy-binding $PROJECT_ID --member "serviceAccount:$JOBS_SA" --role $ROLE
done
gcloud projects add-iam-policy-binding $PROJECT_ID --member "serviceAccount:$SCHED_SA" --role roles/run.invoker
# peran pada bucket, secret, dan service ditambahkan di bagian 04, 06, 09
04 · Secret Manager

Satu secret: MAP_KEY FIRMS

Minta MAP_KEY gratis di situs NASA FIRMS. Jangan pernah menaruhnya di repo atau di env var biasa.

Simpan dan beri aksesbash
printf "%s" "ISI_MAP_KEY_DI_SINI" | gcloud secrets create firms-map-key --data-file=-
gcloud secrets add-iam-policy-binding firms-map-key \
  --member "serviceAccount:$JOBS_SA" --role roles/secretmanager.secretAccessor
05 · Firestore

Mode Native, tertutup untuk browser

Buat databasebash
gcloud firestore databases create \
  --location=$REGION --type=firestore-native

# aturan dan indeks (Firebase CLI)
firebase deploy --only firestore:rules,firestore:indexes
Tolak semua akses klieninfra/firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;  // hanya lewat API
    }
  }
}
Indeks kompositinfra/firestore.indexes.json
{ "indexes": [
  { "collectionGroup": "alerts", "queryScope": "COLLECTION", "fields": [
      { "fieldPath": "status", "order": "ASCENDING" },
      { "fieldPath": "level_rank", "order": "DESCENDING" },
      { "fieldPath": "created_at", "order": "DESCENDING" } ] },
  { "collectionGroup": "incidents", "queryScope": "COLLECTION", "fields": [
      { "fieldPath": "province", "order": "ASCENDING" },
      { "fieldPath": "updated_at", "order": "DESCENDING" } ] }
] }

Simpan level juga sebagai angka level_rank (WASPADA = 1, SIAGA = 2, AWAS = 3) supaya urutan di antrean benar.

06 · Cloud Storage

Dua bucket: publik dan privat

BucketIsiAksesLifecycle
asapify-public-…risk/{date}.geojson, thumbs/{incident}.png, layers/peat.geojson, layers/villages.geojsonbaca publik (allUsers), CORS untuk domain webhapus thumbs/ dan risk/ setelah 180 hari
asapify-data-…data mentah gambut dan desa, grid 5 km, hasil backtestprivat, hanya sa-jobstidak ada
Buat bucketbash
gcloud storage buckets create gs://$PUBLIC_BUCKET \
  --location=$REGION --uniform-bucket-level-access
gcloud storage buckets create gs://$DATA_BUCKET \
  --location=$REGION --uniform-bucket-level-access

gcloud storage buckets add-iam-policy-binding gs://$PUBLIC_BUCKET \
  --member=allUsers --role=roles/storage.objectViewer
for B in $PUBLIC_BUCKET $DATA_BUCKET; do
  gcloud storage buckets add-iam-policy-binding gs://$B \
    --member="serviceAccount:$JOBS_SA" --role=roles/storage.objectAdmin
done
gcloud storage buckets update gs://$PUBLIC_BUCKET \
  --cors-file=infra/cors.json --lifecycle-file=infra/lifecycle.json
CORSinfra/cors.json
[{ "origin": ["https://<project>.web.app", "http://localhost:5173"],
   "method": ["GET"], "responseHeader": ["Content-Type"],
   "maxAgeSeconds": 3600 }]
Lifecycleinfra/lifecycle.json
{ "rule": [{ "action": { "type": "Delete" },
  "condition": { "age": 180, "matchesPrefix": ["thumbs/", "risk/"] } }] }
07 · Earth Engine

Daftarkan project lebih dulu

  1. Buka halaman pendaftaran Earth Engine, pilih project tim.
  2. Pilih penggunaan nonkomersial (prototipe hackathon), isi keterangan singkat.
  3. Pastikan API earthengine.googleapis.com aktif (bagian 02).
  4. Beri sa-jobs peran Earth Engine dan Service Usage Consumer (bagian 03).
  5. Uji dari Cloud Shell dengan skrip ToPeCAl pada satu scene.
Lakukan di hari pertama. Kalau persetujuan tertunda, hitung ToPeCAl untuk tanggal demo dari akun pribadi lalu simpan hasilnya ke bucket sebagai cadangan.
Inisialisasi di jobjobs/lib/topecal.py
import os, ee

# Di Cloud Run, kredensial otomatis dari service account job (ADC)
ee.Initialize(project=os.environ["GOOGLE_CLOUD_PROJECT"])

LANDSAT = (ee.ImageCollection("LANDSAT/LC08/C02/T1_TOA")
           .merge(ee.ImageCollection("LANDSAT/LC09/C02/T1_TOA")))

# Thumbnail 7-6-4 untuk bukti visual
thumb_url = img.getThumbURL({
    "bands": ["B7", "B6", "B4"], "min": 0, "max": 0.6,
    "region": aoi, "dimensions": 512, "format": "png"})
08 · Gemini lewat Vertex AI

ADK memakai kredensial service account, tanpa API key

GOOGLE_GENAI_USE_ENTERPRISE
True. Versi ADK lama memakai GOOGLE_GENAI_USE_VERTEXAI=TRUE; sesuaikan dengan versi yang terpasang.
GOOGLE_CLOUD_PROJECT
$PROJECT_ID
GOOGLE_CLOUD_LOCATION
$GEMINI_LOCATION
GEMINI_MODEL
nama model Gemini Flash terbaru dari Model Garden
Setelan generasi
temperature 0,2, output maksimal 1.024 token
Kuota
cek kuota request per menit di halaman Quotas. Satu provinsi di musim puncak menghasilkan puluhan panggilan per jam, jauh di bawah batas umum.
Uji cepat dari Cloud Shellpython
from google import genai
client = genai.Client(vertexai=True,
                      project="asapify-hackathon",
                      location="global")
r = client.models.generate_content(
    model="<GEMINI_MODEL>",
    contents="Tulis satu kalimat tentang kebakaran gambut.")
print(r.text)
09 · Cloud Run service

asapify-api

CPU / memori
1 vCPU, 1 GiB
Concurrency
40
Instance
min 0, max 3. Hari demo dan penjurian: min 1 supaya tidak ada cold start.
Timeout
120 detik (agen butuh beberapa panggilan tools)
Ingress
all, --allow-unauthenticated. Endpoint operator dan internal dijaga di kode.
Service account
sa-api
Deploybash
gcloud run deploy asapify-api --source ./api \
  --region $REGION --service-account $API_SA \
  --allow-unauthenticated \
  --cpu 1 --memory 1Gi --concurrency 40 \
  --min-instances 0 --max-instances 3 --timeout 120 \
  --set-env-vars "GOOGLE_CLOUD_PROJECT=$PROJECT_ID,\
GOOGLE_CLOUD_LOCATION=$GEMINI_LOCATION,\
GOOGLE_GENAI_USE_ENTERPRISE=True,\
GEMINI_MODEL=$GEMINI_MODEL,\
PUBLIC_BUCKET=$PUBLIC_BUCKET,PROVINCE=$PROVINCE,\
INTERNAL_CALLER=$JOBS_SA"

# izinkan job memanggil endpoint internal
gcloud run services add-iam-policy-binding asapify-api \
  --region $REGION --member "serviceAccount:$JOBS_SA" \
  --role roles/run.invoker
10 · Cloud Run job

asapify-ingest dan asapify-daily

Setelanasapify-ingestasapify-daily
CPU / memori1 vCPU, 2 GiB (GeoPandas)1 vCPU, 2 GiB
Task timeout15 menit20 menit
Max retries11
Tasks / parallelism1 / 11 / 1
SecretFIRMS_MAP_KEY=firms-map-key:latesttidak ada
Perintahpython ingest.pypython daily.py
Deploy dan jalankan manualbash
COMMON_ENV="GOOGLE_CLOUD_PROJECT=$PROJECT_ID,PROVINCE=$PROVINCE,\
PUBLIC_BUCKET=$PUBLIC_BUCKET,DATA_BUCKET=$DATA_BUCKET,\
API_URL=$(gcloud run services describe asapify-api --region $REGION --format='value(status.url)')"

gcloud run jobs deploy asapify-ingest --source ./jobs --region $REGION \
  --service-account $JOBS_SA --cpu 1 --memory 2Gi \
  --task-timeout 15m --max-retries 1 \
  --command python --args=ingest.py \
  --set-env-vars "$COMMON_ENV" --set-secrets FIRMS_MAP_KEY=firms-map-key:latest

gcloud run jobs deploy asapify-daily --source ./jobs --region $REGION \
  --service-account $JOBS_SA --cpu 1 --memory 2Gi \
  --task-timeout 20m --max-retries 1 \
  --command python --args=daily.py \
  --set-env-vars "$COMMON_ENV"

# mode replay untuk demo
gcloud run jobs execute asapify-ingest --region $REGION \
  --args=ingest.py,--as-of,2026-09-12T10:00:00+07:00
11 · Cloud Scheduler

Dua jadwal, zona waktu Jakarta

Ingest berjalan pada menit ke-5 setiap jam supaya tidak bertabrakan dengan jadwal lain di menit 0. Scheduler memanggil API Cloud Run Admin dengan token OAuth.

Buat jadwalbash
for JOB in asapify-ingest:"5 * * * *" asapify-daily:"0 6 * * *"; do
  NAME="${JOB%%:*}"; CRON="${JOB#*:}"
  gcloud scheduler jobs create http "$NAME-schedule" \
    --location $REGION --schedule "$CRON" --time-zone "Asia/Jakarta" \
    --uri "https://run.googleapis.com/v2/projects/$PROJECT_ID/locations/$REGION/jobs/$NAME:run" \
    --http-method POST \
    --oauth-service-account-email $SCHED_SA
done
12 · Firebase

Hosting untuk web, Auth untuk operator

Hosting + rewrite ke Cloud Runweb/firebase.json
{
  "hosting": {
    "public": "dist",
    "rewrites": [
      { "source": "/api/**",
        "run": { "serviceId": "asapify-api", "region": "asia-southeast2" } },
      { "source": "**", "destination": "/index.html" }
    ],
    "headers": [{ "source": "/assets/**",
      "headers": [{ "key": "Cache-Control",
                    "value": "public, max-age=31536000, immutable" }] }]
  },
  "firestore": { "rules": "../infra/firestore.rules",
                 "indexes": "../infra/firestore.indexes.json" }
}

Auth

  • Aktifkan penyedia Google Sign-In.
  • Tambahkan domain web ke Authorized domains.
  • Beri akun operator custom claim role: "operator" dengan skrip di bawah.
  • Siapkan satu akun operator demo untuk juri.
Set peran operatorinfra/set_operator.py
import sys, firebase_admin
from firebase_admin import auth
firebase_admin.initialize_app()
user = auth.get_user_by_email(sys.argv[1])
auth.set_custom_user_claims(user.uid, {"role": "operator"})
Pastikan rewrite Hosting ke Cloud Run mendukung region asia-southeast2 saat deploy. Kalau tidak didukung, frontend memanggil URL Cloud Run langsung dan API mengaktifkan CORS untuk domain web.
13 · Monitoring dan biaya

Tahu lebih dulu kalau sistem berhenti

Yang dipantauCaraKapan memberi tahu
Job ingest berhentimetrik berbasis log dari baris run_successtidak ada run sukses selama 2 jam
API matiuptime check ke /api/v1/health tiap 5 menit2 kali gagal berturut-turut
Galat aplikasiError Reporting (otomatis dari log Cloud Run)galat jenis baru
Validator agenmetrik berbasis log validator_failedlebih dari 20% draf gagal dalam 1 jam
BiayaBudget alert di Billing50%, 90%, dan 100% dari anggaran yang disepakati tim

Notifikasi dikirim ke email tim. Untuk menekan biaya: min instance 0 di luar hari demo, Gemini dan Earth Engine hanya dipanggil untuk insiden baru atau yang naik level, lifecycle bucket 180 hari.

14 · Ringkasan env var

Satu tabel untuk semua layanan

VariabelapiingestdailyContoh
GOOGLE_CLOUD_PROJECT✓✓✓asapify-hackathon
GOOGLE_CLOUD_LOCATION✓global
GOOGLE_GENAI_USE_ENTERPRISE✓True
GEMINI_MODEL✓model Flash terbaru
PROVINCE✓✓✓KT
PUBLIC_BUCKET✓✓✓asapify-public-…
DATA_BUCKET✓✓asapify-data-…
API_URL✓✓URL Cloud Run asapify-api
INTERNAL_CALLER✓email sa-jobs
FIRMS_MAP_KEY (secret)✓dari Secret Manager
15 · Urutan deploy

Dari project kosong sampai jadwal berjalan

  1. Buat project, hubungkan billing, jalankan env.sh.
  2. Aktifkan API (02) dan daftarkan Earth Engine (07).
  3. Buat service account dan peran (03), lalu secret FIRMS (04).
  4. Buat Firestore (05) dan kedua bucket (06). Unggah layer gambut dan desa.
  5. Deploy asapify-api (09). Cek /api/v1/health.
  6. Deploy kedua job (10). Jalankan ingest sekali secara manual dengan --as-of tanggal demo.
  7. Buat jadwal Scheduler (11).
  8. Deploy web ke Firebase Hosting, aktifkan Auth, set akun operator (12).
  9. Pasang monitoring dan budget alert (13).