Right, so you want to use the Horizontal Pod Autoscaler (HPA). Excellent choice. It’s basically magic, letting your application breathe in and out based on load. But here’s the thing about magic: it’s mostly just applied science, and the science here requires a specific piece of infrastructure. You can’t just wave a kubectl wand and expect it to work. You need the Metrics Server.

Think of the Metrics Server as the nervous system for your cluster’s autoscaling. The kubelets on each node (the muscle) are constantly measuring resource usage—CPU and memory—of every pod. But those metrics are isolated, trapped on their individual nodes. The Metrics Server’s job is to be the brainstem: it periodically scrapes those usage stats from every kubelet, aggregates them in memory, and exposes them in a format the rest of the Kubernetes API can understand. Without it, the HPA is just a guy in a room staring at a blank teleprompter. He has no data. He can’t make decisions.

Why You Can’t Just Assume It’s There

On managed Kubernetes services like GKE, EKS, or AKS, the Metrics Server is often installed by default. This is a lovely convenience, but it lulls you into a false sense of security. On a bare-metal cluster, a custom k3s/k0s setup, or even some older managed clusters, it might not be. You’ll know it’s missing when you run kubectl top nodes and get a charmingly unhelpful error like: error: Metrics API not available.

This isn’t a Kubernetes design flaw; it’s a deliberate separation of concerns. The core Kubernetes API is lean and mean. Aggregating resource metrics is a specific, higher-level function, so it’s deployed as an API extension. The HPA controller doesn’t talk to kubelets; it makes a standard API call to the Metrics API, which the Metrics Server populates. This is a good architecture! It just means you have to install the thing.

Installing the Nervous System

Installing the Metrics Server is straightforward, thanks to a standard manifest. But I’m not just going to give you a blind kubectl apply command. Let’s look at the most common, robust manifest and, more importantly, why you might need to tweak it.

# metrics-server.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: metrics-server
  namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: metrics-server
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  template:
    metadata:
      labels:
        k8s-app: metrics-server
    spec:
      serviceAccountName: metrics-server
      containers:
      - name: metrics-server
        image: registry.k8s.io/metrics-server/metrics-server:v0.7.0
        args:
          - --cert-dir=/tmp
          - --secure-port=4443
          - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
          - --kubelet-insecure-tls # <-- Read the note below before using this!

Apply it with:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

The official manifest is the better bet, but I showed you the other one to make a critical point.

The TLS Hurdle (This is the part everyone messes up)

The most common reason for the Metrics Server to crash-loop is TLS. Kubelets, by default, use self-signed certificates. The Metrics Server, quite rightly, doesn’t trust those. You have two ways to solve this:

  1. The “Proper” Way: Configure the kubelet to use a proper certificate signed by your cluster’s CA and provide that CA bundle to the Metrics Server via a volume mount. This is more secure but involves more moving parts.
  2. The “Get Stuff Done” Way: Add the --kubelet-insecure-tls argument to the Metrics Server deployment. This tells it to simply ignore the certificate validation errors.

Before you gasp in horror, understand the context: the Metrics Server is communicating with the kubelet over the private pod network inside your cluster. The risk profile is low. For most development and staging clusters, and even many production environments, this flag is a perfectly acceptable trade-off to get autoscaling operational. The official manifest now often includes this flag by default because it’s the path of least resistance that actually works.

Verifying It Actually Works

Don’t just assume the deployment ran. Verify it.

First, check the pods:

kubectl get deployment -n kube-system metrics-server

Then, ask it for the metrics you came for. This is the real test:

# Check node-level resource usage
kubectl top nodes

# Check pod-level resource usage (defaults to current namespace)
kubectl top pods

If these commands return actual numbers and not errors, congratulations. Your cluster now has a functioning nervous system. The HPA can now query the Metrics API and get the data it needs to make decisions. You’ve laid the groundwork. Now the real fun—actually configuring the autoscaler—can begin.