Optimize Your Server: Understand Hourly CPU Spikes

This is an updated, 24-hour historical view of your Linux server using the same Node Exporter Full dashboard template.

Looking at this timeline compared to your first screenshot, the server behaves completely differently over a 24-hour window. This dashboard tells a clear story: The server is running a recurring, hyper-aggressive batch job.

The Main Takeaway: A Periodic “Sawtooth” Spiking Pattern

Instead of being permanently pinned at 95% CPU like before, your server is currently relaxed at 1.9% CPU Busy and a 1.3% Sys Load.

However, looking at the historical graphs, the server experiences aggressive, identical resource spikes every single hour, on the hour (e.g., exactly at 18:00, 19:00, 20:00, 21:00, etc.). The load shoots up to 90%+ CPU, stays pinned for roughly 15 to 20 minutes, and then drops back down to zero.

Detailed Metric Breakdown

1. The Real-Time Gauges (Current State)
  • CPU Busy (1.9%) & Sys Load (1.3%): Right now, the server is completely idling. The CPU queue is empty.
  • RAM Used (14.0%): Memory consumption is flat, stable, and completely safe at roughly 3.2 GiB used out of 23 GiB.
  • Root FS Used (33.0%): Disk capacity is unchanging and perfectly fine.

2. The 24-Hour Historical Patterns

  • CPU Basic: The spikes are entirely solid blue blocks, meaning they are driven by Busy User space applications. This is exactly what those authentik-worker containers running Celery processes look like when they fire off a major scheduled task.
  • Network Traffic Basic: Notice the perfect synchronization between network activity and CPU. Every single hour, network traffic dramatically pulses (transmitting/receiving tens of megabits per second) at the exact same millisecond the CPU usage maxes out.
Diagnosis: This is a Scheduled Synchronization Task

This behavior confirms that your Authentik stack is running a heavy, automated background job. Because it happens precisely every hour, this isn’t random user traffic.

This pattern is typical of an hourly directory synchronization. Authentik is waking up every 60 minutes, querying an external provider (like an active directory, LDAP server, SCIM endpoint, or massive database), pulling down a large volume of network data, processing the differences, writing to Postgres, and then going back to sleep.

Optimization Tips

While the server is handling it fine right now because it has down-time to recover between spikes, you can optimize this so it doesn’t slam your CPU 24 times a day:

  1. Increase the Sync Interval: If you don’t need real-time user updates, change the background task sync interval in your Authentik provider settings from 1 hour to 6 hours, 12 hours, or have it run once a day at midnight.
  2. Implement Throttling / Batch Size Limits: If your LDAP/Active Directory has thousands of objects, check if you can reduce the pagination page size in Authentik so the worker processes smaller chunks over a longer period, flattening those sharp blue spikes.

Why Switch from ELK to Grafana Loki for Kubernetes Logging?

The title says it all. If you are running Kubernetes at scale, moving away from the traditional ELK (Elasticsearch, Logstash, Kibana) stack—or even its modern OpenSearch equivalent—is one of the biggest infrastructure wins you can achieve.

Here is a simplified architectural breakdown of why the combination of Grafana Alloy + Grafana Loki + Cloud Object Storage (S3) has become the modern standard for high-efficiency cloud-native logging.

The Structural Flaw of ELK: The “Index Everything” Penalty

To understand why the Loki stack wins, you have to look at how Elasticsearch works.

Elasticsearch is fundamentally a full-text search engine. When a log line comes in, Logstash parses it, and Elasticsearch splits the text into tokens and builds a massive, complex inverted index (similar to the index at the back of a massive textbook).

The Problem at Scale:
  • Storage Bloat: The index itself can often take up as much disk space as—or more than—the actual raw log data.
  • RAM Hunger: To search that index quickly, Elasticsearch must keep massive portions of it cached in memory. As your cluster grows, your JVM heap requirements skyrocket.
  • The High-SSD Tax: You are forced to run Elasticsearch on expensive, high-speed block storage (like AWS EBS gp3 or NVMe drives) just to keep up with index writes and queries.

The Lean Alternative: Alloy + Loki + S3

Grafana Loki turns the ELK philosophy completely on its head. It is frequently described as “Prometheus, but for logs.”

Instead of parsing and indexing the full text of every log line, Loki only indexes the metadata (labels) attached to the stream—such as kubernetes_pod_name, namespace, or container_name. The actual text of the log line is compressed into raw chunks and sent directly to cheap object storage.

[ Pod Logs ] ──> [ Grafana Alloy ] ──> [ Grafana Loki ] ──> [ Amazon S3 / Object Storage ]
│ │
└───── (Metadata Only Indexed) ───────────────┘
1. Grafana Alloy: The Advanced, Single-Agent Collector

Alloy is Grafana’s modern, OpenTelemetry-compatible collector that replaces older agents like Fluentd, Fluent Bit, or Promtail.

  • It natively auto-discovers Kubernetes pods, extracts their metadata, and forwards the streams.
  • Because it is written in Go and shares components with OpenTelemetry, it consumes an incredibly small CPU and memory footprint on your Kubernetes nodes compared to heavy Logstash instances.
2. Grafana Loki: The Index-Free Engine

Because Loki only indexes labels, its index is microscopic compared to Elasticsearch.

  • Since the index is tiny, it fits completely into RAM, making query routing incredibly fast.
  • Loki doesn’t care if a log line contains a 500 error or a success message; it treats the text as an unindexed blob, eliminating the computational overhead of real-time text parsing during ingestion.
3. S3/Object Storage: The Ultimate Cost Hack

Instead of paying a premium for fast SSDs, Loki batches log chunks and writes them directly to cheap, durable Object Storage (like AWS S3, Google Cloud Storage, or MinIO).

  • You get infinite storage scaling out of the box without ever having to re-shard a database or worry about running out of disk space on a node.

Side-by-Side: ELK vs. Loki Stack

FeatureELK / OpenSearch StackAlloy + Loki + S3 Stack
Indexing PhilosophyFull-text indexing of every log word.Metadata (labels) indexing only.
Storage MediumExpensive SSDs / Block Storage.Ultra-cheap Cloud Object Storage (S3).
Resource ConsumptionHeavy RAM (JVM) and high CPU overhead.Minimal RAM and CPU foot-print.
TCO (Total Cost)High (Scales linearly with log volume).Very Low (Up to 80% cheaper at scale).
The TradeoffLightning-fast ad-hoc text search across billions of lines.Blazing-fast target searches; slower brute-force full-text queries over massive windows.

The Bottom Line: Why it Wins

The Loki architecture accepts a pragmatic tradeoff: It sacrifices raw full-text search speed across historical massive timeframes in exchange for massive operational simplicity and cost reductions.

When debugging Kubernetes applications, engineers rarely need to search the entire infrastructure blindly. They almost always know the namespace, the pod, or the service they are investigating. Because Loki filters by these labels instantly, it pulls the relevant compressed log blocks out of S3 and hands them to the developer in seconds—giving you 95% of the utility of ELK at a fraction of the infrastructure bill.

Fixing High CPU Usage: Analyzing `htop` Results

This is a screenshot of htop, an interactive process viewer and system monitor for Linux. It perfectly confirms our theory from the Grafana dashboard: your server’s CPU is maxed out, and we now know exactly what application is causing it.

Here is the breakdown of what this screen is telling you:

Top Section: System Resource Usage

  • CPUs (0, 1, 2, 3): These represent your 4 CPU cores. All of them are heavily saturated, running between 87.4% and 96.0%. The red bars at the very end of the green rows represent kernel/system processing overhead, but the overwhelming green color means the CPU is busy handling user-space tasks.
  • Load Average (4.15 2.03 1.34): This tracks the average system load over the last 1, 5, and 15 minutes. Because you have 4 cores, a load of 4.0 means your CPU is at 100% capacity. The 1-minute average is 4.15, meaning the CPU queue is fully saturated and slightly overflowing right now. However, looking at the 15-minute average (1.34), we can see this heavy load spike is relatively recent.
  • Mem (3.71G/23.5G): Just like Grafana showed, RAM usage is perfectly fine—only about 3.71 GB out of 23.5 GB is being used.
  • Swp (0K/8.00G): Swap memory is completely untouched (0K), meaning there’s zero memory pressure.

Bottom Section: The Culprit Processes

The table lists the active processes sorted by CPU consumption.

  • The Culprit: Celery (celeryd)Almost all the top CPU-consuming processes are Celery ForkPoolWorkers (e.g., PID 4191, 4193, 4194, 3260, etc.). Celery is an asynchronous task/job queue system commonly used in Python applications.
  • What App is this? Further down the list, you can see postgres: authentik and gunicorn: worker [authentik.root.asgi:application]. This means the server is running Authentik—an open-source identity provider/authentication platform.
  • What are they doing? Each of these Celery workers is actively chewing through roughly 45% to 65% of an individual CPU core (indicated under the CPU% column). Under the S (State) column, they are marked as R (Running), meaning they are actively crunching data, not waiting or sleeping (S).

Summary & What to do Next

Your Authentik deployment is currently running heavy background tasks via Celery, which is maxing out all 4 of your CPU cores. This lines up with the network traffic spike we saw earlier—Authentik is likely processing a massive batch of user syncs, LDAP/Active Directory synchronizations, or a sudden flood of login/outbound API requests.

This is the output of the docker stats command, which provides a live data stream for your running Docker containers.

It acts as the final piece of the puzzle. We started with Grafana showing high CPU and network traffic, moved to htop showing Celery workers burning CPU, and now we can see exactly which containers are responsible and how much data they are churning through.

The Primary Culprits: Authentik Workers

Look closely at these three containers:

  • authentik-worker-1 (ID: b82523a11915): 118.72% CPU
  • authentik-worker2-1 (ID: 00d42ac8a162): 115.24% CPU
  • authentik-worker3-1 (ID: 03aecaf9ef51): 101.79% CPU

💡 Note on CPU %: In Docker, 100% means one full CPU core is completely saturated. Because your server has 4 cores, the maximum total CPU available is 400%. These three workers combined are consuming roughly 335% out of 400%, leaving almost nothing for the rest of the system.

The Smoking Gun: Massive Network I/O

If you look at the NET I/O column for these three workers, they have processed an astronomical amount of network data:

  • They have received (~2.8 TB each) and transmitted (~90 GB to 117 GB each) of data over their lifespans.
  • By comparison, your main web server application container (authentik-server-1) has only handled around 1.54 GB. This proves that the workers are doing some massive, continuous background data processing.

Supporting Evidence: The Database

  • authentik-postgresql-1: Running at 41.61% CPU.This is moderately high for a database and means it is working hard to keep up with the constant read/write queries being thrown at it by those three overloaded Celery workers.
  • dokploy-... containers: Dokploy (your deployment/panel manager) and its internal database/redis/traefik containers are completely idling (0% to 0.25% CPU), meaning this issue is strictly isolated to the Authentik stack.

What is causing this in Authentik?

Given that the workers have pulled terabytes of data, this isn’t a normal login event. This behavior is classic for one of two scenarios in Authentik:

  1. LDAP / Active Directory Sync Loop: If you have an LDAP or Active Directory provider configured, it might be caught in an aggressive sync loop, constantly downloading your entire directory over and over again.
  2. Outbound Sync / User Provisioning: A configured connection to an external application (like SCIM syncing to Slack, Google Workspace, or Microsoft 365) might be failing and continuously retrying a massive user sync.

How to Fix It Right Now

  1. Restart the Workers to clear the immediate load:Run this command on your server to give the CPU some breathing room:Bashdocker restart authentik-worker-1 authentik-worker2-1 authentik-worker3-1
  2. Check the logs immediately after restarting:See what task they pick back up. Run:Bashdocker logs --tail 100 -f authentik-worker-1
  3. Log into the Authentik UI:Go to Directory -> Providers or System -> Background Tasks and check for any LDAP or Sync tasks that are currently running, stuck, or failing frequently. If you find one, increase its sync interval (e.g., change it from every few minutes to once an hour or once a day).
  4. Look for any active sync tasks (like an LDAP/Active Directory provider sync) or heavy background jobs that kicked off recently and see if they can be optimized, throttled, or rescheduled for off-peak hours.

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.

Analyzing RAM Usage in Linux with Grafana

The Grafana Node Exporter Full dashboard memory breakdown above showing how Linux RAM is being used over time.

Let’s interpret it as a Linux administrator or SRE would.

Total Memory Usage Breakdown
CategoryMean
Applications717 MiB
Cache5.46 GiB
Buffers614 MiB
Slab380 MiB
Unused623 MiB
Everything elseSmall

The first thing to notice:

Most of the memory is being used for Cache and Buffers, not applications.

This is normal Linux behavior.


Apps – Memory used by user-space applications

Min: 632 MiB
Mean: 717 MiB
Max: 1.37 GiB

This is the memory actually consumed by running processes:

Examples:

  • Java
  • Nginx
  • Apache
  • PostgreSQL
  • Docker containers
  • Tomcat
  • Kong Gateway

Your server’s applications are only using about:

~717 MB average

which is very low.


Cache

Mean: 5.46 GiB

Linux uses free RAM to cache file contents.

Examples:

  • Recently read files
  • Database files
  • Log files
  • Container images

Linux philosophy:

Free RAM is wasted RAM.

So Linux fills unused memory with cache.

The good news:

5.46 GiB Cache

can be reclaimed instantly if applications need RAM.

This is NOT memory pressure.


Buffers

Mean: 614 MiB

Buffers are cache for block devices.

Examples:

  • Disk metadata
  • Filesystem operations
  • Read/write operations

This is normal.


Slab

Mean: 380 MiB

Kernel memory used for internal structures:

Examples:

  • inode cache
  • dentry cache
  • network structures
  • process descriptors

For example:

slabtop

might show:

dentry
inode_cache
kmalloc

380 MB is reasonable.


PageTables

Mean: 9.09 MiB

Used to translate:

Virtual Address
Physical RAM Address

Every process needs page tables.

9 MB is tiny.

No concern.


Swap

Mean: 2.48 MiB

Almost no swap usage.

This is excellent.

Typically:

0 MB - 100 MB

is negligible.

If you saw:

2 GB
5 GB
10 GB

then we’d investigate memory pressure.


SwapCache

Mean: 131 KiB

Tracks swapped pages that may still exist in RAM.

Tiny amount.

Ignore.


Unused

Mean: 623 MiB
Max: 891 MiB

Actually free RAM.

Linux tries to keep this low because it prefers using RAM for caching.

Having only 623 MB free is not necessarily bad because:

Cache = 5.46 GB

can be reclaimed instantly.


Hardware Corrupted

0 B

No RAM errors detected by the kernel.

Good.


What would I conclude?

If I were reviewing this server:

Memory Health

No memory pressure

No excessive swap usage

Applications use very little RAM

Plenty of reclaimable cache

Kernel memory usage is normal

No hardware memory issues


Interview Answer

If an interviewer asks:

“Memory usage is 90%. Should I worry?”

You could answer:

Not necessarily. Linux aggressively uses free RAM for filesystem cache and buffers. I would look at “Available Memory” and swap activity rather than “Used Memory.” In this example, applications consume less than 1 GB while more than 5 GB is file cache, which can be reclaimed when needed. The negligible swap usage indicates there is no memory pressure.

Useful Commands to Verify
free -h

Look especially at:

available

rather than:

used
vmstat 1

Check:

si
so

(Swap In / Swap Out)

top

or

htop

to identify memory-consuming processes.

From these numbers, the server appears healthy and has significant RAM available through reclaimable cache, even though the “used memory” percentage may look high in Grafana.

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.

Master Grafana: Key Questions for Admins and Engineers

If you’re interviewing for a Grafana Administrator, Monitoring Engineer, SRE, DevOps Engineer, Platform Engineer, OpenShift Monitoring, or Cloud Architect role, these are some of the most common Grafana interview questions and answers.

1. What is Grafana?

Grafana is an open-source observability and visualization platform used to query, visualize, alert on, and analyze metrics, logs, and traces from multiple data sources.

Supported sources include:

  • Prometheus
  • Loki
  • Elasticsearch
  • InfluxDB
  • Graphite
  • OpenSearch
  • Azure Monitor
  • CloudWatch

2. Explain Grafana Architecture

Components

Data Sources

  • Prometheus
  • Loki
  • Elasticsearch
  • CloudWatch

Grafana Server

  • User authentication
  • Dashboard rendering
  • Alerting
  • API access

Database

  • SQLite (default)
  • MySQL
  • PostgreSQL

Users

  • View dashboards
  • Create dashboards
  • Manage alerts

3. What is the difference between Grafana and Prometheus?

GrafanaPrometheus
Visualization ToolMonitoring System
Creates DashboardsCollects Metrics
Alert VisualizationAlert Rules
Multiple Data SourcesPrimarily Metrics

Example:

Prometheus stores:

node_cpu_seconds_total

Grafana displays it as:

  • Graph
  • Gauge
  • Heatmap
  • Table

4. What are Grafana Data Sources?

A data source is the backend from which Grafana retrieves data.

Examples:

  • Prometheus
  • Loki
  • Elasticsearch
  • CloudWatch
  • Azure Monitor
  • SQL Databases

5. Explain Grafana Dashboards

A dashboard is a collection of panels displaying metrics.

Examples:

  • CPU Utilization
  • Memory Usage
  • Disk Usage
  • Network Traffic
  • Application Response Time

6. What are Panels in Grafana?

Panels are visualization widgets.

Common types:

  • Time Series
  • Gauge
  • Stat
  • Table
  • Pie Chart
  • Heatmap
  • Logs
  • Geomap

7. What are Variables?

Variables make dashboards dynamic.

Example:

$cluster
$namespace
$pod

Instead of creating 100 dashboards, create one dashboard and switch values.


8. What is PromQL?

PromQL is the query language used by Prometheus.

Example:

CPU Usage:

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

Memory Usage:

(node_memory_MemTotal_bytes-node_memory_MemAvailable_bytes)
/
node_memory_MemTotal_bytes *100

9. Explain Grafana Alerting

Grafana Alerting evaluates queries and generates notifications.

Flow:

Metric
Query
Condition
Alert
Notification

Example:

CPU > 80%
for 5 minutes

10. What Notification Channels are supported?

  • Email
  • Slack
  • Teams
  • PagerDuty
  • Webhook
  • Opsgenie

11. Explain Alert Rule Components

Query
node_load1
Condition
IS ABOVE 80
Evaluation
Every 1 minute
Alert State
  • OK
  • Pending
  • Alerting
  • No Data

12. Difference Between Loki and Elasticsearch

LokiElasticsearch
Log AggregationSearch Engine
Stores LabelsFull Indexing
Lower CostHigher Storage
Optimized for GrafanaGeneral Purpose

13. What is Loki?

Loki is Grafana’s log aggregation platform.

Components:

Promtail
Loki
Grafana

14. What is Promtail?

Promtail collects logs and forwards them to Loki.

Example:

scrape_configs:
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: syslog
__path__: /var/log/*.log

15. Explain Grafana Mimir

Grafana Mimir is a scalable Prometheus-compatible metrics backend.

Benefits:

  • Multi-tenant
  • Long-term retention
  • HA
  • Massive scale

16. What is Grafana Tempo?

Grafana Tempo stores distributed traces.

Supports:

  • OpenTelemetry
  • Jaeger
  • Zipkin

17. Explain the LGTM Stack

Very common interview question.

L = Loki
G = Grafana
T = Tempo
M = Mimir

Together they provide:

  • Metrics
  • Logs
  • Traces
  • Dashboards
  • Alerting

18. What is OpenTelemetry?

OpenTelemetry is a framework for collecting:

  • Metrics
  • Logs
  • Traces

and exporting them to Grafana.


19. How do you secure Grafana?

Best practices:

  • SSO (SAML/OIDC)
  • RBAC
  • HTTPS
  • MFA
  • LDAP integration
  • Dashboard permissions
  • Network restrictions

20. Explain Grafana RBAC

Roles:

Viewer

Read only

Editor

Create dashboards

Admin

Manage users and settings


21. Grafana High Availability Design

Load Balancer
|
+-----+-----+
| |
Grafana1 Grafana2
| |
+-----+-----+
|
PostgreSQL

Interview answer:

Grafana servers are stateless. Store configuration in PostgreSQL and place multiple Grafana instances behind a load balancer.


22. Grafana Troubleshooting Questions

Dashboard Empty

Check:

  • Data source connectivity
  • Query syntax
  • Time range
  • Permissions
Alert Not Firing

Check:

  • Alert evaluation interval
  • Query results
  • Notification policy
  • Contact point
Slow Dashboard

Check:

  • Query complexity
  • Prometheus cardinality
  • Large time ranges
  • Data source latency

23. Grafana + OpenShift Monitoring

Since you’ve worked with OpenShift, expect these:

What components are included?
  • Prometheus
  • Alertmanager
  • Grafana (user-managed)
  • Thanos
  • Node Exporter
  • kube-state-metrics
How do you monitor OpenShift?

Monitor:

  • Nodes
  • Pods
  • Namespaces
  • etcd
  • API Server
  • OVN Networking
  • Ingress Controllers

24. Scenario Question

Interviewer: Dashboard loading takes 30 seconds. What do you check?

Answer:

  1. Query Inspector
  2. Prometheus query duration
  3. Cardinality explosion
  4. Recording Rules
  5. Dashboard refresh interval
  6. Data source latency
  7. Browser performance

25. Senior-Level Question

How would you design observability for 500 Kubernetes/OpenShift nodes?

Answer:

Node Exporter
kube-state-metrics
OpenTelemetry
Prometheus HA
Mimir
Grafana
Alertmanager

Include:

  • Long-term retention in Mimir
  • Loki for logs
  • Tempo for tracing
  • HA Prometheus
  • Multi-cluster federation
  • SSO integration
  • RBAC
  • Disaster Recovery

Interview Questions Often Asked for Senior Grafana Architects
  1. How do you reduce Prometheus cardinality?
  2. Difference between recording rules and alert rules?
  3. How do you monitor Kubernetes at scale?
  4. How would you deploy Grafana HA?
  5. How would you design LGTM for 1000 servers?
  6. How do you troubleshoot missing metrics?
  7. How do you secure Grafana in an enterprise environment?
  8. How do you integrate Grafana with OpenShift?
  9. How do you implement multi-tenancy?
  10. How do you monitor AWS, Azure, and OpenShift from a single Grafana instance?

These are the questions typically asked for Senior Monitoring Engineer, SRE, OpenShift Architect, and Cloud Architect interviews.

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.