Grafana #
Open the link and log in with Cloudflare Access. No Grafana password, no database credentials.
- Staging: grafana-staging.funnelstory.io
- Production: grafana-production.funnelstory.io
Access #
- Open the link.
- Log in through Cloudflare Access with your
@funnelstory.ioor@funnelstory.aiaccount.
Cloudflare Access is the only gate. Grafana proxies every query, so you need no database credentials of your own.
Start here when something is wrong #
| Symptom | Dashboard | What to look at |
|---|---|---|
| Database looks slow | cloud-sql |
read ops/sec, CPU, disk I/O vs baseline |
| Errors, cause unknown | gcp-operations |
narrow the time range, filter severity>=ERROR |
| API slow or failing | backend |
request and error rates |
| Work not draining | queues |
queue depth and drain rate |
| LLM cost or latency spike | llm-telemetry-gcp |
call volume, latency, cost |
“The database looks slow” #
Open cloud-sql.
Check read ops/sec, CPU, and disk I/O against baseline.
A repeating burst on a fixed interval is usually a scheduled backend job, not an infrastructure fault. The prediction-scores sweep runs every 6 hours and has tripped the disk-I/O alert before — baseline is ~560 read ops/sec, the burst hits 2100–2760 for about 10 minutes.
If the burst is read-heavy on staging, confirm FS_DB_REPLICA_DSN points at funnelstory-staging-replica (172.21.32.13) and not at the writer.
“Something is failing and I cannot tell what” #
- Open
gcp-operationsand narrow the time range to the incident. - Filter to
severity>=ERROR. - Move to
backendfor rates over time. - Use the BigQuery panels to group errors by message.
“Is it one pod or all of them?” #
Grafana shows aggregate behaviour only. Confirm per-pod with GKE diagnostics.
Dashboards #
Eight dashboards ship from one shared source and are identical in both environments.
| Folder | Dashboard | Use it for |
|---|---|---|
| Operations | gcp-operations |
first stop for a GCP-side problem |
| Operations | cloud-sql |
CPU, memory, disk I/O, connections |
| Operations | backend |
backend request and error behaviour |
| Operations | queues |
queue depth and drain rate |
| AI | llm-telemetry-gcp |
LLM call volume, latency, cost |
| AI | agent-analytics |
agent runs and outcomes |
| Business | subscriptions |
subscription state |
| Business | license-subscriptions |
license and seat counts |
Edit dashboards in the ops repo, not in the UI. The JSON at grafana/dashboards/<slug>/<slug>.json is the source of truth.
Grafana’s own database is runtime state, so a UI edit is lost on the next apply.
Datasources #
Both environments provision the same four UIDs, so a dashboard is portable between them.
| UID | Type | Reads |
|---|---|---|
funnelstory-db |
PostgreSQL | the application database, as read-only monitoring |
funnelstory-gcp-logs |
Google Cloud Logging | GCP logs for that project |
funnelstory-gcp-monitoring |
Google Cloud Monitoring | Cloud SQL and infra metrics |
funnelstory-bq-logs |
BigQuery | Log Analytics views over the app log bucket |
Dashboards reference these through datasource-typed template variables (${Database}, ${GCPLogs}, ${BigQuery}) rather than a hardcoded UID.
Why both a logging and a BigQuery datasource: Cloud Logging cannot aggregate.
It returns log rows only, with no stats equivalent.
Any panel that counts, sums, or groups reads the same logs through BigQuery.
See debug backend logs.
Staging lists a second Postgres datasource with the random UID cfscsv22r3kzka.
Use funnelstory-db.
Environment mapping #
| Staging | Production | |
|---|---|---|
| GCP project | funnelstory-infra-staging |
funnelstory-infra-production |
| GKE namespace | observability |
observability |
| Helm release | grafana-staging |
grafana-production |
| Metadata Cloud SQL instance | grafana-staging |
grafana-production |
Grafana metadata lives in its own Cloud SQL instance, separate from the application database. There is no SQLite volume, so a pod restart loses nothing.
Query the app database ad hoc #
From grafana/ in the ops repo:
./query "SELECT count(*) FROM accounts" # production Postgres
./query -s Kh5fT8-Vz "SELECT count(*) FROM accounts" # staging Postgres
Needs a Grafana service-account token in grafana/.env.
Read-only, through the monitoring role.
If Grafana itself is down #
-
Check the pods. Expect two containers:
grafanaandcloud-sql-proxy.kubectl -n observability get pods -
Read both containers’ logs:
kubectl -n observability logs deploy/grafana-staging -c grafana --tail=200 kubectl -n observability logs deploy/grafana-staging -c cloud-sql-proxy --tail=200 -
If Cloudflare returns
524, port-forward and test internally first:kubectl -n observability port-forward svc/grafana-staging 33000:3000 curl -sS http://127.0.0.1:33000/api/health
A proxy authentication failure points at IAM.
A healthy /api/health behind a 524 points at the Cloudflare tunnel, not at Grafana.
Full runbooks live in the ops repo: docs/GRAFANA_STAGING_RUNBOOK.md and docs/GRAFANA_PRODUCTION_RUNBOOK.md.
Next: open the staging link and load the gcp-operations dashboard.