Skip to main content
ContainersBeginner

Reading a Kubernetes YAML You Did Not Write

Four hundred lines, deployed to production, written by somebody who left. How to get oriented fast, in an order that works — and why the file may not be what is running.

N
Neeraj Jha
·Updated September 18, 2026·7 min read

Somebody has left, or is on holiday, or wrote this two years ago. There are four hundred lines of YAML, it is deployed to production, and you need to change one thing without breaking the rest.

Nobody teaches this. Every Kubernetes tutorial writes a manifest from scratch, which is not the situation you are usually in.

Here is how to read one quickly, in an order that gets you oriented before you get lost.

Read it in this order

Not top to bottom. Manifests are not written to be read linearly, and the important parts are scattered.

1. kind and metadata.name — what is this and what is it called

yaml
kind: Deployment
metadata:
  name: checkout-api
  namespace: production

Most files contain several documents separated by ---. Get the list first:

bash
grep -nE '^(kind|  name):' manifest.yaml

Now you know you are looking at a Deployment, a Service, an Ingress and a ConfigMap rather than one enormous object.

2. Labels and selectors — what is wired to what

This is the part that actually determines behaviour, and it is the part people skip.

yaml
# In the Deployment
spec:
  selector:
    matchLabels:
      app: checkout          # which pods this Deployment owns
  template:
    metadata:
      labels:
        app: checkout        # what the pods it creates will carry
---
# In the Service
spec:
  selector:
    app: checkout            # which pods receive traffic

Three places, one string. If they disagree, things break silently: the Deployment manages nothing, or the Service routes to nothing and you get a Service with no endpoints.

Check them against each other before anything else:

bash
grep -nA3 -E 'selector|labels' manifest.yaml

3. The image — what actually runs

yaml
image: registry.example.com/checkout:1.4.2

Ask two things. Is it pinned, or is it :latest or a moving tag — because if it moves, the manifest does not tell you what is running. And does that tag still exist in the registry, because a tag deleted upstream produces ImagePullBackOff the next time a pod is rescheduled, possibly months after anyone touched this file.

4. Ports — three numbers that must agree

yaml
# Container
ports:
  - containerPort: 8080      # what the process binds
---
# Service
ports:
  - port: 80                 # what callers use
    targetPort: 8080         # where it forwards — must match containerPort

targetPort and containerPort must agree, or you get a Service that resolves and refuses every connection. When targetPort is a name rather than a number, that name has to be defined on the container.

5. Resources — what it reserves and what kills it

yaml
resources:
  requests:   { cpu: "100m", memory: "256Mi" }
  limits:     { cpu: "500m", memory: "512Mi" }

Read these as two different mechanisms, because they are: the request is a reservation the scheduler honours, the limit is a ceiling the kernel enforces. Exceeding the memory limit kills the container — that is OOMKilled, exit code 137.

Two things to notice. If requests is absent, the pod is scheduled with no reservation and is first to be evicted under pressure. If requests are much higher than what the thing uses, you are looking at one of the reasons a cluster can be full at 10% utilisation. The distinction itself is worth twenty minutes: requests vs limits.

6. Probes — what decides if it is alive and ready

yaml
readinessProbe:               # decides whether it receives traffic
  httpGet: { path: /healthz, port: 8080 }
livenessProbe:                # decides whether it gets restarted
  httpGet: { path: /healthz, port: 8080 }
  initialDelaySeconds: 30

Read carefully whether they point at the same endpoint. They often do, and that is usually a mistake — a readiness check that fails should remove traffic, and a liveness check that fails restarts the process. Pointing both at one endpoint means a slow dependency can restart your application instead of just draining it. readiness vs liveness is the distinction.

If there is no readiness probe at all, traffic arrives the instant the container starts, which is a common cause of errors during deploys.

7. Everything else, only if relevant

Volumes, affinity, tolerations, securityContext, annotations. Most of it is either boilerplate or a workaround for something specific. Read it when the change you are making touches it.

What the file does not tell you

This is the part that catches people, and it is worth holding in mind the entire time.

It may not be what is running. Somebody may have edited the live object, scaled it, or applied a newer version from a different branch. Always compare:

bash
kubectl get deployment checkout-api -o yaml > live.yaml
diff <(yq -P 'sort_keys(..)' manifest.yaml) <(yq -P 'sort_keys(..)' live.yaml) | head -40

Expect noise — the cluster adds status, creationTimestamp, resourceVersion and defaults for everything you left out. What you are looking for is a difference in something the file sets explicitly.

It may be a template. If this is Helm or Kustomize, the file is an input rather than the output. Render it before reading:

bash
helm template ./chart -f values-production.yaml     # what Helm would apply
kubectl kustomize ./overlays/production             # what Kustomize would apply

Reading the template and reasoning about the output is how people end up debugging a value that gets overridden two layers up.

It may not be the only thing acting on the object. An autoscaler changes replicas, so the number in the file may be irrelevant and overwriting it can cause a scale-down. A mutating webhook or service mesh can inject containers that are not in the file at all.

A quick orientation script

Four commands that answer "what is this" faster than reading:

bash
# What objects, and what are they called
grep -nE '^(kind|  name|  namespace):' manifest.yaml

# Every image, so you know what actually runs
grep -nE 'image:' manifest.yaml

# The wiring: labels, selectors, ports
grep -nE 'app:|selector|targetPort|containerPort' manifest.yaml

# Does it parse and would the cluster accept it — without applying anything
kubectl apply --dry-run=server -f manifest.yaml

That last one is the most underused command in this list. --dry-run=server sends the manifest to the API server, which validates it against the real schema and the admission controllers, and applies nothing. It catches a deprecated API version, a typo in a field name, and a policy that would reject it — before you find out the interesting way.

If you want a second opinion on what the manifest gets wrong before you touch it, the manifest analyzer checks it in your browser.

Before you change anything

Find out if it is in Git and whether that copy is authoritative. If the cluster is managed by GitOps, editing the live object will be reverted by the controller, usually within minutes, and often while you are still wondering whether your change worked.

Change one thing. Multiple simultaneous edits to an unfamiliar manifest make the failure impossible to attribute.

Apply to a non-production namespace first, if one exists that is close enough to be meaningful.

Know how to undo it. For a Deployment that is kubectl rollout undo deployment/<name>, and it is worth having it typed out in another terminal before you apply.

Common misreadings

Assuming replicas in the file is the live count. An HPA may own it.

Assuming the absence of a field means a safe default. No resources block means no limits, which is not conservative — it means the container can consume whatever the node has.

Reading limits as the reservation. It is the ceiling. requests is the reservation, and the scheduler only looks at requests.

Trusting the file's indentation of a list. YAML is unforgiving here, and a list item indented one space too far silently becomes part of the previous mapping rather than an error.

Not checking the namespace. Half a debugging session can go on a manifest that is being applied somewhere other than where you are looking.

The short version

kind and name first, then labels and selectors, then image, ports, resources and probes. Then confirm what is in the file is what is running, because it frequently is not — a template, an autoscaler or a webhook may be between the two. And kubectl apply --dry-run=server will tell you whether the cluster would accept your change without you having to find out from the cluster.

Tagged with

Enjoyed this article?

Get more DevOps insights delivered to your inbox.

Get new posts by email

Subscribe to get an email when a new blog post is published. Skip anytime.

No spam, unsubscribe anytime.

N

Written by

Neeraj Jha

Platform administrator and lead writer.

View all posts

Discussion

0 comments

Sign in to join the conversation.

Be the first to comment

Start a conversation about this post

Share: