Kubernetes Manifest Visualizer

Paste a bundle of manifests and see the object graph: what points at what, and above all what points at nothing. A broken reference in Kubernetes is accepted by the API server, listed as fine by kubectl, and only shows up later as a pod that will not start or a route that returns 503.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Draw the graph. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

A Service selecting nothing

A selector that matches no pod labels, which is a working Service with no endpoints

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    metadata:
      labels:
        app: api
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: nope

A matching pair

The same two objects wired correctly, for comparison

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    metadata:
      labels:
        app: api
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Assuming a Service and a Deployment named the same are connected

    They are joined by labels, not by name. A Service selecting app: api matches nothing if the pod template says app: web, and both objects are valid.

    Instead:Compare the Service selector against the POD TEMPLATE labels, not the Deployment's own labels.

  2. Reading targetPort as the Service port

    port is what clients connect to and targetPort is the container port. Swapping them gives a Service that accepts connections and forwards them nowhere.

    Instead:targetPort must match containerPort, or a named port on the container.

What it catches

Severity here ranks by how long it takes you to find out, not by how broken the thing is. A pod stuck in ContainerCreating is visible in kubectl get within a minute. A Service with no endpoints is not visible anywhere.

The reference that resolves and still returns 503

An Ingress naming a Service that is not in the bundle is bad. An Ingress naming a Service that exists and selects no pods is worse, because now every object reports healthy: the Ingress is admitted, the Service has a ClusterIP and appears in DNS, and the only symptom is a 503 from the controller. That case is reported above the missing one, on purpose.

A missing ConfigMap fails four different ways

Mounted as a volume, an absent ConfigMap leaves the pod in ContainerCreating forever with the reason only in kubectl describe. Read through env, it fails the container at start with CreateContainerConfigError, after the image has already been pulled. Referenced by a key that the ConfigMap does not define, it fails the same way while the object itself looks perfectly correct. Marked optional: true, it does not fail at all. These are four different findings here because they are four different investigations.

volumeClaimTemplates generate claims, they do not reference them

A StatefulSet volumeClaimTemplate creates one PVC per replica, named after the template, the StatefulSet and the ordinal. Reading it as a reference reports every StatefulSet in the world as broken, so it is drawn as generating instead, with the names it will create. A volume using claimName is the opposite case: nothing creates that claim for you.

The Ingress port is the Service port

backend.service.port.name matches the name of a port on the Service, not the containerPort name on the pod, and those are usually different words. backend.service.port.number is the Service's published port, not the targetPort. Both mistakes produce a manifest that reviews cleanly and a path that has no upstream.

Selectors that quietly match nothing

A Service selector matching nothing is the classic silent failure. A PodDisruptionBudget matching nothing reports ALLOWED DISRUPTIONS 0 and lets every drain through. A NetworkPolicy whose podSelector matches nothing is inert, so the traffic it was written to block is still allowed and no packet is dropped to tell you. All three are checked against the pod template labels, which is the only thing any of them ever match.

Not in this input, never does not exist

Anything absent from the paste might exist in the cluster, so a dangling edge is reported as not in this input and nothing here claims otherwise. The same goes for what is not followed at all: ownerReferences, IngressClass, StorageClass, PersistentVolume binding, CSI volumes, webhooks and anything a controller creates at runtime.

Why a broken reference in Kubernetes is silent

Kubernetes has no foreign keys. Every reference between objects is a string in a field, resolved much later by a different component, and nothing in the apply path checks that the thing on the other end exists. So a typo is accepted, stored, and reported as healthy, and the failure surfaces minutes later somewhere that does not mention the field you got wrong.

Nothing validates a name at admission time

The API server validates shape: is this a legal object, are the field types right, does the name match the DNS rules. It does not validate meaning. A ConfigMap name is just a string until the kubelet tries to mount it, an Ingress backend name is just a string until the ingress controller builds its upstream, and a scaleTargetRef is just a string until the HPA controller asks for a scale subresource. Each of those happens in a different process at a different time, which is why each failure looks different.

kubectl apply -f broken.yaml

deployment.apps/api created        <- accepted
service/api created                <- accepted
ingress.networking.k8s.io/web created

kubectl get all
everything Running, everything Ready

the ConfigMap named in the volume does not exist,
and nothing above mentions it

Four absences, four moments, four symptoms

The same missing ConfigMap produces a different investigation depending on how it was referenced. Knowing which one you are in tells you where to look, and the pod phase is the giveaway: Pending means the scheduler never placed it, ContainerCreating means the kubelet cannot finish the mount, and a container that starts and stops means the config was resolved and rejected.

reference                     phase                  where the reason is
volumes[].configMap           ContainerCreating      pod events
env[].valueFrom.*KeyRef       CreateContainerConfig  container status
volumes[].persistentVolume..   Pending                scheduler events
imagePullSecrets              runs, or ImagePull..    kubelet log only
serviceAccountName            no pod is created      replicaset events

only the first four are pods you can describe; a missing
ServiceAccount means there is no pod to look at

A Service selector is not a LabelSelector

A Deployment's own selector, a PodDisruptionBudget's selector and a NetworkPolicy's podSelector are all metav1.LabelSelector: they take matchLabels and matchExpressions. A Service selector is not. It is a flat map of label keys to values, equality only, and adding a matchLabels wrapper to one makes the API server fail to unmarshal it with an error about a Go type rather than about the field.

Service                     Deployment, PDB, NetworkPolicy
selector:                   selector:
  app: api                    matchLabels:
  tier: web                     app: api
                              matchExpressions:
                                - key: tier
                                  operator: In
                                  values: [web]

every key is ANDed in both cases, and both match POD
labels from spec.template.metadata.labels, never the
workload's own metadata.labels

policy/v1 reversed what an empty PDB selector means

In policy/v1beta1 a null selector selected every pod in the namespace and an empty one selected none. policy/v1 swapped them. A PodDisruptionBudget carried over from the old API therefore keeps its YAML, passes every validator, and changes from protecting the namespace to protecting nothing. It still reports as a valid budget, so the discovery usually happens during a node upgrade.

policy/v1beta1        policy/v1
selector: null   ->   all pods       none
selector: {}     ->   none           all pods in namespace

kubectl get pdb api
NAME   MIN AVAILABLE   ALLOWED DISRUPTIONS   AGE
api    1               0                     9d

ALLOWED DISRUPTIONS 0 with no pods selected looks
identical to a budget that is fully consumed

What creates a PVC, and what merely asks for one

A StatefulSet volumeClaimTemplate is the only place in a workload spec that creates storage. The controller creates one PVC per replica, named template-statefulset-ordinal, and deliberately never deletes them when the StatefulSet goes away. A volume with claimName does the opposite: it demands a claim that already exists, and the pod stays Pending until one does.

volumeClaimTemplates:            volumes:
  - metadata:                      - name: data
      name: data                     persistentVolumeClaim:
                                       claimName: api-data

creates data-db-0, data-db-1,    requires api-data to exist
data-db-2 for replicas: 3        already, and stays Pending
and keeps them after delete      if it does not

Orphans are never cleaned up

Kubernetes garbage collects an object only when it carries an ownerReference to something that has been deleted. Nothing you write by hand has one, so a ConfigMap left behind by a rename, or a Secret whose consumer was removed, stays in the namespace indefinitely. That is mostly harmless for a ConfigMap and less so for a Secret, which is still readable by anything with get on secrets in that namespace.

kubectl get configmap -n prod

NAME              DATA   AGE
app-config        4      2d
app-config-v1     4      411d      <- nothing mounts this
app-settings      2      270d      <- nor this

nothing in kubectl get says which are referenced

More kubernetes tools

Kubernetes YAML Generator Answer a few questions, get a manifest Kubernetes YAML Validator Is this YAML valid, and where is it wrong? Kubernetes Rollout & Probe Timing Restart loops, capacity dips and stuck drains Kubernetes Service Checker Will this Service have any endpoints? Kubernetes Network Policy Viewer What can reach what, and what just broke Kubernetes Manifest Diff What actually changed Kubernetes Resource Calculator What these manifests actually reserve Kubernetes RBAC Viewer Who can actually do what Kubernetes Deployment Generator See the capacity dip before you deploy Kubernetes Service Generator port, targetPort, and which one you meant Kubernetes StatefulSet Generator Stable names, and volumes that outlive you Kubernetes QoS Class Calculator And where you sit in the eviction order Kubernetes Node Capacity Planner Allocatable is not capacity Kubernetes Max Pods per Node Calculator The ENI limit, not the 110 default Kubernetes Label Selector Tester Which key failed, not just that one did Kubernetes Taint & Toleration Tester Tolerations permit, they do not attract Kubernetes Resource Name Validator RFC 1123 subdomain, label, or 1035 Kubernetes Label & Annotation Validator Why your image tag is not a legal label Kubernetes CrashLoopBackOff Guide The status is a timer, not a cause Kubernetes TLS Certificate Decoder When it expires, and what it covers Kubernetes ServiceAccount Token Decoder Bound or legacy, and when it dies Kubernetes imagePullSecret Generator The registry URL nobody gets right Kubernetes cert-manager Certificates Issuer scope, and the rate limit that costs a week Kubernetes Flux HelmRelease Generator With the source it cannot work without Kubernetes Helm Chart Generator Chart.yaml, values, and working templates Kubernetes Argo CD Application Generator The defaults that are off, turned on Kubernetes Ingress Generator pathType, and the TLS secret that fails quietly Kubernetes HPA Generator Why it says unknown and never scales Kubernetes SecurityContext Generator Pod level, container level, and which wins Kubernetes Probe Generator How long it gets to start before liveness kills it Kubernetes PVC Generator Access modes your storage can actually do Kubernetes ResourceQuota Generator And the LimitRange that stops it breaking deploys Kubernetes CronJob Generator The fields that decide whether it runs Kubernetes NetworkPolicy Generator With the DNS rule already in it Kubernetes RBAC Generator And what it actually grants Kubernetes PodDisruptionBudget Generator Can this budget ever be satisfied? Kubernetes API Deprecation Checker What breaks on the next upgrade Kubernetes Kubeconfig Viewer Read it without handing it to anyone Kubernetes Quantity Parser What 512Mi, 100m and 1e3 actually mean Kubernetes Multi-Document YAML Splitter One file per object, in apply order Kubernetes .env to ConfigMap & Secret Split the credentials out on the way Kubernetes Secret Decoder See what is actually in there Kubernetes Ingress to Gateway API Which annotations silently stop working Kubernetes Patch Tester Which --type, and what it deletes Kubernetes kubectl JSONPath Tester kubectl's dialect, not generic JSONPath Kubernetes Node Affinity Tester Required excludes, preferred only scores Kubernetes Helm Values Diff A missing key is a third value Kubernetes Pod and Service CIDR Fixed at cluster creation Kubernetes Secret Encoder base64 is not encryption Kubernetes API Version Checker Will this apply on that cluster Kubernetes Go Template Tester Go truthiness is not JavaScript's Kubernetes Cluster Cost Estimator At your rates, not a price table Kubernetes Kustomize Build Transformers run in kustomize's order Kubernetes Helm Chart Validator version is SemVer, appVersion is not

Elsewhere on the site