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.
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.
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
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
| Service account | Dipakai oleh | Peran |
|---|---|---|
sa-api | service asapify-api | roles/datastore.user, roles/aiplatform.user, roles/storage.objectViewer (bucket publik) |
sa-jobs | job ingest dan daily | roles/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-scheduler | Cloud Scheduler | roles/run.invoker untuk menjalankan kedua job |
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
Minta MAP_KEY gratis di situs NASA FIRMS. Jangan pernah menaruhnya di repo atau di env var biasa.
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
gcloud firestore databases create \
--location=$REGION --type=firestore-native
# aturan dan indeks (Firebase CLI)
firebase deploy --only firestore:rules,firestore:indexes
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false; // hanya lewat API
}
}
}
{ "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.
| Bucket | Isi | Akses | Lifecycle |
|---|---|---|---|
asapify-public-… | risk/{date}.geojson, thumbs/{incident}.png, layers/peat.geojson, layers/villages.geojson | baca publik (allUsers), CORS untuk domain web | hapus thumbs/ dan risk/ setelah 180 hari |
asapify-data-… | data mentah gambut dan desa, grid 5 km, hasil backtest | privat, hanya sa-jobs | tidak ada |
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
[{ "origin": ["https://<project>.web.app", "http://localhost:5173"],
"method": ["GET"], "responseHeader": ["Content-Type"],
"maxAgeSeconds": 3600 }]
{ "rule": [{ "action": { "type": "Delete" },
"condition": { "age": 180, "matchesPrefix": ["thumbs/", "risk/"] } }] }
earthengine.googleapis.com aktif (bagian 02).sa-jobs peran Earth Engine dan Service Usage Consumer (bagian 03).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"})
True. Versi ADK lama memakai GOOGLE_GENAI_USE_VERTEXAI=TRUE; sesuaikan dengan versi yang terpasang.$PROJECT_ID$GEMINI_LOCATIONfrom 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)
asapify-api--allow-unauthenticated. Endpoint operator dan internal dijaga di kode.sa-apigcloud 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
asapify-ingest dan asapify-daily| Setelan | asapify-ingest | asapify-daily |
|---|---|---|
| CPU / memori | 1 vCPU, 2 GiB (GeoPandas) | 1 vCPU, 2 GiB |
| Task timeout | 15 menit | 20 menit |
| Max retries | 1 | 1 |
| Tasks / parallelism | 1 / 1 | 1 / 1 |
| Secret | FIRMS_MAP_KEY=firms-map-key:latest | tidak ada |
| Perintah | python ingest.py | python daily.py |
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
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.
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
{
"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" }
}
role: "operator" dengan skrip di bawah.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"})
asia-southeast2 saat deploy. Kalau tidak didukung, frontend memanggil URL Cloud Run langsung dan API mengaktifkan CORS untuk domain web.| Yang dipantau | Cara | Kapan memberi tahu |
|---|---|---|
| Job ingest berhenti | metrik berbasis log dari baris run_success | tidak ada run sukses selama 2 jam |
| API mati | uptime check ke /api/v1/health tiap 5 menit | 2 kali gagal berturut-turut |
| Galat aplikasi | Error Reporting (otomatis dari log Cloud Run) | galat jenis baru |
| Validator agen | metrik berbasis log validator_failed | lebih dari 20% draf gagal dalam 1 jam |
| Biaya | Budget alert di Billing | 50%, 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.
| Variabel | api | ingest | daily | Contoh |
|---|---|---|---|---|
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 |
env.sh.asapify-api (09). Cek /api/v1/health.--as-of tanggal demo.