The question this answers
Which Kubernetes objects actually matter, and what problem does each one exist to solve?
A team shipping an API, a worker, a nightly report and a database needs to express: how many replicas, how they are reached, where configuration comes from, what runs once, what runs on a schedule, and how two teams share a cluster without colliding.
A small, composable vocabulary in which every production concern has a named home — so a new engineer can read a directory of manifests and correctly predict what the cluster will do.
Each object is an answer to a specific problem
The fastest way to drown in Kubernetes is to learn objects as a list of fields. The fastest way to actually understand them is to ask, for each one, *what breaks without it*. Every object below was added because somebody had a real problem that the existing objects could not express.
Read the middle column first. If the problem in that column is not a problem you have, you do not need the object — and a manifest directory containing objects for problems the team does not have is one of the clearest symptoms of cargo-culted infrastructure.
| Object | The problem it solves | What you would do without it |
|---|---|---|
| Pod | A container needs an address, a lifecycle, and possibly a helper process sharing both. | Run a container directly on a host and manage its network by hand. |
| Deployment | N identical stateless replicas must exist, and be replaced gradually on release. | Start and stop replicas manually, and script the rollout order. |
| Service | Pod addresses change on every replacement, so callers cannot hard-code them. | Maintain your own service registry and teach every client to read it. |
| ConfigMap | The same image must behave differently in staging and production. | Bake configuration into the image and build one image per environment. |
| Secret | Credentials must reach the workload without living in the image or the repository. | Ship credentials in environment files on each host, with no access control around them. |
| Ingress / Gateway | External HTTP traffic must reach the right service by host and path, with TLS terminated. | Run and configure a reverse proxy yourself, per cluster, per environment. |
| StatefulSet | Replicas need stable identities and their own durable storage — a database, not an API. | Run stateful workloads outside the cluster, which is frequently the better answer anyway. |
| Job | A task must run to completion exactly once, and be retried if it fails. | Run a container manually and hope nobody starts it twice. |
| CronJob | A Job must run on a schedule without a machine owning the crontab. | A crontab on one machine, which is also a single point of failure. |
| Namespace | Two teams share a cluster and must not collide on names, quotas or access. | One cluster per team, or a naming convention enforced by hope. |
How they compose — the smallest realistic set
Here is what a plain HTTP service actually needs, with nothing added for style. A Deployment declares the replicas. A Service gives them one stable name. A ConfigMap supplies the environment-specific values, and a Secret supplies the credential. That is the whole set — four objects — and it is a complete, defensible production deployment.
Note what the YAML is doing and what it is not. It is *stating desired state*; it is not a sequence of commands, and reading it top to bottom is not reading an execution order. The selector is the load-bearing part and the part most often wrong: the Service finds pods by label, not by belonging to the Deployment, so a mismatched label produces a Service that resolves to nothing while every pod is perfectly healthy.
1apiVersion: apps/v12kind: Deployment3metadata: { name: api }4spec:5 replicas: 3 # the declared count the controller keeps true6 selector:7 matchLabels: { app: api } # which pods this Deployment owns8 template:9 metadata:10 labels: { app: api } # must match the selector above AND the Service below11 spec:12 containers:13 - name: api14 image: registry.example/api:v7 # a tag here; a digest is safer — see container-registry15 envFrom:16 - configMapRef: { name: api-config }17 - secretRef: { name: api-db } # a Secret object is NOT encrypted at rest by default18 resources:19 requests: { cpu: 250m, memory: 512Mi } # what the scheduler reserves20 limits: { memory: 512Mi } # exceed this and the container is killed21 readinessProbe:22 httpGet: { path: /healthz, port: 8080 }23---24apiVersion: v125kind: Service26metadata: { name: api }27spec:28 selector: { app: api } # matches pods by label — a typo here yields an empty Service29 ports:30 - port: 8031 targetPort: 8080The objects a new cluster does not need
The failure mode of this vocabulary is enthusiasm. A team's first service arrives with a HorizontalPodAutoscaler that has never been triggered, a PodDisruptionBudget that blocks node drains, a NetworkPolicy nobody can read, a ServiceMonitor pointing at a metrics endpoint that does not exist, and a custom resource for a mesh whose sidecar doubles cold-start time. Each was added because a blog post had it.
The rule that holds up: add an object when you can name the incident it prevents. An HPA is right once you have measured a traffic pattern that needs it — see Horizontal Pod Autoscaling — and Why New Capacity Is Always Late. A PodDisruptionBudget is right once node drains have actually caused a capacity dip. Until then it is a config file that will page someone at an inconvenient moment.
# api/ # deployment.yaml replicas: 3 # service.yaml # configmap.yaml # secret.yaml # hpa.yaml target 60% CPU — CPU has never exceeded 12% # pdb.yaml minAvailable: 3 of 3 — silently blocks every node drain # networkpolicy.yaml copied from a blog; nobody can say what it denies # servicemonitor.yaml scrapes /metrics — the app does not expose /metrics # virtualservice.yaml service mesh, for four services # destinationrule.yaml # certificate.yaml # ingress.yaml
# api/ # deployment.yaml replicas: 3 -> survives one node loss # service.yaml -> stable name for changing pod IPs # configmap.yaml -> same image, different environments # secret.yaml -> DB credential out of the image # # ingress.yaml added when the service must be reachable from outside. # hpa.yaml added when a measured traffic pattern justifies it. # pdb.yaml added after the first node drain caused a real capacity dip.
Twelve objects are not more production-ready than four; they are eight more things to debug at 03:00. The minAvailable: 3 of 3 budget in the first version is a genuine landmine — it makes every node drain hang forever.
Key points
- Learn each object as the answer to a problem: pod IPs change (Service), config differs per environment (ConfigMap), a task must run once (Job), two teams share a cluster (Namespace).
- A complete production service is often four objects — Deployment, Service, ConfigMap, Secret — and adding more is a decision that needs a reason.
- Services find pods by label selector, not by ownership; a label typo produces a healthy Deployment behind a Service that routes to nothing.
- A Secret object is a separate kind mainly for access control and handling, not because it is encrypted — see ConfigMap vs Secret — and the Honest Limit of a Secret.
- Add an object when you can name the incident it prevents. A PodDisruptionBudget of 3-of-3 blocks node drains and will page someone.
The loop, answered
Every field is required, which is why no lesson here can recommend something without saying what it costs and what simpler thing to consider first.
- • Every object is a record in the cluster datastore with an
apiVersion, akind, metadata including labels, aspec(desired) and astatus(actual). - • Higher-level objects create lower-level ones: a Deployment creates a replica set, which creates pods; a CronJob creates Jobs, which create pods.
- • Objects are wired to each other by label selectors rather than by references, which is why labels are load-bearing configuration rather than documentation.
- • Each object kind has a controller responsible for reconciling its spec against its status.
- • Namespaces scope names, quotas and access; cluster-scoped objects such as nodes and storage classes sit outside them.
- • The manifests themselves: they are source code, they belong in review, and they are production access — see Infrastructure as Code.
- • Label discipline, because selectors are how everything finds everything else and a rename is a routing change.
- • API version migrations: deprecated object versions are removed on a schedule, and an upgrade will refuse manifests that were fine last year.
- • Object sprawl — every object is reconciled forever, so unused ones cost control-plane work and reviewer attention.
- • Selector mismatch: the Service has no endpoints, requests fail immediately, and every pod reports perfectly healthy.
- • A ConfigMap or Secret referenced but not created: pods sit in
CreateContainerConfigErrorand never start. - • A CronJob with a long-running Job and no concurrency policy: overlapping runs pile up and duplicate work.
- • A restrictive PodDisruptionBudget: node drains hang indefinitely, and cluster upgrades stall with no obvious cause.
- • A deprecated API version after an upgrade: manifests that deployed last month are rejected outright.
- • Object count, not traffic, is what pressures the control plane — CI pipelines that create and destroy namespaces per branch are a common surprise.
- • Label cardinality matters for selector evaluation and much more for the monitoring system reading those labels.
- • Namespaces scale organizationally, not technically; the limit you hit is human ownership rather than any cluster ceiling.
- • RBAC is expressed per object kind and namespace, so the object model is also the permission model — read access to Secrets is the one to guard hardest.
- • Namespaces are a name and policy boundary, not a hard security boundary; workloads in different namespaces share nodes and a network unless you add policy.
- • Manifests carry image references, so supply-chain control lives here too — pin by digest rather than by tag, see The Container Registry.
- • Custom resources extend the API and their controllers usually run with broad permissions; adding one is adding a privileged component.
- • Objects themselves cost control-plane storage and reconciliation, which is small until object churn is large.
- • The expensive objects are the ones that provision infrastructure: a Service of load-balancer type creates a real, billed load balancer per Service.
- • Persistent volume claims allocate real disks that keep billing after the workload that used them is deleted — a classic orphaned-cost source.
- • Endpoint counts per Service — the direct check that selectors match something.
- • Object events, which are where the cluster explains its refusals in plain language and are the most under-read signal in Kubernetes.
- • Deprecated API usage warnings, which are an upgrade blocker you want to see months early.
- • The signal that lies:
kubectl get podsshowing everythingRunning. Running says the process started, not that the Service routes to it or that it can serve.
- • A compose file, if the whole system is a handful of containers on one host — it expresses the same four concerns in a tenth of the text.
- • A managed container service where the platform's task definition covers the Deployment, Service and configuration concerns in a single object.
- • A PaaS manifest that reduces all of this to a process list and a set of environment variables.
- • Templating or packaging tools only after the raw objects are understood; a chart that hides an object you cannot debug is a liability, not a simplification.
- • Buys a precise vocabulary for every production concern; costs a genuinely large surface area that every engineer must learn to be productive.
- • Buys composability through label selectors; costs a class of silent misconfiguration where everything is healthy and nothing is connected.
- • Buys declarative review of infrastructure changes; costs verbosity — four objects and eighty lines for what a PaaS expresses in five.
What people believe, and what is true
A Service knows which Deployment it belongs to.
It knows a label selector. It routes to any pod carrying those labels, regardless of what created the pod — which is both a feature and a common outage.
Secrets are encrypted, that is why they are a separate kind.
By default they are base64-encoded in the datastore. The separate kind buys different RBAC and different handling, not encryption.
More objects means a more production-ready deployment.
Every object is a thing that can be misconfigured. An HPA that never triggers and a PDB that blocks drains are net negatives.