OpenTelemetry vs. Prometheus: Understanding Their Roles

No, OpenTelemetry (OTel) will not replace Prometheus. Instead, they have essentially joined forces.

The relationship between the two is one of the most misunderstood topics in DevOps and SRE, but the reality is beautifully collaborative: OpenTelemetry is the industry standard for generating and collecting data, while Prometheus is the industry standard for storing and querying metrics.

The Core Difference

FeaturePrometheusOpenTelemetry (OTel)
What is it?A full monitoring system (scraper, database, querying, alerting).A standardized framework/API for application instrumentation.
Data ScopeMetrics only (CPU, memory, request counts).The “Three Pillars”: Metrics, Traces, and Logs.
StorageHas its own built-in, highly efficient local database (TSDB).No storage. It only collects and routes data; it cannot store it.
Data FlowTraditionally Pull-based (scrapes apps).Push or Pull (via the OTel Collector).

Why OTel Won’t Replace Prometheus (They “Grew Up” Together)

Historically, there was real friction. If you instrumented an application using OTel, it used dot-notation (http.server.request.duration), but Prometheus only accepted underscores (http_server_request_duration). It caused massive formatting headaches.

However, the community completely solved this. With the widespread adoption of Prometheus 3.0, the two systems are perfectly intertwined:

  1. Prometheus Speaks OTel Natively: Prometheus natively ingests OTLP (OpenTelemetry’s native protocol) and fully supports OTel’s naming conventions (like dots and dashes).
  2. OTel Lacks a Backend: Because OTel explicitly refuses to build a storage database or a query language, it needs backends. When OTel collects metrics from your applications, it frequently sends them directly into a Prometheus backend.
  3. Infrastructure vs. Application: * Prometheus remains king for infrastructure monitoring. Thousands of tools (like Kubernetes, Linux Node Exporter, databases) natively output Prometheus metrics.
    • OTel is the new king for application monitoring (APM), because it allows developers to write code once and seamlessly correlate metrics with deep distributed distributed traces.

The Winning Architecture

Most modern engineering teams don’t choose between them—they use them together in a hybrid pipeline:

[ App / Code ] ──(Traces & Metrics)──> [ OTel Collector ]
┌─────────────────────────┴────────────────────────┐
▼ ▼
[ Prometheus / Grafana ] [ Jaeger / Tempo ]
(Stores & Alerts on Metrics) (Stores & Analyzes Traces)

Enhance Grafana Alerts with Custom Annotations

Right now alert email is showing only the default Grafana labels:

alertname = Sys Load
instance = 192.168.231.43:9100
grafana_folder = Alerts

You can make the email much more useful by adding custom annotations to the alert rule.

Option 1 – Add Summary and Description

In the alert rule, scroll down to Annotations and add:

Summary
High system load detected on {{ $labels.instance }}
Description
Server: {{ $labels.instance }}
Current Load: {{ printf "%.2f" $values.A.Value }}%
Threshold: 25%
Please investigate CPU utilization, running processes, and system responsiveness.

Then the email will look like:

ALERT: Sys Load
High system load detected on 192.168.231.43:9100
Server: 192.168.231.43:9100
Current Load: 87.45%
Threshold: 25%
Please investigate CPU utilization, running processes, and system responsiveness.

Option 2 – Add Severity Labels

Add labels:

severity = warning
team = infrastructure
environment = production

Then email shows:

severity: warning
team: infrastructure
environment: production
instance: 192.168.231.43:9100

Very useful when you have many alerts.


Option 3 – Include Useful Dashboard Links

Annotation:

Runbook URL
https://wiki.company.com/runbooks/linux-high-load
Dashboard URL
https://grafana.company.com/d/linux-server/linux-server-dashboard

Then engineers can click directly from the email.


Option 4 – Add Hostname Instead of IP

Currently you see:

192.168.231.43:9100

Much better if Prometheus exposes:

labels:
hostname: cvm-srv44

Then annotation:

Host: {{ $labels.hostname }}
Instance: {{ $labels.instance }}

Email:

Host: cvm-srv44
Instance: 192.168.231.43:9100
Current Load: 87%

Much easier for operations teams.


Option 5 – Enterprise Style Alert

Summary
[{{ $labels.severity | toUpper }}] High System Load on {{ $labels.instance }}
Description
Environment: {{ $labels.environment }}
Server: {{ $labels.instance }}
Current Load: {{ printf "%.2f" $values.A.Value }}%
Alert Threshold: 25%
Recommended Checks:
1. top
2. htop
3. vmstat 1
4. iostat -x 1
5. journalctl -xe
Investigate CPU saturation, runaway processes, or I/O bottlenecks.

Bonus: Add Hostname Automatically in Prometheus

In your prometheus.yml:

scrape_configs:
- job_name: linux-servers
static_configs:
- targets:
- 192.168.231.43:9100
labels:
hostname: cvm-srv44
- targets:
- 192.168.231.122:9100
labels:
hostname: cvm-srv45

Then use:

Host: {{ $labels.hostname }}

in Grafana alerts.

This is usually the biggest improvement because emails become:

[WARNING] High System Load
Host: cvm-srv44
IP: 192.168.231.43:9100
Current Load: 91.7%
Threshold: 80%

instead of just showing an IP address.

Interview Questions Often Asked for Senior Grafana Architects

1. How do you reduce Prometheus cardinality?

Cardinality means too many unique metric/label combinations.

Example of bad label:

http_requests_total{user_id="12345"}

This creates millions of time series.

To reduce cardinality:

- Avoid dynamic labels: user_id, session_id, request_id, pod_uid
- Keep only useful labels: job, instance, namespace, pod, status_code
- Drop unnecessary labels using relabel_configs
- Use recording rules for expensive queries
- Reduce scrape targets if not needed
- Avoid exposing too many custom metrics
- Set retention limits

Interview answer:

I reduce Prometheus cardinality by controlling labels, dropping high-cardinality labels, reviewing /targets and /tsdb-status, and avoiding dynamic values like user IDs or request IDs in metric labels.


2. Difference between recording rules and alert rules

Recording rule

Pre-calculates a query and saves the result as a new metric.

Example:

- record: node:cpu_usage:avg5m
expr: 100 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100

Used for:

- Faster dashboards
- Reusable metrics
- Reducing query load
Alert rule

Triggers an alert when a condition is met.

Example:

- alert: HighCPU
expr: node:cpu_usage:avg5m > 80
for: 5m

Interview answer:

Recording rules improve performance by precomputing metrics. Alert rules evaluate conditions and trigger notifications.


3. How do you monitor Kubernetes at scale?

Use:

- Prometheus HA
- kube-state-metrics
- node-exporter
- cAdvisor/container metrics
- Alertmanager
- Grafana dashboards
- Loki for logs
- Tempo for traces
- Mimir/Thanos for long-term metrics

For large clusters:

- Use recording rules
- Limit scrape intervals
- Reduce metric cardinality
- Use remote_write to Mimir/Thanos
- Separate platform and application monitoring
- Use namespace/team-based dashboards

Interview answer:

At scale, I avoid a single large Prometheus doing everything. I deploy Prometheus per cluster or per domain, use remote_write to a central backend like Mimir or Thanos, apply cardinality controls, and visualize everything centrally in Grafana.


4. How would you deploy Grafana HA?

Architecture:

Load Balancer
|
+----+----+
| |
Grafana Grafana
| |
+----+----+
|
PostgreSQL/MySQL

Key points:

- Multiple Grafana instances
- Shared external database: PostgreSQL or MySQL
- Load balancer in front
- Shared configuration
- SSO integration
- Persistent dashboards stored in DB
- Provision dashboards/data sources using GitOps/IaC

Important:

Do not use SQLite for HA.

Interview answer:

Grafana is mostly stateless. For HA, I run multiple Grafana pods or VMs behind a load balancer and use PostgreSQL as the shared backend database.


5. How would you design LGTM for 1000 servers?

LGTM means:

L = Loki Logs
G = Grafana Dashboards
T = Tempo Traces
M = Mimir Metrics

Design:

1000 Servers
|
Node Exporter / Alloy / Promtail / OpenTelemetry Collector
|
Metrics → Mimir
Logs → Loki
Traces → Tempo
|
Grafana

Important design points:

- Use Grafana Alloy or OpenTelemetry Collector agents
- Use Mimir for scalable metrics
- Use Loki for centralized logs
- Use Tempo for distributed tracing
- Store data in object storage like S3/Azure Blob
- Use retention policies
- Use tenant/team separation
- Use alerting with Alertmanager or Grafana Alerting

Interview answer:

For 1000 servers, I would avoid one single Prometheus scraping everything. I would use agents on servers, remote_write metrics to Mimir, send logs to Loki, traces to Tempo, and use Grafana as the single visualization and alerting layer.


6. How do you troubleshoot missing metrics?

Check in this order:

1. Is the target up?
2. Is Prometheus scraping the endpoint?
3. Is the exporter running?
4. Is the metric exposed on /metrics?
5. Is the scrape config correct?
6. Are relabeling rules dropping it?
7. Is the time range correct in Grafana?
8. Is the PromQL query correct?
9. Is authentication/TLS blocking scrape?
10. Is the metric name changed?

Useful PromQL:

up
scrape_samples_scraped
scrape_duration_seconds

Useful commands:

curl http://server:9100/metrics
kubectl get servicemonitor -A
kubectl get podmonitor -A
kubectl get targets

Interview answer:

I start from the source. First I verify the exporter exposes the metric, then Prometheus target status, scrape configuration, relabeling, and finally Grafana query/time range.


7. How do you secure Grafana in an enterprise environment?

Use:

- HTTPS/TLS
- SSO with SAML/OIDC/LDAP
- MFA through identity provider
- RBAC
- Team-based permissions
- Folder/dashboard permissions
- Disable anonymous access
- Secure admin account
- Use secrets management
- Audit logging
- Network restrictions
- Backup database and dashboards

Also:

- Do not expose Grafana directly to the internet
- Use reverse proxy or ingress with TLS
- Use least privilege for data sources
- Separate dev/test/prod dashboards

Interview answer:

I secure Grafana using enterprise identity integration, RBAC, TLS, least-privilege data source credentials, dashboard permissions, audit logs, and network restrictions.


8. How do you integrate Grafana with OpenShift?

Options:

- Use OpenShift monitoring Prometheus/Thanos as data source
- Connect Grafana to Thanos Querier
- Use ServiceAccount token for authentication
- Import Kubernetes/OpenShift dashboards
- Monitor nodes, pods, namespaces, etcd, API server, ingress, OVN

Common OpenShift components:

- Prometheus
- Alertmanager
- Thanos Querier
- kube-state-metrics
- node-exporter
- Cluster Monitoring Operator

Example data source:

Grafana → Thanos Querier → OpenShift metrics

Interview answer:

In OpenShift, I usually connect Grafana to Thanos Querier or Prometheus using a service account token. Then I build dashboards for cluster health, nodes, pods, namespaces, etcd, API server, ingress, and OVN networking.


9. How do you implement multi-tenancy?

Ways to implement multi-tenancy:

- Separate organizations in Grafana
- Separate folders per team
- Team-based RBAC
- Dashboard permissions
- Data source permissions
- Separate tenants in Mimir/Loki/Tempo
- Separate Kubernetes namespaces
- Separate alert contact points

Example:

Team A → Folder A → Data source tenant A
Team B → Folder B → Data source tenant B

For strong isolation:

- Use separate Grafana organizations
- Use separate Mimir/Loki tenants
- Use SSO groups mapped to Grafana teams

Interview answer:

I implement multi-tenancy using SSO group mapping, teams, folders, RBAC, data source permissions, and backend tenant isolation in Mimir, Loki, or Tempo.


10. How do you monitor AWS, Azure, and OpenShift from one Grafana instance?

Architecture:

AWS CloudWatch
Azure Monitor
OpenShift Thanos/Prometheus
Linux Node Exporter
Kubernetes Metrics
|
v
Grafana

Data sources:

AWS → CloudWatch
Azure → Azure Monitor
OpenShift → Prometheus/Thanos
Logs → Loki or Elasticsearch
Traces → Tempo

Best practices:

- Use separate folders per platform
- Use variables: cloud, region, cluster, namespace
- Use RBAC per team
- Use central alerting
- Use standardized dashboards
- Use tags and labels consistently

Interview answer:

I would use Grafana as the central visualization layer with multiple data sources: CloudWatch for AWS, Azure Monitor for Azure, and Prometheus/Thanos for OpenShift. Then I would organize dashboards by platform, region, cluster, and application, with RBAC and alerting policies per team.


Strong closing answer for interview

My approach is to design Grafana as the central observability portal, but not as the only backend. Prometheus handles metrics collection, Loki handles logs, Tempo handles traces, and Mimir or Thanos handles scalable long-term metrics. For enterprise environments, I focus on HA, RBAC, SSO, cardinality control, recording rules, proper alerting, and multi-tenant separation.

Resolving CPU Bottlenecks on Linux Servers with Grafana

Based on the provided Grafana dashboard image (utilizing the popular Node Exporter Full template), here is a breakdown of what is happening on this Linux server (cvm-srv51).

The Main Takeaway: CPU Bottleneck

Your server is currently under a heavy CPU bottleneck, while memory and disk resources are mostly relaxed. Around 12:43 to 12:45, there was a brief dip where the load dropped significantly, but it immediately spiked back up to its previous high levels.

Detailed Metric Breakdown

1. Quick CPU / Mem / Disk (Gauges)
  • CPU Busy (95.2%): Your CPU is running almost at maximum capacity. The red ring indicates this is in a critical threshold.
  • Sys Load (136.3%): This represents a system load average that exceeds the server’s capacity. Since the server has 4 CPU Cores (shown in the info box), a 100% load capacity would mean a load average of 4.0. Running at 136.3% means the CPU queue is constantly backed up, and processes are waiting for CPU time.
  • RAM Used (17.2%) & SWAP Used (0.0%): Out of 23 GiB available, memory usage is very low and healthy. SWAP isn’t being touched, meaning this is strictly a compute problem, not a memory-starvation problem.
  • Root FS Used (32.7%): Storage space is perfectly fine, with plenty of room left on the root filesystem.
2. Historical Graphs (Last 15 Minutes)
  • CPU Basic: The large blue block dominating the graph represents Busy User space. This means the high CPU usage is being driven by user-level applications or services (e.g., a heavy application, database queries, a running script, or containerized apps), rather than kernel overhead (Busy System) or waiting for hardware (Busy Iowait).
  • Memory Basic: A completely flat line showing steady, low memory utilization.
  • Network Traffic Basic: There is a noticeable drop in network traffic that perfectly correlates with the dip in CPU usage around 12:44. When network traffic resumed, CPU usage shot back up. This suggests that incoming network requests or data transfers are likely triggering the high CPU workload.
Next Steps for Troubleshooting

Because the CPU is being hammered by user-space applications linked to network activity, you should log into the server (cvm-srv51) via SSH and investigate:

  1. Run top or htop: Check which specific processes are consuming the most CPU.
  2. Inspect Containers: Since the network graph lists several virtual interfaces (like docker0 and veth...), it’s highly likely this server is running Docker containers. Use docker stats to see which container is pulling all the compute.
  3. Check Application Logs: Match the timeline (the dip at 12:44) against your application or web server logs to see what traffic paused or restarted at that moment.

Understanding PromQL: A Complete Guide

What is PromQL?

PromQL (Prometheus Query Language) is the query language used by Prometheus to retrieve, filter, aggregate, and analyze time-series metrics.

It is the primary language used in:

  • Prometheus UI
  • Grafana dashboards
  • Alerting rules
  • Recording rules

PromQL Data Model

Metrics are stored as:

metric_name{label1="value1",label2="value2"} value timestamp

Example:

node_cpu_seconds_total{instance="server1",mode="idle"} 12345

Where:

ComponentMeaning
node_cpu_seconds_totalMetric name
instance=”server1″Label
mode=”idle”Label
12345Metric value

Basic PromQL Examples

1. Show a Metric
up

Returns all monitored targets.

Example:

up{instance="server1"} 1
up{instance="server2"} 1
  • 1 = healthy
  • 0 = down

2. Filter by Label
up{instance="server1:9100"}

Returns metrics only for that server.


3. Multiple Labels
node_cpu_seconds_total{
instance="server1:9100",
mode="idle"
}

Range Queries

Retrieve values over a time period.

Example:

node_cpu_seconds_total[5m]

Returns the last 5 minutes of data.


Rate Functions

One of the most common interview topics.

rate()

Calculates the per-second increase of a counter.

Example:

rate(http_requests_total[5m])

Meaning:

How many requests per second occurred during the last 5 minutes?


irate()

Calculates the rate using only the two most recent samples.

irate(http_requests_total[5m])

More responsive but noisier.


CPU Usage Example

Node Exporter provides:

node_cpu_seconds_total

Idle CPU:

avg(rate(node_cpu_seconds_total{mode="idle"}[5m]))

CPU Usage Percentage:

100 - (
avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100
)

Very common Grafana dashboard query.


Memory Usage Example

Used Memory:

node_memory_MemTotal_bytes
-
node_memory_MemAvailable_bytes

Memory Percentage:

(
(node_memory_MemTotal_bytes
-
node_memory_MemAvailable_bytes)
/
node_memory_MemTotal_bytes
) * 100

Aggregation Functions

sum()
sum(http_requests_total)

Adds all values together.


avg()
avg(node_load1)

Average load.


max()
max(node_memory_MemAvailable_bytes)

Highest value.


min()
min(node_memory_MemAvailable_bytes)

Lowest value.


Group By

Example:

sum(rate(http_requests_total[5m])) by (instance)

Output:

server1 = 100 req/s
server2 = 150 req/s

Top Consumers

Top 5 CPU-consuming containers:

topk(
5,
sum(rate(container_cpu_usage_seconds_total[5m]))
by (pod)
)

Very common in Kubernetes/OpenShift interviews.


Kubernetes Examples

Pod Count
count(kube_pod_info)

Running Pods

count(kube_pod_status_phase{phase="Running"})

Node Count
count(kube_node_info)

OpenShift Examples

API Server Latency
histogram_quantile(
0.99,
sum(rate(apiserver_request_duration_seconds_bucket[5m]))
by (le)
)

etcd Latency
histogram_quantile(
0.99,
rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])
)

OVN Pod Status
up{job="ovn-kubernetes-node"}

Alert Rule Example

CPU > 80%

groups:
- name: cpu-alerts
rules:
- alert: HighCPU
expr: 100 - (
avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100
) > 80
for: 5m

Meaning:

  • CPU above 80%
  • For 5 minutes
  • Fire alert

Recording Rule Example

Instead of calculating CPU every dashboard refresh:

- record: node:cpu_usage:avg
expr: 100 - (
avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100
)

Then dashboards query:

node:cpu_usage:avg

This improves performance.


Common Interview Questions

What is the difference between rate() and irate()?
rate()irate()
Uses many samplesUses last two samples
SmootherMore responsive
Good for alertsGood for graphs

What is a counter?

A metric that only increases.

Examples:

http_requests_total
container_cpu_usage_seconds_total

What is a gauge?

A metric that can increase or decrease.

Examples:

node_memory_MemAvailable_bytes
node_load1

What is a histogram?

Used to measure distributions such as latency.

Example:

http_request_duration_seconds_bucket

What is cardinality?

The number of unique metric/label combinations.

Example:

http_requests_total{user="1"}
http_requests_total{user="2"}
http_requests_total{user="3"}
...

Millions of unique labels create high cardinality, which can cause Prometheus performance and memory issues.

Interview answer

PromQL is Prometheus’s query language used to retrieve, filter, aggregate, and calculate metrics. It supports functions such as rate(), sum(), avg(), histogram_quantile(), and label filtering, making it the foundation for Grafana dashboards, alerting rules, and monitoring in Kubernetes and OpenShift environments.

Scaling Prometheus with Thanos: A Comprehensive Guide

Thanos is an open-source, CNCF incubating project designed to scale out Prometheus setups.

While Prometheus is an industry favorite for scraping and alerting on time-series metrics, it has two major technical limitations when scaled to enterprise levels:

  1. Limited Storage Retention: Prometheus saves data to local disks. Keeping metrics for months or years requires massive, expensive solid-state storage.
  2. Lack of Global Querying: If you have 20 clusters running 20 isolated Prometheus instances, you cannot run a single dashboard query to view metrics across all of them simultaneously.

Thanos breaks through these barriers by converting Prometheus into a highly available, distributed monitoring system with virtually infinite long-term metric storage using low-cost cloud object storage (like AWS S3, Google Cloud Storage, or Azure Blob).

The Modular Architecture of Thanos

Thanos does not run as a single, monolithic background program. Instead, it follows a microservices architecture where separate components do one operational task well.

1. Thanos Sidecar

The Sidecar runs inside the exact same pod/server as your existing Prometheus instance.

  • The Upload Engine: Every 2 hours, when Prometheus finishes baking its local metric files (TSDB blocks), the Sidecar grabs them and moves them to cloud object storage. This allows you to set a low disk retention on Prometheus (e.g., just 2 to 4 hours), keeping cluster disks small and inexpensive.
  • The Real-Time Proxy: When a user queries for brand-new data, the Sidecar intercepts the request and pulls it straight out of Prometheus’s local, in-memory data.
2. Thanos Store Gateway

While the Sidecar handles new metrics, the Store Gateway manages historical data. It acts as a proxy sitting in front of your cloud storage bucket. It indexes the massive pool of historical metrics in your S3 bucket, making them searchable for user queries without crashing system memory.

3. Thanos Querier (Query Engine)

The Querier is the central engine of the system. It exposes a standard PromQL API endpoint, meaning you can point your Grafana dashboards directly to Thanos instead of Prometheus.

  • Global View: When you request a metric graph, the Querier fans out the request to all connected Sidecars (for live data) and Store Gateways (for old data), stitching the answers together seamlessly.
  • Deduplication: If you run two identical Prometheus instances side-by-side for High Availability (HA), they will scrape the exact same data points. The Thanos Querier automatically detects these duplicates on the fly and cleans them up into a single line on your dashboard.
4. Thanos Compactor

The Compactor runs as a background process over your cloud storage bucket to organize your metrics.

  • Compaction: It merges small 2-hour metric chunks into larger daily files to reduce storage overhead.
  • Downsampling: If you want to look at data from a year ago, you don’t need 15-second precision data points. The Compactor automatically downsamples historical code into 5-minute and 1-hour averages, speeding up long-term data queries from minutes to milliseconds.

Two Deployment Flavors: Sidecar vs. Receiver

Depending on how your organization secures its network boundaries, Thanos can be deployed in two different configurations:

ApproachArchitecture StyleData FlowBest For
Sidecar PatternPull-BasedThe central Thanos Querier pulls metrics out of remote cluster sidecars.Environments with flat networks where clusters can safely talk to one another over a private connection.
Receiver PatternPush-BasedRemote Prometheus instances use remote_write to actively push metrics over HTTPS to a central Thanos Receiver endpoint.Strict, multi-tenant network structures or edge locations where remote clusters cannot accept inbound traffic.

Summary: The Business Value of Thanos

By adding Thanos on top of an enterprise monitoring stack like OpenShift or Kubernetes, operations teams achieve:

  • Cost Reduction: Offloading old data from premium block storage (EBS/SAN) to low-cost object storage (S3) slashes monitoring bills.
  • Infinite Retention: You can keep historical infrastructure metrics indefinitely to satisfy business compliance or audit reviews.
  • Unified Panes of Glass: Dev teams can build high-level Grafana views that showcase metric trends across multiple worldwide clusters at the exact same time.

OpenTelemetry Breakdown: Specifications, Tools, and Collector

To successfully implement OpenTelemetry (OTel), it helps to understand its distinct parts. OpenTelemetry isn’t a single piece of software; it is a modular toolkit broken down into specification, code-level tools, and infrastructure components.

Here is a detailed breakdown of the core OTel components and how they work together to process your data.

1. The Core Specifications (The Blueprint)

Before any code is written, OpenTelemetry defines a universal standard. This ensures that no matter what programming language or vendor you use, telemetry data behaves exactly the same way.

  • The Specification: A formalized document outlining the requirements and standards for all OTel implementations. It defines what a “trace,” “metric,” and “log” must look like.
  • OTLP (OpenTelemetry Protocol): The official network protocol of OTel. It defines how data is formatted and encoded (usually via gRPC or HTTP/Protobuf) when it travels between your application and your storage systems.

2. Code-Level Components (Inside Your App)

To get telemetry data out of your custom applications, you use OTel code libraries. These are divided into two distinct layers to protect your codebase from breaking changes.

The API (Application Programming Interface)

The API is the abstract interface you use to write your code. It contains the functions used to generate data (e.g., “start a trace span” or “increment this error counter”). The API layer contains zero implementation logic—if you install just the API, your code runs normally but outputs nothing. This ensures that if you ever need to disable monitoring, your core application code doesn’t break.

The SDK (Software Development Kit)

The SDK is the actual engine that implements the API for a specific language (Java, Python, Go, Node.js, etc.). It sits quietly in the background, manages the heavy lifting like memory buffering, handles data compression, batches the data to save network performance, and handles the actual transmission of the data.

Instrumentation Libraries

Writing manual tracking code for every single database query or HTTP request is exhausting. OTel provides pre-built instrumentation packages for popular frameworks (like Express, Django, Spring Boot, or PostgreSQL drivers).

  • Auto-Instrumentation: In languages like Java or Python, OTel can inject itself at runtime, automatically capturing database calls and incoming web requests without you altering a single line of your actual application source code.

3. The Infrastructure Component: The OTel Collector

The OpenTelemetry Collector is a highly efficient, high-performance proxy service that runs as a standalone binary or a Docker container alongside your infrastructure.

While you can send data directly from your application to a database, passing it through the Collector first is an enterprise best practice. The Collector is built using a Pipeline architecture divided into three main components:

┌────────────────────────────────────────────────────────┐
│ OpenTelemetry Collector │
│ │
│ ┌───────────┐ ┌────────────┐ ┌─────────┐ │
│ │ Receivers │ ───► │ Processors │ ───► │Exporters│ │
│ └───────────┘ └────────────┘ └─────────┘ │
└───────▲────────────────────────────────────────┬───────┘
│ │
(Pushes OTLP Data) (Sends Data Out)
│ ▼
┌───────┴───────┐ ┌─────────────┐
│Your App (SDK) │ │ Prometheus │
└───────────────┘ │Grafana Tempo│
└─────────────┘
A. Receivers (How data gets IN)

Receivers define how the Collector accepts data. While it natively receives modern OTLP data from your applications, it is incredibly flexible. It can also act as a receiver for older formats—it can pretend to be a Jaeger agent, a Zipkin endpoint, or even pull metrics directly from a Linux host.

B. Processors (How data gets MODIFIED)

Once data is inside the Collector, processors clean and optimize it before it touches a database. Processors can:

  • Batch: Group data together to minimize network calls.
  • Memory Limiter: Drop data safely if the server starts running out of RAM.
  • Obfuscate/Filter: Strip out sensitive user data (like credit card numbers or passwords) from logs and traces before they get stored.
  • Attributes: Inject useful labels dynamically (e.g., automatically adding environment: production to every log passing through).
C. Exporters (How data gets OUT)

Exporters handle translating and sending the processed data to its final destination. The Collector can translate your unified OTel data into vendor-specific languages.

  • It can send metrics to Prometheus format.
  • It can send traces to Grafana Tempo or Jaeger.
  • It can securely ship logs to cloud vendors like Datadog or New Relic.

How Components Work Together: A Real-World Example

  1. A user logs into your website.
  2. The Auto-Instrumentation layer detects the login request.
  3. The API records how long the database took to look up the user profile.
  4. The SDK bundles this data, packages it into the OTLP format, and streams it to your server’s local host.
  5. The OTel Collector picks it up via an OTLP Receiver.
  6. The Collector’s Processor scrubs out the user’s password hash from the metadata.
  7. The Collector’s Exporter sends the numeric timing data to your central Prometheus database and the trace path over to Grafana for you to view.

Understanding OpenTelemetry: The Future of Observability

OpenTelemetry (often abbreviated as OTel) is an open-source, vendor-neutral observability framework. It provides a standardized set of APIs, SDKs, and tools to design, generate, collect, and export telemetry data (metrics, logs, and traces) from your software applications.

Managed by the Cloud Native Computing Foundation (CNCF)—the same body behind Kubernetes and Prometheus—OpenTelemetry has quickly become the absolute industry standard for modern application monitoring.

Why OpenTelemetry Exists (The Problem It Solves)

Historically, if you wanted to monitor an application, you had to install vendor-specific code or agents.

  • If you used Datadog, you had to install the Datadog SDK.
  • If you switched to Dynatrace, New Relic, or AppDynamics, you had to rewrite parts of your application code to use their specific libraries.

This created massive vendor lock-in.

OpenTelemetry completely decouples data collection from data storage. You instrument your application once using the universal OpenTelemetry standard. If you want to change your monitoring backend later from Datadog to an open-source stack like Prometheus and Grafana, you just change a single line in a configuration file—no code changes required.

The Three Pillars of Telemetry (M.E.L.T.)

OpenTelemetry is designed to handle all three primary types of observability data:

  1. Traces: Tracks the end-to-end journey of a single request as it travels across different microservices, databases, and APIs. It helps you pinpoint exactly which function or database query is causing a slowdown.
  2. Metrics: Numeric values measured over time (e.g., CPU utilization, memory usage, request counts, error rates).
  3. Logs: Structured text records of discrete events (e.g., a system crash log or an authentication failure message).

The Architecture: How It Works

An OpenTelemetry implementation generally consists of three main parts:

┌─────────────────────────┐
│ Your Application │ (App is instrumented with OTel SDK)
│ [Go, Python, Java...] │
└────────────┬────────────┘
│ (OTLP Protocol)
┌─────────────────────────┐
│ OpenTelemetry Collector│ (A lightweight proxy running on the host)
└────────────┬────────────┘
├────────────────────────┬────────────────────────┐
▼ ▼ ▼
[ Prometheus ] [ Grafana Loki ] [ Jaeger / Tempo ]
(Metrics) (Logs) (Traces)
1. The API and SDK

You include the OpenTelemetry library directly inside your application code. OpenTelemetry features Auto-Instrumentation for popular languages. If you run a Python FastAPI or Java Spring Boot application, OTel can automatically capture database queries and HTTP requests without you writing a single line of custom telemetry code.

2. The OTLP Protocol

All data generated by the SDK uses a unified language called OTLP (OpenTelemetry Protocol), ensuring standard formatting across every application in your environment.

3. The OpenTelemetry Collector (The Muscle)

The Collector is a separate, lightweight binary or container that runs alongside your application (similar to how Node Exporter runs on a Linux host). It receives the data from your applications, processes it (batches it, strips sensitive PII data, compresses it), and exports it to your chosen backend databases.

How It Fits With Your Prometheus & Grafana Stack

If you are already working with Prometheus, Grafana, and Alertmanager, OpenTelemetry integrates seamlessly into your world:

  • Prometheus excels at pulling metrics, but it historically doesn’t handle tracing well.
  • OpenTelemetry is unmatched at generating application traces and logs.

In a modern production stack, engineers often use OpenTelemetry inside their application code to generate traces and metrics. They send that data to the OpenTelemetry Collector, which is then configured to forward the metrics straight into Prometheus, logs into Grafana Loki, and traces into Grafana Tempo.

Ultimately, everything gets visualized on a unified Grafana dashboard, giving you total visibility from the bare-metal hardware all the way down to a single line of application code.

Install Prometheus Alertmanager on Ubuntu: Step-by-Step Guide

Here’s a complete step-by-step guide to install and configure Prometheus Alertmanager on Ubuntu.


Step 1: Update System

sudo apt update && sudo apt upgrade -y

Step 2: Create a Dedicated User

sudo useradd --no-create-home --shell /bin/false alertmanager

Step 3: Download Alertmanager

Download the latest release from GitHub:

cd /tmp
wget https://github.com/prometheus/alertmanager/releases/latest/download/alertmanager-0.27.0.linux-amd64.tar.gz
tar -xvf alertmanager-0.27.0.linux-amd64.tar.gz
cd alertmanager-0.27.0.linux-amd64

Check https://github.com/prometheus/alertmanager/releases for the latest version and substitute accordingly.


Step 4: Install Binaries

Move the binaries to /usr/local/bin/:

sudo mv alertmanager /usr/local/bin/
sudo mv amtool /usr/local/bin/

Step 5: Create Directories

Create config and data directories and set ownership:

sudo mkdir /etc/alertmanager
sudo mkdir /var/lib/alertmanager
sudo chown alertmanager:alertmanager /etc/alertmanager
sudo chown alertmanager:alertmanager /var/lib/alertmanager

Step 6: Create the Config File

sudo nano /etc/alertmanager/alertmanager.yml

Paste a basic configuration (example with email):

global:
smtp_smarthost: 'smtp.gmail.com:587'
smtp_from: 'alertmanager@example.com'
smtp_auth_username: 'your@gmail.com'
smtp_auth_password: 'your-app-password'
route:
group_by: ['alertname']
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
receiver: 'email-alerts'
receivers:
- name: 'email-alerts'
email_configs:
- to: 'you@example.com'
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']

Set proper ownership:

sudo chown alertmanager:alertmanager /etc/alertmanager/alertmanager.yml

Step 7: Create a systemd Service

Create the service file:

sudo nano /etc/systemd/system/alertmanager.service
[Unit]
Description=Prometheus Alertmanager
Wants=network-online.target
After=network-online.target
[Service]
User=alertmanager
Group=alertmanager
Type=simple
ExecStart=/usr/local/bin/alertmanager \
--config.file=/etc/alertmanager/alertmanager.yml \
--storage.path=/var/lib/alertmanager
[Install]
WantedBy=multi-user.target

Step 8: Start and Enable the Service

sudo systemctl daemon-reload
sudo systemctl start alertmanager
sudo systemctl enable alertmanager
sudo systemctl status alertmanager

Alertmanager will be available at http://<your-server-ip>:9093.


Step 9: Integrate with Prometheus

Add this to your prometheus.yml to wire Alertmanager in:

alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']

Optionally, also scrape Alertmanager’s own metrics:

scrape_configs:
- job_name: alertmanager
static_configs:
- targets: ['localhost:9093']

Then restart Prometheus:

sudo systemctl restart prometheus

Step 10: (Optional) Firewall Rules

If Alertmanager should only be accessible locally:

sudo ufw allow from 127.0.0.1 to any port 9093
sudo ufw deny 9093

Verify & Troubleshoot

# Check logs
sudo journalctl -u alertmanager -f
# Validate config file
amtool check-config /etc/alertmanager/alertmanager.yml

Common Receivers Beyond Email
ReceiverKey Config Field
Slackslack_configs with api_url
PagerDutypagerduty_configs with service_key
Webhookwebhook_configs with url
OpsGenieopsgenie_configs with api_key

Here’s the complete email configuration for Alertmanager:

Update /etc/alertmanager/alertmanager.yml

sudo nano /etc/alertmanager/alertmanager.yml
global:
smtp_smarthost: 'smtp.gmail.com:587'
smtp_from: 'alertmanager@yourdomain.com'
smtp_auth_username: 'your@gmail.com'
smtp_auth_password: 'your-app-password' # Gmail App Password, NOT your login password
smtp_require_tls: true
route:
receiver: 'email-alerts'
group_by: ['alertname', 'severity']
group_wait: 30s # Wait before sending first alert in a group
group_interval: 5m # Wait before sending new alerts for an existing group
repeat_interval: 4h # Resend if alert is still firing after this duration
# Optional: route critical alerts separately
routes:
- match:
severity: critical
receiver: 'email-critical'
repeat_interval: 1h
receivers:
- name: 'email-alerts'
email_configs:
- to: 'your-team@example.com'
send_resolved: true # Also notify when alert resolves
- name: 'email-critical'
email_configs:
- to: 'oncall@example.com'
send_resolved: true
headers:
Subject: '[CRITICAL] {{ .GroupLabels.alertname }}'
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'instance']

Gmail Setup (Important)

Gmail blocks plain passwords — you need an App Password:

  1. Go to https://myaccount.google.com/security
  2. Enable 2-Step Verification (required)
  3. Go to App Passwords → select “Mail” → generate
  4. Use that 16-character password in smtp_auth_password

For other providers, swap out the SMTP settings:

Providersmtp_smarthost
Gmailsmtp.gmail.com:587
Outlook/Office365smtp.office365.com:587
SendGridsmtp.sendgrid.net:587
AWS SESemail-smtp.<region>.amazonaws.com:587

Apply & Test

# Validate config before restarting
amtool check-config /etc/alertmanager/alertmanager.yml
# Restart to apply
sudo systemctl restart alertmanager
# Send a test alert manually
amtool alert add alertname="TestAlert" severity="warning" \
--alertmanager.url=http://localhost:9093
# Check it appears
amtool alert --alertmanager.url=http://localhost:9093

Check Logs if Email Fails

sudo journalctl -u alertmanager -f

Common issues to look for:

  • authentication failed → wrong App Password
  • connection refused → check SMTP host/port
  • tls: no supported versions → set smtp_require_tls: false for port 25

Once you confirm email is working, you can layer in Slack or PagerDuty alongside it in the same config.

Understanding Alertmanager in the Prometheus Ecosystem

Alertmanager is the specialized component of the Prometheus ecosystem that handles alerts.

While Prometheus is responsible for recording metrics and triggering an alert when something goes wrong (like a server running out of RAM), Prometheus itself has no idea how to send an email, slack message, or page an engineer. It hands that problem off to Alertmanager.

The Workflow: How an Alert Moves

[ Your Servers ] ──(Metrics)──► [ Prometheus ]
▼ (Metrics hit a threshold)
[ Alertmanager ]
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
[ Email ] [ Slack ] [ PagerDuty ]
  1. Prometheus constantly evaluates your data against rules you write (e.g., Is CPU usage > 90% for more than 5 minutes?). If yes, Prometheus fires the alert and shoots it over to Alertmanager.
  2. Alertmanager takes that raw alert and figures out how to handle it intelligently.
  3. The Receiver (Slack, Email, PagerDuty, Discord, etc.) actually delivers the message to your team.

Why do we need a separate tool just for alerts?

You might wonder why Prometheus doesn’t just send emails directly. Alertmanager exists because in a large infrastructure (like your 20+ servers), raw alerts can quickly turn into a nightmare without three critical features:

1. Grouping (Preventing “Alert Fatigue”)

Imagine a network switch dies, cutting off 15 of your Docker hosts at the exact same moment. If Prometheus sent alerts directly, you would get 15 individual emails or Slack pings within 10 seconds, blowing up your phone.

Alertmanager groups them. It sees that 15 servers failed for the same reason, bundles them into one single notification: “Alert: 15 hosts are currently unreachable.”

2. Inhibition (Muting Dependent Alerts)

If a physical server completely crashes, both the Host Down alert and the 10 Containers on this Host are Down alerts will trigger. You don’t need to be told the containers are down if you already know the whole machine is dead. Alertmanager allows you to mute (inhibit) the container alerts if the main host alert is already active.

3. Silencing

If you are doing planned maintenance on server-05 on a Tuesday morning, you can log into Alertmanager’s web interface and click Silence for that specific server for 2 hours. Prometheus will keep tracking the metrics, but Alertmanager will block any notifications from waking up your team while you work on it.

A Quick Look at How It Connects to Your Stack

Since you are running your central monitoring stack via Portainer, you would add an Alertmanager service to your existing Docker Compose file right alongside Prometheus and Grafana:

YAML

  alertmanager:
    image: prom/alertmanager:latest
    container_name: central-alertmanager
    ports:
      - "9093:9093"
    volumes:
      - /opt/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml
    restart: unless-stopped


In your alertmanager.yml file, you define your routing. For example, telling it to route critical alerts to Slack and warning alerts to Email:

YAML

route:
receiver: 'slack-notifications'
receivers:
- name: 'slack-notifications'
slack_configs:
- api_url: 'https://hooks.slack.com/services/T000/B000/XXXXXX'
channel: '#ops-alerts'
Alertmanager vs. Grafana Alerting

Note that Grafana can also send alerts based on your dashboards. However, in professional setups using Prometheus, engineers prefer Alertmanager because it can process math across your entire fleet instantly at the database level, rather than waiting for a Grafana graph to load and trigger.