← Back to DevBytes

Monitoring CloudRun: Metrics, Alarms, and Dashboards

Understanding Cloud Run Monitoring

Monitoring Cloud Run is the practice of collecting, analyzing, and acting on performance data from your serverless container workloads. Google Cloud Run is a fully managed compute platform that automatically scales your stateless containers, but "fully managed" doesn't mean "invisible." You still need deep visibility into request latency, error rates, container health, resource utilization, and cost signals to operate services reliably in production.

Cloud Run integrates natively with Cloud Monitoring (formerly Stackdriver), which automatically collects a rich set of metrics without any additional instrumentation. These metrics flow into time-series databases, power alerting policies, and surface in pre-built and custom dashboards. The monitoring surface covers three layers: the service level (inbound requests, instance counts), the container level (CPU, memory, startup latency), and the application level (custom metrics you emit via OpenTelemetry or the Cloud Monitoring API).

Why Monitoring Matters for Cloud Run

Cloud Run's autoscaling model is powerful but nuanced. Without monitoring, you can easily miss signals that lead to degraded user experience or runaway costs. Here are the concrete reasons monitoring is non-negotiable:

Built-in Cloud Run Metrics

Cloud Monitoring automatically collects the following Cloud Run metrics. You don't need to enable them — they exist as soon as your service receives traffic. These are available under the resource type cloudrun_revision.

Request-Based Metrics

Container Instance Metrics

Billable Resource Metrics

Accessing Metrics in Cloud Monitoring

You can query these metrics through the Cloud Monitoring UI (Metrics Explorer) or via the API. Here's how to build a query for request latency using the Metrics Explorer query language (MQL) and the gcloud CLI:


# Query 95th percentile request latency for a specific Cloud Run service
# using the gcloud monitoring command

gcloud monitoring metrics time-series list \
  --filter='metric.type="run.googleapis.com/request_latencies"' \
  --filter='resource.label.service_name="my-service"' \
  --aggregation="per=99percentile" \
  --interval="2025-01-01T00:00:00Z/2025-01-02T00:00:00Z"

In the Cloud Console Metrics Explorer, you would use MQL directly:


# MQL query: 99th percentile latency over the last hour, grouped by response code class
fetch cloudrun_revision
| metric 'run.googleapis.com/request_latencies'
| filter resource.service_name = 'my-service'
| group_by 1h, [value_latencies_percentile: percentile(value.latencies, 99)]
| group_by [resource.response_code_class], mean(value_latencies_percentile)
| time_series 1h

Setting Up Alarms and Alerting Policies

Metrics alone won't wake you up at 2 AM. Alerting policies in Cloud Monitoring define conditions that trigger notifications via email, SMS, Slack, PagerDuty, or webhooks. Each policy combines a condition (metric threshold or absence of data), a notification channel, and optional documentation to help responders.

Creating a Latency Alert with gcloud


# Step 1: Create a notification channel (email in this case)
gcloud beta monitoring channels create \
  --display-name="On-call Team" \
  --type=email \
  --channel-labels=email_address=oncall@example.com

# Note the channel ID returned, e.g., projects/my-project/notificationChannels/123456789

# Step 2: Create the alerting policy
gcloud alpha monitoring policies create \
  --display-name="Cloud Run High Latency Alert" \
  --condition='resource.type="cloudrun_revision"
               AND metric.type="run.googleapis.com/request_latencies"
               AND metric.labels.response_code_class="2xx"
               filter: resource.label.service_name = "my-service"
               aggregations: per=99percentile
               aggregations: alignment=60s
               comparison: gt
               threshold: 2000
               duration: 300s' \
  --notification-channels=projects/my-project/notificationChannels/123456789 \
  --documentation="99th percentile latency exceeded 2 seconds for 5 minutes. Check cold starts and downstream dependencies."

Creating an Alert on Instance Count (Terraform Example)

For teams managing infrastructure as code, here is a Terraform resource that alerts when the active instance count is zero — meaning your service may be down or failing to deploy:


resource "google_monitoring_alert_policy" "cloudrun_no_instances" {
  display_name = "Cloud Run - No Active Instances"
  combiner     = "OR"
  conditions {
    display_name = "Zero active instances for 5 minutes"
    condition_threshold {
      filter     = <

Error Rate Alert for SLO Monitoring

A common SRE pattern is alerting on error budget burn rate. The following condition triggers when the error rate exceeds 5% over a 60-minute window, useful for catching slow-burn degradations:


resource "google_monitoring_alert_policy" "error_rate_warning" {
  display_name = "Cloud Run Error Rate > 5% (Warning)"
  combiner     = "OR"
  conditions {
    display_name = "Error rate threshold"
    condition_threshold {
      filter = <

For a more precise error budget alert, use an MQL-based condition that computes the ratio of 5xx requests to total requests:


# MQL condition for error rate ratio
fetch cloudrun_revision
| { 
    metric 'run.googleapis.com/request_count' 
    | filter metric.response_code_class = '5xx' 
    | align rate(5m) 
  ; 
    metric 'run.googleapis.com/request_count' 
    | align rate(5m) 
  }
| ratio
| condition gt(0.05)  # 5% error rate
| window 15m

Building Dashboards for Cloud Run

Dashboards provide at-a-glance operational awareness. Cloud Monitoring supports both pre-built Cloud Run dashboards (automatically created per service) and fully customizable dashboards you build from widgets. Custom dashboards are defined as JSON or YAML and can be version-controlled alongside your infrastructure code.

Pre-Built Cloud Run Dashboard

Every Cloud Run service gets an automatic dashboard accessible from the Cloud Console: navigate to Cloud Run → select your service → click the "Metrics" tab. This dashboard includes charts for request count, latency percentiles, error rates, instance count, CPU and memory usage, and billable instance time. It's a good starting point for operational checks.

Building a Custom Dashboard with gcloud

Custom dashboards let you aggregate metrics across multiple services, add logs-based panels, and tailor the view for specific audiences (developers, SREs, cost analysts). Here is a complete custom dashboard JSON definition you can deploy via gcloud:


# Save this as dashboard.json and deploy with:
# gcloud monitoring dashboards create --config=dashboard.json

{
  "displayName": "Cloud Run Production Overview",
  "dashboardFilters": [
    {
      "filterKey": "resource.label.service_name",
      "labelValue": "SERVICE_NAME",
      "template": ".*"
    }
  ],
  "mosaicLayout": {
    "columns": 12,
    "tiles": [
      {
        "title": "Request Rate (All Services)",
        "width": 6,
        "height": 4,
        "widget": {
          "xyChart": {
            "dataSets": [
              {
                "timeSeriesQuery": {
                  "timeSeriesFilter": {
                    "filter": "metric.type=\"run.googleapis.com/request_count\" resource.type=\"cloudrun_revision\"",
                    "aggregation": {
                      "perSeriesAligner": "ALIGN_RATE",
                      "alignmentPeriod": "60s"
                    }
                  }
                },
                "plotType": "LINE"
              }
            ],
            "chartOptions": {
              "displayHorizontal": false
            }
          }
        }
      },
      {
        "title": "P99 Latency (ms)",
        "width": 6,
        "height": 4,
        "widget": {
          "xyChart": {
            "dataSets": [
              {
                "timeSeriesQuery": {
                  "timeSeriesFilter": {
                    "filter": "metric.type=\"run.googleapis.com/request_latencies\" resource.type=\"cloudrun_revision\"",
                    "aggregation": {
                      "perSeriesAligner": "ALIGN_PERCENTILE_99",
                      "alignmentPeriod": "300s"
                    }
                  }
                },
                "plotType": "LINE"
              }
            ],
            "chartOptions": {
              "displayHorizontal": false
            }
          }
        }
      },
      {
        "title": "Active Instances",
        "width": 6,
        "height": 4,
        "widget": {
          "xyChart": {
            "dataSets": [
              {
                "timeSeriesQuery": {
                  "timeSeriesFilter": {
                    "filter": "metric.type=\"run.googleapis.com/container/instance_count\" metric.labels.state=\"active\" resource.type=\"cloudrun_revision\"",
                    "aggregation": {
                      "perSeriesAligner": "ALIGN_MEAN",
                      "alignmentPeriod": "60s"
                    }
                  }
                },
                "plotType": "LINE"
              }
            ]
          }
        }
      },
      {
        "title": "5xx Error Rate (%)",
        "width": 6,
        "height": 4,
        "widget": {
          "xyChart": {
            "dataSets": [
              {
                "timeSeriesQuery": {
                  "timeSeriesFilter": {
                    "filter": "metric.type=\"run.googleapis.com/request_count\" metric.labels.response_code_class=\"5xx\" resource.type=\"cloudrun_revision\"",
                    "aggregation": {
                      "perSeriesAligner": "ALIGN_RATE",
                      "alignmentPeriod": "300s"
                    }
                  }
                },
                "plotType": "LINE"
              }
            ]
          }
        }
      },
      {
        "title": "Container Startup Latency (Cold Start Indicator)",
        "width": 6,
        "height": 4,
        "widget": {
          "xyChart": {
            "dataSets": [
              {
                "timeSeriesQuery": {
                  "timeSeriesFilter": {
                    "filter": "metric.type=\"run.googleapis.com/container/startup/latency\" resource.type=\"cloudrun_revision\"",
                    "aggregation": {
                      "perSeriesAligner": "ALIGN_PERCENTILE_95",
                      "alignmentPeriod": "3600s"
                    }
                  }
                },
                "plotType": "LINE"
              }
            ]
          }
        }
      },
      {
        "title": "CPU Utilization (% of allocated)",
        "width": 6,
        "height": 4,
        "widget": {
          "xyChart": {
            "dataSets": [
              {
                "timeSeriesQuery": {
                  "timeSeriesFilter": {
                    "filter": "metric.type=\"run.googleapis.com/container/cpu/usage\" resource.type=\"cloudrun_revision\"",
                    "aggregation": {
                      "perSeriesAligner": "ALIGN_MEAN",
                      "alignmentPeriod": "60s"
                    }
                  }
                },
                "plotType": "LINE"
              }
            ],
            "chartOptions": {
              "displayHorizontal": false,
              "thresholds": [
                {
                  "label": "High CPU (80%)",
                  "value": 0.8,
                  "color": "YELLOW"
                }
              ]
            }
          }
        }
      }
    ]
  }
}

Deploy this dashboard with:


gcloud monitoring dashboards create --config=dashboard.json

Custom Application Metrics

Beyond the platform metrics, you'll want application-level signals: order processing time, cache hit ratios, user signup completions. Cloud Run supports OpenTelemetry as the recommended path for custom metrics. You instrument your code with the OpenTelemetry SDK, export metrics to the Cloud Monitoring backend, and query them alongside platform metrics.

Emitting Custom Metrics with OpenTelemetry (Node.js Example)


// Install dependencies:
// npm install @opentelemetry/sdk-node @opentelemetry/exporter-metrics-otlp-proto

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-proto');
const { MeterProvider, PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');

// Configure resource attributes to identify your Cloud Run service
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: 'my-cloud-run-service',
  [SemanticResourceAttributes.SERVICE_NAMESPACE]: 'production',
});

// Set up the metric exporter pointing to Cloud Monitoring
// In Cloud Run, the OTLP exporter auto-discovers the project
const metricExporter = new OTLPMetricExporter({
  // No endpoint needed — Cloud Run injects the default
});

const metricReader = new PeriodicExportingMetricReader({
  exporter: metricExporter,
  exportIntervalMillis: 10000, // Export every 10 seconds
});

const meterProvider = new MeterProvider({
  resource: resource,
});
meterProvider.addMetricReader(metricReader);

const meter = meterProvider.getMeter('my-app-metrics');

// Create a histogram for order processing time
const orderProcessingHistogram = meter.createHistogram('order.processing.duration', {
  description: 'Duration of order processing in milliseconds',
  unit: 'ms',
});

// In your request handler, record observations
app.post('/orders', async (req, res) => {
  const start = Date.now();
  try {
    await processOrder(req.body);
    const duration = Date.now() - start;
    orderProcessingHistogram.record(duration, {
      'order.status': 'success',
      'order.type': req.body.type,
    });
    res.status(201).send({ ok: true });
  } catch (err) {
    const duration = Date.now() - start;
    orderProcessingHistogram.record(duration, {
      'order.status': 'error',
      'order.type': req.body.type,
    });
    res.status(500).send({ error: err.message });
  }
});

// Initialize the SDK
const sdk = new NodeSDK({
  metricReader: metricReader,
  resource: resource,
});
sdk.start();

// Graceful shutdown
process.on('SIGTERM', () => {
  sdk.shutdown().then(() => console.log('Metrics flushed'));
});

Once emitted, these custom metrics appear in Cloud Monitoring under the custom.googleapis.com/order.processing.duration metric type. You can query them, alert on them, and add them to dashboards just like the built-in metrics.

Best Practices for Cloud Run Monitoring

  • Alert on symptoms, not causes: Alert on high latency or error rates (user-facing symptoms) rather than CPU usage or instance count (potential causes). The symptom alerts tell you when user experience is degraded; the cause metrics go on dashboards for debugging.
  • Use SLO-based alerting: Define Service Level Objectives (SLOs) for your Cloud Run services — e.g., 99.9% of requests succeed within 500ms — and build alerts that fire when error budgets burn faster than acceptable. This reduces alert fatigue compared to static thresholds.
  • Separate warning and critical alerts: Create two tiers: a warning alert that notifies the on-call channel with low urgency, and a critical alert that pages after a longer duration. For example, warn after 5 minutes of elevated latency, page after 30 minutes.
  • Monitor cold starts proactively: Keep the container/startup/latency metric on your dashboard and alert if P95 startup latency exceeds acceptable thresholds (e.g., 2 seconds). High cold start times often indicate large container images or slow initialization code.
  • Correlate metrics with revision tags: Cloud Run revisions are immutable. When you deploy a new revision, tag metrics queries with the revision label to compare performance before and after the deploy. This enables quick rollback decisions.
  • Dashboard for your audience: Build separate dashboards for developers (request latency breakdowns, error traces), SREs (SLO compliance, error budget burn charts), and cost analysts (billable instance time, CPU-seconds per service).
  • Version-control your dashboards and alerts: Store dashboard JSON and alerting policy Terraform/Config Connector definitions in your Git repository. Changes go through code review, and deployments are automated via CI/CD.
  • Set up log-based metrics for rich context: When you need metrics that aren't available from the platform (e.g., specific business logic errors), create log-based metrics from structured log entries. These behave like custom metrics and can be alerted on.
  • Test alerts regularly: Use the Cloud Monitoring alert testing feature or simulate conditions (e.g., deploy a broken revision in a dev environment) to verify that notifications reach the right channels and contain actionable runbook links.
  • Watch costs: Monitor billable_instance_time and billable_cpu_seconds to detect cost anomalies. Set up a billing alert as a safety net, but use these metrics for per-service cost attribution and optimization.

Putting It All Together: A Monitoring Workflow

Here is a practical, end-to-end monitoring setup for a production Cloud Run service:


# 1. Deploy a custom dashboard with all key metrics
gcloud monitoring dashboards create --config=production-dashboard.json

# 2. Create notification channels for email and Slack
gcloud beta monitoring channels create \
  --display-name="Engineering Email" \
  --type=email \
  --channel-labels=email_address=eng@example.com

gcloud beta monitoring channels create \
  --display-name="Slack Alerts" \
  --type=slack \
  --channel-labels=slack_channel=#alerts,slack_token=your-token

# 3. Create a multi-condition alerting policy for high-severity incidents
#    Condition A: High latency (P99 > 3s for 10 minutes)
#    Condition B: High error rate (> 10% 5xx for 5 minutes)
#    Either condition triggers the alert
gcloud alpha monitoring policies create \
  --display-name="Cloud Run Critical Incident" \
  --combiner="OR" \
  --condition-from-file=condition-latency.yaml \
  --condition-from-file=condition-errors.yaml \
  --notification-channels=projects/my-project/notificationChannels/email-id,projects/my-project/notificationChannels/slack-id \
  --documentation="https://wiki.example.com/runbooks/cloud-run-critical"

Conclusion

Monitoring Cloud Run effectively means layering platform metrics, custom application signals, well-tuned alerts, and audience-specific dashboards into a cohesive observability stack. The built-in metrics give you immediate visibility into requests, instances, resources, and costs — but the real value comes when you connect these signals to your SLOs, automate alerting with clear runbooks, and empower your team with dashboards that answer their specific questions. Start with the pre-built dashboard, then evolve toward infrastructure-as-code dashboards and alerts stored in Git, tested in pre-production, and continuously refined as your services grow. The combination of Cloud Run's auto-instrumentation and OpenTelemetry's extensibility means you can achieve production-grade observability without sacrificing the serverless simplicity that brought you to Cloud Run in the first place.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles