Full lesson
Explore the full explanation, examples, and visuals at your own pace.
team-a namespace, five minutes after apply
The Deployment says zero of three ready, but there are no Pods at all. Not Running, not even Pending. Five minutes in, kubectl get pods returns nothing, so there's no Pod to describe and no logs to read. That means the failure happened upstream, before any Pod existed.
$ kubectl get deployment orders-api -n team-a
NAME READY UP-TO-DATE AVAILABLE AGE
orders-api 0/3 0 0 5m
$ kubectl get pods -n team-a -l app=orders-api
No resources found in team-a namespace.Who actually creates a Pod
A Deployment doesn't create Pods itself. It creates a ReplicaSet, and the ReplicaSet asks the API server to create each Pod. The API server runs that request through admission, and only if admission passes does the Pod get stored in etcd. Until it's stored, the Pod doesn't exist, so there's nothing to list.
The rejection happens before storage
Follow the call. The ReplicaSet asks the API server to create a Pod. The API server asks quota admission whether it's allowed. The quota check denies it, and the API server returns 403 Forbidden. The Pod is never stored. The ReplicaSet records a FailedCreate event instead, then retries with backoff. So the evidence lands on the ReplicaSet, not on a Pod.
Two very different failures
These two failures look similar but differ in one thing: whether the Pod object exists. A Pending Pod is stored, just unscheduled, so kubectl describe pod shows its events. A Pod that was never created was refused at admission, so there's nothing to describe. Look at the controller that tried to create it.
Where the error is recorded
When admission rejects the Pod, nothing gets stored, so the error has to land somewhere else. The ReplicaSet records a FailedCreate event each time its create call is refused. It then reports ReplicaFailure on the Deployment. Meanwhile, get pods shows nothing, because there's no Pod object to list. So read the ReplicaSet events.
Cause 1: quota demands resources, Pod sets none
The quota tracks requests and limits for CPU and memory, so every new Pod has to declare them. But the orders-api container has no resources block, and there's no LimitRange to fill in defaults. Quota admission can't count what isn't declared, so it rejects each Pod create.
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-quota
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
---
# orders-api container
containers:
- name: orders-api
image: orders-api:1.4
# no resources block, no LimitRangeCause 2: quota already used up (illustrative)
Now the manifest is fine, with resources set. The problem is capacity. The team-a quota allows 4 CPU cores, and other workloads already use 3.8. That leaves 0.2. One replica asks for 0.5, so quota admission rejects the create, even though the Pod spec is correct.
Chart values
| Label | CPU cores |
|---|---|
| Hard limit | 4 |
| Used | 3.8 |
| Remaining | 0.2 |
| One replica | 0.5 |
Same symptom, other gatekeepers
Quota isn't the only gatekeeper. Pod Security Admission, policy webhooks like Kyverno or Gatekeeper, or any validating admission can deny creation. You'd see the same zero Pods and the same FailedCreate trail, though message wording varies by version and tool.
- Pod Security Admission blocks privileged specs
- Policy webhooks like Kyverno or Gatekeeper
- Any validating admission that denies creation
Diagnostic path (output illustrative; wording varies)
Start at the Deployment. Its ReplicaFailure condition says FailedCreate, which tells you Pods were never made. Next, describe the ReplicaSet. Its event gives the reason: the quota rejected the Pod. Finally, check the quota's used versus hard values. That separates a missing resource spec from an exhausted 4 CPU limit.
$ kubectl describe deployment orders-api -n team-a
ReplicaFailure True FailedCreate
$ kubectl describe replicaset orders-api-7d9f8c -n team-a
Warning FailedCreate pods "orders-api-7d9f8c-x2k" is forbidden:
failed quota: team-a-quota: must specify limits.cpu,...
$ kubectl describe resourcequota team-a-quota -n team-a
Resource Used Hard
requests.cpu 3800m 4Pick a fix, accept its trade-off
You have three fixes, and each costs something. Setting explicit requests and limits is the most accurate, but you edit every workload. A LimitRange gives blanket defaults, which are really guesses. Freeing quota needs cleanup, and raising it needs approval.
- Set requests/limits: precise, edit every workload
- LimitRange defaults: easy, may misfit some apps
- Free or raise quota: cleanup or approval
Pending Pods exist and wait; rejected Pods never exist anywhere.
Pending Pods exist and wait; rejected Pods never exist anywhere. So when kubectl get pods comes back empty, describe the ReplicaSet first, because that's where the rejection gets recorded.

