Performance Profiling of the Kubernetes Control Plane
Every deployment, every scale-up, every self-healing restart passes through a handful of components that most teams never look at until something slows down. This guide teaches you, step by step, how to measure those components, find the real bottleneck, and fix it with confidence.
Picture the control tower at a busy airport. Pilots, who are your applications, do the actual flying. But nothing takes off, lands, or changes course without the tower granting permission and recording the decision. When the tower works smoothly, nobody notices it. When the tower becomes slow, planes queue on the runway, pilots grow frustrated, and a small delay in one corner ripples across the entire schedule. The tower rarely fails dramatically; it simply gets slower, and slowness is much harder to diagnose than a clean failure.
The Kubernetes control plane is that tower. It receives every request to create, change, or remove anything in the cluster, it remembers the desired state of everything, it decides where workloads should run, and it constantly nudges reality to match the plan. Your applications may keep running even when the control plane is struggling, which makes problems easy to miss, but the moment you try to deploy, scale, or recover, you feel every millisecond of delay. This tutorial shows you how to profile the control plane, which means measuring where its time and resources go, so that you can find slow spots before your users do.
1What Does “Profiling the Control Plane” Actually Mean?
Profiling is the practice of measuring where time and resources are spent, so you improve the real problem instead of a guess.
Profiling in everyday words
To profile something is to measure it carefully and break the result into parts. If your morning commute takes ninety minutes, you can complain about traffic, or you can time each stage: walking to the station, waiting for the train, riding, and walking to the office. Suddenly you learn that thirty of those minutes are spent waiting on a single platform. That breakdown is a profile. It tells you where to act, and just as importantly, where acting would be a waste of effort.
Profiling the control plane works the same way. Instead of saying “the cluster feels slow”, we measure how long the interface server takes to answer different kinds of requests, how long the database takes to save changes, how long the scheduler takes to place a pod, and how long the controllers take to react to a change. The result is a timeline that shows which stage consumes the time.
Why this matters more as clusters grow
A small cluster with a few dozen workloads rarely shows control plane stress. The components have plenty of room. As a cluster grows to hundreds of nodes, thousands of pods, and many automation tools constantly watching and updating objects, the work done by the control plane grows faster than the cluster itself. Each new controller, each new custom resource type, and each new dashboard that polls the interface adds load. Problems that were invisible at small scale suddenly appear: deployments that take minutes to start, pods stuck waiting to be scheduled, and commands that hang before returning.
Think of a small town post office. One clerk handles everything and nobody waits. Now imagine the town becomes a city of a million people, yet the post office still has one clerk, one filing cabinet, and one queue. The clerk is not lazy and the cabinet is not broken, but the design no longer fits the load. Profiling is how you discover exactly which part of the post office, the clerk, the cabinet, or the queue, has become the limit.
What profiling is not
Profiling is not the same as monitoring, though they are close relatives. Monitoring watches known numbers over time and warns you when they cross a line. Profiling goes deeper: it investigates why a number looks the way it does by drilling into the internals. Monitoring tells you the interface server is slow today. Profiling tells you that a particular kind of list request, issued by a particular tool, is responsible for most of the slowness. You need both, and good profiling usually starts because monitoring raised a question.
Many teams assume that adding more powerful machines to the control plane is the first and best remedy. Often the true cause is a single misbehaving client or an overly heavy request pattern, and bigger machines would only hide the issue while increasing cost. Profile first, spend second.
2The Anatomy of the Control Plane
You cannot profile what you cannot name. Meet the parts, and what each one is responsible for.
The components and their jobs
The control plane is a small group of cooperating programs. Each has a narrow responsibility, and they communicate almost entirely through one shared hub, the interface server. Understanding this hub-and-spoke design is the key to profiling, because it means that almost every performance problem eventually shows up as extra work at the hub or at the database behind it.
API Server
Receives every request, checks who is asking and whether they are allowed, validates the request, saves it, and tells interested listeners about changes.
etcd
The consistent key-value database that stores the entire desired and current state of the cluster. Everything durable lives here.
Scheduler
Watches for new pods without a home and chooses the best node for each, based on resources, rules, and preferences.
Controller Manager
Runs many small loops that compare what you asked for with what exists, and take action to close the gap.
Cloud Controller Manager
Connects the cluster to the cloud provider for things like load balancers, routes, and node lifecycle.
Webhooks and Aggregated APIs
Optional add-ons that the interface server calls out to during requests, which can add delay if they are slow.
flowchart TD
U["Users, tools, and automation"] --> A["API server"]
K["Kubelets on nodes"] --> A
A E[("etcd database")]
S["Scheduler"] A
C["Controller manager"] A
X["Cloud controller manager"] A
A --> W["Admission webhooks and aggregated APIs"]
Why the hub-and-spoke design shapes profiling
Notice that the scheduler never talks directly to the controller manager, and neither of them ever touches the database. They all go through the interface server. This keeps the system simple and consistent, but it also means the interface server is a shared resource. If one client sends heavy requests, every other client feels it. If the database is slow, the interface server becomes slow, and then everything attached to it slows down together. When you profile, you will often find that symptoms appear in one component while the cause lives in another.
Imagine a restaurant where every waiter, cook, and cashier must communicate through a single order window. The window is efficient because everyone sees the same orders. But if one customer places a gigantic order that takes ten minutes to read aloud, everyone else waits. The window is shared, so its time is shared.
Beginners often profile the scheduler first because “scheduling” sounds like the slow part of starting a pod. In practice, the scheduler is frequently the fastest component, and delays more often hide in the interface server, the database, admission webhooks, or the nodes themselves. Let measurements, not intuition, choose where you look.
3Life of a Request: Where Time Can Hide
Follow one request from arrival to answer. Each stop is a place where time can be spent, and where you can measure it.
The journey through the API server
When you ask Kubernetes to create a pod, your request does not go straight to the database. It passes through a series of stages, like a visitor moving through airport security. Each stage has a purpose, and each stage costs some time. For a healthy cluster each stage is tiny. For a struggling cluster, one stage often grows to dominate the total.
flowchart LR
R["Request arrives"] --> A1["Authentication: who are you"]
A1 --> A2["Authorization: are you allowed"]
A2 --> A3["Mutating admission: adjust the request"]
A3 --> A4["Schema validation"]
A4 --> A5["Validating admission: final checks"]
A5 --> A6["Write to etcd"]
A6 --> A7["Response returned"]
A6 --> A8["Notify watchers of the change"]
Authentication and authorization
The server identifies the caller and checks permissions. These checks are usually quick, but they can slow down if they depend on an external identity service that responds slowly.
Admission control
Built-in and custom checks may modify or reject the request. Custom webhooks are external programs the server must call and wait for, so they add their own network time and processing time to every matching request.
Validation and storage
The server confirms the object is well formed and writes it to the database. Writes must be agreed upon by a majority of database members, so storage speed and network speed between members matter greatly here.
Notification of watchers
After the change is stored, the server tells every client that has asked to hear about such changes. With many watchers, this fan-out becomes a real cost.
Reaction by controllers and the scheduler
These components hear about the new object, do their work, and write back changes of their own, each of which repeats the same journey. A single pod creation can cause many separate requests.
Reads are different from writes
A read request, such as listing all pods in a namespace, takes a different and often cheaper path. The server can answer many reads from a copy it keeps in its own memory, called the watch cache, without bothering the database. But a read that demands the very freshest data, or one that asks for an enormous result, can bypass this shortcut and cause real work. Understanding which requests use the shortcut and which do not is one of the most valuable pieces of control plane knowledge.
Whenever someone reports “creating things is slow”, ask first whether the delay is before the object is stored, which points to the front door and admission, or after it is stored, which points to controllers, the scheduler, or the nodes. This single question cuts the search space in half.
4The Language of Performance: Latency, Throughput, and Saturation
A handful of ideas explain almost every performance conversation. Learn them once and the rest becomes much easier.
Latency: how long one thing takes
Latency is the time between asking and receiving. If you request a pod listing and the answer arrives in twenty milliseconds, the latency of that request was twenty milliseconds. Latency is what humans feel directly. A person running a command notices when it takes three seconds instead of three hundred milliseconds, even if they could not describe why.
Throughput: how many things per unit of time
Throughput is the number of requests handled each second. A control plane might serve two hundred requests per second comfortably and struggle at two thousand. Throughput describes capacity, while latency describes experience, and the two are linked: as throughput approaches the limit, latency rises sharply.
Saturation: how full the system is
Saturation measures how close a resource is to its limit, such as processor usage, memory, disk activity, or the number of requests waiting in a queue. A resource at ninety-five percent saturation has almost no room to absorb a spike, and a tiny burst of extra work will cause a pile-up. Saturation is the best early warning, because it rises before users complain.
Think of a highway. Latency is how long your trip takes. Throughput is how many cars pass a point each minute. Saturation is how packed the lanes are. A half-empty highway has fast trips. As lanes fill, trips slow down gradually, and at some point one small incident causes a total jam. The jam seems sudden, but the packing that caused it built up quietly.
Why averages lie and percentiles tell the truth
Suppose ninety-nine requests finish in ten milliseconds and one request takes ten seconds. The average is about one hundred and ten milliseconds, which sounds fine and hides the slow request completely. Percentiles fix this. The ninety-ninth percentile, often written p99, is the value below which ninety-nine percent of requests fall. It reveals the slow tail that real users hit regularly. Control plane health is almost always judged by percentiles, because the slow tail is where controllers time out, retries pile up, and people lose patience.
| Measure | Question it answers | Typical control plane example | Danger sign |
|---|---|---|---|
| Latency | How long does one request take? | Time to create a pod object | High p99 even when the average looks fine |
| Throughput | How many requests per second? | Requests handled by the interface server each second | Throughput flat while demand keeps rising |
| Saturation | How close to the limit? | Disk activity on the database machine, or requests waiting in queues | Sustained high usage with queues growing |
| Errors | How many requests fail? | Timeouts and throttling responses | Rising error rate during busy periods |
SLIs and SLOs: turning feelings into targets
A service level indicator, or SLI, is a measured number that reflects user experience, such as “the share of write requests finishing within one second”. A service level objective, or SLO, is the target you set for that number, such as “ninety-nine percent of write requests finish within one second over a month”. The Kubernetes community itself publishes scalability targets in this style, including expectations that common write and single-object read requests complete within about a second at the ninety-ninth percentile, with larger target times for broader list requests, and a short startup target for ordinary stateless pods. Setting your own objectives, informed by these published ones, turns vague complaints into measurable goals.
Watching only average latency is the most common reason teams miss control plane trouble. Always chart high percentiles beside the average, and set alerts on the high percentiles.
5What to Measure: Metric Families and Where They Come From
The control plane tells you a great deal about itself. You only need to know where to listen.
Metrics exposed by every component
Each control plane component publishes a rich set of numbers describing its own behavior, available over a standard web endpoint in a format that popular monitoring systems understand. A monitoring system scrapes these endpoints every few seconds, stores the history, and lets you chart and alert on them. This is the foundation of all control plane profiling, and it costs almost nothing to turn on.
Request duration and count
How long requests take and how many arrive, broken down by action, resource type, and response code.
In-flight and queued requests
How many requests are being processed right now and how many are waiting, which shows saturation directly.
Disk sync and commit time
How long the database takes to make writes durable on disk, the most important health signal for the database.
Database size and leader changes
How big the stored data is and how often the group of database members changes its leader.
Scheduling attempt duration and pending pods
How long placement decisions take and how many pods are waiting for a home.
Work queue depth and processing time
How many pieces of work are waiting for each controller and how long each takes to process.
Three levels of insight
| Level | What it gives you | Cost | Best used for |
|---|---|---|---|
| Metrics | Numbers over time, cheap and always on | Very low | Detecting that something is wrong and roughly where |
| Traces and audit records | The story of individual requests, including who sent them and how long each stage took | Moderate | Finding which client or request pattern causes the load |
| Code-level profiles | Where inside the program processor time, memory, and waiting are spent | Higher, used briefly | Explaining why a component itself is slow once it is confirmed to be the culprit |
Work from cheap to expensive. Start with metrics to locate the problem area, move to traces and audit records to identify the responsible clients, and use code-level profiles only when you must understand the internals of a specific component. This order saves time and avoids adding load to an already struggling system.
On managed Kubernetes services, the provider runs the control plane and decides which metrics and logs you can see. Check what your provider exposes before planning your profiling approach, and learn whether you can view interface server metrics, database health, and audit records. Some providers expose many; some expose only a few.
6Profiling the API Server: The Busiest Component
The front door is where most control plane stories begin and where most of them can be understood.
Reading request duration the right way
The interface server records how long each request takes, grouped by the kind of action, such as get, list, create, update, patch, delete, and watch, and by the type of object involved. These durations are stored in buckets, which lets a monitoring system calculate percentiles. The first habit to build is to never look at all requests together. A flood of fast reads can hide a handful of painfully slow writes, and a single enormous list request can distort every chart. Always split by action and by resource type.
Long-running watch requests need special care. A watch is a request that stays open for minutes, so its duration is long by design and says nothing about health. Good dashboards exclude watches from latency charts and track them separately, as a count of open connections and as a rate of events delivered.
Signals that point to trouble
Rising high-percentile latency for writes
Writes depend on the database and on admission webhooks. If write latency climbs while read latency stays calm, suspect storage speed, slow webhooks, or a very busy database leader.
Rising latency for list requests
Lists scale with the size of the result. If list latency rises, look for clients asking for huge result sets, requests that bypass the cache, or growth in the number of objects.
Increasing throttled and timed-out responses
When the server rejects requests because it is overloaded, or when clients give up waiting, the system is already past comfortable capacity.
Growing memory use in the server
Memory growth often follows large list responses, many watchers, or very large objects. Because the server keeps a cache in memory, big objects and many objects directly raise memory needs.
Uneven load across multiple server instances
In a highly available setup, a single instance may carry most of the traffic because long-lived client connections stick to it. That instance becomes the weak link while others sit idle.
Request size and response size matter
Time is not the only cost. A request that returns forty megabytes costs processor time to assemble, memory to hold, and network time to send, even if it finishes quickly. Tracking response sizes by resource type reveals the heaviest consumers. It is common to discover that one resource type, perhaps a collection of configuration maps or events, accounts for the majority of bytes served.
A library clerk may answer a hundred quick questions in the time it takes to photocopy one thousand-page book for a customer. If you only count questions answered, the clerk looks productive. If you count effort, the book was the real cost. Request counts alone hide heavy requests in exactly this way.
Group metrics by the identity of the caller wherever your tooling allows it, through audit records or request attribution. Knowing that a specific controller or tool produces most of a heavy request pattern turns an abstract load problem into a concrete conversation with its owner.
7Fairness Under Pressure: API Priority and Fairness
When demand exceeds capacity, someone has to wait. This mechanism decides who, and keeps important traffic moving.
The problem it solves
Before modern fairness controls, the interface server protected itself with a simple limit on the total number of requests in flight at once. When the limit was reached, extra requests were rejected. This crude approach had a nasty flaw: one noisy client could consume the entire limit and starve everyone else, including critical components like the node agents and controllers that keep the cluster healthy. A runaway script could effectively take the cluster down.
How fairness works
The modern mechanism, called API Priority and Fairness, sorts incoming requests into categories using rules that look at who is asking and what they are asking for. Each category belongs to a priority level, and each priority level has its own share of the server’s capacity and its own set of waiting lines. Within a priority level, requests from different sources are placed into separate lines and served in a fair rotation, so that one chatty source cannot crowd out the rest. Requests that must wait are held in a line for a short time; if the line is too long or the wait too great, the request is rejected so the client can back off.
flowchart LR
I["Incoming requests"] --> C["Classify by caller and request type"]
C --> P1["High priority level: system and leader election"]
C --> P2["Medium priority level: workload controllers"]
C --> P3["Lower priority level: general clients"]
P1 --> Q1["Fair queues"]
P2 --> Q2["Fair queues"]
P3 --> Q3["Fair queues"]
Q1 --> E["Executed within capacity share"]
Q2 --> E
Q3 --> E
Think of an airport security checkpoint with a fast lane for crew and emergency staff, a priority lane for frequent flyers, and a general lane for everyone else. Inside each lane, people are served in order, so no single large tour group can block the others. When everything gets too crowded, staff politely ask late arrivals to try again in a few minutes rather than letting the whole terminal seize up.
What to look for when profiling fairness
Requests waiting in queues
Shows how many requests are delayed, by priority level. Persistent queues in an important level are a clear warning.
Wait time before execution
Shows how long requests spend waiting before they begin running. Rising wait time means capacity is tight.
Rejected requests
Counts requests turned away because queues were full or waits too long. Any sustained rejection in a critical level needs attention.
Seat usage per level
Shows how much of each level’s share is used. A level always at its limit may need a larger share or fewer heavy clients.
Create a dedicated, lower-priority category for noisy but non-critical tools, such as reporting jobs and experimental automation. This protects the cluster’s essential traffic automatically, and it gives you a clear place to watch when a tool misbehaves.
Raising capacity shares to make rejections disappear can simply move the overload down to the database, which has no such protection. Fix the heavy client first; adjust shares only when the demand is genuine and the downstream components can support it.
8Expensive Requests: Lists, Watches, and Giant Objects
A few request patterns cause most control plane pain. Learn to recognize them on sight.
The unbounded list
A list request asks for every object of some type, possibly across the entire cluster. In a cluster with fifty thousand pods, an unfiltered cluster-wide list produces an enormous response. Building that response uses memory proportional to its size, and sending it ties up network and processor time. If many clients do this at once, the interface server can run out of memory and restart, and the restart makes every client reconnect and list again, which causes the problem to repeat. This feedback loop is one of the classic ways clusters fall into a spiral.
How good clients avoid the trap
Well-written controllers do not repeatedly list. They list once, then open a watch, and from then on they receive only changes, keeping a local copy in memory. This pattern, implemented by shared helper libraries often called informers, reduces load dramatically. Poorly written tools, by contrast, poll the full list every few seconds, and a handful of them can dominate the server.
List once, then watch
Receive changes as a stream and keep a local copy, instead of repeatedly asking for everything.
Filter at the source
Ask only for objects with specific labels or fields, so the server returns a small result.
Paginate large lists
Request results in chunks so memory use stays bounded and one request cannot monopolize resources.
Allow cached reads when freshness is not critical
Reads that accept slightly older data can be served from the server’s memory instead of the database.
The watch cache and the cost of freshness
The interface server keeps a recent copy of objects in memory and can answer many reads from it. A client can usually choose between “give me the freshest possible data”, which forces a trip to the database to confirm the latest state, and “any reasonably recent data is fine”, which can be served from memory. The first choice is safer for correctness but costs much more. Many tools request the freshest data out of habit, even when they do not need it, and those habitual requests can make up a large share of database reads.
Watches at scale
Every watch is a long-lived connection that costs memory and requires the server to check each change against it. Ten thousand watchers across a large cluster mean that every single write must be considered for delivery to many listeners. The rate of change multiplied by the number of watchers is the true workload. Tools that open many separate watches, instead of sharing one, multiply this cost needlessly.
Large objects
Objects have a size limit, but even objects well below the limit can cause trouble when there are many of them or when they change often. A configuration object holding a megabyte of data that is updated every few seconds creates constant database traffic, constant cache updates, and constant watch notifications. Oversized objects are an especially common cause of unexplained database growth and slow writes.
Problem
A dashboard or script repeatedly lists all objects of a large type across the whole cluster every few seconds.
Why It’s Harmful
Each repetition forces the server to build and send a huge response. Several such tools together can exhaust memory, trigger restarts, and cause a reconnect storm that worsens the overload.
Correct Approach
Use the list-then-watch pattern through shared client helpers, filter requests narrowly, paginate when listing is unavoidable, and accept cached reads wherever slight staleness is acceptable.
Newer Kubernetes releases continue to improve how large lists are streamed to clients, reducing the memory spikes they cause. Check the release notes of the version you run to see which of these improvements are available and enabled by default.
9Extension Points That Add Delay: Webhooks, Custom Resources, and Aggregated APIs
Kubernetes is deliberately extensible. Every extension is also a place where performance can leak away.
Admission webhooks
An admission webhook is a separate program that the interface server calls during a request, to inspect or modify the object before it is saved. Security tools, policy engines, and sidecar injectors all use them. Because the server waits for the answer, a webhook’s speed becomes part of every matching request. If a webhook takes half a second, every pod creation takes at least half a second longer. If the webhook is unreachable, requests may fail or stall until a timeout expires, depending on how the webhook is configured.
Benefits of webhooks
- Enforce security and compliance rules automatically
- Inject standard settings so developers need not remember them
- Keep policy logic outside the core, so it can evolve independently
- Block risky configurations before they exist
Performance risks of webhooks
- Every matching request waits for the webhook’s response
- A slow or unavailable webhook can stall or fail many requests
- Webhooks that match too broadly add delay to unrelated traffic
- A webhook that depends on the cluster it guards can create circular failures
Profiling webhook cost
The interface server records how long admission steps take, broken down by the individual webhook. Reviewing these durations quickly reveals which webhooks consume the most time. Also review how many requests each webhook intercepts. A webhook limited to specific namespaces and object types costs far less than one that intercepts everything.
Narrow the match
Configure each webhook to apply only to the object types, actions, and namespaces it truly needs, so unrelated requests skip it entirely.
Set sensible timeouts
Use short timeouts so a failing webhook cannot hold requests for long, and decide deliberately whether a failure should block the request or let it through.
Keep webhooks healthy and close
Run several replicas, spread them across failure zones, and keep them on fast, reliable network paths close to the interface server.
Custom resources and controllers
Custom resource definitions let you add new object types to the cluster. Each one adds to the amount the interface server must track and cache, and each usually comes with its own controller that watches and updates objects. A cluster with hundreds of custom resource types and tens of thousands of instances carries a significantly heavier load than one with a few. Review which custom types are truly needed and how many instances exist.
Aggregated APIs
Some extensions register entire API groups served by separate programs. The interface server forwards matching requests to them. Their speed and availability become part of the cluster’s perceived speed, and a failing aggregated API can cause slow or failing discovery for every client, which affects tools that scan all API groups. Monitor the health and latency of every aggregated service just as you monitor the core.
Installing extensions freely and never measuring their cost is how control planes gradually slow down over a year without any single obvious change. Review the list of webhooks, custom types, and aggregated APIs regularly, and remove anything unused.
10etcd: The Memory of the Cluster
If the interface server is the front door, etcd is the vault behind it. Its speed sets the ceiling for everything above.
What etcd is and why it is special
etcd is a distributed key-value database that stores every object in the cluster: every pod, every secret, every configuration, every lease. It is designed for correctness above all else. It promises that once a change is confirmed, it is durable and that all members agree on the order of changes. That promise is valuable, and it comes at a performance price that you must understand to profile it well.
How a write works inside etcd
etcd runs as a group of members, typically three or five. One member is the leader. All writes go to the leader, which records the change in a log on its own disk and sends it to the other members. Each member records it on its own disk too. Only when a majority of members have safely written the change to disk does the leader confirm it. This means that the speed of a write is limited by the slowest disk among the majority and by the network between the members.
sequenceDiagram
participant A as API server
participant L as etcd leader
participant F1 as Follower one
participant F2 as Follower two
A->>L: Write request
L->>L: Append to log on disk
L->>F1: Send log entry
L->>F2: Send log entry
F1->>F1: Append to log on disk
F2->>F2: Append to log on disk
F1-->>L: Acknowledge
F2-->>L: Acknowledge
L->>L: Majority reached, commit
L-->>A: Write confirmed
Imagine three notaries who must each stamp a contract before it becomes official, and the contract is valid once two of the three have stamped. The overall speed depends on how quickly the second-fastest notary stamps and how fast messengers travel between them. A single slow notary may not matter, but two slow notaries, or slow messengers, delay everything.
The three resources etcd cares about
Disk write latency
The most critical factor. Every committed change must be flushed to durable storage, so slow disks directly slow every write in the cluster.
Network latency between members
Members exchange messages for every write and for regular heartbeats. High or unstable latency causes slow writes and unwanted leader changes.
Memory and database size
The database is designed to be read quickly from memory-mapped files. A very large database needs more memory and takes longer to maintain.
Processor for bursts
Usually modest, but heavy list traffic and large objects can create bursts that need headroom.
Give etcd fast, dedicated storage, ideally local solid-state drives, and keep its members on a low-latency network, preferably within a single region. A modest control plane with excellent disks often outperforms a much larger one with slow shared disks.
11Profiling etcd: Reading Its Vital Signs
A handful of etcd numbers tell you almost everything about the health of the cluster’s memory.
The vital signs
Just as a doctor checks pulse, temperature, and blood pressure first, an engineer profiling etcd checks a short list of numbers before anything else. Each one maps to a specific kind of problem, so reading them together quickly narrows the diagnosis.
| Vital sign | What it tells you | Healthy pattern | Warning pattern |
|---|---|---|---|
| Write-ahead log sync duration | How long the disk takes to make log entries durable | Consistently in single-digit milliseconds at the high percentile | High percentile climbing into tens of milliseconds or more, which signals slow storage |
| Backend commit duration | How long the database takes to commit batched changes to its data file | Low and steady | Spikes that match periods of heavy writes or maintenance |
| Database size | How much data is stored, including space that could be reclaimed | Stable or slowly growing, comfortably below the configured quota | Rapid growth, or size approaching the quota |
| Leader changes | How often the group elects a new leader | Rare, mostly during planned maintenance | Frequent changes, which pause writes while a new leader is chosen |
| Proposals pending and failed | How many writes are waiting for agreement or have failed | Near zero | Growing backlog or a steady stream of failures |
| Request duration seen by etcd | How long etcd itself takes to serve requests by type | Low and stable | Slowness in etcd that matches slowness at the interface server |
Why storage is the usual suspect
In a great many real incidents, slow control planes turn out to be caused by slow disks on the database machines. This happens most often when etcd shares a disk with other busy programs, when it runs on network-attached storage with variable latency, or when a cloud volume hits its input and output limits. The symptoms are subtle: everything works, but writes take longer, leader elections become more frequent because heartbeats miss their deadlines, and the whole cluster feels sluggish.
Why the database grows and what to do about it
etcd keeps a history of changes so that watchers can resume from where they left off. Over time, old versions pile up. A process called compaction discards history older than a chosen point, which marks space as reusable. A second process called defragmentation actually returns that space so the file shrinks. Without regular compaction, the database grows. If it reaches its size quota, etcd stops accepting writes and the whole cluster becomes read-only, which is one of the most disruptive failures a control plane can suffer. Kubernetes normally triggers compaction automatically, but you must still watch size and plan defragmentation during quiet periods, since defragmenting a member briefly blocks it.
flowchart TD
A["Many updates create many versions"] --> B["Database file grows"]
B --> C["Compaction discards old history"]
C --> D["Free space inside the file"]
D --> E["Defragmentation returns space to the disk"]
B -->|"Quota reached"| F["Writes refused: cluster becomes read-only"]
Hitting the database size quota is an emergency, not a gradual slowdown. Alert well before the limit, for example when usage passes a comfortable fraction of the quota, so you have time to find out what is growing and to reclaim space calmly.
Finding what fills the database
When the database grows unexpectedly, the question is always which object types and which objects take the most space. Usually a small number of types account for most of it, commonly events, large configuration objects, custom resources written by operators, or leftover objects from jobs that are never cleaned up. Counting objects by type and measuring their combined size points straight to the culprit. A common remedy is to move high-churn, low-value data such as events to a separate database so it cannot crowd the main one.
Time-based cleanup of finished jobs, expired events, and abandoned custom objects is among the cheapest and most effective performance improvements available. Smaller databases are faster to read, faster to back up, and faster to restore.
12Profiling the Scheduler: How Fast Do Pods Find a Home?
The scheduler solves a matching puzzle thousands of times a day. Profiling shows whether it keeps up.
What the scheduler actually does
When a new pod appears without a node, the scheduler picks one. It does this in two broad phases. First it filters, removing every node that cannot run the pod, for reasons such as insufficient resources, mismatched labels, or rules forbidding the placement. Then it scores the remaining nodes, ranking them by how well they fit, and picks the best. Finally it records the decision by telling the interface server which node was chosen, and the node’s agent takes over from there.
flowchart LR
A["Pod waiting in queue"] --> B["Filter: remove nodes that cannot run it"]
B --> C["Score: rank remaining nodes"]
C --> D["Choose best node"]
D --> E["Reserve resources in scheduler memory"]
E --> F["Bind: tell API server the choice"]
What to measure
Scheduling attempt duration
How long each decision takes, split by outcome. Rising times suggest heavy rules or very large clusters.
Pending pods by queue
How many pods are waiting, and in which queue. Pods that cannot be placed sit in a separate waiting area and are retried later.
Time from creation to placement
The delay a user actually feels, including time spent waiting in queues before the scheduler even looked.
Binding errors and retries
Failures when recording decisions, which usually point back to interface server or database trouble.
Common reasons scheduling feels slow
The pod simply cannot fit anywhere
The scheduler is not slow; the cluster has no suitable node. The pod stays pending, and its status explains why. Profiling must separate “slow to decide” from “no valid answer exists”.
Complex placement rules
Rules that relate pods to each other, such as spreading across zones or avoiding certain neighbors, require comparing each candidate against many existing pods. These are powerful but expensive in large clusters.
Slow dependencies
Decisions that rely on volume checks or external plugins can stall if the underlying systems respond slowly.
Bursty arrivals
Launching thousands of pods at once, such as at the start of a large batch job, creates a queue. The scheduler handles them one decision at a time per scheduling cycle, so large bursts take real time.
For very large clusters, the scheduler can be configured to examine only a portion of eligible nodes before choosing, trading a small loss in placement quality for a large gain in speed. Measure the effect on your own workloads before and after adjusting such settings.
Blaming the scheduler for pods stuck in a pending state without reading the reason attached to the pod. Most pending pods are waiting for a legitimate reason, such as insufficient capacity or an unsatisfied storage request, which no amount of scheduler tuning will fix.
13Profiling Controllers: Queues, Retries, and Client-Side Limits
Controllers are the muscle of Kubernetes. When they fall behind, the cluster reacts late to everything.
How a controller works
A controller follows a simple loop. It watches objects of some kind, and whenever one changes, it places a small note in a work queue. A set of workers takes notes from the queue, compares desired state with actual state, and takes action to close any gap. For example, the controller for replicated sets notices that fewer pods exist than requested and creates the missing ones. The loop repeats forever. Because everything flows through queues, queue behavior is the best window into controller health.
flowchart LR
W["Watch events from API server"] --> Q["Work queue"]
Q --> K["Worker takes an item"]
K --> R["Compare desired and actual state"]
R --> A["Act: create, update, or delete objects"]
A -->|"Failure"| Y["Retry later with backoff"]
Y --> Q
The queue metrics that matter
| Queue metric | Meaning | If it looks bad |
|---|---|---|
| Depth | How many items wait right now | A depth that keeps growing means workers cannot keep up with arrivals |
| Time in queue | How long items wait before being picked up | Long waits mean the cluster reacts to changes slowly, even if each item is quick to process |
| Processing time | How long a worker spends per item | Slow processing points to expensive reconciliation logic or slow calls to the interface server |
| Retries | How often items must be tried again | High retries signal repeated failures, conflicts, or a dependency that keeps erroring |
| Longest running worker | The age of the oldest unfinished item | A stuck worker blocks progress for the items that depend on it |
Client-side rate limiting: the hidden brake
Every controller talks to the interface server through a client library that limits how fast it may send requests, to protect the server. This limit has two settings: a steady rate of requests per second, and a burst allowance for short spikes. If a controller needs to send more requests than its limit allows, the library makes it wait, silently. The controller appears slow, its queue grows, yet the interface server shows no stress at all, because the delay happens inside the client. This confuses many investigations.
Imagine a delivery driver who has been told never to make more than a few stops per hour, no matter how many parcels are on the truck. The warehouse is idle and the roads are empty, yet parcels pile up. The bottleneck is a rule inside the driver’s own company, invisible to anyone looking at the roads or the warehouse.
Reading client-side throttling
Look for metrics and log messages showing time spent waiting on the client’s own rate limiter. Compare this with server-side latency: if the server answers quickly but the controller is slow, client-side limits are the likely explanation. Raising a client’s limits can fix this, but only after confirming that the server and database have spare capacity, because a higher limit simply moves the pressure forward.
Increasing client limits across all controllers at once is a risky way to “make things faster”. If every client then sends much more traffic, the interface server and database can be overwhelmed. Raise limits for a specific, proven bottleneck, measure, and proceed step by step.
Custom controllers and operators
The controllers you write or install yourself deserve the same scrutiny as built-in ones. Operators for databases, message brokers, and certificates often become the heaviest clients of the interface server, because they reconcile large numbers of objects frequently. Check that they use shared informer caches, that they avoid updating objects when nothing has changed, and that they do not trigger endless reconciliation loops by reacting to their own writes.
14Nodes Also Talk to the Control Plane
Thousands of node agents send constant status reports. At scale, their chatter is a major part of the load.
Heartbeats and node status
Each node runs an agent called the kubelet, which regularly tells the control plane that the node is alive and reports its condition. Early versions sent a full status update frequently, and each update was a write to the database. In a cluster with thousands of nodes, this created enormous write traffic. Modern versions use lightweight lease objects for the frequent heartbeat and send the full status much less often, which greatly reduces the load. Still, the number of nodes multiplied by their reporting rate remains an important contributor to control plane work.
Pods and their status updates
Every pod also generates status updates as it starts, becomes ready, changes, or stops. A cluster that frequently creates and destroys short-lived pods, for instance one running many small batch tasks, produces a constant stream of writes. Likewise, readiness and liveness checks that flip frequently cause repeated updates. Profiling often reveals that a few noisy workloads generate a large share of status traffic.
Node heartbeats
Frequent but small. Scale with the number of nodes.
Pod lifecycle changes
Scale with how often pods are created, restarted, and removed.
Flapping health checks
A check that keeps switching between passing and failing creates constant updates and notifications.
Events
Every notable action emits an event object. Crash loops and scheduling failures can produce floods of events.
A single crash-looping workload with many replicas can produce a surprising volume of events and status updates. Fixing one broken deployment sometimes lowers control plane load more than any tuning effort.
Reduce churn at the source
Prefer longer-lived workloads where possible, batch very small tasks into fewer pods, and fix pods that restart repeatedly.
Tune health checks sensibly
Choose check intervals and thresholds that reflect real needs, so brief hiccups do not cause constant flips between ready and not ready.
15Going Deeper: Code-Level Profiling with pprof and Flame Graphs
When you know which component is slow, code-level profiling shows exactly which part of its program is responsible.
What a code-level profile shows
The control plane components are written in the Go programming language, which has excellent built-in profiling support. A profile is a sampled record of what the program was doing. At regular tiny intervals, the profiler notes which functions were running, and over thousands of samples, a clear picture emerges of where time and memory go. The components expose profiling endpoints that authorized users can request, and the results can be viewed with standard tools.
Processor profile
Shows which functions use the most processor time. Best for finding computation-heavy work.
Heap profile
Shows which parts of the program hold the most memory. Best for explaining memory growth and restarts.
Goroutine profile
Shows what each concurrent task is doing. Best for finding leaks and tasks stuck waiting.
Mutex and blocking profiles
Show where tasks wait for locks or other tasks. Best for explaining slowness when processors are idle.
Reading a flame graph
A flame graph turns sampled data into a picture. Each horizontal bar represents a function. The bar’s width shows how much of the total time that function and everything it called consumed. Bars are stacked, with callers below and callees above. The wide bars are your suspects. A wide bar near the top, with nothing above it, means that function itself uses a lot of time. A wide bar lower down with many narrower bars above it means time is spread across the things it calls.
Imagine a time-lapse photograph of a busy kitchen taken every few seconds all evening. Afterward, you count in how many photos the chef was chopping, stirring, plating, or waiting for ingredients. The counts reveal where the chef’s evening really went. A flame graph is that photo count drawn as a picture, so the biggest time sinks stand out immediately.
Capture during the problem
A profile taken while the system is healthy teaches little. Capture during the slowdown, for long enough to collect a representative sample, typically tens of seconds.
Capture a baseline too
Take another profile during normal operation. Comparing the two highlights what changed, which is often more informative than either profile alone.
Look for the widest bars
Focus on functions that dominate the picture, and read their names to understand what the program was doing, for example serializing objects, filtering, or waiting for storage.
Connect findings back to requests
Link what the profile shows to the request patterns you found earlier. If time goes to serializing huge list responses, return to the offending client rather than modifying the server.
Continuous profiling
Because slowdowns are often brief and unpredictable, some teams run continuous profiling, which samples programs at a very low rate all the time and stores the results. When an incident occurs, the profile from that exact moment is already available. The cost is small, and the benefit is that you stop missing the evidence of problems that vanish before anyone can investigate.
Profiling endpoints expose internal details of a critical component, and heavy profiling adds load. Restrict access to authorized administrators, never expose these endpoints publicly, and avoid capturing many large profiles at once on a component that is already under stress.
16Tracing and Audit Records: Finding Who Causes the Load
Metrics show that load exists. Traces and audit records show who creates it and why.
Audit records
The interface server can record each request: who sent it, what they asked for, when, and what the response was. At a modest detail level, this creates a searchable history of activity. Analyzing it answers questions metrics cannot, such as which service account made the most list requests last night, which tool updated the same object ten thousand times, or which user deleted a particular resource.
Top callers by request volume
Group requests by user or service account to find the heaviest clients, then check whether their volume makes sense.
Top callers by expensive actions
Focus on large list requests and unfiltered cluster-wide queries, which cost far more than their count suggests.
Repeated updates without change
Spot tools that write the same data over and over, wasting database capacity and triggering unnecessary watch notifications.
Distributed tracing
Tracing follows a single request across components and records how much time was spent in each stage. The interface server and the database can both emit traces, which lets you see, for one slow request, whether the time went to authentication, admission webhooks, or the database write. Because tracing is more expensive than metrics, it is usually sampled: only a small fraction of requests are traced, which is enough to reveal patterns without adding much load.
flowchart LR
M["Metrics: something is slow"] --> A["Audit records: who is sending the heavy requests"]
A --> T["Traces: which stage of those requests is slow"]
T --> P["Profiles: which code is responsible"]
P --> F["Fix at the source"]
Audit logging at high detail can itself become heavy, producing large volumes of data and adding work to every request. Choose detail levels deliberately: record metadata for most requests and full bodies only for the sensitive operations that truly require them.
17Load Testing: Learning Limits Before Production Teaches Them
Waiting for a real traffic spike to discover your limits is an expensive way to learn. Controlled tests are cheaper.
Why test the control plane on purpose
Metrics from a healthy production cluster tell you how the control plane behaves under ordinary load, but they cannot tell you where the cliff is. Load testing pushes the system deliberately, in a safe environment, to reveal the point where latency begins to climb and the component that gives way first. Knowing your cliff in advance lets you set alerts at sensible distances from it and plan capacity before demand arrives.
What a good test looks like
A useful test imitates real behavior instead of simply hammering one endpoint. It creates many objects of realistic shapes and sizes, runs many watchers, mixes reads and writes in realistic ratios, and exercises the scheduler and controllers by creating real workloads. The Kubernetes community maintains dedicated testing tools for this purpose, and other open-source projects specialize in creating large numbers of objects and measuring how the control plane responds. The best of them measure the same service level indicators you use in production, so results are directly comparable.
Define the questions
Decide what you want to learn, for example how many nodes the current control plane supports, or how a new operator affects latency.
Build a realistic environment
Use the same versions, settings, and storage types as production, since results from a different setup may not transfer.
Ramp up gradually
Increase load in steps and hold each step long enough to observe steady behavior, instead of jumping straight to the maximum.
Record everything
Capture metrics, profiles, and logs throughout, so you can explain results later rather than only observing them.
Simulated nodes and their limits
Creating thousands of real machines for a test is costly, so some tools simulate nodes: lightweight programs that pretend to be node agents and report status to the control plane. They are excellent for measuring control plane load from node traffic. They cannot reproduce everything real nodes do, such as actually running workloads, so use them to test the control plane and not the nodes.
| Test type | What it reveals | When to run it |
|---|---|---|
| Steady-state load | Normal capacity and baseline latency | Before and after major upgrades |
| Ramp test | The load at which latency starts rising | When planning growth |
| Spike test | Behavior under sudden bursts, such as many pods starting at once | Before launching large batch or event-driven workloads |
| Soak test | Slow problems like memory growth and database bloat | Before trusting a new component in production |
| Failure test | Behavior while a member is lost or restarted | Regularly, as a reliability exercise |
Running load tests against a production cluster to “see what happens” can cause the very outage you hoped to prevent. Test in an isolated environment that mirrors production, and only ever run gentle, well-announced tests in live environments.
18A Repeatable Method for Hunting Bottlenecks
Good engineers do not guess. They follow a method that narrows the search with every step.
Seven steps from symptom to fix
State the symptom precisely
“Deployments are slow” is vague. “Creating pods takes eight seconds at the ninety-ninth percentile since Tuesday” is something you can investigate.
Find when it started
Look at charts over several days. A sudden step usually matches a change, such as an upgrade, a new tool, or a configuration edit. A slow ramp suggests growth.
Locate the slow stage
Compare latencies across the front door, admission, the database, the scheduler, and controllers to find where time is added.
Check saturation of that component
Determine whether processor, memory, disk, network, or a queue is at its limit. A saturated resource is almost always the proximate cause.
Identify the source of the load
Use audit records and request attribution to find which clients and request types are responsible for the extra work.
Form a hypothesis and test it
State what you believe is the cause and what change should confirm it. Apply one change at a time, ideally to a test environment first.
Verify and prevent recurrence
Confirm the symptom is gone, then add an alert or a guardrail so the same problem is caught earlier next time.
flowchart TD
S["Symptom reported"] --> A{"Did it start with a change?"}
A -->|"Yes"| B["Review recent changes and consider rollback"]
A -->|"No, gradual"| C["Check growth in objects, nodes, and clients"]
B --> D["Locate slow stage"]
C --> D
D --> E{"Which stage?"}
E -->|"Front door or admission"| F["Check request mix, webhooks, fairness queues"]
E -->|"Database"| G["Check disk latency, size, leader changes"]
E -->|"Scheduler or controllers"| H["Check queues, client limits, placement rules"]
F --> I["Fix source, then verify"]
G --> I
H --> I
Two helpful lenses
Two simple frameworks organize this thinking. The first examines each resource by its utilization, its saturation, and its errors. The second examines each service by its request rate, its error rate, and its duration. Used together, they ensure you look at both the machines and the work they serve, and they prevent the common trap of staring at only one kind of chart.
Write down every hypothesis and what you did to test it, even the ones that turned out wrong. A written trail prevents repeating dead ends, and it becomes a valuable runbook for the next person who sees a similar symptom.
19From Symptom to Likely Cause: A Field Guide
Patterns repeat across clusters. This table maps common symptoms to the places you should look first.
| Symptom | Likely causes | First things to check |
|---|---|---|
| Commands hang or take many seconds | Overloaded interface server, slow admission webhook, slow authentication provider | Request latency by action, admission step durations, identity provider health |
| Interface server restarts repeatedly | Memory exhaustion from large lists or many watches | Memory trend, heavy list callers, number of open watches |
| Writes slow, reads fine | Slow disks on the database, slow webhooks, large objects | Database disk sync times, webhook durations, object sizes |
| Frequent leader elections in the database | Slow disks, network jitter between members, overloaded machines | Disk latency, network round trip times, processor use on database machines |
| Cluster suddenly read-only | Database size quota reached | Database size against quota, largest object types, compaction status |
| Pods stay pending long | No capacity, unsatisfied volume, complex placement rules, scheduler backlog | The reason shown on the pod, scheduler queue sizes and attempt durations |
| Rollouts progress very slowly | Controller queues backed up, client-side throttling, slow readiness checks | Queue depth and wait time, client rate limiter waits, readiness probe settings |
| Many rejected or throttled requests | One noisy client, undersized capacity share, runaway automation | Fairness metrics by priority level, top callers from audit records |
| Nodes flap between ready and not ready | Heartbeats delayed by overloaded interface server or network problems | Lease update latency, interface server saturation, node network health |
| Slow discovery by tools | A failing or slow aggregated API, or very many custom resource types | Health of aggregated services, count of registered API groups |
| Everything slows after an upgrade | Changed defaults, new features creating extra load, incompatible extensions | Release notes, metrics before and after, extension versions |
A table of likely causes is a starting point, not a verdict. Real incidents often involve two or three contributing factors that interact. Use the table to choose where to look first, then confirm with data before making changes.
20Scale Limits and Sizing the Control Plane
Kubernetes publishes tested limits. Knowing them tells you how far you can push, and when to plan ahead.
Published scalability thresholds
The Kubernetes community tests and documents the largest clusters it considers supported, expressed as upper bounds on things like the number of nodes, the number of pods in total, the number of pods per node, and the number of services. These limits describe configurations the project continuously tests against its published service level objectives. Exceeding them does not mean failure, but it means you are in territory that is less thoroughly tested and where you must rely more on your own measurements. Check the current documentation for the exact numbers of the version you run, because they are revisited as the software improves.
Dimensions of scale
Number of nodes
Drives heartbeat traffic, watch counts, and scheduler work.
Number of pods and objects
Drives memory in the interface server cache and size of the database.
Rate of change
Drives write load, watch fan-out, and controller work. A quiet large cluster is easier than a busy medium one.
Number of clients
Drives request volume and the number of long-lived watches.
Kinds of objects
Each additional custom type adds caching, watching, and controller overhead.
Object sizes
Large objects amplify every operation: storage, transfer, caching, and notification.
Sizing the machines
Control plane machine sizes are usually chosen by how many nodes the cluster has and how busy it is. The interface server mostly needs processor and memory in proportion to the number of objects and clients. The database needs fast storage first, then adequate memory for its data file. Rather than picking a size from a generic chart, observe the actual utilization of existing machines during peak periods and keep comfortable headroom, since control plane problems tend to appear as sudden cliffs rather than gentle slopes.
Measure peak, not average
Look at the busiest hour of the busiest day, including events like large deployments and nightly jobs, because that is when the control plane is tested.
Leave headroom for failure
In a group of three interface server instances, losing one pushes its traffic onto the other two. Size so that the remaining instances can carry the load without saturation.
Prefer fixing load over adding size
Remove wasteful clients and unneeded objects before buying larger machines. Savings from efficiency compound; savings from hardware do not.
Revisit sizing at each growth step
Reassess when node counts, object counts, or the number of operators grows significantly, and retest with load tests where possible.
Very large organizations often split one huge cluster into several moderately sized ones. Several clusters of manageable size can be simpler to operate and safer, since a control plane problem then affects only a portion of the workloads. Weigh that benefit against the extra effort of managing multiple clusters.
21High Availability, Leader Election, and Failover Performance
Running several copies of each component keeps the cluster alive during failures. It also changes how performance behaves.
Different components, different redundancy
The interface server is stateless, so you can run several copies and spread requests across them with a load balancer. All copies are active at the same time. The scheduler and controller manager are different: only one copy of each does the work at any moment, because two schedulers choosing placements independently would conflict. The copies use a leader election process, where each competes to hold a lease stored in the cluster, and only the holder acts. If the leader fails or loses its lease, another takes over.
flowchart TD
LB["Load balancer"] --> A1["API server one"]
LB --> A2["API server two"]
LB --> A3["API server three"]
A1 --> E[("etcd group: three or five members")]
A2 --> E
A3 --> E
S1["Scheduler one: leader"] --> A1
S2["Scheduler two: standby"] -.-> A2
C1["Controller manager one: leader"] --> A1
C2["Controller manager two: standby"] -.-> A3
Performance consequences of leader election
Failover gaps
When a leader fails, the standby must wait for the old lease to expire before taking over. During that gap, no scheduling or reconciliation happens. Lease timing settings trade failover speed against the risk of needless leader changes.
Lease renewal traffic
Leaders renew their lease frequently, and each renewal is a write. If the interface server or database is slow, renewals can fail, causing a leader to give up even though it was healthy. This produces confusing flapping.
Cold caches after takeover
A new leader may need to rebuild its view of the world, which causes a burst of requests right when the cluster is already stressed by a failure.
Uneven traffic across interface server copies
Because clients keep long-lived connections, traffic can become lopsided. After a rolling restart, the first instance restarted may end up with few connections while the last ones carry many. Watch per-instance request rates and memory, and consider settings that gently recycle connections so load spreads out again.
A rolling upgrade of the control plane is a stress test. Restarted components reconnect all their clients at once, rebuild caches, and trigger bursts of list requests. Upgrade one instance at a time, wait for latency to settle between steps, and watch the charts during the process.
22Managed Control Planes: What You Can and Cannot Do
Many teams use a cloud provider’s managed Kubernetes. Profiling still matters, but the levers are different.
What changes with a managed service
In a managed service, the provider runs and scales the control plane. You usually cannot log into its machines, change its low-level settings, capture code-level profiles, or choose its disk types. The provider handles patching, failover, and often automatic scaling of capacity. That removes a great deal of work, but it also removes some of your visibility, and it does not remove your responsibility for how your own workloads and tools use the control plane.
What you gain
- No need to operate the database or interface servers yourself
- Provider expertise and automatic scaling in many cases
- Built-in high availability across failure zones
- Published service commitments and support channels
What you give up
- Limited access to internal metrics and profiles
- Fewer tuning options for the components themselves
- Provider-imposed limits on request rates and object counts
- Dependence on provider support for deep investigations
What you can still profile
Your own clients
Measure request rates, latency, and errors from your controllers, tools, and pipelines. You fully control these.
Exposed metrics and logs
Many providers expose interface server metrics, audit logs, and fairness data. Enable and collect whatever is offered.
Object counts and sizes
Track how many objects of each type you create, and how large they are. You control the demand placed on the service.
Your webhooks and operators
Their speed and request patterns are yours to measure and fix, even when everything else is the provider’s.
When you open a support case with a provider, bring evidence: the time window, the affected operations, latency measurements from your clients, and any error messages. Precise, data-backed reports lead to faster, more useful answers than a general complaint that the cluster is slow.
23Dashboards and Alerts That Actually Help
Charts nobody understands and alerts nobody trusts are worse than none. Design them for the person using them at three in the morning.
A dashboard that tells a story
The best control plane dashboard reads from top to bottom like a short story. The top row shows overall health against your objectives: is the front door meeting its latency targets, and are errors low? The next rows break the story down by component: interface server, fairness queues, database, scheduler, and controllers. Lower rows show resource usage, and a final row lists the heaviest clients. A person arriving during an incident can start at the top, see whether the objective is violated, and work downward to find the guilty component, without needing to remember dozens of metric names.
Row one: objectives
Show the share of requests meeting latency targets and the error rate. This answers “are users hurting right now?”
Row two: the interface server
Latency percentiles by action, request rates, in-flight requests, fairness queue waits, and rejections.
Row three: the database
Disk sync and commit times, database size against quota, leader changes, and pending proposals.
Row four: scheduler and controllers
Pending pods, scheduling durations, queue depths, queue wait times, and retries.
Row five: resources and callers
Processor, memory, disk, and network of each component, plus the heaviest clients by request volume and cost.
Alerting on symptoms and on early warnings
Alerts fall into two useful groups. Symptom alerts fire when users are affected, such as when write latency violates its objective for a sustained period. These should wake someone up. Early-warning alerts fire when something is trending toward trouble, such as database size passing a comfortable fraction of its quota or disk sync times creeping upward. These should usually create a ticket for working hours, because they give you time to act calmly.
Burn rate: alerting on how fast you spend your allowance
If your objective allows a small share of slow requests each month, that share is an error budget, like a monthly allowance. Burn rate measures how fast you are spending it. A fast burn, which would exhaust the whole budget in a day or two, deserves immediate attention. A slow burn, which would exhaust it in weeks, deserves a ticket. Alerting this way reduces false alarms from brief blips while still catching real, sustained problems quickly.
Think of a monthly phone data allowance. If you used half of it in the first two days, you would worry immediately even though you have not yet run out. If you are slightly above the daily average, you might just keep an eye on it. Burn rate alerts apply the same common sense to reliability.
Creating alerts on every available metric produces constant noise, and people learn to ignore the pager. Alert on a small number of things that mean users are suffering or soon will be, and leave the rest as charts for investigation.
24Tuning Levers and Their Trade-offs
Every improvement costs something somewhere else. Wise tuning means understanding both sides before changing anything.
| Lever | Potential benefit | Trade-off or risk |
|---|---|---|
| Faster, dedicated storage for the database | Lower write latency, fewer leader changes | Higher cost; requires planned migration |
| Separate database for events | Keeps high-churn data from crowding the main database | Extra component to run and monitor |
| Regular compaction and defragmentation | Smaller, faster database | Defragmentation briefly blocks a member, so it needs scheduling |
| Adjusting fairness shares and categories | Protects critical traffic, contains noisy clients | Misconfiguration can starve legitimate clients |
| Raising interface server resources | More headroom for bursts and larger caches | Cost, and may only mask inefficient clients |
| Adding interface server replicas | More request capacity and resilience | More connections to the database and uneven load if not balanced well |
| Narrowing webhook scope and timeouts | Lower request latency, fewer cascading failures | Possible policy gaps if scope is too narrow |
| Raising client rate limits for a specific controller | Faster reconciliation | More load on the server and database |
| Limiting scheduler node search | Faster placement in very large clusters | Slightly less optimal placement |
| Shortening event retention and cleaning old objects | Smaller database, fewer objects to cache | Less history available for debugging |
| Splitting into multiple clusters | Smaller blast radius, easier scaling per cluster | More operational overhead and cross-cluster complexity |
A rule of thumb for choosing levers
Prefer changes that reduce load over changes that increase capacity, and prefer reversible changes over irreversible ones. Removing a wasteful client, narrowing a webhook, or cleaning up old objects reduces demand permanently and usually costs nothing. Adding capacity helps only until demand catches up, and it costs money every month. When you must add capacity, do it after you have removed the waste, so you are not paying to serve work nobody needed.
Change one thing at a time and record the date, the change, and the observed result. When improvements stack up from many small changes made together, nobody can tell which ones helped and which ones quietly caused new problems.
25Anti-patterns: Habits That Slowly Strangle a Control Plane
Most control plane crises are not sudden surprises. They are the final result of habits that were allowed to continue.
Problem
Operating the control plane without objectives, relying on the feeling that things seem fine.
Why It’s Harmful
Gradual degradation goes unnoticed until it becomes an outage, and there is no shared definition of acceptable performance when people disagree.
Correct Approach
Define a few measurable objectives, track them continuously, and review them regularly with the teams who depend on the cluster.
Problem
Letting the database share a disk and machine with other busy programs, or placing it on slow shared storage.
Why It’s Harmful
Disk latency is the strongest influence on write speed. Contention produces unpredictable stalls, missed heartbeats, and unnecessary leader changes.
Correct Approach
Give the database fast, dedicated storage and a stable, low-latency network between members, and monitor its disk metrics closely.
Problem
Installing webhooks, operators, and custom resource types without measuring their cost or reviewing them later.
Why It’s Harmful
Each adds latency, memory, and watch traffic. Together they can turn a fast control plane into a slow one with no single culprit.
Correct Approach
Evaluate the cost of each extension before adopting it, scope it narrowly, and review the list periodically, removing what is no longer needed.
Problem
Treating the control plane as a free, unlimited service for scripts, dashboards, and pipelines.
Why It’s Harmful
Uncontrolled clients poll aggressively and request huge results, competing with critical components for the same shared capacity.
Correct Approach
Use fairness categories to isolate non-critical clients, give teams guidance on efficient client behavior, and review the heaviest callers regularly.
Problem
Fixing slowness only by upgrading machine sizes, never investigating the cause.
Why It’s Harmful
The underlying problem remains and returns as soon as demand grows to fill the new capacity, while costs keep rising.
Correct Approach
Profile first, remove waste, and add capacity only for demand that is genuine and verified.
Problem
Never testing the effect of upgrades, new features, or new tools on control plane performance before production.
Why It’s Harmful
Defaults change between versions and new components alter load patterns. Surprises appear in production at the worst possible time.
Correct Approach
Run a representative load test in a staging environment for significant changes, and compare results with a recorded baseline.
26Real-World Scenarios
These illustrative stories show how the ideas above combine in practice. Each one follows the same path: symptom, investigation, cause, and fix.
Scenario one: the deployment pipeline that slowed everything
A company noticed that deployments took minutes at certain hours. Charts showed rising latency for list requests at the interface server and memory growth followed by restarts. Audit records revealed that a monitoring dashboard was listing all pods in the cluster every few seconds, on behalf of many open browser tabs. Switching the dashboard to a watch-based approach with filtering and lowering its fairness priority removed the spikes without any hardware change.
Scenario two: the cluster that turned read-only
A team found that every create and update failed one morning. The database had reached its size quota. Investigation showed that a batch system had created millions of finished job objects and never cleaned them up. After clearing old objects, compacting, and defragmenting, writes resumed. Afterward the team added automatic cleanup and an early-warning alert at a conservative fraction of the quota.
Scenario three: slow writes with a calm interface server
Write latency crept up over weeks while reads stayed fast. Database disk sync times showed occasional spikes, and leader changes were more frequent than before. The database volumes turned out to share input and output capacity with a logging agent. Moving the database to dedicated local drives stabilized sync times and ended the leader flapping.
Scenario four: the operator that argued with itself
An operator for a data service updated its own objects on every reconciliation, which triggered another reconciliation, in an endless loop. The interface server saw thousands of updates per second for objects that had not meaningfully changed. Fixing the operator to skip no-op updates cut write traffic dramatically and lowered database growth.
Scenario five: the webhook that stalled every pod
Pod creation began taking several seconds. Admission step durations showed one policy webhook responsible for nearly all of the delay, because its backing service had been scaled down and requests waited for a timeout. Restoring replicas, shortening the timeout, and narrowing the webhook’s scope fixed the symptom and prevented a repeat.
Scenario six: slow rollouts that were nobody’s server problem
A large rollout crawled while all server charts looked calm. Controller queue wait times were high, and a log message revealed waiting on the controller’s own client-side rate limiter. After confirming the server had spare capacity, the team raised that one controller’s limits carefully, and rollouts accelerated.
In every story above, the cause was found by following data from a broad symptom to a specific source, and the best fix removed or reduced demand instead of adding raw capacity. That pattern repeats across almost every real control plane investigation.
27A Maturity Path for Control Plane Performance Work
You do not need advanced tooling on day one. Progress in stages, and gain value at each step.
| Stage | Focus | Typical activities | Success looks like |
|---|---|---|---|
| Stage one: Observe | Basic visibility | Collect metrics from every component, build a single overview dashboard, learn what normal looks like | Anyone on call can say whether the control plane is healthy within a minute |
| Stage two: Objectives | Meaningful targets | Define latency and availability objectives, add symptom alerts and early warnings, set up audit analysis | Alerts are rare, trusted, and tied to real user impact |
| Stage three: Investigate | Fast diagnosis | Enable sampled tracing, practice capturing profiles, build a field guide and runbooks | Typical incidents are diagnosed in minutes instead of hours |
| Stage four: Prevent | Staying ahead | Regular load tests, performance checks for upgrades, guardrails for noisy clients, capacity forecasts | Capacity is added before demand arrives, and upgrades cause no surprises |
Numbers worth reviewing every month
Objective compliance
The share of time the control plane met its latency and availability targets.
Object and node counts
How quickly the cluster is growing, which predicts when limits will be reached.
Database size trend
Growth rate against quota, and the largest object types by size.
Heaviest callers
The clients responsible for the most requests and the most expensive requests.
Extension inventory
Counts of webhooks, custom types, and aggregated services, with owners and measured cost.
Time to diagnose
How long incidents take to understand, which shows whether your tooling and runbooks are improving.
Hold a short monthly review with platform engineers and a few application owners. The platform team knows what the control plane can bear; the application teams know what they plan to launch. Sharing both views lets you prepare for upcoming load instead of reacting to it.
28Upgrades, Change Management, and Performance Regressions
The most common moment for performance to change is the moment you change something.
Why changes cause regressions
Every new Kubernetes release brings improvements, but it can also change defaults, add features that generate extra requests, or alter caching behavior. Third-party components that work well on one version can behave differently on the next. Even small configuration edits, such as changing a webhook’s scope or a controller’s number of workers, shift load patterns. A disciplined approach treats performance as a property that can regress, like any other quality of the system.
Record a baseline
Before the change, capture the key latency percentiles, request rates, queue depths, and resource usage for a representative period.
Read the release notes with performance in mind
Look for changed defaults, new feature gates, deprecations, and notes on caching, fairness, and storage behavior.
Test in a mirror environment
Apply the change to a staging cluster with realistic load, and compare against the baseline. Investigate any difference larger than your normal variation.
Roll out gradually and watch
Change one control plane member at a time, wait for latency to settle, and keep a clear rollback plan ready.
Compare and document
After the change, compare against the baseline again and record what you learned, so the next change starts from better knowledge.
Making several unrelated changes in one maintenance window, such as a version upgrade, a storage migration, and a new webhook. If performance changes afterward, nobody can tell which change caused it. Separate changes in time so each can be judged on its own.
29A Worked Investigation from Start to Finish
Follow one realistic investigation to see the method in action. The details are illustrative, but the reasoning is typical.
The report
On a Monday morning, several teams complain that their deployments take far longer than usual. Some report that command-line tools pause for many seconds before showing results. Nothing is down, applications are running, and the on-call engineer sees no alarms. The engineer begins by stating the symptom precisely: write requests and list requests at the interface server are slow, starting sometime over the weekend.
Locating the slow stage
The engineer opens the control plane dashboard. The top row shows the write latency objective violated since Saturday evening. In the second row, write latency at the interface server is high, but the database’s own request durations look normal, and its disk sync times are healthy. That rules out storage. Admission step durations show one webhook taking much longer than before, but not enough to explain everything. In-flight requests are high and the fairness queue for general clients shows long waits and a growing count of rejected requests.
Finding the source
Audit records for the weekend reveal that a new reporting job, started Saturday, makes frequent cluster-wide list requests for pods and configuration objects, with no filters. Its request count is not extreme, but its response sizes are enormous. Memory use of the interface server instances has climbed in step. The job belongs to a data analytics team that did not realize its tool lists everything every thirty seconds.
sequenceDiagram
participant J as Reporting job
participant A as API server
participant E as etcd
participant O as Other clients
J->>A: List all pods and configuration objects, no filters
A->>E: Fetch large result set
E-->>A: Very large response
A-->>J: Build and send huge reply
O->>A: Normal requests
Note over A: Memory and processor busy with huge replies
A-->>O: Slow responses and rejections
Confirming and fixing
The engineer asks the analytics team to pause the job, and within minutes list latency falls, memory stabilizes, and rejections stop. That confirms the hypothesis. The permanent fix has three parts. The analytics team changes the tool to watch for changes and filter by label, instead of listing everything. The platform team assigns that tool’s service account to a lower-priority fairness category, so future misbehavior is contained. And the team adds an alert on unusually large response sizes and a short guide for teams on efficient use of the cluster interface.
What went well
The dashboard’s top-down layout led quickly from objective violation to the interface server, and audit records identified the caller without guesswork.
What was missing
No alert existed for large response sizes, and no fairness category protected critical traffic from a new, unvetted client.
What changed afterward
A new alert, a containment category, a client guideline, and a short onboarding check for any new tool that talks to the cluster.
30A Plain-English Glossary
Keep these short definitions nearby while reading dashboards, audit records, and incident reports.
Control plane
The group of components that stores the cluster’s desired state, makes decisions, and drives reality toward the plan.
API server
The central front door through which every component and user reads and changes cluster state.
etcd
The consistent, distributed database that durably stores all cluster objects.
Latency
The time one request takes from being sent to being answered.
Percentile
A value below which a given share of measurements fall. The ninety-ninth percentile shows the slow tail that averages hide.
Saturation
How close a resource is to its limit, such as a nearly full queue or a very busy disk.
SLI and SLO
A measured indicator of experience, and the target value you promise to meet for it.
Watch
A long-lived request that streams changes to a client as they happen.
Watch cache
The recent copy of objects the API server keeps in memory to answer many reads without touching the database.
Informer
A client helper that lists once, watches for changes, and keeps a local copy to avoid repeated heavy requests.
Admission webhook
An external program the API server calls during a request to inspect, modify, or reject an object.
Priority and fairness
The mechanism that sorts requests into priority levels and queues them fairly so no single client can starve others.
Compaction
Discarding old versions of data in etcd so space can be reused.
Defragmentation
Returning reclaimed space to the disk so the etcd data file actually shrinks.
Work queue
The list of items a controller still needs to process, whose depth and wait time reveal its health.
Flame graph
A picture of profiling samples where wider bars represent functions that consumed more time or memory.
Share this glossary with application teams who use your cluster. When everyone means the same thing by latency, saturation, and objectives, incident conversations become shorter and decisions become faster.
31Frequently Asked Questions
Quick answers to the questions beginners ask most often.
Compare request latency at the interface server with the symptoms. If creating, listing, or updating objects is slow, suspect the control plane. If objects are created quickly but your application responds slowly to users, the control plane is probably not involved, because running applications do not depend on it for each request.
Existing workloads generally keep running, since node agents and networking continue to function with the last known state. What stops is the ability to change things: deploy, scale, replace failed pods, or update configuration. This is why control plane trouble can go unnoticed until you try to make a change.
Start with the ninety-ninth percentile latency of requests at the interface server, split by action. It reflects what clients feel and points you toward either the front door or the database for deeper investigation.
Averages hide the slow tail. A small share of very slow requests barely moves the average but causes timeouts and retries that users notice. Always look at high percentiles.
Gentle, short profiling by authorized administrators is commonly done in production, but each capture adds some load. Avoid taking many large profiles at once from a component already under stress, restrict access to the endpoints, and prefer sampled or continuous low-rate profiling where possible.
Slow storage under the database is the most frequent cause, followed by slow admission webhooks and oversized or constantly updated objects. Checking disk sync times and webhook durations quickly separates these possibilities.
Typically a few object types: high-volume events, large configuration objects, leftover finished jobs, or custom resources written by operators. Counting objects and total size by type finds the main contributors, and cleanup plus compaction and defragmentation reclaim the space.
Only after you understand the load. Larger machines can help genuine growth, but if a wasteful client or slow webhook is the cause, extra capacity just delays the problem while raising cost. Profile first, remove waste, then add capacity for demand that remains.
Monitoring watches known numbers over time and alerts when they cross thresholds. Profiling digs into why a number behaves as it does, using audit records, traces, and code-level samples. Monitoring raises the question; profiling answers it.
Partly. You can usually collect the metrics and audit logs the provider exposes and fully profile your own clients, webhooks, and workloads, but you typically cannot capture code-level profiles of the provider’s components. Bring measured evidence to the provider when deeper investigation is needed.
Before significant changes such as upgrades or new platform components, before major launches that will change load, and periodically as the cluster grows. Treat the results as a recorded baseline for later comparison.
They may be waiting on their own client-side rate limits, or their work queues may be backed up by expensive processing. Compare client-side wait times and queue metrics with server latency to tell these cases apart.
There is no single answer. Fewer, larger clusters share resources efficiently but have larger blast radius and scaling limits; several moderate clusters limit the blast radius at the cost of more management effort. Choose based on your scale, isolation needs, and operational capacity.
32Summary and Key Takeaways
The Kubernetes control plane is a small group of cooperating components organized around one hub, the interface server, backed by one durable store, etcd. Because everything flows through those two places, almost every performance problem eventually appears there, even when the true cause lives in a heavy client, a slow webhook, a slow disk, or a controller waiting on its own limits. Profiling means measuring where time and resources go, moving from cheap broad signals to expensive precise ones, and following the evidence to the source instead of guessing. Used together with clear objectives, fair queuing, careful extensions, regular testing, and disciplined change management, profiling turns the control plane from an invisible risk into a well-understood, predictable part of your platform.
Key Takeaways
- Profile before you spend — measure where time goes first; most control plane slowness comes from a specific client, extension, or disk, not from a lack of raw capacity.
- Think in percentiles and objectives — judge health by the high-percentile latency against defined targets, because averages hide the slow tail that users actually feel.
- The API server and etcd are the center — nearly every symptom shows up at these two places, so learn their vital signs: request duration by action, fairness queues, disk sync time, database size, and leader changes.
- Heavy requests cost far more than they count — unfiltered lists, constant polling, oversized objects, and bypassed caches are the classic causes of overload; use list-then-watch, filtering, and pagination.
- Extensions are never free — webhooks, custom resources, operators, and aggregated APIs each add latency and load, so scope them narrowly, set sensible timeouts, and review them regularly.
- Work from cheap signals to expensive ones — metrics locate the problem, audit records and traces identify the caller and stage, and code-level profiles explain the internals only when truly needed.
- Prevent surprises — load test before big changes, record baselines, change one thing at a time, alert on objectives and early warnings, and keep a field guide and runbooks for the next incident.