Skip to content
Documentation Background

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.


Kubernetes API and Object Model

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)

Even though you only write to spec and read from status, API GET and PUT requests always send and return the entire object.


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:

Controller Events in etcd
  • 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
Controller Events description

Example: Viewing Controller Events

Terminal window
kubectl get events
LAST SEEN TYPE REASON OBJECT MESSAGE
3m45s Normal Scheduled pod/nginx-deployment-7fb96c846b-8q2zm Successfully assigned default/nginx-deployment-7fb96c846b-8q2zm to node-1
3m44s Normal Pulling pod/nginx-deployment-7fb96c846b-8q2zm Pulling image "nginx:1.14.2"
3m42s Normal Pulled pod/nginx-deployment-7fb96c846b-8q2zm Successfully pulled image "nginx:1.14.2"
3m41s Normal Created pod/nginx-deployment-7fb96c846b-8q2zm Created container nginx
3m41s Normal Started pod/nginx-deployment-7fb96c846b-8q2zm Started container nginx
3m45s Normal SuccessfulCreate replicaset/nginx-deployment-7fb96c846b Created pod: nginx-deployment-7fb96c846b-8q2zm
3m45s Normal ScalingReplicaSet deployment/nginx-deployment Scaled up replica set nginx-deployment-7fb96c846b to 1

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

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>

Example: kubectl describe command on a Node object

Terminal window
kubectl describe node controlplane
Name: controlplane
Roles: control-plane
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=controlplane
kubernetes.io/os=linux
node-role.kubernetes.io/control-plane=
node.kubernetes.io/exclude-from-external-load-balancers=
Annotations: node.alpha.kubernetes.io/ttl: 0
volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp: Mon, 20 Jul 2026 19:13:35 +0000
Taints: <none>
Unschedulable: false
Lease:
HolderIdentity: controlplane
AcquireTime: <unset>
RenewTime: Thu, 23 Jul 2026 16:13:57 +0000
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
NetworkUnavailable False Mon, 20 Jul 2026 19:14:16 +0000 Mon, 20 Jul 2026 19:14:16 +0000 CiliumIsUp Cilium is running on this node
MemoryPressure False Thu, 23 Jul 2026 16:11:45 +0000 Mon, 20 Jul 2026 19:13:34 +0000 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False Thu, 23 Jul 2026 16:11:45 +0000 Mon, 20 Jul 2026 19:13:34 +0000 KubeletHasNoDiskPressure kubelet has no disk pressure
PIDPressure False Thu, 23 Jul 2026 16:11:45 +0000 Mon, 20 Jul 2026 19:13:34 +0000 KubeletHasSufficientPID kubelet has sufficient PID available
Ready True Thu, 23 Jul 2026 16:11:45 +0000 Mon, 20 Jul 2026 19:13:35 +0000 KubeletReady kubelet is posting ready status
Addresses:
InternalIP: 172.30.1.2
Hostname: controlplane
Capacity:
cpu: 1
ephemeral-storage: 19221248Ki
hugepages-2Mi: 0
memory: 2300144Ki
pods: 110
Allocatable:
cpu: 1
ephemeral-storage: 18698430040
hugepages-2Mi: 0
memory: 2197744Ki
pods: 110
System Info:
Machine ID: b4f277f01b6649c285b0f3d7f640d633
System UUID: 232503de-96e1-46b1-9460-6b9e6c84006d
Boot ID: 0f48326f-48eb-4636-82e1-c7917fba2d61
Kernel Version: 6.8.0-136-generic
OS Image: Ubuntu 24.04.4 LTS
Operating System: linux
Architecture: amd64
Container Runtime Version: containerd://2.2.1
Kubelet Version: v1.35.1
Kube-Proxy Version:
PodCIDR: 192.168.0.0/24
PodCIDRs: 192.168.0.0/24
Non-terminated Pods: (10 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age
--------- ---- ------------ ---------- --------------- ------------- ---
kube-system cilium-7l688 100m (10%) 1 (100%) 10Mi (0%) 1Gi (47%) 2d21h
kube-system cilium-envoy-tc5rr 0 (0%) 0 (0%) 0 (0%) 0 (0%) 2d21h
kube-system cilium-operator-768c98966f-9n259 0 (0%) 0 (0%) 0 (0%) 0 (0%) 2d21h
kube-system coredns-5f68d5bd7f-cl5xn 50m (5%) 0 (0%) 50Mi (2%) 170Mi (7%) 2d20h
kube-system coredns-5f68d5bd7f-qrs4d 50m (5%) 0 (0%) 50Mi (2%) 170Mi (7%) 2d20h
kube-system etcd-controlplane 25m (2%) 0 (0%) 100Mi (4%) 0 (0%) 2d20h
kube-system kube-apiserver-controlplane 50m (5%) 0 (0%) 0 (0%) 0 (0%) 2d20h
kube-system kube-controller-manager-controlplane 25m (2%) 0 (0%) 0 (0%) 0 (0%) 2d20h
kube-system kube-scheduler-controlplane 25m (2%) 0 (0%) 0 (0%) 0 (0%) 2d20h
local-path-storage local-path-provisioner-644f8b49d7-f2rts 0 (0%) 0 (0%) 0 (0%) 0 (0%) 2d20h
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 325m (32%) 1 (100%)
memory 210Mi (9%) 1364Mi (63%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
Events: <none>

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

Properties of an Event Object:

PropertyDescription
NameThe name of this Event object instance. Useful only if you want to retrieve the given object directly from the API.
TypeThe type of the event. Typically Normal or Warning.
ReasonA machine-facing description of why the event occurred.
SourceThe component that reported this event, usually a controller.
ObjectThe object instance to which the event refers (e.g., pod/nginx).
Sub-objectThe specific sub-object to which the event refers (e.g., a specific container within a pod).
MessageThe human-facing description of the event.
First seenThe first time this event occurred. Since Event objects are purged over time, this may not be the absolute first time the event actually occurred in the cluster’s history.
Last seenEvents often occur repeatedly. This field indicates when this specific event last occurred.
CountThe number of times the event has occurred.
Status 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.

Example:

Terminal window
kubectl get node controlplane -o json | jq .status.conditions
[
{
"lastHeartbeatTime": "2026-03-20T19:14:16Z",
"lastTransitionTime": "2026-03-20T19:14:16Z",
"message": "Cilium is running on this node",
"reason": "CiliumIsUp",
"status": "False",
"type": "NetworkUnavailable"
},
{
"lastHeartbeatTime": "2026-03-23T15:56:26Z",
"lastTransitionTime": "2026-03-20T19:13:34Z",
"message": "kubelet has sufficient memory available",
"reason": "KubeletHasSufficientMemory",
"status": "False",
"type": "MemoryPressure"
},
{
"lastHeartbeatTime": "2026-03-23T15:56:26Z",
"lastTransitionTime": "2026-03-20T19:13:34Z",
"message": "kubelet has no disk pressure",
"reason": "KubeletHasNoDiskPressure",
"status": "False",
"type": "DiskPressure"
},
{
"lastHeartbeatTime": "2026-03-23T15:56:26Z",
"lastTransitionTime": "2026-03-20T19:13:34Z",
"message": "kubelet has sufficient PID available",
"reason": "KubeletHasSufficientPID",
"status": "False",
"type": "PIDPressure"
},
{
"lastHeartbeatTime": "2026-03-23T15:56:26Z",
"lastTransitionTime": "2026-03-20T19:13:35Z",
"message": "kubelet is posting ready status",
"reason": "KubeletReady",
"status": "True",
"type": "Ready"
}
]

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