DevOps

Prometheus, Grafana, OpenTelemetry: Observability Stack

Master your system's health with Prometheus, Grafana, and OpenTelemetry. Learn to build a robust monitoring and observability stack for cloud-native applications.

Khader Vali October 3, 2026 15 min read

Mastering Observability: Prometheus, Grafana, and OpenTelemetry in Action

As applications grow in complexity, moving from monoliths to distributed microservices, the traditional approach to simply “monitoring” them often falls short. What you need is a deeper, more comprehensive understanding of your system’s internal state. This is where observability comes into play, and a powerful triumvirate of open-source tools—Prometheus, Grafana, and OpenTelemetry—forms the backbone of a modern, effective observability stack.

In this extensive guide, we’ll embark on a journey to demystify monitoring and observability, explore each component of this powerful stack in detail, and illustrate how they integrate seamlessly to give you unparalleled insight into your applications and infrastructure. Whether you’re a seasoned SRE, a DevOps engineer, or a developer looking to build more resilient systems, this article will equip you with the knowledge to implement a world-class observability solution.

Monitoring vs. Observability: A Crucial Distinction

Before we dive into the tools, let’s clarify two terms often used interchangeably but with distinct meanings: monitoring and observability.

  • Monitoring: Traditionally, monitoring involves collecting a predefined set of metrics and logs to track the health and performance of known aspects of a system. You define what you want to measure (e.g., CPU utilization, memory usage, request rates) and set alerts based on thresholds. Monitoring answers the question, “Is the system working as expected based on what I already know to check?” It’s like checking your car’s fuel gauge, speed, and engine temperature lights—you know what to look for.

  • Observability: Observability, on the other hand, is a property of a system that allows you to infer its internal state purely by examining the data it outputs (metrics, traces, and logs). It empowers you to ask arbitrary questions about your system and understand *why* something is happening, even for unforeseen issues. It’s about having enough rich data to troubleshoot completely novel problems without deploying new code. Think of it as having access to every sensor in your car, its internal diagnostics, and a full log of every event, allowing you to diagnose a strange new noise without prior knowledge of its cause.

The shift from monitoring to observability is driven by the dynamic nature of modern distributed systems. With microservices, serverless functions, and ephemeral containers, it’s impossible to predict every failure mode. Observability provides the necessary depth of insight to navigate this complexity.

The Holy Trinity: Prometheus, Grafana, and OpenTelemetry

These three tools, when combined, offer a comprehensive solution for both monitoring and achieving deep observability:

  • Prometheus: A powerful open-source monitoring system and time-series database. It excels at collecting and storing metrics from your applications and infrastructure.

  • Grafana: An open-source platform for data visualization and analytics. It allows you to create beautiful, interactive dashboards to explore and understand your metrics, and configure alerts.

  • OpenTelemetry (OTel): A vendor-neutral set of APIs, SDKs, and tools designed to standardize the collection of telemetry data (metrics, traces, and logs). It acts as the instrumentation layer, ensuring consistent data generation across your diverse technology stack.

Let’s dive into each component.

Prometheus: The Metrics Powerhouse

Prometheus has become the de facto standard for monitoring cloud-native applications, particularly within the Kubernetes ecosystem. Its design philosophy, centered around a pull model and a powerful query language, makes it incredibly effective for dynamic environments.

Prometheus Architecture and Data Model

At its core, Prometheus works by scraping metrics endpoints exposed by your applications and services. Here’s a simplified architectural overview:

Prometheus, Grafana, OpenTelemetry: Observability Stack
Generated Image

Architecture in Words:

  1. Targets: These are your instrumented applications, services, or infrastructure components that expose metrics, typically over HTTP, in a simple text format.

  2. Prometheus Server: The central component. It periodically scrapes (pulls) metrics from configured targets. It then stores these metrics in its time-series database (TSDB).

  3. Service Discovery: Prometheus integrates with various service discovery mechanisms (e.g., Kubernetes, Consul, EC2) to dynamically find targets to scrape, eliminating the need for manual configuration.

  4. Exporters: For services that don’t natively expose Prometheus-compatible metrics (e.g., databases, operating systems, third-party APIs), an “exporter” is used. An exporter is a small service that translates metrics from the target system into the Prometheus format.

    • node_exporter: For host-level metrics (CPU, memory, disk I/O, network).
    • cAdvisor: For container resource usage (often integrated with Kubernetes).
    • Database exporters (e.g., postgres_exporter, mysql_exporter).
  5. Pushgateway: While Prometheus primarily uses a pull model, the Pushgateway allows short-lived jobs (e.g., batch jobs that don’t live long enough to be scraped) to push their metrics to Prometheus.

  6. Alertmanager: Handles alerts sent by the Prometheus server. It de-duplicates, groups, and routes alerts to appropriate notification receivers (email, PagerDuty, Slack, etc.).

  7. Grafana: The visualization layer, which queries Prometheus for metrics and displays them in dashboards.

Data Model: Time Series and Labels

Prometheus stores all data as time series. Each time series is uniquely identified by a metric name and a set of key-value pairs called labels. Labels are crucial; they allow for powerful multi-dimensional data modeling.

Example:

http_requests_total{method="POST", path="/api/v1/users", status="200"} 1234
http_requests_total{method="GET", path="/api/v1/products", status="500"} 56

Here, http_requests_total is the metric name. {method="POST", path="/api/v1/users", status="200"} is a unique set of labels that identifies a specific time series. You can filter and aggregate data based on these labels.

PromQL: The Query Language

Prometheus Query Language (PromQL) is a powerful functional query language that allows you to select and aggregate time series data in real-time. It’s designed for flexibility and efficiency, enabling complex queries for dashboarding and alerting.

Basic PromQL Examples:

  • Current value of a metric:

    http_requests_total
  • Rate of HTTP requests over the last 5 minutes, grouped by path and method:

    rate(http_requests_total{job="my-service"}[5m])
  • Top 5 busiest HTTP paths:

    topk(5, sum by (path) (rate(http_requests_total[5m])))
  • Percentage of HTTP 5xx errors:

    sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) * 100

Prometheus Configuration Example

A basic prometheus.yml:

global:
  scrape_interval: 15s # How frequently to scrape targets
  evaluation_interval: 15s # How frequently to evaluate rules

alerting:
  alertmanagers:
  - static_configs:
    - targets: ['alertmanager:9093'] # Address of your Alertmanager

rule_files:
  - "alert.rules" # File containing alerting rules

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
    - targets: ['localhost:9090'] # Prometheus scrapes itself

  - job_name: 'node_exporter'
    static_configs:
    - targets: ['node-exporter-host:9100'] # Example node_exporter

  - job_name: 'my-application'
    metrics_path: /metrics # Default path, can be overridden
    static_configs:
    - targets: ['my-app-instance-1:8080', 'my-app-instance-2:8080']

  - job_name: 'kubernetes-pods'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)
      - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
        action: replace
        regex: ([^:]+)(?::\d+)?;(\d+)
        replacement: $1:$2
        target_label: __address__
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)
      - source_labels: [__meta_kubernetes_namespace]
        action: replace
        target_label: kubernetes_namespace
      - source_labels: [__meta_kubernetes_pod_name]
        action: replace
        target_label: kubernetes_pod_name

This configuration snippet demonstrates global settings, integrating with Alertmanager, defining rule files, and several scraping configurations: scraping Prometheus itself, a static node_exporter, a custom application, and a more complex example using Kubernetes service discovery with relabeling to dynamically discover and scrape pods based on annotations.

Grafana: The Visualization Maestro

Once Prometheus collects your metrics, Grafana steps in to transform that raw data into meaningful insights. Grafana is a powerful, open-source analytics and interactive visualization web application.

Key Features of Grafana

  • Data Sources: Grafana supports a vast array of data sources, including Prometheus, InfluxDB, PostgreSQL, MySQL, Elasticsearch, CloudWatch, Azure Monitor, Google Cloud Monitoring, and many more. This multi-data source capability makes it a central hub for all your operational data.

  • Dashboards: The core of Grafana. Dashboards are collections of panels, each displaying data from your data sources. You can organize panels into rows and tabs, making complex data sets digestible.

  • Panels: Various visualization types (graphs, single stats, gauges, tables, heatmaps, etc.) to represent your data effectively. Each panel uses queries specific to its data source.

  • Templating and Variables: This feature is a game-changer for dynamic dashboards. Instead of creating a separate dashboard for each server, service, or environment, you can use variables (e.g., $server, $service, $namespace) to make your dashboards interactive and reusable. Users can select values from dropdowns, and all panels update accordingly.

  • Alerting: Grafana can evaluate PromQL queries (or queries from other data sources) and send alerts to various notification channels (Slack, PagerDuty, email, webhooks) when conditions are met.

  • Annotations: Mark important events (e.g., deployments, incidents) on your graphs to correlate them with changes in metrics.

Creating a Basic Grafana Dashboard (Conceptual Walkthrough)

  1. Add Prometheus as a Data Source:

    • Go to Configuration -> Data Sources -> Add data source.
    • Select Prometheus.
    • Set the URL to your Prometheus server (e.g., http://prometheus:9090).
    • Save & Test.
  2. Create a New Dashboard:

    • Click the ‘+’ icon -> Create dashboard.
    • Add a new panel.
  3. Configure a Graph Panel:

    • In the “Query” tab, select your Prometheus data source.
    • Enter a PromQL query, e.g., to show CPU usage: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
    • In the “Visualization” tab, choose “Graph.”
    • Configure axes, legend, and other display options.
    • Add a title (e.g., “CPU Utilization by Instance”).
  4. Add a SingleStat Panel:

    • Add another panel.
    • Query for a single value, e.g., total HTTP requests per second: sum(rate(http_requests_total[1m]))
    • Choose “SingleStat” visualization.
    • Configure units, thresholds, and gauges.
  5. Add a Variable:

    • Go to Dashboard settings -> Variables -> Add variable.
    • Name: instance
    • Type: Query
    • Data source: Prometheus
    • Query: label_values(node_cpu_seconds_total, instance) (This will populate the dropdown with all available instance labels.)
    • Now you can use $instance in your panel queries, e.g., 100 - (rate(node_cpu_seconds_total{mode="idle", instance="$instance"}[5m]) * 100)

Grafana’s flexibility makes it indispensable for turning raw metrics into actionable insights, and its alerting capabilities ensure you’re notified when critical thresholds are crossed.

OpenTelemetry: Standardizing Telemetry Data

While Prometheus is fantastic for metrics and Grafana for visualization, how do you collect metrics, traces, and logs consistently across diverse programming languages and services? This is the problem OpenTelemetry solves.

OpenTelemetry is a Cloud Native Computing Foundation (CNCF) project that provides a single set of APIs, SDKs, and data formats for instrumenting your applications to generate telemetry data. It aims to be vendor-neutral, allowing you to switch backend observability platforms without re-instrumenting your code.

The Three Pillars of Observability (Revisited with OTel)

OpenTelemetry provides specifications and implementations for collecting all three pillars:

  • Metrics: Numerical measurements representing the state of a system over time (e.g., request count, latency, error rates). OTel provides APIs for Counters, Gauges, and Histograms.

  • Traces: Represent the end-to-end journey of a request through a distributed system. A trace is composed of “spans,” where each span represents an operation (e.g., a function call, a database query, an HTTP request). Traces are crucial for understanding latency and pinpointing failures in microservices.

  • Logs: Discrete, timestamped events that provide context about what happened at a specific point in time. While OTel has specifications for logs, its primary strength historically has been metrics and traces, though log collection is maturing rapidly within the project.

Key Components of OpenTelemetry

OpenTelemetry consists of several main components:

  1. API (Application Programming Interface): Defines how applications interact with the OpenTelemetry SDKs to produce telemetry data. This is what developers use to instrument their code.

  2. SDK (Software Development Kit): Language-specific implementations of the API. The SDK processes the telemetry data generated by the API, applies configuration (e.g., samplers, resource detectors), and exports it.

  3. Collector: A proxy that can receive, process, and export telemetry data. It’s a powerful and flexible component that decouples instrumentation from backend concerns. You can run it as an agent on each host or as a gateway.

OpenTelemetry Collector: The Swiss Army Knife of Telemetry

The OTel Collector is a critical component for several reasons:

  • Vendor Agnostic: It can receive data in multiple formats (OTLP, Jaeger, Prometheus, Zipkin, etc.) and export it to various backends (Prometheus, Jaeger, Zipkin, OTLP, Kafka, Loki, etc.).

  • Processing Capabilities: Before exporting, the Collector can process data, including batching, filtering, transforming, enriching with attributes, and sampling traces.

  • Reduces Overhead: By acting as an agent, it can offload processing and exporting logic from your application instances.

A Collector configuration defines a pipeline:

receivers:
  otlp:
    protocols:
      grpc:
      http:

  prometheus:
    config:
      scrape_configs:
        - job_name: 'otel-collector'
          static_configs:
            - targets: ['0.0.0.0:8888'] # Scrape the collector's own metrics

processors:
  batch:
    send_batch_size: 100
    timeout: 10s
  attributes:
    actions:
      - key: environment
        value: production
        action: insert

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889" # Expose Prometheus format metrics for scraping by Prometheus server
  otlp/jaeger:
    endpoint: "jaeger:4317" # Send traces to Jaeger
  otlp/loki:
    endpoint: "loki:3100" # Send logs to Loki

service:
  pipelines:
    metrics:
      receivers: [otlp, prometheus]
      processors: [batch, attributes]
      exporters: [prometheus] # Metrics go to Prometheus exporter
    traces:
      receivers: [otlp]
      processors: [batch, attributes]
      exporters: [otlp/jaeger] # Traces go to Jaeger exporter
    logs:
      receivers: [otlp]
      processors: [batch, attributes]
      exporters: [otlp/loki] # Logs go to Loki exporter

In this example:

  • The receivers section defines how the Collector gets data (e.g., OTLP from applications, Prometheus format from itself).
  • The processors section defines how data is transformed (e.g., batching for efficiency, adding global attributes like environment).
  • The exporters section defines where the data goes (e.g., Prometheus for metrics, Jaeger for traces, Loki for logs).
  • The service.pipelines section ties these components together, defining separate pipelines for metrics, traces, and logs.

Integrating OpenTelemetry Metrics with Prometheus and Grafana

This is where OpenTelemetry bridges into our existing Prometheus/Grafana stack:

  1. Application Instrumentation: Your applications are instrumented using OpenTelemetry SDKs.

    from opentelemetry import metrics
    from opentelemetry.sdk.metrics import MeterProvider
    from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
    from opentelemetry.exporter.prometheus import PrometheusMetricReader
    
    # Configure OpenTelemetry to export metrics in Prometheus format
    reader = PrometheusMetricReader()
    provider = MeterProvider(metric_readers=[reader])
    metrics.set_meter_provider(provider)
    
    # Create a meter from the global meter provider
    meter = metrics.get_meter("my-app-service", "1.0.0")
    
    # Create a counter instrument
    requests_counter = meter.create_counter(
        "http.server.requests",
        description="Total number of HTTP requests",
        unit="1",
    )
    
    # Example usage in a web framework
    def my_web_handler(request):
        requests_counter.add(1, {"method": request.method, "route": "/my-path"})
        # ... rest of your handler logic
    

    Alternatively, and more commonly in a microservices setup, applications export metrics via OTLP to an OpenTelemetry Collector.

    from opentelemetry import metrics
    from opentelemetry.sdk.metrics import MeterProvider
    from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
    from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
    
    # Configure OpenTelemetry to export metrics via OTLP GRPC
    exporter = OTLPMetricExporter(endpoint="otel-collector:4317") # Collector endpoint
    reader = PeriodicExportingMetricReader(exporter)
    provider = MeterProvider(metric_readers=[reader])
    metrics.set_meter_provider(provider)
    
    meter = metrics.get_meter("my-app-service", "1.0.0")
    requests_counter = meter.create_counter(...)
    # ... usage as above
    

  2. OpenTelemetry Collector (as an intermediary): The Collector receives OTLP metrics from your applications. It then uses the prometheus exporter to expose these metrics in the Prometheus format on a dedicated endpoint (e.g., 0.0.0.0:8889 in our example config).

  3. Prometheus Server: The Prometheus server is configured to scrape the OpenTelemetry Collector’s Prometheus exporter endpoint. This means Prometheus effectively pulls metrics that originated from your OTel-instrumented applications.

  4. Grafana: Grafana continues to query Prometheus, now getting metrics that are consistently instrumented across your services, regardless of the underlying language or framework, thanks to OpenTelemetry.

This approach offers incredible flexibility. Your applications only need to know how to talk to the OTel Collector, and the Collector handles the translation and routing to your chosen observability backends. This future-proofs your instrumentation.

While this article focuses on Prometheus/Grafana, remember that OpenTelemetry also generates traces and logs. These would typically be exported by the OTel Collector to other specialized backends like Jaeger (for traces) or Loki (for logs), and then potentially visualized alongside your metrics in Grafana (using its tracing/logging data source plugins).

Architecting for Comprehensive Observability

Let’s tie it all together with a conceptual architecture for a modern, observable microservices environment.

Prometheus, Grafana, OpenTelemetry: Observability Stack
Generated Image

Conceptual Architecture Diagram (in words):

--------------------------------------------------------------------------------------------------
|                                         Your Applications/Services                               |
|                               (Java, Python, Go, Node.js, Ruby, .NET, etc.)                      |
|------------------------------------------------------------------------------------------------|
|                                   OpenTelemetry SDKs (Auto/Manual Instrumentation)             |
|                                       - Generate Metrics, Traces, Logs                       |
--------------------------------------------------------------------------------------------------
       | (OTLP GRPC/HTTP)
       V
--------------------------------------------------------------------------------------------------
|                                       OpenTelemetry Collector (Agent/Gateway)                    |
|------------------------------------------------------------------------------------------------|
| Receivers: OTLP (from apps), Prometheus (from self-metrics, other exporters)                   |
| Processors: Batching, Attributes enrichment, Sampling                                          |
| Exporters:                                                                                     |
|   - Prometheus (for metrics) ------------------------------------------------------------------|
|   - OTLP/Jaeger (for traces)                                                                   |
|   - OTLP/Loki (for logs)                                                                       |
--------------------------------------------------------------------------------------------------
       | (Prometheus scrape)          | (OTLP GRPC)            | (OTLP HTTP/GRPC)
       V                              V                        V
-----------------------      -----------------------      -----------------------
|   Prometheus Server   |    |    Jaeger (Tracing)   |    |      Loki (Logging)   |
| (Time-Series Database)|    |                       |    |                       |
-----------------------      -----------------------      -----------------------
       | (PromQL)
       V
--------------------------------------------------------------------------------------------------
|                                            Grafana                                             |
|------------------------------------------------------------------------------------------------|
| Data Sources: Prometheus, Jaeger, Loki                                                         |
| Visualizations: Dashboards (Metrics), Trace views, Log exploration                             |
| Alerting: Based on Prometheus queries, sent to Alertmanager                                    |
--------------------------------------------------------------------------------------------------
       |
       V
--------------------------------------------------------------------------------------------------
|                                          Alertmanager                                          |
|                                  (Grouping, Deduplication, Routing)                            |
--------------------------------------------------------------------------------------------------
       |
       V
--------------------------------------------------------------------------------------------------
|                                        Notification Channels                                   |
|                                    (Slack, PagerDuty, Email, Webhooks)                         |
--------------------------------------------------------------------------------------------------

Explanation of the Flow:

  1. Instrumentation: Your microservices and applications are instrumented using OpenTelemetry SDKs. This means adding code that creates counters, records durations (metrics), generates spans for operations (traces), and enriches logs with trace IDs and other context.

  2. Telemetry Collection (OTel Collector): Applications send their raw telemetry data (in OTLP format) to a local or centralized OpenTelemetry Collector. The Collector acts as a buffer and a processing layer. It can batch data, add common attributes (like `cluster_name`, `environment`), and perform basic filtering or sampling.

  3. Data Export:

    • Metrics: The OTel Collector’s Prometheus exporter exposes the processed metrics in the Prometheus exposition format.
    • Traces: The OTel Collector forwards traces to a distributed tracing backend like Jaeger.
    • Logs: The OTel Collector forwards logs to a logging aggregation system like Loki.
  4. Storage & Querying:

    • Prometheus: Scrapes the OTel Collector’s Prometheus endpoint, storing and allowing PromQL queries for metrics.
    • Jaeger: Stores and allows querying of traces.
    • Loki: Stores and allows querying of logs (using LogQL).
  5. Visualization & Alerting:

Written by

Khader Vali

Senior Software Engineer specializing in cloud architecture, real-time systems, and enterprise-scale applications.

Share this article

Related Articles

Blue/Green & Canary: Zero-Downtime Deployment Mastery

Jul 12, 2026 · 16 min read

Zero Downtime Deployments: Blue/Green & Canary Releases

Oct 12, 2026 · 18 min read

SBOM & Sigstore: Securing Your Software Supply Chain

Sep 26, 2026 · 16 min read