KYAML(1)toolbelt manualKYAML(1)

kyaml

Print clean YAML of a live object, ready to reuse.

Synopsis

kyaml [-n NAMESPACE] [-o FILE] [--status] [--no-namespace] KIND/NAME [-- kubectl options]
kyaml [options] KIND NAME

Description

kubectl get -o yaml prints an object the way the cluster stores it, not the way you wrote it. Half of it is bookkeeping. There are managedFields, which can run to hundreds of lines, and there is the status block. On top of that come the uid, resourceVersion, creationTimestamp and generation, and the last-applied annotation that holds a second copy of the whole object as JSON. Apply that output to another namespace or cluster and some of it fails, since a uid or resourceVersion from one cluster means nothing in another.

kyaml runs kubectl get -o yaml and removes what the cluster added. The rest is the object as a person would write it, with its labels, annotations of your own, and the spec. You can read it, diff it against the file in git, or save it and apply it somewhere else.

kyaml only reads the cluster. It writes a file only with -o, and never over a file that exists.

Options

OptionWhat it does
-n, --namespace NSThe object's namespace. The context's own namespace by default.
-o, --out FILEWrite to FILE instead of stdout. It refuses when FILE exists.
--statusKeep the status block.
--no-namespaceDrop metadata.namespace too.
-q, --quietNo line about the file it wrote.
-v, --verbosePrint the kubectl command and the list of fields it removed.
-h, --helpShow the help.

The object is KIND/NAME as kubectl writes it, such as deploy/api, svc/api or configmap/app-config, or the same as two words. Any short name kubectl knows works, since kyaml hands it to kubectl as it is.

What it removes

FieldWhy it goes
statusThe cluster writes it. Keep it with --status.
metadata.managedFieldsServer-side apply bookkeeping, often most of the output.
metadata.uidUnique to this cluster.
metadata.resourceVersionA version counter for this copy. Applying it elsewhere can fail with a conflict.
metadata.creationTimestampThe cluster sets it.
metadata.generationThe cluster counts it.
metadata.selfLinkLeft over in objects from older clusters.
kubectl.kubernetes.io/last-applied-configurationA JSON copy of the object that kubectl apply keeps.
deployment.kubernetes.io/revisionThe Deployment controller's counter.
spec.clusterIP, spec.clusterIPs of a ServiceThe cluster picks the address. Copying it clashes in another cluster. A headless Service keeps clusterIP: None and clusterIPs: [None].
metadata.ownerReferencesPoints at the owner's uid in this cluster. In another cluster the copy has no owner, and the garbage collector deletes it.
spec.selector and the controller-uid and job-name labels of a JobThe Job controller made them for this Job. A copy that keeps them fails to apply. A Job with manualSelector: true keeps its selector.
spec.nodeName of a PodTies the copy to one node, which may not exist. Without it, the scheduler picks.
metadata.namespaceOnly with --no-namespace, so kubectl apply -n OTHER works on the file.

An annotations: block left empty after that goes too. Every other field stays as the cluster printed it, including defaults it filled in, such as progressDeadlineSeconds or sessionAffinity. Those apply cleanly.

Pass-through

Options after -- go to kubectl get.

kyaml deploy/api -- --context kind-kind

Needs

kubectl. The filter is built in and uses awk, so no YAML tool is needed.

Examples

A Deployment, ready to reuse

kyaml -n shop deploy/api
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: api
  name: api
  namespace: shop
spec:
  progressDeadlineSeconds: 600
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - image: registry.example.com/api:2.3.0
        name: api
        ports:
        - containerPort: 8080
          protocol: TCP

What it took out

kyaml -v -n shop deploy/api > api.yaml
+ kubectl get deploy/api -n shop -o yaml
kyaml: removed deployment.kubernetes.io/revision, kubectl.kubernetes.io/last-applied-configuration, creationTimestamp, generation, managedFields (8 lines), resourceVersion, uid, status

The + and kyaml: lines go to stderr, so the file holds only the YAML.

Save a Service to a file

kyaml -v -n shop svc/api -o api-svc.yaml
+ kubectl get svc/api -n shop -o yaml
kyaml: removed creationTimestamp, resourceVersion, uid, clusterIP, clusterIPs, status
kyaml: wrote api-svc.yaml, 23 lines
kyaml -n shop svc api -o api-svc.yaml
kyaml: api-svc.yaml exists, pick another name or move it away first
echo $?
4

Move an object to another namespace

kyaml --no-namespace -n shop svc/api | head -8
apiVersion: v1
kind: Service
metadata:
  annotations:
    example.com/owner: shop-team
  labels:
    app: api
  name: api
kyaml --no-namespace -n shop svc/api | kubectl apply -n staging -f -

Only the annotation you wrote yourself is left.

Keep the status

kyaml --status -n shop svc/api | tail -3
  type: ClusterIP
status:
  loadBalancer: {}

Mistakes

kyaml -n shop api
kyaml: give a kind and a name, for example deploy/api or svc/api
Try 'kyaml --help' for the options.
kyaml -n shop deploy/nope
kyaml: no deploy/nope in shop. List them with: kubectl get deploy -n shop
kyaml pizza/x
kyaml: the cluster has no resource type pizza. List them with: kubectl api-resources

Troubleshooting

kubectl apply of the file says the object has been modified
The file still has a resourceVersion. That happens only when the field sits somewhere kyaml does not look, such as inside a List. Delete the line and apply again.
The Service in the new cluster gets a different IP
That is on purpose. kyaml drops clusterIP so the new cluster picks a free address. A headless Service is the exception. It keeps clusterIP: None, since without it the copy would get an address and stop being headless.
The output still has fields you did not write
The cluster fills in defaults, such as terminationMessagePath or dnsPolicy. They are valid and apply cleanly, so kyaml leaves them.

Exit status

CodeMeaning
0It printed or wrote the YAML.
1No such object or resource type, or kubectl failed.
2Bad usage, such as a name without a kind.
3kubectl is missing.
4Refused, since the file given to -o exists.

See also

ksecret, yamlcheck, kubectl-get(1), kubectl-apply(1)