📊 metrics-server — The Cluster Core

metrics-server is a lightweight, in-memory scraper that collects CPU and memory from kubelet's Summary API. It powers three critical Kubernetes features:

kubectl top

kubectl top pods and kubectl top nodes — real-time resource usage without a full Prometheus stack.

HPA

Horizontal Pod Autoscaler uses metrics-server's CPU/memory figures to scale deployments up and down automatically.

VPA

Vertical Pod Autoscaler reads historical metrics-server data to recommend right-sized resource requests/limits.

⚠️ metrics-server is NOT for monitoring It stores only the latest data point per resource — no history, no time-series, no alerting. It's purely an API shim for the autoscaler. For monitoring and alerting you need Prometheus.

Installing metrics-server

# Production install via Helm
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm install metrics-server metrics-server/metrics-server \
  --namespace kube-system

# For clusters with self-signed certs (kind, kubeadm)
helm install metrics-server metrics-server/metrics-server \
  --namespace kube-system \
  --set args[0]="--kubelet-insecure-tls"

# Verify it works
kubectl top nodes
kubectl top pods -A --sort-by=memory

The Metrics API

metrics-server exposes data through the Kubernetes Metrics API (metrics.k8s.io/v1beta1). You can query it directly:

# Raw API call
kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | jq .
kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/default/pods | jq .

# Example node metrics response
{
  "metadata": { "name": "node-1" },
  "timestamp": "2024-01-15T10:00:00Z",
  "window": "15s",
  "usage": {
    "cpu": "312m",      // millicores
    "memory": "2134Mi"
  }
}

🎯 ServiceMonitor — Telling Prometheus What to Scrape

The Prometheus Operator introduces CRDs that let you configure scraping declaratively in Kubernetes. A ServiceMonitor selects Services by label and tells Prometheus which port and path to scrape. No more editing prometheus.yml by hand.

# Your app Service (must have a named port)
apiVersion: v1
kind: Service
metadata:
  name: my-app
  namespace: production
  labels:
    app: my-app
    monitoring: "true"   # ← ServiceMonitor selects on this
spec:
  ports:
  - name: metrics        # named port — required by ServiceMonitor
    port: 9090
  selector:
    app: my-app

---
# ServiceMonitor — discovers and scrapes the Service
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-app
  namespace: monitoring   # can live in a different namespace
spec:
  namespaceSelector:
    matchNames: [production]
  selector:
    matchLabels:
      monitoring: "true"
  endpoints:
  - port: metrics
    interval: 30s
    path: /metrics
    scheme: http
💡 PodMonitor for pods without a Service Use PodMonitor when your pods expose metrics but don't need a Service (e.g. batch jobs, DaemonSet agents). Same concept — label-based selection, but targets pods directly.

🚨 PrometheusRule — Alerts as Code

A PrometheusRule defines recording rules and alert rules as Kubernetes resources. The Prometheus Operator watches for them and automatically reloads Prometheus configuration.

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: my-app-alerts
  namespace: monitoring
  labels:
    prometheus: kube-prometheus   # must match Prometheus CR's ruleSelector
    role: alert-rules
spec:
  groups:
  - name: my-app.rules
    rules:

    # Recording rule — pre-compute expensive query
    - record: job:http_requests:rate5m
      expr: rate(http_requests_total[5m])

    # Alert — pod restart storm
    - alert: PodRestartingTooFast
      expr: increase(kube_pod_container_status_restarts_total[15m]) > 3
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "Pod {{ $labels.pod }} restarting frequently"
        description: "{{ $labels.pod }} in {{ $labels.namespace }} restarted {{ $value }} times in 15m"
        runbook_url: "https://wiki.example.com/runbooks/pod-restarts"

    # Alert — high error rate
    - alert: HighErrorRate
      expr: >
        rate(http_requests_total{status=~"5.."}[5m])
          / rate(http_requests_total[5m]) > 0.05
      for: 2m
      labels:
        severity: critical
      annotations:
        summary: "Error rate above 5% for {{ $labels.service }}"

Alert lifecycle — pending → firing

The for field prevents alert flapping: the condition must be true for that duration before the alert transitions from pending to firing. Alertmanager then routes, deduplicates, and silences.

📈 Grafana Dashboards

Grafana is the visualisation layer. kube-prometheus-stack ships ~30 pre-built dashboards covering nodes, pods, namespaces, API server, etcd, and more.

Essential built-in dashboards

DashboardIDWhat to look for
Kubernetes / Compute Resources / Cluster17001CPU/memory utilisation vs requests cluster-wide
Kubernetes / Compute Resources / Namespace (Pods)17002Per-pod CPU throttling, OOM kills
Node Exporter / Full1860Disk I/O, network saturation, load average
Kubernetes / Networking / Cluster15761Network traffic, dropped packets
Alertmanager9578Active alerts, silences, inhibitions

Dashboard-as-Code with ConfigMap

# Grafana auto-discovers dashboards from ConfigMaps with the right label
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-app-dashboard
  namespace: monitoring
  labels:
    grafana_dashboard: "1"   # sidecar discovers this label
data:
  my-app.json: |
    { "title": "My App", "panels": [...] }   # paste exported Grafana JSON
🔵 The four golden signals Build every service dashboard around Google SRE's four golden signals:
  • Latency — p50/p95/p99 request duration
  • Traffic — requests per second
  • Errors — 5xx rate, failed requests
  • Saturation — CPU throttling %, memory usage vs limit

🔥 Prometheus — The Metrics Standard

Prometheus is the de-facto metrics system for Kubernetes (CNCF graduated). It uses a pull model — Prometheus scrapes HTTP /metrics endpoints on a schedule, stores time-series data locally, and evaluates alert rules.

App /metrics node-exporter kube-state-metrics Prometheus scrape · store · evaluate TSDB (15d default) PromQL engine scrape Alertmanager route · dedupe · silence Grafana dashboards · explore Thanos / Mimir (long-term)

Installing kube-prometheus-stack (recommended)

The kube-prometheus-stack Helm chart installs Prometheus Operator, Prometheus, Alertmanager, Grafana, node-exporter, and kube-state-metrics in one shot — with sane defaults for Kubernetes monitoring:

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm install kube-prometheus-stack \
  prometheus-community/kube-prometheus-stack \
  --namespace monitoring --create-namespace \
  --set grafana.adminPassword=changeme \
  --set prometheus.prometheusSpec.retention=30d \
  --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=50Gi

Key exporters to know

ExporterWhat it exposesDeployment
node-exporterCPU, memory, disk, network, filesystem stats from the OSDaemonSet
kube-state-metricsKubernetes object state: Deployment replicas, Pod phases, PVC status, HPA scaling eventsDeployment
kubelet /metrics/cadvisorContainer-level CPU/memory via cAdvisor, built into kubeletBuilt-in (no install)
blackbox-exporterExternal probing: HTTP, TCP, DNS, ICMP — synthetic monitoringDeployment
Application /metricsCustom business metrics exposed by your app via a Prometheus client libraryIn your app

Essential PromQL patterns

# CPU usage % per pod (across all namespaces)
100 * sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (pod, namespace)
  / sum(kube_pod_container_resource_requests{resource="cpu"}) by (pod, namespace)

# Memory usage vs limit (% — alert if > 80%)
container_memory_working_set_bytes{container!=""}
  / container_spec_memory_limit_bytes{container!=""} * 100

# Pod restart rate — alert if > 0 in last 5m
increase(kube_pod_container_status_restarts_total[5m]) > 0

# HTTP error rate for a service
rate(http_requests_total{status=~"5.."}[5m])
  / rate(http_requests_total[5m]) * 100

# p99 request latency
histogram_quantile(0.99,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
)

🧠 Knowledge Check

Q1. What does kubectl top pods use under the hood — and what is its key limitation?

A) It queries Prometheus directly and shows the last 5 minutes of data
B) It uses metrics-server (Metrics API) — only the latest snapshot, no history or alerting
C) It reads cgroup files directly from the node
D) It calls the kube-state-metrics exporter

Q2. What is the difference between node-exporter and kube-state-metrics?

A) node-exporter monitors containers; kube-state-metrics monitors nodes
B) node-exporter is for bare-metal; kube-state-metrics is for cloud clusters
C) node-exporter exposes OS/hardware metrics; kube-state-metrics exposes Kubernetes object state (replicas, pod phases, PVC status)
D) They are identical — just different names for the same exporter

Q3. A PrometheusRule alert has for: 5m. An anomaly lasts 3 minutes then resolves. Does the alert fire?

A) Yes — it fires immediately when the condition is first true
B) No — the condition resolved before the 5m pending duration completed; alert resets to inactive
C) It fires after 5m regardless of whether the condition is still true
D) It fires and stays firing until manually silenced

Q4. What is the purpose of a recording rule in Prometheus?

A) To define which targets Prometheus should scrape
B) To record which alerts have fired for audit purposes
C) To pre-compute expensive PromQL expressions and store results as a new time-series for fast querying
D) To configure how long Prometheus retains raw metrics data