📊 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.
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 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
| Dashboard | ID | What to look for |
|---|---|---|
| Kubernetes / Compute Resources / Cluster | 17001 | CPU/memory utilisation vs requests cluster-wide |
| Kubernetes / Compute Resources / Namespace (Pods) | 17002 | Per-pod CPU throttling, OOM kills |
| Node Exporter / Full | 1860 | Disk I/O, network saturation, load average |
| Kubernetes / Networking / Cluster | 15761 | Network traffic, dropped packets |
| Alertmanager | 9578 | Active 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
- 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.
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
| Exporter | What it exposes | Deployment |
|---|---|---|
| node-exporter | CPU, memory, disk, network, filesystem stats from the OS | DaemonSet |
| kube-state-metrics | Kubernetes object state: Deployment replicas, Pod phases, PVC status, HPA scaling events | Deployment |
| kubelet /metrics/cadvisor | Container-level CPU/memory via cAdvisor, built into kubelet | Built-in (no install) |
| blackbox-exporter | External probing: HTTP, TCP, DNS, ICMP — synthetic monitoring | Deployment |
| Application /metrics | Custom business metrics exposed by your app via a Prometheus client library | In 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)
)