Aggregated APIs vs CRDs in Kubernetes

Aggregated APIs vs CRDs in Kubernetes

A plain-English guide to the two ways of teaching Kubernetes new tricks: how each one works inside, what each one costs, and how to choose the right door before you build.

Imagine a city hall. Citizens can file a handful of standard forms: a building permit, a business license, a marriage certificate. Over the years, new needs appear. Drone registration. Electric scooter parking permits. Rooftop garden approvals. The city can respond in two ways. It can design a new paper form and give it to the existing clerks, who store it in the same filing cabinet and follow the same stamping routine as every other form. Or it can open a brand-new office down the hall, with its own staff, its own filing system and its own rules, and simply tell the front desk, “send anything about drones to that office.”

Kubernetes faces the same choice. Its API understands pods, services and deployments out of the box. Platform teams constantly need new kinds of objects, such as a database, a certificate, a virtual machine or a pipeline. Kubernetes offers two official ways to add them. The first is the Custom Resource Definition, or CRD: a new form handled by the existing clerks. The second is the aggregated API: a new office with its own staff, plugged in behind the same front desk.

Both give you real Kubernetes objects that work with kubectl, permissions and watches. But they differ enormously in effort, power, risk and operating cost. This guide explains each one from the ground up, shows how they work internally, compares them fairly, and ends with a decision method you can use in a design review. No prior knowledge is assumed, and every new term is explained in plain words.

1Why Extend the Kubernetes API at All?

Before comparing the two doors, we need to understand why anyone wants to open a door. The answer lies in what Kubernetes really is.

Kubernetes is an API first

Most people meet Kubernetes as a tool that runs containers. But under the surface, Kubernetes is mainly a very well-designed API, a standard way for programs to ask for things and to learn what exists. Every action, from creating a pod to scaling a deployment, is just a request to this API. Every piece of cluster state is just an object stored behind it. Even the built-in components, such as the scheduler and the kubelet, are simply clients of the same API that you use.

This design has a powerful consequence. If you can teach the API a new kind of object, you gain a bundle of features for free: a consistent way to create, read, update and delete it, a way to watch for changes, permission control, audit logs, command-line tooling and an ecosystem of other tools that already know how to talk to the API. Instead of building a whole new management system, you extend the one that your teams already know.

Everyday Analogy

Think of Kubernetes as a smartphone operating system. It ships with built-in apps, but its real value is that you can add new apps that behave like first-class citizens. They use the same notifications, the same permissions screen and the same app switcher. Extending the API is like installing a new app that feels native.

The declarative idea

Kubernetes objects are declarative. You do not give step-by-step instructions. You write down what you want, such as “I want a database with three replicas”, and store it as an object. A program called a controller then watches the object and works to make reality match the description. This split between a stored wish and a tireless worker is the foundation of the operator pattern, and both CRDs and aggregated APIs exist to support it.

What teams build on top

Example

Databases as objects

A developer writes a short object that says “PostgreSQL, three replicas, daily backups”, and an operator creates the pods, storage and backup jobs.

Example

Certificates as objects

An object describes a certificate that is needed for a domain, and a controller requests it and renews it before it expires.

Example

Pipelines and workflows

A build or data pipeline is described as an object, and a controller runs its steps in order.

Example

Virtual machines and clusters

Even whole virtual machines or entire Kubernetes clusters can be requested by creating an object.

Two official extension mechanisms

The Kubernetes project documents exactly two ways to add new API types that look and feel native. These are the subject of this guide.

2
official ways to add new API types
CRD
simpler, built into the API server
Aggregation
more powerful, you run your own API server
i
Good to Know

The Kubernetes documentation advises choosing a CRD unless you need something that only an aggregated API can give. By the end of this guide, you will know exactly what those needs are.

2The Kubernetes API in Five Minutes

To understand how to extend the API, we need a clear picture of what the API looks like and how requests move through it.

Resources, groups, versions and kinds

Every object type in Kubernetes has a few names that identify it. Learning these words makes everything else easier.

  • A resource is a type of object that the API can serve, such as pods or deployments. It appears in web addresses in its plural form.
  • A kind is the singular, formal name of the type, such as Pod or Deployment. It is the word you write at the top of a manifest.
  • A group is a family that the resource belongs to, such as the apps group or the batch group. The oldest types live in a nameless “core” group. Groups prevent name clashes, so two projects can both define something called “Backup” without confusion.
  • A version marks the maturity and shape of the type, such as v1beta1 or v1. Types can have several versions at once, so that old clients keep working while new ones adopt the improved shape.

Together, group, version and resource form the web address of a type. A custom resource gets its own address in the same way as a built-in one, which is why tools treat them equally.

Verbs: what you can do

The API is organized around a small set of actions called verbs: get, list, watch, create, update, patch and delete. Permissions are expressed in these terms. A role might say “may list and watch databases, but not delete them”. Because every type uses the same verbs, one permission system covers everything.

The watch superpower

The watch verb is what makes Kubernetes feel alive. Instead of asking repeatedly “has anything changed?”, a client opens a long-lived connection and the server pushes every change as it happens. Controllers depend on this. An extension that cannot support watching efficiently is a poor citizen, so both extension mechanisms must provide it.

Where the data lives

Kubernetes stores its state in a small, consistent key-value database called etcd. The API server is the only component that talks to etcd directly. Everything else asks the API server. The API server validates each request, applies rules, and then saves the object. When you create a custom resource using a CRD, it is saved here, right beside pods and secrets. When you use an aggregated API, you decide where your data lives.

flowchart LR
    U["kubectl, controllers, CI tools"] -->|"HTTPS requests"| API["Kubernetes API Server"]
    API --> A["Authentication - who are you?"]
    A --> Z["Authorization - are you allowed?"]
    Z --> AD["Admission - rules and defaults"]
    AD --> V["Validation of the object"]
    V --> E["etcd - the cluster database"]
    E -.->|"change events"| W["Watchers and controllers"]
Figure 1 – The journey of a normal API request, from the client to the database and back out to watchers.
Everyday Analogy

Picture an airport check-in desk. The agent checks your passport (authentication), checks your ticket class (authorization), applies the airline’s rules to your bag (admission), makes sure the booking is complete (validation), and finally records your seat in the central system (etcd). Everyone else, from the gate staff to the baggage handlers, learns about you from that central record.

3Three Servers Wearing One Coat

The Kubernetes API server looks like one program. Inside, it is actually a chain of three, and that chain is the secret behind both extension mechanisms.

The delegation chain

The main Kubernetes API server binary is built from three cooperating servers stacked in a line. A request enters at the front and, if the first server does not own the answer, it is handed down the line.

1

The aggregator

This is the front desk. It knows about registered extension API servers. If a request is for a type owned by one of them, the aggregator forwards it there. Otherwise it hands the request down to the next server.

2

The core API server

This serves the built-in types such as pods, services, nodes and deployments. If the request is not for one of its types, it passes the request on.

3

The API extensions server

This serves everything defined through CRDs. It creates and removes web addresses on the fly whenever a CRD is added or deleted.

4

The not-found handler

If nobody claims the request, the answer is a simple “not found”.

flowchart TB
    REQ["Incoming API request"] --> AGG["Aggregator - front desk"]
    AGG -->|"type owned by an APIService"| EXT["External extension API server"]
    AGG -->|"not owned - delegate"| CORE["Core API server - pods, services, nodes"]
    CORE -->|"not a built-in type - delegate"| CRDS["API extensions server - custom resources from CRDs"]
    CRDS -->|"unknown - delegate"| NF["Not found"]
    CORE --> ETCD["etcd"]
    CRDS --> ETCD
    EXT --> OWN["Its own storage - your choice"]
Figure 2 – The API server is a chain. CRDs are served by a built-in link of the chain, while aggregated APIs are served by a separate program behind the front desk.

What this tells us

This picture already reveals the heart of the comparison.

  • With a CRD, your new type is served by the third link in the chain, a part of the Kubernetes API server itself. You write no server code. You only describe the type, and the existing machinery does the rest.
  • With an aggregated API, you write and run a completely separate API server. You tell the aggregator at the front desk to forward certain requests to it. You gain full control, and you take on full responsibility.
Everyday Analogy

A CRD is like adding a new menu item to a restaurant’s existing kitchen. The same cooks, the same stoves and the same pantry handle it. An aggregated API is like renting a food truck, parking it next to the restaurant and putting a sign on the front door. The truck has its own cooks and its own fridge, so it can do things the kitchen cannot, but it also needs its own staff, fuel and cleaning.

i
Good to Know

Because the aggregator sits at the front, both kinds of extension look identical to users. A person running kubectl get cannot tell, without checking, whether an object comes from a CRD or from an aggregated server.

4First, Do You Need an API at All?

Many teams reach for a new API type when something far simpler would do. A good architect asks this question first.

A ladder of options

Think of extension options as steps on a ladder, from lightest to heaviest. Always start at the bottom and climb only as far as you must.

Step 1

ConfigMap or Secret

If you only need a place to keep some settings that your own program reads, a ConfigMap is enough. No new API is needed.

Step 2

Annotations and labels

For small bits of extra information attached to existing objects, simple tags can be enough.

Step 3

Custom resource with a CRD

When you want a first-class declarative object that users create and controllers act upon, a CRD is the usual answer.

Step 4

Aggregated API

Only when a CRD cannot give you what you need, because of storage, behavior or protocol, do you run your own API server.

Signs you need a real API type

  • You want users to manage the thing with the same tools as other Kubernetes objects.
  • You want permissions to be controlled per object type, and per namespace.
  • You want to watch for changes and react automatically.
  • You want the object to appear in listings, with status that shows its health.
  • You want a controller to turn declared intent into real-world actions.

Signs you do not

  • Your program just reads a few settings at startup. Use a ConfigMap.
  • The data is large, changes constantly or is queried in complex ways. A real database is better, and an API object can just point to it.
  • Only one team and one tool will ever use the data, and nobody needs Kubernetes permissions or watches.
  • You are building a general-purpose business application. Kubernetes objects are best for describing infrastructure and platform intent, not for storing customer records.
!
Watch Out

Do not turn the cluster’s database into your application’s database. etcd is small and precious. A flood of custom objects can slow down the whole cluster for everyone.

5CRDs: A New Form for the Existing Clerks

A Custom Resource Definition is the lightest and most popular way to extend Kubernetes. It is a description of a new type, written as a normal Kubernetes object.

The two words, carefully

People often mix up two similar terms. The CustomResourceDefinition, or CRD, is the blueprint: a Kubernetes object that describes a new type. A custom resource, or CR, is an actual instance of that type. The CRD is like the design of a form. A custom resource is a filled-in copy of that form. Once you apply a CRD, the API server immediately starts accepting custom resources of that type.

What a CRD describes

Identity

Group, kind and names

The group the type belongs to, its kind, its plural and singular names and optional short names. Together these decide the web address and how users type the name on the command line.

Scope

Namespaced or cluster-wide

Whether each object lives inside a namespace, like a pod, or sits at the cluster level, like a node. This choice shapes permissions and cannot easily be changed later.

Shape

Schema

A structured description of the allowed fields, their types and rules, written in the OpenAPI format. This is the contract between users and controllers.

Versions

Version list

The versions that exist, which ones are served to clients, which one is used for storage and how to convert between them.

Extras

Subresources and columns

Optional features such as a separate status section, a scale interface and extra columns shown in command-line listings.

Rules

Validation and defaults

Constraints such as minimum values, allowed choices, default values and custom expression rules that are checked on every write.

Why the schema matters so much

In the current stable API for CRDs, every custom resource type must have a structural schema. This is a strict, well-defined way of describing fields so that the API server can understand every part of the object. The benefits are large:

  • Validation. A malformed object is rejected immediately with a clear message, instead of failing mysteriously inside a controller hours later.
  • Pruning. Fields that are not in the schema are removed before saving, so typos cannot silently sneak into the database.
  • Defaulting. Missing fields can be filled in automatically with sensible values.
  • Documentation. Tools can read the schema to show help text and offer autocompletion.
Everyday Analogy

A schema is like the printed boxes on a paper form. “Name: ____”, “Age: ____ (number only)”, “Phone: ____”. If someone writes a poem in the age box, the clerk hands the form back. Without the printed boxes, every form would be a free-for-all and the person processing it later would struggle.

Rules written as expressions

Beyond basic types, modern Kubernetes lets CRD authors write validation rules using the Common Expression Language, known as CEL. These are small, safe expressions that run inside the API server. A rule can say “the maximum must be greater than or equal to the minimum”, or “this field cannot be changed after creation”. Because these rules run inside the server, you often do not need to build and run a separate validation web service, which removes a major source of complexity and failure.

i
Good to Know

Built-in admission policies written in CEL can also target custom resources, so teams can add organization-wide rules without writing any code or running any webhook.

6How the API Server Serves Custom Resources

The magic of CRDs is that no restart and no new program is needed. Let us watch what happens inside the server when a CRD is applied.

From CRD to working API

1

You create the CRD

The CRD itself is an ordinary object in a built-in group. It goes through the usual checks and is saved to etcd.

2

The extensions server notices

A controller inside the API server watches for CRDs. It sees the new one and starts to build handlers for the new type.

3

Names are checked

The server makes sure the new names do not clash with anything existing. A condition on the CRD reports whether the names were accepted.

4

The new web address appears

The server registers REST handlers for create, read, update, delete, list and watch. A second condition, called Established, tells you when the type is ready for use.

5

Clients discover it

The new group and version show up in the list of available APIs, so command-line tools and controllers can find it.

6

Users create custom resources

Each custom resource is validated against the schema, defaulted, pruned and stored in etcd under its own key, next to every other object.

sequenceDiagram
    participant U as User
    participant A as API Server
    participant X as Extensions server inside
    participant E as etcd
    U->>A: Create CRD
    A->>E: Save CRD
    E-->>X: CRD change event
    X->>X: Build REST handlers and schema
    X-->>A: Type is Established
    U->>A: Create custom resource
    A->>A: Authenticate, authorize, admit
    A->>X: Validate, default and prune with schema
    X->>E: Save custom resource
    E-->>U: Success and watch events
Figure 3 – A CRD teaches the running API server a new type without a restart or any new program.

Features you receive for free

  • Status subresource. A separate section for what the controller observes, kept apart from what the user wants. Users edit the wish, the controller edits the status, and neither can overwrite the other by accident.
  • Scale subresource. A standard interface that lets tools like the Horizontal Pod Autoscaler scale your custom type just like a deployment.
  • Printer columns. Extra columns in listings, so a database object can show its version and readiness at a glance.
  • Finalizers. Markers that stop an object from being fully deleted until a controller has cleaned up the outside resources it created.
  • Owner references and garbage collection. A parent object can own children, so deleting the parent automatically cleans up everything it created.
  • Watch with caching. The server supports efficient change streams out of the box.
  • Permissions, auditing and tooling. Role-based access control, audit logs, kubectl and GitOps tools all work immediately.
!
Watch Out

Deleting a CRD deletes every custom resource of that type, because without the blueprint the instances have no meaning. In production, protect CRDs of important operators with tight permissions and with backups.

7CRD Versioning, Conversion and Migration

Every API changes over time. A CRD must support old and new shapes at the same time, and this is where real planning begins.

Why versions exist

Imagine your database type starts with a simple field for the size. Later you realize you need to specify the storage and the compute separately. You cannot just rename fields, because thousands of stored objects and many tools use the old shape. Instead, you add a new version of the type, and keep the old one working until everyone has moved.

Served versus stored

A CRD can list several versions. Two flags control their roles. A version marked served can be used by clients. Exactly one version is marked storage, and this is the shape in which objects are written to etcd. When a client uses a different version than the storage version, the API server must convert the object on the way in and out.

Two conversion strategies

None strategy

  • The server only changes the version label and leaves all fields as they are.
  • Works only when versions have identical shapes.
  • No extra service to run.
  • Good for simple promotion, such as beta to stable with no changes.

Webhook strategy

  • The server calls your conversion service to transform objects between versions.
  • Handles any change, such as renamed or restructured fields.
  • The service becomes a critical dependency: if it is down, reads and writes can fail.
  • Requires careful testing, since conversions must be loss-free in both directions.

Moving stored data to a new storage version

Changing the storage version does not automatically rewrite old objects. They stay in etcd in the old shape until they are next written. Before you can safely stop serving an old version, you must make sure no stored object still uses it. A storage version migration, which simply reads and re-writes every object, takes care of this. The project provides tooling for such migrations, and many operators run their own.

StepWhat you doWhy
1Add the new version and serve bothOld clients keep working
2Provide conversion between themObjects look correct in either version
3Switch the storage version to the new oneNew writes use the new shape
4Migrate all stored objectsNo object remains in the old shape
5Stop serving the old versionRetire it safely
6Remove the old version from the listOnly after confirming nothing depends on it
i
Good to Know

Plan versions from day one. Choose a stable group name that you own, start at an early version such as v1alpha1 to signal that things may change, and promote only when the shape is truly settled.

8Controllers and Operators: The Brain Behind the Form

A CRD on its own does nothing except store data. The real value appears when a controller reads the objects and acts on them.

What a controller does

A controller is a program, usually running as a pod in the cluster, that watches custom resources and makes the real world match what they describe. It follows the reconciliation loop you may know from other Kubernetes parts: observe the desired state, observe the actual state, take the smallest action that moves them closer, then repeat. A controller bundled with a custom resource type, designed to manage one application or system, is called an operator. The word suggests a human operator whose knowledge has been captured in software.

Everyday Analogy

A CRD is the order form at a bakery. The controller is the baker. You fill in “one chocolate cake, birthday, for Saturday”. The baker reads the order, checks what is in the oven, bakes what is missing, and marks the order as ready. If someone eats the cake early, the baker notices and bakes another, because the order still stands.

The reconciliation cycle

flowchart TB
    W["Watch custom resources"] --> Q["Work queue of changed objects"]
    Q --> R["Reconcile one object"]
    R --> O["Read desired spec and actual world"]
    O --> D{"Do they match?"}
    D -->|"yes"| S["Update status: healthy"]
    D -->|"no"| A["Create, update or delete child resources"]
    A --> S2["Update status: progressing"]
    S --> W
    S2 --> W
Figure 4 – The standard controller loop. The spec is the wish, the status is the report.

Good controller habits

  • Idempotence. Running the same reconcile twice must be safe. Controllers can be restarted at any time and will see the same objects again.
  • Level-based thinking. React to the current state, not to a list of past events. Events can be missed, but the state is always there to read.
  • Owner references. Mark everything the controller creates as owned by the custom resource, so cleanup is automatic.
  • Finalizers. Use them to clean up outside resources, such as cloud disks, before the object disappears.
  • Status conditions. Report progress in a standard way that humans and tools can read.
  • Leader election. Run several copies for availability, with only one active at a time.

Controllers work with both mechanisms

Notice that a controller is independent of how the API type is served. The same controller pattern works whether the objects come from a CRD or from an aggregated server, because it simply talks to the Kubernetes API. The choice between the two doors affects how the objects are stored and served, not how controllers behave. Many operator frameworks make building controllers much easier, and they are written mostly with CRDs in mind.

9Aggregated APIs: A Whole New Office Behind the Front Desk

The second door is heavier but much more flexible. Instead of describing a type to the existing server, you bring your own server.

The big idea

With API aggregation, you write and run your own API server, called an extension API server. It could be a pod inside the cluster or even something running elsewhere. You then register it with the main API server by creating a small object called an APIService. This object says, in effect: “Any request for group X and version Y should be forwarded to this service.”

From then on, the main API server acts as a front door and a bouncer. It checks who the caller is and whether they are allowed to proceed, and then passes the request along to your server. Your server answers, and the reply travels back through the front door to the client. To the user, it feels like one single Kubernetes API.

Everyday Analogy

Think of a large company with a main reception desk. A new department opens in a separate building with its own staff and filing system. Reception is told: “Anything about the Drone Department goes to the other building.” Visitors still enter through the same lobby, show the same badge and use the same directory. Behind the lobby, the department runs things its own way.

What an APIService contains

Field

Group and version

The exact address that this entry claims, such as a group name and version. Requests matching it are forwarded.

Field

Service reference

Which Kubernetes service in which namespace, and which port, leads to your extension server. This is how the main API server finds it.

Field

CA bundle

The certificate authority that the main API server uses to check that it is talking to the genuine extension server and not an impostor.

Field

Priorities

Numbers that decide ordering when several groups or versions compete for display and preference in discovery.

Availability is reported

The APIService object also carries a status. The main API server regularly checks that the extension server is reachable and healthy, and records an Available condition. If it is not available, requests for that address fail with an error saying the service is unavailable. This honest reporting is helpful, but it also reveals a risk: the extension server is now on the critical path for its part of the API.

Real examples you may already use

  • Metrics Server registers the metrics group so that kubectl top and the Horizontal Pod Autoscaler can ask for live CPU and memory. The data comes from memory, not from the database.
  • Custom metrics adapters serve the custom and external metrics groups, translating questions into queries against monitoring systems.
  • KubeVirt, which runs virtual machines on Kubernetes, uses CRDs for its main objects but also adds an aggregated API for special actions such as opening a console to a running machine.
  • Service Catalog, an earlier project for provisioning external services, was built as an aggregated API server. It has since been retired, a reminder of the long-term cost of this approach.
i
Good to Know

Notice that the best-known aggregated APIs serve data that does not belong in etcd at all, such as live metrics. That is the classic reason to choose aggregation.

10Inside Aggregation: How a Request Travels

The trickiest part of aggregation is trust. The extension server must trust the main API server, and the main API server must trust the extension server. Let us see how this is arranged.

The journey of one request

1

The client sends a request

A user runs a command that touches the aggregated type. The request goes to the main API server like any other.

2

Authentication happens at the front

The main API server works out who the caller is, using certificates, tokens or whatever login method the cluster uses.

3

Authorization happens at the front

It checks whether this user may perform this action on this type, using the normal permission rules.

4

The aggregator matches an APIService

It sees that the requested group and version belong to an extension server, and prepares to proxy the request.

5

The request is forwarded with identity

The main API server connects to the extension server over a secure channel, using its own special client certificate, and passes the caller’s name and groups in trusted headers.

6

The extension server verifies and double-checks

It confirms that the connection really came from the main API server, reads the identity headers, and may ask the main API server whether this user is allowed to perform the action.

7

The extension server does the work

It validates the object, reads or writes its own storage, and builds the response, which flows back to the client.

sequenceDiagram
    participant C as Client
    participant M as Main API Server with aggregator
    participant X as Extension API Server
    participant S as Its own storage or data source
    C->>M: Request for aggregated type
    M->>M: Authenticate and authorize caller
    M->>X: Proxy request with front-proxy certificate and identity headers
    X->>X: Verify proxy certificate and read identity
    X->>M: Ask permission check for this user
    M-->>X: Allowed
    X->>S: Read or write
    S-->>X: Data
    X-->>M: Response
    M-->>C: Response
Figure 5 – Request flow through an aggregated API. The extension server trusts the front door, but still verifies.

The trust machinery

  • Front-proxy client certificate. The main API server presents a special certificate when it forwards requests. The extension server accepts requests only from a caller holding such a certificate signed by the expected authority.
  • Request header authentication. The identity of the original caller is carried in headers, such as the user name and group names. Because only the trusted front door can set them, the extension server can believe them.
  • Published settings. The main API server publishes the details that extension servers need, such as the certificate authority and the header names, in a well-known configuration object in the system namespace. Extension servers read it on startup.
  • Delegated authorization. Rather than maintaining its own permission rules, the extension server asks the main API server to check whether this user may perform this action. This keeps all permission logic in one place, and means ordinary Kubernetes roles work for aggregated types too.
  • CA bundle in the APIService. This lets the main API server verify the extension server’s own certificate in the other direction.
Everyday Analogy

Picture a hotel concierge who phones a partner restaurant: “This guest is Ms. Rao, a verified resident, please seat her.” The restaurant recognizes the concierge’s voice and special code, so it trusts the guest’s identity. Even so, it may call the front desk to confirm that Ms. Rao has dining privileges. Two checks, one trusted channel.

!
Watch Out

The APIService has an option to skip checking the extension server’s certificate. Never use it in production. It removes the main guarantee that the front door is talking to the genuine server, and exposes you to impersonation inside the cluster network.

11What It Takes to Build an Extension Server

With a CRD, you write a description. With aggregation, you write a real server. It helps to see the long list of things that become your job.

Responsibilities you inherit

Core

REST handlers

Every verb for every version of every resource: create, read, update, patch, delete, list and watch, all following Kubernetes conventions exactly.

Core

Storage

Choose and operate a place for the data. It could be your own etcd, a relational database, a cache, or computed on the fly. You must make it durable and fast.

Core

Watch support

Efficient change notifications with resource versions so controllers can resume cleanly after disconnects. This is subtle to get right.

Contract

Discovery and schema publishing

Describe your types, their versions and the shapes of objects so that tools can find and understand them.

Contract

Versioning and conversion

Write your own code to convert between versions, and your own strategy for storing and migrating data.

Safety

Validation and admission

Implement rules in code, and decide how admission policies and webhooks interact with your types.

Safety

Security plumbing

Certificates, delegated authentication and authorization, secure serving, and keeping those certificates rotated.

Run

Operations

Deployment, scaling, high availability, upgrades, monitoring and the on-call rotation for yet another critical server.

Tools that help

You are not starting from nothing. The Kubernetes project publishes a generic API server library that contains most of the machinery, including request handling, authentication, authorization, watch and storage on etcd. A sample extension server in the project shows how to put the pieces together, and community frameworks exist to generate boilerplate. Still, even with these tools, you take on a project of significant size, written in the same language and style as Kubernetes itself, and you must keep it up to date with every Kubernetes release.

Effort at a glance

TaskWith a CRDWith an aggregated API
Define the typeWrite a schema descriptionWrite Go types and generate code
Serve itAutomatic, built inWrite and run a server
Store itAutomatic in cluster etcdYour own design and operation
Validate itSchema plus expression rulesCode in your server
Secure itInherited from the API serverCertificates and delegation you must wire up
Upgrade itApply a new CRDBuild, release and roll out a new server
Typical time to first working versionHours to daysWeeks to months
!
Watch Out

The ongoing cost matters more than the first build. Every Kubernetes upgrade may bring changes to the libraries you depend on. Plan for a team to own this server for as long as it exists.

12Side by Side: Aggregated APIs vs CRDs

Now that you know both mechanisms from the inside, here is a direct comparison across the dimensions that matter in practice.

DimensionCRDAggregated API
Who serves itThe built-in extensions server inside the API serverYour own separate API server
Code you writeNone for the API. A controller if you want behaviorA full API server plus any controllers
Where data is storedThe cluster’s etcdAnywhere you choose
ValidationSchema, defaults, pruning and CEL rulesAny logic in code
VersioningDeclared versions with none or webhook conversionYour own conversion code
Custom actions beyond create, read, update and deleteOnly status and scale subresourcesAny subresource or special verb you design
ProtocolsStandard REST and watchCan also support streaming and special protocols on subresources
Extra network hopNoneYes, through the aggregator
Failure impactTied to the main API serverYour server can fail independently, and affect discovery and cleanup
PermissionsStandard role-based access controlStandard role-based access control through delegation
Command-line and tool supportFullFull, if you implement conventions correctly
Operational burdenLowHigh
Typical useOperators and platform APIsComputed data, special storage or special verbs
flowchart LR
    subgraph CRD["CRD path"]
        C1["Client"] --> C2["API Server"]
        C2 --> C3["Built-in extensions server"]
        C3 --> C4["Cluster etcd"]
    end
    subgraph AGG["Aggregated path"]
        A1["Client"] --> A2["API Server with aggregator"]
        A2 --> A3["Your extension API server"]
        A3 --> A4["Your own storage or live data"]
    end
Figure 6 – The CRD path stays inside the main API server. The aggregated path leaves it to your own server.

The short version

A CRD gives you a free, fast, inherited API with limits on what it can do. An aggregated API gives you total freedom with total responsibility. In nearly every case, the free option is enough, and the freedom of the second option goes unused.

13What Only Aggregated APIs Can Do

There are real situations where a CRD simply cannot do the job. Knowing them helps you avoid both over-engineering and under-engineering.

1. Data that does not live in etcd

A CRD always stores objects in the cluster’s etcd. If your data is huge, changes many times per second, already lives in another system, or is computed from live sources, putting it into etcd is unwise or impossible. An aggregated API can read from a database, a monitoring system or even memory. Metrics Server is the classic case: every number is fresh, short-lived and never saved.

2. Custom actions on objects

Kubernetes pods have special actions like opening a shell, streaming logs and forwarding ports. These are not plain create-or-update operations. They are subresources with their own behavior. CRDs allow only two subresources, status and scale. If you need an action such as “open a console to this virtual machine” or “stream the output of this job”, an aggregated server can provide it. This is why KubeVirt, which mostly uses CRDs, still runs an aggregated server for its console and similar actions.

3. Different storage rules

Perhaps you need strong history, data in a relational database, special indexes, or storage with a different consistency model. Or perhaps you need an object that exists only virtually, without being saved at all. Aggregation lets you design this.

4. Complex or non-standard logic on writes

CRDs support schema rules, expression rules and, with extra webhooks, custom checks. An aggregated server can perform any logic inside the request itself: look up outside systems, generate values, or reject changes based on arbitrary conditions, without separate webhook calls.

5. Performance at a different scale

If you must serve a huge number of objects, or huge individual objects, you may need your own storage, caches and indexes tuned to your workload, rather than sharing the cluster database.

6. Managing a different system as if it were Kubernetes

Sometimes a team wants Kubernetes-style access, tooling and permissions in front of a completely different system. An aggregated server can present such systems through familiar API conventions, using its own backing data.

Fits aggregation

Live computed data

Metrics, status snapshots and anything calculated at request time.

Fits aggregation

Special verbs

Consoles, streaming, attach, port-forward-like features.

Fits aggregation

External stores

Data owned by a relational database, a cloud service or a large dataset.

Fits aggregation

Unusual semantics

Custom conversion, history and server-side logic not possible in schema rules.

i
Good to Know

A hybrid is often the best answer: use CRDs for the main declarative objects, which controllers act upon, and add a small aggregated API only for the special actions or computed views that CRDs cannot offer.

14What CRDs Give You for Free

The reason CRDs dominate real-world use is not only that they are simple. It is how much comes bundled with them without any effort.

The free bundle

Built in

Storage, durability and backups

Custom resources live in the same database as everything else. Whatever backup plan protects the cluster state also protects your objects.

Built in

High availability

Your API is as available as the main API server itself, whether that is a single node or a managed multi-zone control plane.

Built in

Security model

Authentication, authorization, audit logging and admission all apply automatically. There are no extra certificates to rotate.

Built in

Watch and caching

Efficient change streams and server-side caching are provided by the platform and well tested at scale.

Built in

Garbage collection

Owner references and finalizers behave exactly as they do for built-in types.

Built in

Upgrades

Nothing extra to build, release or roll out when Kubernetes itself upgrades. Your CRD just continues to work.

The ecosystem effect

Because custom resources look like ordinary Kubernetes objects, the entire world of Kubernetes tooling treats them as first-class. Configuration management and GitOps tools can apply them from a repository. Policy engines can validate them. Dashboards can display them. Backup tools can include them. Command-line completion can suggest their fields. A CRD plugs into all of this on day one.

This is why so many well-known projects are built primarily on CRDs.

  • cert-manager defines certificates and issuers as custom resources, and a controller keeps certificates valid.
  • Prometheus Operator defines monitoring servers and scrape rules as custom resources.
  • Argo CD and Argo Workflows describe applications and pipelines as custom resources.
  • Istio and the Gateway API describe traffic routing and gateways as custom resources.
  • Cluster API describes entire Kubernetes clusters as custom resources, which controllers create and manage.
  • Crossplane describes cloud resources and platform building blocks as custom resources.
  • Knative describes serverless services and revisions as custom resources.
Everyday Analogy

Choosing a CRD is like building a new room inside an existing house. The plumbing, wiring, heating, roof and insurance are already there. Choosing aggregation is like building a new detached house on the same street. You get more freedom in design, but you need your own plumbing, wiring, roof and insurance.

Limits you should expect

  • You cannot choose where or how data is stored.
  • You are limited to the standard set of actions, plus the status and scale subresources.
  • Complex checks that need outside information require admission webhooks, which are separate services that add moving parts.
  • Object size and count are bounded by what etcd and the API server comfortably handle.
  • Very complex version changes require a conversion webhook, with its own care and risk.

15Performance and Scalability

Both mechanisms can scale well, but each has its own pressure points. Knowing them keeps surprises away.

About 1.5 MiB
default maximum size of a single request to etcd
2 GiB
default etcd storage quota before it goes read-only
+1 hop
extra network step for every aggregated request

The figures above are the default limits of a standard etcd setup, and managed Kubernetes services may configure them differently. Treat them as signs of scale, not guarantees.

Pressure points with CRDs

  • Shared database. Custom resources share etcd with everything else. A controller that writes constantly, or objects that are large, can slow down or even endanger the entire cluster. If etcd hits its storage quota, writes stop for everyone.
  • Object size. Very large objects, such as those carrying embedded files or long lists, are heavy to store, transfer and cache. Keep objects small and store bulk data elsewhere.
  • Object count. Hundreds of thousands of objects of one type can strain listing and caching in the API server, which holds recent data in memory for fast watches.
  • Number of CRDs. Each CRD adds handlers, schemas and cached discovery data to the API server. Very large numbers of CRDs can increase memory use and slow discovery for every client.
  • Write frequency. Controllers that update status many times per second create heavy write load and heavy watch traffic. Batch updates and avoid pointless writes.

Pressure points with aggregated APIs

  • The extra hop. Every request passes through the main API server and is then proxied to your server. This adds a little latency and some load to the front door, which is rarely a problem but should be measured for very busy APIs.
  • Your own caching and watch. If your server does not cache and manage watches as efficiently as the main one, many controllers watching your types can overload it.
  • Your own storage. You are now responsible for the scalability of whatever holds your data.
  • Connection handling. Long-lived watch connections and streams all pass through the proxy and need to be handled carefully, including timeouts and reconnection.
flowchart TB
    subgraph SHARED["CRD - shared resources"]
        CR["Many custom resources"] --> ETCD["Cluster etcd"]
        PODS["Pods, secrets, nodes"] --> ETCD
        ETCD --> RISK["One busy neighbor can slow everyone"]
    end
    subgraph ISO["Aggregated - isolated resources"]
        AR["Your objects"] --> OWN["Your own storage"]
        OWN --> SAFE["Cluster etcd is protected, but you operate more systems"]
    end
Figure 7 – CRDs share a database with the whole cluster. Aggregated APIs isolate the load, at the price of running more infrastructure.
i
Good to Know

The usual recipe for healthy CRD use is simple: small objects, moderate counts, quiet controllers and large data kept elsewhere. If your design needs to break these rules, that is a strong hint that you may need a different storage, and therefore an aggregated API or no API at all.

16Availability and Failure Scenarios

Every design should be examined by asking one question: what breaks when this part fails? The two mechanisms fail in very different ways.

When an aggregated server goes down

If your extension server is unreachable, requests for its types fail. That is expected. But there are side effects that surprise many teams.

  • Discovery errors. Tools that list all available APIs may print warnings or even fail, because one group could not be reached. Users see confusing messages about being unable to retrieve the complete list of server APIs.
  • Stuck namespace deletion. When a namespace is deleted, Kubernetes must look through every type to remove objects in it. If one aggregated API is unavailable, the check cannot complete and the namespace may remain in a terminating state indefinitely.
  • Controller trouble. Controllers and tools that scan all types, such as garbage collection and backup tools, may stall or log errors.
  • Autoscaler impact. If the unavailable server is the metrics provider, the autoscalers that depend on it cannot make decisions.

When something fails with a CRD

  • The CRD is deleted. All its custom resources are deleted with it. This is the most dangerous CRD mistake.
  • The conversion webhook is down. Reads and writes that need conversion between versions can fail, which can break controllers and tools for that type.
  • An admission webhook is down. Depending on its failure setting, writes are either blocked or silently unchecked.
  • The controller is down. The API still works, and objects can still be created, but nothing acts on them. Status goes stale, and users are left waiting. This is a softer failure than a missing API.
  • A bad schema change. A stricter schema can cause existing stored objects to become invalid, which makes updating them fail until they are fixed.
FailureCRD effectAggregated effect
The extension server is downNot applicableType unavailable, discovery warnings, risk of stuck namespaces
The main API server is downEverything is downEverything is down
The controller is downAPI works, no reconciliationAPI works, no reconciliation
Certificate expiresOnly affects webhooks, if anyProxy connection to the extension server can fail
Storage is fullCluster etcd writes stop for allOnly your own storage is affected
Bad release of the serverNo server to releaseThe type could return errors until you roll back

Protecting yourself

  • Run extension servers with multiple replicas, spread across zones, with proper health checks and disruption budgets.
  • Monitor APIService availability and alert quickly when it flips.
  • Automate the rotation of certificates well before they expire.
  • Keep conversion and admission webhooks as few and as simple as possible, and design their failure policy on purpose.
  • Treat CRDs as precious data: limit who can delete them, and back up both CRDs and their instances.
  • Test the failure of every component in a staging environment before relying on it.

17Security Considerations

Both mechanisms widen the cluster’s attack surface. The question is who carries the burden of protecting that surface.

Shared protections

  • Role-based access control. Permissions work on custom types just as on built-in ones. You can grant read-only access to a type, restrict creation to a team, or limit access to specific namespaces.
  • Audit logging. Both kinds of types appear in the cluster audit trail, so you can see who created or changed what.
  • Admission policies. Cluster-wide policies, whether written in CEL or run by policy engines, can examine custom objects.

CRD-specific points

Risk

Who can create CRDs

Creating or editing a CRD changes the shape of the API. It should be a highly restricted permission, since it can affect every user of that type.

Risk

Cluster-scoped types

Objects with cluster scope cannot be separated by namespace, so permission mistakes have wider effects. Prefer namespaced types unless the thing is truly cluster-wide.

Risk

Controller privileges

Operators often hold powerful permissions to create pods, secrets or cloud resources. A bug or break-in in the operator is a path to those powers. Grant it only what it needs.

Benefit

Strict schemas

A tight schema is a form of input validation. It rejects unexpected fields and values before they reach any controller.

Aggregation-specific points

Risk

Your server is new code

It has its own bugs and its own dependencies. A flaw in it is exposed to the cluster network and can be used to run attacks against all callers.

Risk

Certificate handling

Mistakes in verifying the front-proxy certificate could allow someone to impersonate any user by faking the identity headers.

Risk

Skipped TLS checks

Disabling verification of the extension server’s certificate lets any pod that can hijack its network address answer requests meant for the real server.

Risk

Own data store

The storage has its own credentials, network exposure, encryption and backup needs, none of which the cluster handles for you.

Practical hardening

  • Use network policies so that only the main API server and the necessary clients can reach the extension server.
  • Run it with the least privileges possible, as a non-root user, with a read-only file system where feasible.
  • Always rely on delegated authorization so that permissions stay defined in one place.
  • Never use the option to skip certificate checks.
  • Scan and sign the images of extension servers and operators, and verify them at admission.
  • Encrypt sensitive fields at rest. Remember that secrets must not be placed in plain custom resource fields; reference a Secret instead.
!
Watch Out

Never place passwords, tokens or keys inside the fields of a custom resource. Objects are widely readable, logged and copied by tools. Store secrets in proper secret objects or an external vault, and let your custom resource point to them by name.

18Monitoring and Troubleshooting

When an extension misbehaves, you need to know where to look. Both kinds expose clear signals if you know their names.

What to look at

CRD

CRD conditions

The CRD reports whether names were accepted and whether it is Established. A type that is not Established cannot be used yet.

Aggregated

APIService conditions

The Available condition and its message explain whether the extension server is reachable, and why not. Listing all APIServices shows the health of every extension at a glance.

Both

API server metrics

Request counts, error rates and latencies per resource and verb show which types are slow or failing. A dedicated metric counts unavailable aggregated services.

Both

Audit logs

Show who changed or deleted a type or an object, and when.

CRD

Controller metrics

Queue depth, reconcile durations and error counts reveal whether the operator is keeping up.

Storage

etcd health

Database size, write latency and object counts per type tell you whether CRD usage is straining the cluster.

A troubleshooting walk-through

1

The type does not exist

Check that the CRD exists and is Established, that the group and version are spelled correctly, and that the version is marked as served.

2

Creating an object is rejected

Read the error message. It usually names the exact field that failed the schema or expression rule. Also check for admission policies and webhooks.

3

Objects exist but nothing happens

This is a controller problem. Check that the controller pod is running, has permissions and is not stuck in a failing loop. Read its logs and its metrics.

4

Errors about unavailable API groups

List APIServices and look for ones that are not Available. Check the extension server’s pods, its service, its certificates and the network path from the API server.

5

A namespace is stuck terminating

Look for unavailable aggregated APIs and for custom resources with finalizers whose controllers are no longer running. Restore the controller or remove the dead API registration with care.

6

Reads or writes fail after a version change

Check the conversion webhook, the storage version and whether stored objects still match the schema.

i
Good to Know

Alert on two simple things above all: any APIService that stops being Available, and any CRD whose controller has gone quiet. Together they catch most extension failures before users report them.

19A Decision Guide You Can Use Tomorrow

Here is a simple method for choosing between the two doors. Ask the questions in order, and stop at the first clear answer.

flowchart TB
    S["New API need"] --> Q1{"Is a ConfigMap or plain settings enough?"}
    Q1 -->|"yes"| CM["Use a ConfigMap"]
    Q1 -->|"no"| Q2{"Must data live outside etcd or be computed live?"}
    Q2 -->|"yes"| AGG["Use an aggregated API"]
    Q2 -->|"no"| Q3{"Need custom verbs or subresources beyond status and scale?"}
    Q3 -->|"yes, essential"| HYB["CRD for objects plus aggregated API for the special actions"]
    Q3 -->|"no"| Q4{"Can schema, CEL rules and webhooks express your validation?"}
    Q4 -->|"no"| AGG
    Q4 -->|"yes"| Q5{"Small objects, moderate counts, modest write rate?"}
    Q5 -->|"no"| AGG
    Q5 -->|"yes"| CRD["Use a CRD with a controller"]
Figure 8 – A decision flow. Most paths end at a CRD, and that is by design.

The questions in words

  • Is a plain setting enough? If yes, stop. Use a ConfigMap and avoid a new API.
  • Must the data live outside etcd, or be computed live? Metrics, giant datasets and external records say yes. That points to aggregation.
  • Do you truly need special actions? Consoles, streams and other custom verbs cannot be done by a CRD. A hybrid might serve you best.
  • Can your validation rules be written as schema and expressions? If your rules need unusual logic inside the request, aggregation gives the freedom.
  • Is the scale reasonable for shared storage? Huge or hot data harms the cluster database. If your numbers are high, move away from etcd.
  • Do you have a team to run a server forever? If not, that alone is a reason to pick a CRD and live with its limits.

Scoring your situation

SignalPoints toward CRDPoints toward aggregation
Main purposeDeclarative intent that a controller acts onServing computed or external data
Data size and rateSmall and slow-changingLarge or fast-changing
Special actionsNone beyond status and scaleConsoles, streams, custom verbs
Team capacityLimited operations timeDedicated platform team
Time to marketWeeksMonths are acceptable
Risk appetitePrefer inherited safetyWilling to own security and availability
Ecosystem needsWant maximum compatibility with GitOps and policy toolsWilling to verify compatibility yourself
i
Good to Know

Switching later is expensive. Moving from a CRD to an aggregated API changes where data lives and how it is served, and users must be migrated. If you are unsure, begin with a CRD and a clearly separated data layer so that a later move stays possible.

20Design Patterns and Anti-Patterns

Experienced platform teams reuse a small set of patterns. They also share a list of traps they once fell into.

Patterns that work

Pattern

Spec and status split

Users write the spec, the controller writes the status. Always use the status subresource so the two never collide.

Pattern

Reference, do not embed

Point to secrets, config and large data by name instead of copying them into the object. Keeps objects small and safe.

Pattern

Small, focused types

Several small types with clear owners beat one giant type with dozens of options.

Pattern

Hybrid API

CRDs for the declarative core, with a small aggregated server only for special actions or computed views.

Pattern

Schema as the contract

Invest in a precise schema with defaults and expression rules. It is the cheapest and most reliable validation you can have.

Pattern

Conventional conditions

Report health with standard condition lists so humans and automation read status the same way everywhere.

Anti-patterns to avoid

ANTI-PATTERN 01Avoid
What it looks like

A team stores application records, user profiles or event streams as custom resources, because the API is convenient.

Why it hurts

The cluster database fills up, listings slow down and a problem in the application can threaten the whole platform. Kubernetes is not a general-purpose database.

Better approach

Keep bulk and business data in a real database. Use custom resources only to describe infrastructure intent, and have them point to the data.

ANTI-PATTERN 02Avoid
What it looks like

A team builds a full aggregated API server for something a CRD could easily describe, because it sounds more powerful.

Why it hurts

They inherit months of build effort, a permanent on-call burden and a new security surface, without using a single capability that CRDs lack.

Better approach

Start with a CRD. Move to aggregation only when you can name the specific capability that a CRD cannot provide.

ANTI-PATTERN 03Avoid
What it looks like

A CRD is published with a loose schema that accepts anything, or with no versioning plan, and the shape changes between releases.

Why it hurts

Bad data enters the cluster, controllers crash on unexpected input, and users cannot upgrade without breaking their existing objects.

Better approach

Define strict schemas, start with an early version name, add conversion before any breaking change and migrate stored objects before retiring old versions.

ANTI-PATTERN 04Avoid
What it looks like

A critical aggregated API runs as a single replica, with a hand-managed certificate, and without alerts on its availability.

Why it hurts

One crash or one expired certificate makes the API unavailable, causes discovery errors across the cluster and can leave namespaces stuck during deletion.

Better approach

Run multiple replicas across zones, automate certificate rotation and alert on the APIService condition.

ANTI-PATTERN 05Avoid
What it looks like

A controller writes the status of every object every few seconds, even when nothing has changed.

Why it hurts

It generates constant write traffic and watch events, wasting etcd capacity and waking every other controller that watches the type.

Better approach

Only update when something truly changed, and batch changes together.

21Best Practices and Common Mistakes

Here is a checklist to take into your next design review.

Best practices for CRDs

  • Own your group name. Use a domain you control, so that names never clash with other projects.
  • Write a strict structural schema with types, limits, defaults and descriptions for every field.
  • Use expression rules for cross-field checks, rather than adding a webhook that needs to be run and kept alive.
  • Enable the status subresource, and keep user intent and observed state apart.
  • Add printer columns so that a simple listing tells users what they need to know.
  • Plan versions early. Start with an alpha version, use conversion only when needed and migrate stored data before removing old versions.
  • Protect the CRD. Tight permissions for create, edit and delete, and backups of both the CRD and its instances.
  • Prefer namespaced scope unless the thing is genuinely cluster-wide.

Best practices for aggregated APIs

  • Justify it in writing. Document the capability that a CRD cannot provide, and revisit it at each major Kubernetes release as CRDs gain features.
  • Use the official libraries rather than inventing your own handling of watch, versions and authentication.
  • Delegate authentication and authorization to the main API server.
  • Verify certificates in both directions and automate their renewal.
  • Run it highly available and monitor the APIService condition.
  • Plan the data layer for backups, encryption, scaling and recovery.
  • Keep up with upgrades. Assign a team to follow Kubernetes releases and update the server’s dependencies regularly.

Common mistakes

  • Reaching for aggregation because it feels more serious, when a CRD would do.
  • Treating the cluster database as an application database.
  • Putting secrets directly into custom resource fields.
  • Deleting a CRD without realizing that all its objects disappear.
  • Forgetting finalizers, leaving outside resources such as cloud disks behind.
  • Leaving finalizers on objects after the controller has been removed, which makes them impossible to delete.
  • Building conversion that loses information, so objects change when read through another version.
  • Skipping alerts on APIService availability and controller health.
  • Making many tiny CRDs with overlapping meaning, instead of a clear small set.

22Real-World Usage

The balance of use in the wild shows how the guidance plays out in practice: a great many CRDs, and a small number of carefully justified aggregated servers.

The operator ecosystem: CRDs by default

Nearly all popular platform projects, including certificate management, monitoring operators, GitOps controllers, service meshes, cluster provisioning and cloud resource managers, are built on CRDs with controllers. Their authors chose the free storage, the inherited security and the broad tool compatibility over custom control.

Metrics: aggregation for live data

The resource metrics, custom metrics and external metrics interfaces used by autoscalers are served by aggregated API servers. The data is short-lived and computed from monitoring sources, so keeping it out of etcd is exactly the right call, and a standard group name lets autoscalers find it.

Virtual machines: a hybrid

KubeVirt describes virtual machines with CRDs, so that controllers and tools can manage them like any other object. For interactive actions such as console and display access, it adds an aggregated API that serves subresources, which CRDs cannot do. This shows the hybrid pattern working at scale.

Lessons from retired projects

Service Catalog was built as an aggregated API server when CRDs were less capable. As CRDs gained schemas, validation and versioning, the case for running a separate server weakened, and the project was eventually retired. The lesson: the capability gap between the two mechanisms shrinks over time, so recheck your reasons before committing to aggregation.

Platform teams building internal APIs

Many companies expose their own internal platform concepts, such as an application, a team environment or a database request, as custom resources, so developers use the same declarative tools for infrastructure as for code. Some go further and layer higher-level abstractions on top, composing many lower-level resources into a single simple request.

23Frequently Asked Questions

Quick answers to the questions that come up most often.

Q1Is a CRD an API?
Yes, in the practical sense. Once a CRD is applied, the Kubernetes API serves a new, fully working REST API for that type, with create, read, update, delete, list and watch. You did not write the server. The built-in extensions server provides it.
Q2Do CRDs and aggregated APIs look different to users?
No. Both appear as normal Kubernetes types in command-line tools and are controlled by the same permission system. Only the cluster operators know which kind serves which type.
Q3Can a CRD run code?
Not by itself. A CRD stores and validates data. Behavior comes from a controller, a separate program that watches the objects. The schema can include expression rules that run inside the API server, but those only check data and never make changes in the outside world.
Q4Why can’t a CRD store data outside etcd?
Because the built-in extensions server always saves custom resources in the cluster’s own database. Choosing other storage is a capability that only your own extension server can offer.
Q5What is an APIService?
It is a Kubernetes object that registers an extension API server. It tells the aggregator at the front of the API server to forward requests for a certain group and version to a named service.
Q6Do I need webhooks with CRDs?
Not always. Schemas, defaults and expression rules cover many needs. Webhooks are needed for checks that depend on outside information, for complicated changes to incoming objects and for conversion between versions that differ in shape.
Q7What is the difference between a CRD and an operator?
A CRD defines a new type. An operator is a CRD together with the controller that manages it, usually encoding the knowledge of running one specific application. The CRD is the form, and the operator is the form plus the person who acts on it.
Q8Can I use both in one project?
Yes. A common design uses CRDs for the main objects and an aggregated server only for special actions or computed views, as KubeVirt does.
Q9What happens to my objects if the extension server is deleted?
If the server owned the storage, the data stays where you stored it, but the API is unavailable until the server returns. Check the APIService, because an unavailable registration can also cause discovery warnings and stuck namespace deletions.
Q10How many CRDs are too many?
There is no fixed number, but each CRD adds memory and discovery work to the API server. Large numbers, in the many hundreds or beyond, deserve testing and monitoring. Keep the set of types small, clear and well designed.
Q11Will CRDs eventually replace the need for aggregation?
They keep closing the gap, with richer validation, better versioning and more features over time. But some needs, such as external storage and custom streaming actions, are fundamental to how aggregation works, so both mechanisms are expected to remain.
Q12How do I decide quickly?
Default to a CRD with a controller. Choose aggregation only if you can point to a specific need, such as data outside the cluster database, live computed data or custom actions, that a CRD cannot meet, and only if a team is ready to run the server for years.

24Summary and Key Takeaways

What to remember

  • Kubernetes is an extensible API. Adding a new type gives you permissions, watches, tooling and audit trails that your teams already know.
  • There are two official doors. A CRD teaches the built-in extensions server a new type. An aggregated API registers your own separate server behind the front desk.
  • CRDs are the default. You write a schema, and the API server handles storage, validation, watch, security and high availability. A controller adds behavior.
  • Aggregation is for special needs. Choose it for data outside etcd, computed or live data, custom verbs and subresources, or logic that schemas cannot express, and accept the cost of running a server.
  • The failure modes differ. A broken extension server can cause discovery errors and stuck namespaces. A deleted CRD removes all its objects. Plan monitoring and protection for both.
  • Security is shared, but owned differently. CRDs inherit the cluster’s protections. Aggregated servers must correctly wire up certificates, delegated authorization and their own data security.
  • Start simple and decide with a method. Ask whether a ConfigMap is enough, then whether any need truly rules out a CRD. Hybrids are valid, and the capability gap between the two keeps shrinking.