Skip to content

Kubernetes API & Object Model

Kubernetes is completely API-centric. Every resource - Pods, Services, Deployments, StatefulSets - is defined in the API. All configuration and all querying must pass through the API server. Understanding the API’s shape is the prerequisite for understanding everything else in Kubernetes.

The API server is the front-end for the Kubernetes API and the central communication hub for the entire cluster. Every interaction with Kubernetes - kubectl commands, kubelet status reports, control plane components sharing state - is a REST call directed at the API server.

  • Exposes a secure RESTful HTTPS interface, typically on port 443 or 6443
  • All connections are encrypted with TLS; every request must pass authentication and authorization before it is accepted
  • Runs as a set of Pods in the kube-system namespace on self-managed clusters; cloud providers fully manage it on hosted services
  • Is stateless - it reads and writes all cluster state to etcd

The API server manages resources using standard HTTP verbs mapped directly to CRUD operations:

CRUDHTTP methodkubectl equivalent
CreatePOSTkubectl create -f / kubectl apply -f
ReadGETkubectl get
UpdatePUT / PATCHkubectl apply -f / kubectl edit
DeleteDELETEkubectl delete

Kubernetes supports two serialisation formats:

FormatUsed forWhy
JSONExternal clients (kubectl, your code)Human-readable, universally supported
ProtobufInternal cluster component communicationFaster, more compact, better at scale

Clients indicate the format they accept via the HTTP Accept header; the API server responds with Content-Type: application/json for external requests.


The fundamental rule of Kubernetes: every entity in the cluster is an object. Pods, Nodes, Namespaces, ConfigMaps, Deployments, Services - all are objects stored in etcd, all are manipulated through the same API, and all follow the same manifest structure.

In object-oriented terms: a primitive (or resource type) is the blueprint, and an object is a running instance of that blueprint. Kubernetes assigns every object a system-generated UID to ensure it is uniquely identifiable even if objects of the same name are created and destroyed over time.


Every Kubernetes object is defined in a manifest - a structured YAML (or JSON) file. Manifests share a consistent five-section structure regardless of object type:

apiVersion: apps/v1 # API group + version
kind: Deployment # resource type
metadata: # identity fields
name: my-app
namespace: production
labels:
app: my-app
spec: # desired state - you write this
replicas: 3
...
status: # actual state - system writes this
availableReplicas: 3
...
SectionPurposeWho writes it
apiVersionDefines the schema version and API group (e.g. apps/v1, v1)You
kindDeclares the resource type (Pod, Deployment, Service…)You
metadataIdentity: name, namespace, labels, annotations, uid, resourceVersionYou (uid/resourceVersion set by system)
specDesired state - what you want the object to look likeYou
statusActual state - what is currently runningSystem (controllers)

Kubernetes originally stored all resources in a single flat namespace. As the project grew, the API was reorganised into groups - modular collections of related resources.

Group typeREST pathContains
Core group/api/v1Original objects: Pods, Nodes, Services, Namespaces, ConfigMaps, Secrets
Named groups/apis/{group}/{version}/All newer resources: e.g. apps/v1 for Deployments, networking.k8s.io/v1 for Ingresses

The core group predates the concept of API groups and carries no group name in its path (represented as "" in documentation).

A useful mnemonic for the named-group REST path structure is GVR - Group, Version, Resource:

/apis/apps/v1/deployments
^^^^ ^^ ^^^^^^^^^^^
Group Version Resource
Terminal window
kubectl api-resources # list all resource types, groups, shortnames, and scope
kubectl api-versions # list all enabled API versions (e.g. apps/v1, batch/v1)

All new Kubernetes resources move through three maturity stages:

StageCharacteristicsDefault enabled
AlphaExperimental; bugs expected; features may be removed without notice❌ Usually disabled
BetaNear-final; enabled by default; sometimes used in production (with care)✅ Yes
GA (Stable)Production-ready; long-term commitment from the project✅ Yes

Kubernetes has strict rules preventing API sprawl:

  • Beta resources must either release a newer beta version or graduate to GA within 9 months of their last release
  • GA resources are only deprecated once a newer stable version exists, then supported for 12 months or 3 releases (whichever is longer) before removal
  • Deprecated resources trigger a CLI deprecation warning, a k8s.io/deprecated:true audit log annotation, and a Prometheus gauge metric

Kubernetes strongly prefers a declarative approach to managing objects: you describe the end state you want, and Kubernetes figures out how to get there.

ModelHow it worksWhen to use
DeclarativeDescribe desired state in a YAML manifest; kubectl apply itProduction, GitOps, anything that needs version control
ImperativeIssue direct commands (kubectl run, kubectl create)Quick experiments, exam environments, one-off debugging

The declarative model rests on three concepts that apply to every object in the cluster:

  • Desired state - what you specified in the manifest (spec)
  • Observed state - what is actually running right now (status)
  • Reconciliation - the continuous process of closing the gap between the two
flowchart LR
    A["Desired state (spec)"] --> B["Controller compares"]
    B --> C{Gap?}
    C -- Yes --> D["Controller acts to close gap"]
    C -- No --> E["No action"]
    D --> B

Why the declarative model matters in practice:

  • Self-healing: if a node kills 2 of your 10 replicas, the observed count drops to 8 - a controller immediately schedules 2 replacements
  • Rollouts: update the image tag in your YAML and apply it - the Deployment controller performs a rolling replacement without you scripting a thing
  • GitOps-ready: manifests are plain files that live in version control, giving you auditable history and one-command rollback

Applications are rarely deployed as bare Pods. Kubernetes uses object nesting - wrapping objects inside higher-level controllers - where each layer adds operational value:

flowchart TD
    A["Container - app code + dependencies"] --> B["Pod - schedules container on the cluster"]
    B --> C["Deployment - self-healing, scaling, rolling updates"]

The Pod is the atomic, minimum unit of scheduling in Kubernetes. No workload - container, VM, or Wasm app - runs directly on a cluster; it must be wrapped in a Pod first.

Key characteristics:

  • A Pod creates a shared execution environment (network stack, storage volumes, shared memory) for one or more containers
  • All containers in a Pod share the same IP address and can reach each other via localhost
  • Starting a Pod is atomic - Kubernetes only marks it ready once every container inside it is running
  • Pods are mortal and immutable - when a Pod dies it is replaced, never resurrected; the replacement gets a new ID and IP. You never mutate a running Pod; you replace it

Pods are also the unit of scaling: to scale up, Kubernetes adds more Pods; to scale down, it removes Pods. You do not add containers to an existing Pod to scale.

A Deployment wraps Pods and adds:

  • Self-healing - replaces failed Pods to maintain the desired replica count
  • Scaling - declaratively change the replica count
  • Rolling updates and rollbacks - replace Pods progressively with zero downtime; revert if something goes wrong

Deployments run as a background watch loop: the Deployment controller continuously compares the observed Pod count against the desired count in the spec and takes corrective action.

Because Pods are mortal, their IP addresses change whenever they are replaced. This IP churn problem makes it impossible to hard-code Pod IPs into client configuration.

A Service solves this by providing a stable network front-end for a dynamic set of Pods:

  • Front-end: a stable DNS name, ClusterIP, and port that never changes
  • Back-end: a continuously updated list of healthy Pods, selected by labels; traffic is load-balanced across them

As Pods come and go (scaling, rollouts, node failures), the Service tracks the changes automatically - clients always reach a live Pod without any reconfiguration.


Controllers are the engines that bring objects to life. For each object type there is usually a dedicated controller (Deployment controller, ReplicaSet controller, StatefulSet controller, etc.).

Every controller runs the same loop:

  1. Read the desired state from the object’s spec
  2. Observe the actual state of the cluster
  3. Act to close any gap - this may mean creating/deleting objects, talking to cloud APIs, updating node configuration
  4. Report the result by updating the object’s status

Controllers never act on infrastructure directly - they write objects to the API server and let downstream components (kubelets, the cloud controller manager) react.

Controllers also generate events. As they work, they emit Event objects that record what they did or warn about failures. Events are:

  • Categorised as Normal (informational) or Warning (something prevented reconciliation)
  • Short-lived - automatically purged ~1 hour after creation to avoid flooding etcd
  • The first place to look when diagnosing cluster problems

kubectl explain is an inline documentation tool that lets you explore any object’s fields without leaving the terminal:

Terminal window
kubectl explain pods # top-level fields for Pod
kubectl explain pods.spec.containers # drill into containers list
kubectl explain deployment.spec.strategy.rollingUpdate
kubectl explain nodes --recursive # full field tree (no descriptions)
kubectl explain nodes --api-version=v1 # pin to a specific schema version

Use it liberally when writing manifests - it prevents typos and tells you whether a field expects a string, a list, or a nested object.

kubectl describe - human-readable summaries

Section titled “kubectl describe - human-readable summaries”

kubectl describe aggregates data from multiple API endpoints into one readable view. For a Node it shows properties, running Pods, and recent events in a single output:

Terminal window
kubectl describe node <name>
kubectl describe pod <name>
kubectl describe deployment <name>

Because Event objects are standalone (not embedded in the object they describe), you query them separately:

Terminal window
kubectl get events # all recent events
kubectl get events -o wide # extra columns (source, count, timestamps)
kubectl get events --field-selector type=Warning # warnings only
kubectl get node <name> -o json | jq .status.conditions # isolate Node conditions

Many objects express their health as an array of conditions rather than a single state field. Each condition is independent:

Condition fieldValuesMeaning
typee.g. Ready, MemoryPressureWhat aspect is being measured
statusTrue / False / UnknownCurrent evaluation
reasonshort machine stringWhy it last changed
messagehuman-readable stringDetailed context

Using an array of orthogonal conditions - rather than a single state - makes the API extensible: new conditions can be added without breaking existing clients that only watch for specific ones.


The Kubernetes API is designed to be extended. The primary extension mechanism is CustomResourceDefinitions (CRDs):

  • A CRD registers a new resource type in the API - it gets its own REST path, works with kubectl, and is stored in etcd like any native object
  • CRDs alone are inert - to make a custom resource do something you must deploy a custom controller that watches for objects of that type and reconciles them
  • This controller + CRD pairing is the foundation of the Operator pattern - the standard way to encode operational knowledge about stateful applications into Kubernetes itself