API Aggregation Layer in Kubernetes

API Aggregation Layer in Kubernetes

How one front door lets Kubernetes grow new kinds of APIs without rebuilding the cluster, and how to run that safely at production scale.

Imagine a huge office building with one reception desk at the entrance. Every visitor, delivery and phone call goes through that desk. The receptionist does not know how to do every job in the building, but she knows exactly which department handles what, and she sends each request to the right place. If the company opens a new department tomorrow, nobody has to rebuild the building. Someone simply tells the receptionist, “Requests about this topic go to the new department on the third floor.”

Kubernetes works in a very similar way. The Kubernetes API server is the reception desk of the whole cluster. Everything you do, from listing Pods to scaling a Deployment, is a request that goes through it. The API aggregation layer is the feature that lets you add new departments behind that desk. In this guide you will learn what it is, why it exists, how a single request travels through it, how it stays secure, and how to run it in production without surprises.

By the end you will be able to explain every moving part in plain words, read the symptoms when something breaks, and decide confidently whether a project needs an aggregated API or a simpler tool.

1What Is the API Aggregation Layer?

Before any detail, let us build a clear picture of the idea in one sitting.

Kubernetes is controlled entirely through an API. An API (Application Programming Interface) is simply a menu of requests that a program is allowed to make, such as “show me all Pods” or “create a Service”. In Kubernetes, that menu is served over HTTPS by a component called the kube-apiserver. Whether you use the kubectl command line, a dashboard, a CI pipeline or an internal controller, they all talk to this one server.

Out of the box, the kube-apiserver knows about the built-in resource types: Pods, Services, Deployments, Nodes, Secrets and so on. But real companies need more. They want to ask Kubernetes for “the current CPU use of every Pod”, or “a virtual cluster”, or “a database backup”. Those things are not built in.

The API aggregation layer is the part of the kube-apiserver that lets you plug in your own separate API servers and make them appear as if they were part of Kubernetes itself. A user keeps using the same address, the same login and the same kubectl. Behind the scenes, the kube-apiserver quietly forwards the request to your extra server, waits for the answer, and passes it back.

Everyday analogy

Think of a shopping mall directory. You walk in through one main entrance and see one map. The map says “shoes on level two, electronics on level three”. Each shop is run by a different owner with their own staff and stock, yet you never need a separate ticket for each shop. The mall entrance is the kube-apiserver. The directory is the aggregation layer. Each shop is an extension API server.

The one-line definition

The aggregation layer lets Kubernetes serve extra API groups from separate, independently run servers, while presenting them to users under one front door with one set of login rules.

1
front door for every request
N
extension servers behind it
0
changes needed to client tools

Where the name comes from

The word “aggregation” means gathering many pieces into one. Several different API servers, each owning its own group of resources, are gathered together into one combined API surface. The internal component that does the gathering is called kube-aggregator, and in modern clusters it runs inside the same process as the kube-apiserver. You do not install it separately.

2Why It Exists: The Problem of Extending Kubernetes

Kubernetes was designed from day one to be extended. Its creators knew that no single project could ever predict every resource that people would want to manage. So they built several extension points. The aggregation layer is one of the two main ways to add new resource types to the API. The other is the CustomResourceDefinition, usually called a CRD, which we compare in detail later.

What life looked like before

In the early days, if you wanted a new kind of resource, you had two bad choices. You could change the Kubernetes source code itself, rebuild the whole API server and run your own fork. That meant every upgrade became a painful merge exercise. Or you could build a completely separate tool with its own address, its own login and its own client, and ask your users to learn yet another interface.

!
Why forks hurt

A forked API server must be patched, tested and security-fixed by you on every Kubernetes release. Most teams cannot keep up, and the cluster slowly falls behind on security updates.

The needs that pushed the design

Need 1

One address for everything

Users and tools should talk to one endpoint and not memorise many.

Need 2

One login system

Existing users, groups and role rules must keep working for the new APIs.

Need 3

Independent release cycle

A new API should be upgradable without upgrading the whole cluster.

Need 4

Freedom in behaviour

Some APIs need custom storage, special verbs or computed answers that a plain database record cannot give.

A real example that made it famous

The best-known case is resource metrics. People wanted the cluster to answer “how much CPU and memory is each Pod using right now?” That data is not stored as a normal object. It is measured live on every node and changes every few seconds. Storing it as ordinary records in the main database would be wasteful and slow. Instead, a small server called metrics-server gathers the numbers, keeps them in memory, and answers through the aggregation layer under the group metrics.k8s.io. Tools like kubectl top and the Horizontal Pod Autoscaler then read it as if it were a native part of Kubernetes.

Extend the cluster by adding departments, not by rebuilding the building.

3Core Concepts You Must Know

Six terms carry almost the whole topic. Learn them once and the rest becomes easy.

API group and version

Every resource in Kubernetes lives in an API group and carries a version. Think of the group as a surname and the version as an edition number. Deployments belong to the group apps at version v1. The metrics data belongs to the group metrics.k8s.io at version v1beta1. Groups keep names from colliding, and versions let an API evolve (for example from alpha to beta to stable) without breaking old clients.

Extension API server

An extension API server is a normal program, usually running as Pods inside the cluster, that speaks the Kubernetes API conventions. It owns one or more API groups. It knows how to list, get, create, update, delete and watch its own resource types. It is also called an aggregated API server.

APIService object

The APIService is a small Kubernetes object that acts like an entry in the mall directory. It belongs to the group apiregistration.k8s.io. It says: “the group G at version V is served by the Service named S in namespace N.” Creating an APIService is the single act that tells the aggregator to start forwarding requests.

FieldWhat it meansPlain-words picture
group and versionWhich part of the API this entry coversThe department name on the directory
service referenceName, namespace and port of the Kubernetes Service in front of the extension serverThe phone number of the department
CA bundleThe certificate authority used to trust the extension serverThe ID check that proves the department is genuine
group priority minimumHow high the group sits when the aggregator orders the groupsPosition on the directory board
version priorityHow versions inside one group are rankedWhich edition is shown first
insecure skip TLS verifySwitch that turns off certificate checkingLetting anyone in without ID, only for experiments

The aggregator

The aggregator is the code inside the kube-apiserver that reads APIService objects, keeps a live routing table, and proxies matching requests. It also checks whether each extension server is healthy and records the result on the APIService.

Delegated authentication and authorization

The extension server does not keep its own list of users. Instead it delegates the questions “who is this person?” and “may they do this?” back to the main API server. This is how one login system covers every API. We cover the exact flow in chapter 6.

Local versus remote APIService

Built-in groups such as apps and batch are also registered as APIService objects, but with no service reference. These are called local entries because the same process answers them. Only entries with a service reference are remote and get proxied. This detail explains why a fresh cluster already shows many APIService objects before you add anything.

i
Tip

Run kubectl get apiservices on any cluster. The AVAILABLE column tells you instantly whether every registered group is reachable. It is the first command to try when an API call behaves strangely.

4Architecture and Components

The kube-apiserver is not one block. It is a small chain of servers, each passing along what it cannot handle.

Inside the control plane process there are three API servers joined together in a chain, a design pattern known as delegation. Each server tries to handle a request. If the path does not belong to it, it hands the request to the next server in the chain. The last link returns a “not found” answer.

flowchart TB
  C["Client: kubectl or a controller"] --> LB["Load Balancer in front of control plane"]
  LB --> AGG["Aggregator Server"]
  AGG -->|"built-in groups"| KAS["Core Kube API Server"]
  KAS -->|"path not known"| CRD["API Extensions Server for CRDs"]
  CRD -->|"still not known"| NF["Not Found Handler"]
  AGG -->|"remote APIService matched"| PX["Proxy Handler"]
  PX --> SVC["Kubernetes Service"]
  SVC --> E1["Extension Server Pod A"]
  SVC --> E2["Extension Server Pod B"]
  KAS --> ETCD[("Cluster etcd")]
  CRD --> ETCD
  E1 --> ST[("Extension Storage or Memory")]
  E2 --> ST
    
Figure 1. The delegation chain and the proxy path to extension servers

Reading the picture step by step

The request enters from the top. The aggregator server sits at the front of the chain because it must see every request first. It looks at the URL path, such as /apis/metrics.k8s.io/v1beta1, and checks its routing table of APIService objects.

  • If the path matches a remote entry, the proxy handler forwards the request to the extension server.
  • If the path matches a local entry, the request is delegated to the core kube-apiserver, which serves built-in resources from etcd.
  • If the core server does not know the path, it delegates to the API extensions server, which serves resources defined by CRDs.
  • If nobody knows the path, the final handler answers with “404 not found”.

The main building blocks

Component

Aggregator

Holds the routing table, proxies remote requests and watches APIService health.

Component

Core API server

Serves built-in resources and stores them in the cluster database.

Component

API extensions server

Serves custom resources created through CRDs, using the same database.

Component

Extension API server

Your separate program, with its own code, storage and release cycle.

Component

Service and Endpoints

The stable address and the list of healthy Pods that receive proxied traffic.

Component

Proxy client certificate

The identity the main server shows to the extension server so it can be trusted.

How the aggregator reaches your Pods

The control plane often runs on nodes where the usual in-cluster networking helper, kube-proxy, is absent. In that case the cluster Service address may not be reachable from the aggregator. A setting called --enable-aggregator-routing tells the aggregator to resolve the Service to its endpoint addresses and connect to Pod addresses directly. This is a common reason an otherwise correct setup reports “unavailable”, and we revisit it in the troubleshooting chapter.

i
Good to know

Managed Kubernetes services such as GKE, EKS and AKS enable and configure the aggregation layer for you. You normally only create the Deployment, the Service and the APIService.

5Internal Working: The Life of One Request

Follow a single request from the moment you press Enter until the answer appears on your screen.

Suppose a platform engineer runs kubectl top pods. Behind that short command, a precise relay race takes place. The diagram below shows every runner in order.

sequenceDiagram
  autonumber
  participant U as User or Controller
  participant A as Aggregator inside kube-apiserver
  participant S as Service and Endpoints
  participant E as Extension API Server
  participant D as Data Store or Memory
  U->>A: GET pods under metrics.k8s.io
  A->>A: Authenticate the user and check path permission
  A->>S: Proxy request with identity headers over TLS
  S->>E: Forward to a healthy Pod
  E->>E: Verify the proxy certificate against request header CA
  E->>A: Ask SubjectAccessReview for the original user
  A-->>E: Allowed or denied
  E->>D: Read or write the data
  D-->>E: Result
  E-->>A: Response
  A-->>U: Response returned to the user
    
Figure 2. Sequence of one proxied request

Stage by stage

1

The client knocks on the front door

The request arrives at the kube-apiserver over HTTPS, carrying a client certificate, a token or another credential.

2

The user is identified

The main server runs its normal authentication chain and learns the user name and group memberships.

3

A first permission check

The main server checks that this user is allowed to touch that API path at all. A user with no rights is stopped here and the extension server never hears about it.

4

The routing table is consulted

The aggregator matches the path to an APIService. If the entry is remote and marked available, it prepares to proxy.

5

The request is forwarded with identity headers

The aggregator opens a TLS connection using its proxy client certificate and adds headers that state who the original user was.

6

The extension server checks the sender

It confirms the certificate truly came from the main API server, so it can trust the identity headers.

7

A second, detailed permission check

The extension server asks the main server whether the original user may perform this exact action on this exact resource.

8

The work is done and the answer returns

The extension server reads or writes its data, builds the response, and sends it back through the same path.

Why two permission checks?

The first check is a coarse gate at the front door. The second check is the fine-grained one, performed by the server that actually understands the resource. The extension server knows which verbs, namespaces and subresources exist in its own API, so it is best placed to judge them. Having both means a mistake in one place does not open the whole system.

Long-lived requests

Many Kubernetes clients use watch requests, which keep a connection open so the server can push changes as they happen. The aggregator supports proxying these streaming connections. This is important because controllers depend on watches to react quickly. It also means a restart of the extension server briefly cuts those streams, and well-written clients reconnect automatically.

i
Tip

The kube-apiserver does not run its own admission step for requests handled by an extension server. If you need validation or defaulting rules, the extension server must implement them itself or call admission webhooks it configures.

6Trust and Identity: Authentication and Authorization

Security is the heart of the design, so let us slow down and see exactly who trusts whom.

Everyday analogy

A hotel front desk phones a restaurant inside the hotel and says, “Guest in room 412 is coming down for dinner.” The restaurant trusts the call only because it recognises the desk’s internal phone line. It then asks the desk, “Is that guest allowed in the VIP room?” The restaurant never keeps its own guest list. The desk is the single source of truth.

Step one: the extension server must trust the sender

When the aggregator proxies a request, it adds identity headers such as the user name, the group list and optional extra attributes. Anyone on the network could try to forge such headers, so the extension server must be certain the request really came from the main API server. This is done with a client certificate.

  • The kube-apiserver is configured with a proxy client certificate and key.
  • That certificate is signed by a certificate authority named in the setting called requestheader-client-ca-file.
  • The extension server reads that authority from a ConfigMap named extension-apiserver-authentication in the kube-system namespace, which the control plane publishes for exactly this purpose.
  • It also reads the list of allowed certificate names and the exact header names to trust, so a stranger with a valid certificate from another source is still rejected.

Step two: the extension server asks about the person

Once the sender is trusted, the extension server uses two standard Kubernetes review requests, sent to the main API server:

Question

TokenReview

“Who owns this token?” Used when the request reaches the extension server with a bearer token instead of proxy headers.

Question

SubjectAccessReview

“May this user perform this verb on this resource?” Answered by the same RBAC rules as any native request.

Because answers come from the main server, a RoleBinding that lets Alice read metrics.k8s.io works exactly like a RoleBinding that lets her read Pods. Nothing new to learn and no second user database to maintain.

The permissions the extension server itself needs

For this delegation to work, the extension server’s own service account needs two things:

  • Permission to create TokenReview and SubjectAccessReview objects, commonly granted by binding the built-in ClusterRole named system:auth-delegator.
  • Permission to read the extension-apiserver-authentication ConfigMap, commonly granted by binding the built-in Role named extension-apiserver-authentication-reader in kube-system.
!
Common trap

If these two bindings are missing, the extension server cannot learn who the caller is. Symptoms look confusing: requests fail with authorization or “unable to load configuration” messages even though the Pods look healthy.

Step three: the main server must trust the extension server

Trust must also flow the other way. When the aggregator connects, it validates the extension server’s serving certificate. The APIService object carries a CA bundle, which is the certificate authority that signed the extension server’s certificate. The aggregator verifies the server’s certificate against this bundle and checks that it was issued for the Service’s DNS name. This prevents an impostor Pod from answering in place of the real one.

There is an escape switch called insecure skip TLS verify. It exists for quick experiments on a laptop. In production it removes the guarantee that you are talking to the right server, so it should never be used there.

Caching of decisions

Asking the main server about every single request would be expensive. Extension servers built with the standard libraries cache recent answers for a short time. This keeps latency low, but it also means a permission change can take a few seconds to be reflected.

7Discovery, Priorities and Availability

Discovery: how clients learn what exists

Clients like kubectl do not have a hard-coded list of resources. They ask the server. A request to the path /apis returns every group, and a request to /apis/GROUP/VERSION returns the resource types inside that group. This process is called discovery. The aggregator answers the top-level request itself using the APIService objects, and for the detailed listing it asks the extension server.

In large clusters with many extension groups, old-style discovery meant dozens of separate calls every time a client started. Newer Kubernetes releases added aggregated discovery, which lets a client fetch all groups and resources in just two requests, one for the core group and one for all named groups. This feature became stable in Kubernetes 1.30 and makes tools noticeably quicker to start on busy clusters.

Priorities: who wins when names clash

Two numbers on each APIService decide ordering:

SettingControlsWhy it matters
group priority minimumThe ranking of a whole group in discovery listsHigher numbers are listed first, which affects how tools choose between groups offering similar names
version priorityThe ranking of versions inside one groupDecides which version is treated as the preferred one, for example choosing a stable version over a beta one

Use clear, deliberate numbers. A newly added group with an accidentally high priority can change what existing tools show first, which surprises users.

Availability: the health badge on every APIService

The aggregator runs an availability controller that regularly calls each remote extension server and updates a condition called Available on the APIService. This is the health badge you see in kubectl get apiservices.

Reason

ServiceNotFound

The Service named in the APIService does not exist in that namespace.

Reason

MissingEndpoints

The Service exists but no ready Pod stands behind it.

Reason

FailedDiscoveryCheck

Pods exist but the aggregator could not complete a healthy discovery call, often due to certificate or network trouble.

Reason

ServicePortError

The port named in the APIService does not match the Service.

While an entry is marked not available, requests to that group receive a “service unavailable” style error. Requests to every other group continue normally, which is a nice isolation property: one broken extension does not take down the rest of the API.

!
Hidden side effect

Some cluster components list every API group during their work. A permanently broken APIService can therefore slow down or even stall tasks like namespace deletion and garbage collection. Treat an unavailable APIService as an incident, not as background noise.

8Data Flow and Lifecycle of an Aggregated API

An extension API has a life of its own, from first build to final retirement. Here is the full journey.

1

Design the API

Choose a group name you own, such as a domain you control, plus resource names, versions and the verbs you will support.

2

Build the server

Teams commonly use the official Kubernetes libraries, which provide the generic API server machinery, or a scaffolding tool built on them. The result is packaged as a container image.

3

Prepare identity and certificates

Create a namespace, a service account, the two delegation bindings from chapter 6, and a serving certificate whose names match the Service.

4

Deploy Pods and the Service

Run at least two replicas and expose them through a Service. Add readiness checks so only healthy Pods receive traffic.

5

Register the APIService

Create one APIService per group and version, with the CA bundle. The moment it is created, the aggregator starts checking it.

6

Verify

Confirm the entry shows as available, then request the group path through the main server and confirm a normal answer arrives.

7

Operate and upgrade

Roll out new versions with rolling updates. Add new API versions side by side, and keep serving the older ones until clients move.

8

Retire

Announce deprecation, stop serving old versions, delete the APIService, then remove the Pods, certificates and stored data in that order.

The order matters when retiring

Deleting the Pods while the APIService still exists leaves a broken entry that makes the whole cluster noisy. Removing the APIService first makes the group disappear cleanly. Only then is it safe to tear down the workload behind it.

Versioning the API itself

Like every Kubernetes API, an aggregated API passes through maturity stages. An alpha version may change or vanish. A beta version is more careful about changes. A stable version is a promise. Serving two versions at once requires the extension server to convert between them, and that conversion code is entirely your responsibility, which is a real part of the cost we discuss in chapter 10.

9Where the Data Lives: Storage Choices

The aggregation layer forwards requests. It does not store anything for you. Your extension server decides where its data lives.

This freedom is one of the biggest reasons people choose an aggregated API. A native Kubernetes resource is always a record in the cluster’s main database, called etcd. An extension server can choose differently depending on what the data is like.

Option A

Pure memory

Fast and simple. Used when data is rebuilt continuously from live sources. Metrics-server works this way, holding recent measurements gathered from every node.

Option B

Its own etcd

A separate small etcd cluster owned by the extension. The main cluster database is protected from extra load, at the price of one more system to back up and monitor.

Option C

An external database

A relational or document database outside Kubernetes. Useful when the data already lives there or must be queried in ways etcd handles badly.

Option D

A translation layer

The extension stores nothing and converts each request into calls to another system, such as a cloud provider or a monitoring product, and shapes the reply as Kubernetes objects.

Everyday analogy

Picture four kinds of notebooks. A whiteboard in the kitchen that gets wiped every night (memory). A personal diary kept in a drawer (own etcd). A public library where the books are held by someone else (external database). And a translator who speaks to a foreign office on your behalf and keeps no papers at all (translation layer). Each fits a different need.

Why not just use the main etcd?

Giving an extra server direct access to the main cluster database widens the blast radius. A bug or a flood of writes from that server could slow down every other resource in the cluster, including the Pods and Nodes that keep the business running. Most teams therefore keep the extension’s data separate, or choose a design with no persistent data at all. Teams who only need durable custom objects often step back and use a CRD, which is stored and protected by the main server itself.

i
Design hint

Ask one question first: “If this server lost all its data tonight, could it rebuild everything from somewhere else?” If yes, memory or a translation layer is perfect. If no, plan backups and restore drills before launch.

Consistency and watches

Kubernetes clients expect each object to carry a version marker that always moves forward, so that a watch can resume from “where it left off”. If your storage cannot provide ordered change numbers, supporting watches correctly becomes difficult. Extensions backed by their own etcd get this ordering almost for free. Extensions that translate from other systems must create an ordering themselves or declare that watches are unsupported.

10Aggregation Layer versus CRDs: Advantages, Disadvantages and Trade-offs

The most valuable skill here is knowing when not to use the aggregation layer.

A CustomResourceDefinition lets you teach Kubernetes a new resource type by simply describing its shape. You write the description, apply it, and the built-in API extensions server immediately serves a full REST API for it, storing objects in etcd. No new server to run. For the large majority of needs, such as “an Application object” or “a Backup request”, this is the right answer.

QuestionCRDAggregated API
Extra server to run?NoYes, with its own Pods, certificates and upgrades
Where is data stored?Cluster etcdAnywhere you choose, or nowhere
ValidationSchema in the definition and rule expressions evaluated by the serverAny code you write
Custom actions beyond standard verbsOnly status and scale subresourcesAny subresource, including streaming ones
Multiple versionsSupported through a conversion webhookConversion code inside your server
Who can installAnyone allowed to create CRDsNeeds rights to deploy servers and create APIService objects
Operational effortLowHigh
Fits computed or live dataPoorlyVery well

Advantages of aggregation

  • Total freedom over storage, behaviour and protocols.
  • Can serve data that is computed live, so it never goes stale in a database.
  • Independent release schedule, separate from the cluster.
  • Reuses the cluster’s login and permission system.
  • Works with every existing Kubernetes client without changes.

Disadvantages of aggregation

  • You must build, secure, scale and patch a full API server.
  • Extra network hop adds delay to every call.
  • A broken server can disturb cluster-wide tasks that list all groups.
  • Certificate handling and rotation add ongoing chores.
  • Fewer engineers on a team know how to debug it.

A simple decision path

  • Do you just need new kinds of objects that controllers will manage? Choose a CRD.
  • Do you need data that is computed, streamed or lives in another system, with the API looking native? Consider aggregation.
  • Do you need custom subresources like a live log stream or an interactive session? Aggregation fits.
  • Do you need strict protection of the main database from your own load? Aggregation with separate storage helps.
  • Is your team small and busy? Prefer the CRD and revisit later only if a real limit appears.
Choose the lightest tool that solves the problem, and reach for the heavier one only when a real limit forces you.

11Real-World Case Study: The Metrics Pipeline

The clearest production use of aggregation is the path that feeds autoscaling.

flowchart LR
  K["Kubelet on every node"] -->|"resource usage"| MS["metrics-server"]
  MS --> API1["APIService metrics.k8s.io"]
  API1 --> AGG["Aggregator in kube-apiserver"]
  AGG --> HPA["Horizontal Pod Autoscaler controller"]
  AGG --> TOP["kubectl top"]
  HPA -->|"change replica count"| DEP["Deployment"]
  ADP["Metrics adapter or vendor agent"] --> API2["APIService custom or external metrics"]
  API2 --> AGG
    
Figure 3. How aggregated APIs feed the autoscaler

Walking through the flow

Every node runs a program called the kubelet. It already measures CPU and memory for each container. The metrics-server collects these numbers from all nodes at a regular interval, which defaults to about fifteen seconds, and keeps only the latest values in memory. It then registers an APIService for metrics.k8s.io. When the Horizontal Pod Autoscaler wants to decide “do we need more replicas?”, it asks the main API server, which proxies to metrics-server. The answer comes back, the controller does a small calculation, and the Deployment is scaled. The autoscaler repeats this loop about every fifteen seconds.

Three metric families, three API groups

Autoscaling uses three standard aggregated groups. metrics.k8s.io gives basic CPU and memory. custom.metrics.k8s.io gives per-object numbers such as requests per second for one Service. external.metrics.k8s.io gives numbers from outside the cluster, such as the length of a message queue. Each is registered by a different provider, and the autoscaler treats them all as ordinary parts of the API.

Who uses this in production

Managed cloud platforms

Google Kubernetes Engine and Azure Kubernetes Service ship metrics-server ready to use, which is why autoscaling by CPU works on a fresh cluster there. On Amazon EKS, administrators typically install it themselves, then rely on the same aggregated API.

Datadog

The Datadog Cluster Agent can act as an external metrics provider. It registers the external metrics group, so a Deployment can scale based on a number held in Datadog, for example the depth of a queue, with no change to Kubernetes itself.

Red Hat OpenShift

OpenShift adds platform concepts such as projects, routes and image streams through its own API servers that sit behind the same front door. Users keep one login and one command line while the platform grows its own vocabulary.

Event-driven scaling projects

KEDA, a CNCF project, registers an external metrics API so workloads can scale on events like queue length or stream lag, including scaling down to zero. The Prometheus Adapter does a similar job for numbers stored in Prometheus.

i
Pattern to notice

In every example the data is live or lives elsewhere, and users want it to look like part of Kubernetes. That combination is the natural home for the aggregation layer.

12Performance, Scalability, High Availability and Reliability

Performance: the cost of one extra hop

Every proxied request crosses the network twice more than a native one: main server to extension server, and the review call back. For occasional calls this is invisible. For a controller that polls constantly, the delay adds up. Keep responses small, support filtering by labels and fields where it makes sense, and avoid design where clients must list everything to find one item.

Your extension server should also use the same caching tricks the main server uses. A shared in-memory cache fed by a watch on the underlying data means a read never has to touch the slow store.

Scalability: protect both servers

Limit

Request fairness

The main server shares its capacity among callers using a fairness system. Heavy callers of your API still count against that capacity, so noisy clients can be queued rather than allowed to starve others.

Limit

Your own limits

Extension servers built on the standard libraries bring similar protections. Set sensible concurrent request limits and request timeouts so one slow caller cannot exhaust your Pods.

Limit

Watch counts

Each open watch holds memory and a connection. Plan capacity for thousands of watchers if many controllers will subscribe.

Limit

Payload size

Huge objects slow every hop and every cache. Split large data into several objects or serve it as a subresource.

High availability: never run just one copy

A single Pod means any restart, node drain or crash turns the APIService unavailable. The standard recipe is simple and effective:

  • Run at least two or three replicas, spread across failure zones using anti-affinity or topology spread rules.
  • Add a PodDisruptionBudget so planned maintenance never removes all replicas at once.
  • Use readiness checks so a starting Pod receives no traffic until it is truly ready.
  • Set CPU and memory requests so the scheduler places Pods sensibly and the autoscaler for the extension itself has data.
  • Make the storage layer highly available too, otherwise the front tier is redundant but the back tier is a single point of failure.

Failure scenarios and recovery

What failsWhat users seeHow to recover
All extension Pods downThat group returns service unavailable, other groups unaffectedRestore Pods, check rollout, confirm Endpoints are populated
Serving certificate expiredAvailability flips to failed discovery checkReissue the certificate, restart Pods, update the CA bundle if the authority changed
Network policy blocks control planePods look healthy but the entry is unavailableAllow traffic from the control plane to the extension port
Storage layer downReads and writes error even though the server is upRestore storage from backup or fail over
Slow extension serverTimeouts and tools hanging at discoveryScale out, find slow queries, lower timeouts to fail fast
Delegation bindings removedAuthorization errors for every callRestore the two built-in role bindings
!
Operational reality

Because some cluster tasks walk every API group, a long-dead extension that nobody uses anymore can still cause stuck namespace deletions. Remove stale APIService objects as part of normal housekeeping.

13Security Deep Dive

An extension server sits inside the trust boundary of the cluster, so it deserves the same care as the control plane.

Everyday analogy

Adding an extension server is like hiring a contractor who works inside the secure area of a building. The badge you hand over, the rooms they can enter and the log of their visits all decide whether that decision is safe or dangerous.

Threats worth naming

Threat

Impersonation of the extension

An attacker stands up a fake server and tries to receive proxied traffic. Defended by the CA bundle and a correct service name in the certificate.

Threat

Forged identity headers

An attacker connects directly to the extension Pod and claims to be an administrator. Defended by requiring the proxy client certificate and an allowed name list.

Threat

Traffic redirection

Someone edits an APIService so a group points to a server they control. Defended by tight rights on APIService objects.

Threat

Over-powerful service account

The extension runs with broad cluster rights and a bug lets an attacker use them. Defended by least privilege.

The protection checklist

  • Treat APIService write access like cluster-admin. Redirecting a group sends all of its traffic, including user identities, to wherever the entry points.
  • Always validate certificates. Provide a real CA bundle and never enable the skip-verification switch outside a lab.
  • Restrict the network. Allow only the control plane and required clients to reach the extension port. Block everything else with network policies.
  • Grant minimum rights. The service account needs the two delegation bindings plus whatever the logic truly requires, and nothing more.
  • Plan certificate rotation. Short-lived certificates reduce damage from leaks, but only if renewal is automated and monitored.
  • Harden the workload. Run as a non-root user, use a read-only filesystem where possible, pin and scan container images, and keep them patched.
  • Protect stored data. Encrypt data at rest and in transit, and restrict who can reach the storage layer.
  • Audit. The main server records which user called which path, including aggregated groups. Also keep the extension server’s own logs, because it sees details the main server does not.
!
Never do this

Do not expose the extension server directly to the internet or to a broad internal network “just for testing”. Its trust model assumes that only the control plane delivers identity headers.

14Monitoring, Logging, Metrics and Troubleshooting

What to watch

SignalWhere to find itWhat it tells you
APIService Available conditionThe APIService objects themselvesWhether the aggregator can reach your server right now
Unavailable APIService gauge and counterThe main server’s metrics endpointHow often and for how long entries flip to unavailable
Request count and duration by groupThe main server’s request metrics, labelled by group and resourceTraffic volume, error rate and slowness per API
Extension server request metricsThe extension server’s own metrics endpointWhere time is spent inside your code
Certificate expiry datesYour certificate toolingEarly warning before a silent outage
Pod restarts and readinessCluster monitoringCrash loops and slow starts

Alerts that earn their keep

  • Any APIService unavailable for longer than a few minutes.
  • Error ratio above a threshold for requests to your group.
  • Ninety-ninth percentile latency rising above an agreed limit.
  • Certificates within thirty days of expiry.
  • Fewer ready replicas than the minimum you promised.

Troubleshooting path

flowchart TD
  A["API call fails or tool shows warnings"] --> B{"Is the APIService Available?"}
  B -->|"No"| C["Read the reason and message on the APIService"]
  C --> D["Check Service name, port and Endpoints"]
  D --> E["Check Pod readiness and logs"]
  E --> F["Check certificates and CA bundle"]
  F --> G["Check network policy and aggregator routing"]
  B -->|"Yes"| H["Check delegation role bindings"]
  H --> I["Check latency and timeouts"]
  I --> J["Check storage layer health"]
    
Figure 4. A calm path from symptom to cause

Reading common symptoms

“The server is currently unable to handle the request”

The aggregator reached no healthy backend. Start with the APIService condition, then the Endpoints, then Pod readiness.

Tools warn about being unable to retrieve the complete list of server APIs

One group failed discovery. The tool still works for other groups. Find the named group and fix it, or delete the APIService if the extension is retired.

Namespace stuck in Terminating

A controller could not list resources in some group. Look for an unavailable APIService first, because it is a frequent hidden cause.

Forbidden errors for users who should have access

Check the user’s RoleBinding for the extension’s group, then check that the extension’s service account still has the delegation bindings.

Logging advice

Log each request with the user name, verb, resource and result, using a request identifier that you also pass along. During an incident, the ability to follow one request from the main server’s audit record into your own log lines saves hours. Keep log volume sensible so a busy API does not drown the storage that holds the logs.

15Design Patterns and Anti-patterns

Patterns that work

Pattern

Read-only view over live data

Expose measurements or status that change quickly. Nothing needs saving, and the API is simple and fast. Metrics-server is the textbook example.

Pattern

Façade over an external system

Give users a Kubernetes-style way to see or request things that really live in a cloud or product. The extension translates both directions.

Pattern

Subresource-rich API

Offer actions such as streaming output or interactive sessions on an object, which CRDs cannot do.

Pattern

Hybrid design

Use CRDs for durable intent objects and an aggregated API for computed or live views, so each tool does what it does best.

Pattern

Platform vocabulary

A platform team presents its own concepts, such as projects or environments, behind the shared front door, as larger platforms like OpenShift do.

Pattern

Side-by-side versions

Serve old and new API versions together during migrations so clients can move at their own pace.

Anti-patterns to avoid

ANTI-PATTERN 01 Avoid
What it looks like

A team builds a full extension server just to store a few configuration objects.

Why it hurts

They now own certificates, upgrades, scaling and backups for something a CRD would give them for free.

Better approach

Use a CRD, and add an extension server only when a real limitation appears.

ANTI-PATTERN 02 Avoid
What it looks like

One replica, no disruption budget, no readiness checks, and certificates created by hand.

Why it hurts

Every node drain or certificate expiry becomes a cluster-wide warning and sometimes a stuck deletion.

Better approach

Run several replicas across zones, automate certificates, and alert on availability.

ANTI-PATTERN 03 Avoid
What it looks like

Turning on skip-verification to make a failing setup “work” and leaving it in place.

Why it hurts

The cluster can no longer prove it is talking to the right server, and the real fault stays hidden.

Better approach

Fix the CA bundle and service name in the certificate, then verify properly.

ANTI-PATTERN 04 Avoid
What it looks like

Pointing the extension’s heavy write traffic at the main cluster database.

Why it hurts

A burst of writes slows the whole control plane and threatens unrelated workloads.

Better approach

Give the extension separate storage with its own limits and backups.

16Best Practices and Common Mistakes

Best practices in one place

1

Start from the simplest option

Confirm that a CRD truly cannot do the job before committing to run a server.

2

Follow API conventions

Use the standard verbs, status codes, list and watch behaviour and object metadata so every existing tool works unchanged.

3

Build for failure

Multiple replicas, zone spread, disruption budgets, readiness checks and fast, honest timeouts.

4

Automate certificates

Issue and renew serving certificates through a controller, and alert well before expiry.

5

Keep rights minimal

Separate service accounts, only needed permissions, tight control of who can edit APIService objects.

6

Version carefully

Introduce new versions beside old ones, document deprecations and give clients time.

7

Rehearse failure

Practise killing Pods, expiring certificates in a test cluster, and restoring from backup before production teaches the lesson the hard way.

Mistakes seen again and again

  • Forgetting the delegation bindings. Pods are healthy, yet every request fails to authorize.
  • Certificate names that do not match the Service. The aggregator rejects the server, and the only hint is a failed discovery check.
  • Blocking the control plane with network rules. Everything works from inside the cluster but not through the aggregator.
  • Leaving dead APIService objects behind. Namespace deletion and discovery pay the price for years.
  • Assuming the main server runs admission rules for the extension. It does not, so invalid data can slip in unless the extension validates.
  • Ignoring the extra latency. A design that needs many round trips per user action feels slow even if each call is fast.
  • Skipping backups because “it is just an API”. If the data is not rebuildable, it must be backed up.

17Advanced Topic: Inside the Aggregator

Now that the big picture is clear, let us open the hood and look at the small controllers that keep the routing table honest.

The aggregator is not a single piece of code that just forwards traffic. It is a group of cooperating loops, each with one narrow job. Understanding them explains almost every strange behaviour you will meet in production.

The routing table and its handler

When an APIService is created, changed or deleted, a controller notices and updates an in-memory table that maps each group and version to a handler. For a remote entry the handler is a reverse proxy. For a local entry the handler simply passes the request along the delegation chain. The table lives in memory, which is why lookups cost almost nothing: matching a URL path to a handler takes a tiny fraction of a millisecond, far smaller than the network time that follows.

Because this table is in the memory of each kube-apiserver process, every replica of the control plane keeps its own copy. They all build the same picture from the same stored APIService objects, so after a change there is a very short window where one replica already knows about a new entry and another does not. Clients that hit the slower replica receive a “not found” answer for a moment, and a retry succeeds. This is normal and usually lasts only a second or two.

The availability controller

This loop is the health inspector. For every remote APIService it performs a lightweight call to the extension server and records the outcome on the object as the Available condition. It also reacts to changes in the Service and Endpoints objects, so if the last ready Pod disappears the entry turns unavailable quickly rather than waiting for the next scheduled check. When the entry flips to unavailable, the proxy handler stops sending real user traffic there and returns an error right away. This protects the extension server from a pile-up of requests while it is recovering, and it protects users from long waits.

The discovery and OpenAPI loops

Besides routing, the aggregator maintains two published documents that clients read.

Document

Discovery data

The list of groups, versions and resource names. The aggregator merges what each extension server reports into one combined answer and refreshes it when entries change.

Document

OpenAPI schema

A machine-readable description of every field of every resource. The aggregator collects a schema from each extension server and merges them so tools see one complete description of the cluster.

The merged schema is what powers features such as client-side validation, the explain command and code generators. If an extension server publishes a broken or enormous schema, the merge can become slow or even fail for that one group. Keep your published schema accurate and reasonably small, and test that it merges cleanly before shipping.

Automatic entries for CRD groups

Here is a subtle fact that surprises many people. When you create a CRD, the cluster also creates an APIService entry for its group, automatically. These entries carry a special label that marks them as managed by the system, and they are local entries because the API extensions server in the same process answers them. If you edit or delete one of these managed entries by hand, the controller that owns them will usually restore it. Do not fight it. Manage the CRD and the entry follows.

i
Tip

When you list APIService objects and see many entries with no service reference, those are the local and automatically managed ones. Only entries with a service reference involve a network hop to another program.

How the proxy handles connections

The proxy opens secure connections to the extension server and reuses them for many requests, which saves the cost of repeating the handshake each time. For watch requests it keeps the connection open and streams events as they arrive. It also carries upgrade-style connections, which are needed for features like interactive sessions into containers when an extension offers them. When the extension server restarts, these long connections break, and clients are expected to reconnect and resume from the last version they saw. If your extension cannot resume cleanly, clients are forced to fetch the full list again, which is costly in a big cluster. Plan resumption support from the start.

Everyday analogy

Think of the aggregator as an airport control tower. It keeps a live board of which runways are open, sends planes to the right runway, closes a runway the moment it becomes unsafe, and keeps the public timetable up to date. The pilots never need to know how the tower works, but a calm and accurate tower is why flights land on time.

18Advanced Topic: Inside an Extension API Server

An extension server is a real Kubernetes-style API server. It must keep the same promises as the built-in one, or the tools around it will behave strangely.

Kubernetes clients make a number of quiet assumptions about how a server behaves. A good extension honours all of them. The following ideas are the ones that matter most.

Resource version: the ever-moving bookmark

Every object carries a resource version, an opaque marker that changes whenever the object changes. Clients treat it as a bookmark. A watch can say “tell me everything that happened after this bookmark”. An update can say “change this object only if it still matches this bookmark”. The second use is called optimistic concurrency. It prevents two people from silently overwriting each other, because the later writer is told “your copy is stale, fetch it again”. An extension that ignores this rule will lose data under concurrent writes.

List, watch and consistency

Controllers usually begin with a full list and then switch to a watch starting from the list’s bookmark. To make this reliable the server must return a consistent snapshot and then deliver every change after it, in order and without gaps. If the server can no longer remember changes from an old bookmark, it must say so clearly so the client lists again. This is the foundation of how controllers stay correct for months without drifting.

Feature

Pagination

Large lists are returned in chunks with a continue marker, so a client never needs to hold a million objects at once.

Feature

Label and field selection

Clients ask for only the objects they care about. Without it, every controller must download everything.

Feature

Server-side apply

Several tools can own different fields of one object. The server tracks who set what and reports conflicts.

Feature

Table output

The server can describe the columns to show in a list, so command line output looks neat and useful.

Subresources

A subresource is an extra address hanging off an object, such as its status or an action. The standard ones are status and scale. An aggregated API can invent others, for example a path that streams a live view of something, or a path that runs an operation on the object. Because each subresource has its own address, it can have its own permission rule. A person may be allowed to read an object but not trigger its action.

Defaulting, validation and admission

When a request creates or changes an object, a well-built server runs it through a pipeline. First it fills in missing fields with defaults. Then it checks the values are valid. Then it runs admission steps, which can approve, reject or adjust the request. Extension servers built on the official libraries include a place to plug these steps in and can call external webhooks too. Remember that the main server’s own admission rules do not cover your group, so any policy that must apply to your objects has to be wired into your server.

Finalizers and owner references

Kubernetes cleans up related objects through two small fields. A finalizer is a note saying “do not fully delete this until I have finished some cleanup”. An owner reference is a note saying “this object belongs to that object, so remove me when it goes away”. The garbage collector reads these notes across all groups, including yours. If your objects use them correctly, deletion behaves like it does for native resources. If they are wrong, cleanup can hang forever.

Conversion between versions

If you serve two versions, a client can ask for either one, and the server must translate in both directions without losing information. A common technique is to keep one internal form and convert to and from each public version. Round-trip tests that convert a value there and back and compare the result are cheap and catch most mistakes.

!
Common trap

Adding a field in a newer version without a place to keep it in the older version silently drops the value when a client uses the old version. Always decide where every new field lives in every served version.

Why the official libraries matter

The Kubernetes project provides a generic API server library that already implements the behaviours above: authentication and authorization delegation, request handling, watch support, schema publishing, fairness controls and health endpoints. Building on it means you spend your effort on your own resources and rules rather than reinventing the plumbing. Teams that write an API server from scratch usually discover, one outage at a time, which of the quiet assumptions they forgot.

19Networking, Certificates and Cloud-Specific Details

Most real incidents with aggregated APIs are plumbing problems, not logic problems. This chapter is about the plumbing.

Names and addresses

Inside a cluster, every Service gets a DNS name of the form service-name dot namespace dot svc. The serving certificate of your extension must be valid for exactly that name, because the aggregator checks the name it is connecting to against the certificate. A certificate for a different name, or one that only lists the short name, will fail the check. A neat habit is to include the short name, the namespace-qualified name and the full name in the certificate so that every route works.

The control plane may live in another network

On managed services the control plane often runs in a network that you do not control. Traffic from that network to your Pods must be explicitly permitted.

SituationWhat goes wrongWhat to check
Private cluster on a public cloudThe control plane cannot reach the extension port on the nodesFirewall or security group rules that allow the control plane range to the Pod or node port in use
Strict network policiesPods are reachable from inside the namespace but not from the control planeA rule that allows ingress from the control plane source
Hand-built control plane without kube-proxyThe Service address is not routable from the control planeThe aggregator routing setting that connects to endpoints directly
Service mesh with strict mutual TLSThe mesh sidecar rejects the control plane’s connectionExcluding the extension port from sidecar interception, or allowing plain connections from the control plane
Overlapping network rangesTraffic is dropped or misroutedAddress plan of cluster, control plane and peered networks

The port question

An APIService names a port on the Service. If you leave it out, the standard secure port is assumed. The extension must listen with TLS on the matching target port. A frequent beginner error is to start the extension on a plain, non-secure port because it seemed simpler. The aggregator always speaks TLS to remote entries, so the connection fails and the symptom is the same vague discovery failure.

Certificates: three different jobs

There are three kinds of certificate in this story, and confusing them is the number one source of mistakes.

  • The proxy client certificate belongs to the main server and proves its identity to the extension. It is managed by whoever manages the control plane.
  • The serving certificate belongs to the extension and proves its identity to the main server. You manage it, and its authority goes into the CA bundle on the APIService.
  • The client authentication authority is what the extension uses to recognise ordinary client certificates, if it accepts them directly. It comes from the published ConfigMap.
Everyday analogy

Think of a government building with two doors. The guard on the main door shows a badge to the inner office so the office knows the visitor came through the proper guard. The inner office shows its own badge to the guard so the guard knows he is sending people to the real office. Two badges, two directions, and different issuers. Mixing them up leaves one door locked.

Rotation without downtime

When the authority behind the serving certificate changes, the CA bundle on the APIService must be updated, and during the switch both the old and the new authority should be trusted for a time. A neat pattern is to publish a bundle containing both, roll out the new serving certificates, and only later remove the old authority. Tools such as certificate management controllers can inject the bundle into the APIService automatically, which removes a manual step that people forget at three in the morning.

i
Tip

Put certificate expiry on a dashboard that your whole team can see. A year-old certificate that nobody remembers is the classic cause of “it worked yesterday” outages.

20Aggregation Layer versus Other Extension Tools

The word “aggregation” appears in many places in software. Let us make sure it is not confused with its neighbours.

Not an API gateway

In the wider world, an API gateway is a front door for many services that handles login, rate limiting and routing for web traffic. Some gateways also “aggregate” by combining several backend answers into one reply. The Kubernetes aggregation layer is related only in spirit. It does not merge answers from several backends into one response. It routes each request to exactly one owner of that group. Its purpose is to extend one specific API, the Kubernetes API, and not to front a company’s web services.

Compare the extension tools

ToolWhat it doesBest used for
CustomResourceDefinitionAdds a new stored object type served by the built-in machineryDeclaring desired state that controllers act on
Aggregated APIAdds a whole separate API server under the main front doorLive data, custom storage, special subresources
Operator or controllerA program that watches objects and makes the world match themAutomation of running applications
Admission webhookReviews or changes requests before they are savedPolicy and defaults
API gatewayFront door for application trafficPublic and internal service APIs
Service meshAdds security and routing between application PodsService-to-service traffic control

How they work together

These tools are partners, not rivals. A very common production design looks like this: a CRD describes what the user wants, an operator watches those objects and builds the real thing, an admission webhook enforces company policy on them, and an aggregated API exposes live status and metrics about the result. Each tool does the job it is best at. The user sees a single, tidy Kubernetes experience.

A worked combination

Imagine a platform that lets teams request managed databases. A CRD called Database holds the request. An operator creates the actual instances in the cloud. A webhook blocks requests for sizes the company does not allow. An aggregated API named for live usage lets the team run one command to see current connections and storage for each database, computed on demand from the cloud provider rather than stored in the cluster.

What the aggregation layer is not for

  • It is not a place to run heavy business logic that has nothing to do with Kubernetes resources.
  • It is not a substitute for a message queue or a general database.
  • It is not needed just to react to cluster events. A controller watching existing resources does that without any new API.
  • It is not a way to bypass permission rules. Everything still passes through the same login and role checks.

21Upgrades, Version Skew and Deprecation

An extension lives alongside a cluster that keeps moving. Planning for change is part of the design, not an afterthought.

Two things that change at once

Your extension has its own releases, and the cluster has its own too. Kubernetes publishes new minor releases several times a year, and managed services upgrade the control plane on a schedule you may not fully control. Both sides must keep speaking the same language during the upgrade, when some control plane replicas run the old version and some run the new one.

Compatibility rules of thumb

Rule

Stay near the cluster version

Build against libraries close to the version of the cluster you run. Large gaps increase the chance of surprising behaviour in review calls and discovery.

Rule

Test on the next version early

Run your extension in a test cluster on the upcoming release before your production control plane upgrades.

Rule

Never remove a version abruptly

Announce, warn, serve both, then retire. Users with automation need months, not days.

Rule

Roll out gradually

Use rolling updates with readiness checks so there is always a healthy replica serving traffic.

A safe upgrade sequence for your extension

1

Read the cluster release notes

Look for changes to API behaviour, removed features and anything touching authentication, discovery or the review calls.

2

Upgrade a non-production cluster first

Confirm the APIService stays available and that your key workflows still pass.

3

Upgrade the extension if needed

Release a version built on newer libraries, again with a rolling update.

4

Upgrade production in stages

Watch the availability signal and error rate during the control plane change, and hold if they move.

5

Confirm and clean up

Verify discovery lists every expected version, then remove any temporary workarounds.

Deprecating your own API

Treat your API like a product. Mark old versions as deprecated in the documentation and, where the libraries allow, return warnings to clients that still use them. Track which clients call the old version using request metrics labelled by version. Only when that number reaches zero, or an agreed small figure, is it safe to stop serving it. Deleting an old version while an unknown tool still calls it creates the kind of outage that is hard to diagnose, because the failing tool might be a forgotten cron job.

i
Good to know

Stored data also has a version. If your storage keeps objects in an older form, plan a migration that rewrites them to the newest stored form before the old form is dropped.

22End-to-End Scenarios

Three short stories show how the ideas fit together when real things go right and wrong. The teams in these stories are illustrations, not specific companies.

Scenario one: the autoscaler that stopped scaling

A retail platform notices at peak hour that its web Pods are not increasing even though traffic doubled. The on-call engineer checks the autoscaler and sees a message saying that the metrics for the Pods are unavailable. Rather than guess, the engineer lists the APIService objects. The entry for resource metrics shows not available, with the reason that no endpoints exist. A quick look at the Pods shows that the metrics-server Pod was evicted from a node under memory pressure and the replacement was stuck waiting for a place to run because only one replica existed and its resource request was too large for the remaining space.

The fix was immediate: free space so the Pod could schedule. The lasting improvements were more valuable. The team ran two replicas spread across zones, added a disruption budget, lowered the resource request to a realistic figure, and created an alert on the unavailable APIService signal so that the next occurrence pages a human within minutes rather than being discovered by customers.

Lesson

A single replica turned a small node problem into a failure of the whole autoscaling loop. Redundancy at the extension layer is cheap compared with the cost of a stalled autoscaler at peak traffic.

Scenario two: the namespace that would not die

A platform team deletes a test namespace and it stays in the terminating state for hours. Everything inside seems gone. The team reads the namespace conditions and finds a message that discovery failed for one API group. That group belongs to an experimental extension installed months earlier by a team that has since left. Its Pods were removed during a cleanup, but the APIService entry was left behind and now points to a Service that no longer exists. The namespace controller must ask every group whether anything remains, and one group never answers.

Deleting the stale APIService made the namespace finish terminating within seconds. Afterward, the team wrote a short housekeeping rule: whenever an extension is retired, its APIService must be removed first, and a weekly check lists any entries that are not available and have been so for more than a day.

Lesson

An unused but broken registration still has a cost. The retirement order from chapter 8 exists to prevent exactly this story.

Scenario three: the certificate that quietly expired

A company runs an extension that exposes live cost data per team. One Monday morning every cost command fails, while the rest of the cluster is perfectly healthy. The APIService shows failed discovery check. The Pods are running and ready. The engineer inspects the serving certificate and finds it expired over the weekend. It had been created manually a year earlier with a one-year life, and the person who made it had changed teams.

Reissuing the certificate and restarting the Pods fixed the outage. To prevent a repeat, the team moved to an automated certificate controller with short lifetimes, configured it to update the CA bundle on the APIService itself, and added a dashboard and a warning alert for any certificate within thirty days of expiry.

Lesson

Manual certificates are time bombs with a long fuse. Automation plus monitoring turns a surprise into a routine renewal.

Most outages in this area are not clever bugs. They are forgotten chores.

23Testing, Ownership and the Operations Runbook

Running an extension is a long-term commitment. A little structure now saves many bad nights later.

What to test before launch

TestWhat it provesWhen to run it
Functional tests of every verbCreate, read, update, delete, list and watch behave like native resourcesOn every change
Permission testsUsers with the right role succeed and users without it are refusedOn every change to roles or code
Concurrency testsTwo writers cannot silently overwrite one anotherBefore launch and after storage changes
Conversion round tripsNo field is lost when moving between versionsOn every API change
Chaos testsThe system survives Pod kills, node drains and network cutsRegularly in a test cluster
Load testsLatency and error rate stay healthy with expected numbers of clients and watchersBefore launch and before big growth
Upgrade testsThe extension survives control plane upgradesBefore each cluster upgrade

Who owns it?

Every extension needs a named owning team. That team answers alerts, renews certificates, reviews who has access, upgrades libraries and retires the API when its time comes. When ownership is vague, the extension becomes the thing everyone depends on and nobody maintains. Write the owner, the escalation path and the documentation link into the description of the APIService or a nearby document so a stranger can find them at night.

A compact runbook

1

Confirm the symptom

Reproduce the failing request and note the exact error message and group name.

2

Read the APIService condition

The reason and message usually point straight at the cause: missing Service, no endpoints or failed discovery.

3

Check the workload

Look at readiness, restarts, recent deployments and resource pressure on the nodes hosting the Pods.

4

Check trust

Verify the serving certificate dates and names, the CA bundle, and the two delegation bindings.

5

Check the network

Confirm the control plane can reach the port, considering firewalls, policies and meshes.

6

Mitigate first, then investigate

Add replicas, roll back the last release or renew the certificate to restore service, then study the root cause calmly.

7

Write it down

Record the cause, the fix and one preventive change. A short written note is the cheapest insurance against a repeat.

Capacity planning in plain terms

Start with three numbers: how many requests per second you expect at peak, how many watchers will stay connected, and how big the typical object is. Multiply requests by the time each takes to find how many requests are in flight at once, then give each replica comfortable headroom, such as running at no more than half of its tested limit during normal peaks. Add replicas for failure tolerance on top of that, so that losing one zone still leaves enough capacity. Revisit the numbers each quarter, because a successful API tends to attract more callers than anyone predicted.

i
Tip

Keep a short list of the three checks you will do first in any incident: the APIService condition, the Pod readiness, and the certificate dates. Those three explain a large majority of real problems.

24Governance, Cost and Multi-Team Platform Concerns

Technology is only half the story. In a company with many teams, rules and costs decide whether an extension helps or hurts.

Who is allowed to add an API?

Because a new APIService can change what the whole cluster appears to offer, mature organisations treat it as a controlled change. A typical approach is a short review that asks the same questions every time. What problem does this solve that a CRD cannot? Who owns it? How is it secured? How will it be monitored and upgraded? What is the plan if the owning team disappears? Teams that answer these questions in writing before launch rarely face the orphaned extension problem later.

Naming and group hygiene

Choose group names from a domain your organisation controls, so there is never a clash with a project or vendor. Keep a simple catalogue of all groups in use, with the owner next to each. When two teams want similar names, the catalogue settles the question in minutes instead of weeks. Avoid names that end in the reserved suffixes used by the Kubernetes project itself, because those are managed by the community.

The true cost of an extension

Cost

Compute and storage

Replicas across zones, memory for caches and storage for data. Usually modest, but not zero.

Cost

People time

Upgrades, certificate care, incident response and support for users. This is normally the largest cost by far.

Cost

Risk

Another component inside the trust boundary of the control plane. Each one must be reviewed and kept patched.

Cost

Cognitive load

New team members must learn one more moving part. Good documentation lowers this cost.

Shared clusters and fairness

In a cluster shared by many teams, one team’s busy extension can affect everyone through the shared front door. Set fair limits on how much traffic any one caller may send, watch the request metrics by group, and talk to heavy users early. If a team truly needs heavy and unpredictable traffic, a separate cluster for that workload is often cleaner than trying to protect everyone else with tuning.

Documentation people actually read

Publish a one-page guide for every extension: what it is for, example requests, the permissions needed, the owning team, known limits and how to ask for help. Keep an example for each resource and subresource. A user who can copy a working example in under a minute will use your API well. A user who has to read the source code will send you questions at midnight.

i
Tip

Review the list of APIService objects every quarter. For each remote entry ask: is it still used, is it still owned, and is it still healthy? Remove the ones that fail any of the three questions.

25Glossary of Terms

A quick reference to every important word in this guide, in plain language.

TermMeaning in plain words
APIA menu of requests that a program is allowed to make to another program
kube-apiserverThe front door of Kubernetes that receives every request about the cluster
Aggregation layerThe feature that lets extra API servers appear under the same front door
AggregatorThe code inside the front door that reads the directory and forwards requests
APIServiceA directory entry saying which server handles a given group and version
Extension API serverYour own server that provides additional resources through the front door
API groupA named collection of related resource types
VersionThe edition of an API group, such as alpha, beta or stable
ResourceA kind of object you can ask for, such as Pods or metrics
SubresourceAn extra address under an object for a special view or action
CRDA simple way to teach Kubernetes a new stored object type with no extra server
etcdThe consistent database where Kubernetes keeps its own records
DelegationPassing a request along a chain of servers until one can handle it
ProxyA go-between that forwards a request and returns the answer
TokenReviewA question to the main server: who owns this login token?
SubjectAccessReviewA question to the main server: may this person do this action?
RBACRole-based access control, the rule system that grants permissions to users and groups
CA bundleThe trusted authority certificates used to check that a server is genuine
TLSThe secure connection method that encrypts traffic and proves identity
DiscoveryHow clients learn which groups and resources a cluster offers
WatchA long open connection that streams changes as they happen
Resource versionA bookmark that changes whenever an object changes
FinalizerA note that delays deletion until cleanup is finished
Owner referenceA note saying an object belongs to another and should go with it
AdmissionThe review step that can approve, change or reject a request before it is saved
HPAThe Horizontal Pod Autoscaler, which changes replica counts based on metrics
PodDisruptionBudgetA rule limiting how many replicas may be taken down voluntarily at once
Readiness checkA test that tells Kubernetes when a Pod is ready to receive traffic
Network policyA rule that controls which network traffic may reach which Pods

26Frequently Asked Questions

Q1Is the aggregation layer enabled by default?

On clusters created with common installers and on managed cloud services, yes. The relevant settings for the proxy certificate and request headers are usually configured for you. On a hand-built cluster you must set them yourself.

Q2Do I need to restart the kube-apiserver when I add an APIService?

No. The aggregator watches APIService objects and updates its routing table on the fly. The first-time setup of the proxy certificate settings does need a restart, but adding or removing individual APIServices does not.

Q3Can an extension server run outside the cluster?

Yes, an APIService can point to an external address instead of a cluster Service. The same trust rules still apply, so certificates and network reachability from the control plane must be correct. Running inside the cluster is simpler for most teams.

Q4What happens to other APIs if my extension goes down?

Requests to your group fail with an unavailable error. Other groups keep working. The exception is the side effect on tasks that list every group, such as namespace cleanup, which is why a down extension should be fixed or removed promptly.

Q5Is the aggregation layer the same as an admission webhook?

No. A webhook is called by the main server to approve or modify requests for resources it already serves. An aggregated API is a whole separate API surface with its own resources.

Q6Can kubectl use my aggregated API without a plugin?

Yes. Because discovery tells kubectl which resources exist, commands such as get, describe and delete work immediately. Table-style output needs your server to return column information, otherwise the output is plain.

Q7Can one extension server serve several groups?

Yes. You create one APIService per group and version, and all of them can point to the same Service.

Q8Do aggregated resources appear in a cluster backup of etcd?

Only if your extension stores them in that etcd, which is generally discouraged. Data in separate storage must be backed up separately.

Q9How do I choose between a CRD and an aggregated API for a new project?

Start with a CRD. Move to aggregation only for needs a CRD cannot meet, such as live computed data, special storage or custom subresources.

Q10Who should be allowed to create APIService objects?

Only highly trusted cluster administrators or automation they control, because an APIService can redirect traffic for an entire group.

Q11Does an upgrade of Kubernetes affect my extension?

It can. Keep your libraries reasonably close to the cluster version, test against new releases, and read upgrade notes about API changes, since your server speaks to the main API for its review calls.

27Summary and Key Takeaways

What to remember

  • One front door, many back rooms. The aggregation layer lets separate API servers appear as part of Kubernetes through APIService entries that the aggregator uses to route requests.
  • A chain of servers. The kube-apiserver is an aggregator, a core server and a CRD server joined by delegation, with remote extension servers reached through a proxy.
  • Trust runs both ways. The extension trusts the main server through a proxy client certificate and request-header settings, and the main server trusts the extension through the CA bundle.
  • Identity is borrowed, not duplicated. Extensions delegate login and permission checks to the main server, so existing roles and bindings keep working.
  • Choose the lightest tool. A CRD is right for most new resources, and aggregation fits live data, custom storage and rich subresources.
  • Operate it like part of the control plane. Multiple replicas, automated certificates, tight rights, clear alerts and clean retirement keep one extension from disturbing the rest of the cluster.
  • Watch the hidden effects. An unavailable APIService can slow namespace deletion and discovery, so treat it as an incident and delete stale entries.