Notes to Self

Alex Sokolsky's Notes on Computers and Programming

Kubernetes Manifest Best Practices

A Kubernetes manifest is a declarative description of the desired state of one or more API objects. Keep manifests in source control and use kubectl apply instead of a sequence of imperative kubectl create, set, or edit commands. Repeatedly applying the same manifest converges on the same declared state, which makes changes reviewable, repeatable, and suitable for automation.

YAML is data that describes the desired result, not an imperative sequence of instructions to execute.

Object fields

Kubernetes object manifests use these top-level fields:

The API server and controllers populate fields such as status, uid, resourceVersion, creationTimestamp, and managedFields. Do not copy these server-managed fields into source-controlled manifests.

This Deployment shows the common structure:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: example
  labels:
    app.kubernetes.io/name: web
    app.kubernetes.io/instance: web-production
    app.kubernetes.io/managed-by: kubectl
  annotations:
    example.com/owner: platform-team
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/instance: web-production
  template:
    metadata:
      labels:
        app.kubernetes.io/name: web
        app.kubernetes.io/instance: web-production
    spec:
      containers:
      - name: web
        image: nginx:1.27.3
        ports:
        - name: http
          containerPort: 80

Multiple objects in one file

YAML supports multiple documents in one stream. Put --- on its own line between objects:

apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
data:
  LOG_LEVEL: info
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app.kubernetes.io/instance: web-production
  ports:
  - name: http
    port: 80
    targetPort: http

kubectl apply -f manifests.yaml processes both documents. Group objects that share a lifecycle; use separate, clearly named files when objects are deployed or reviewed independently. Document order is not a dependency mechanism, so do not rely on it for readiness.

Verify before applying

Use both generic YAML checks and Kubernetes-aware validation:

  1. Format consistently and run a YAML parser or linter. Use spaces, not tabs, and quote strings when YAML could interpret them as numbers, booleans, or dates.

  2. Ask the target API server to parse, default, and strictly validate every object without persisting it:

    kubectl apply --dry-run=server --validate=strict -f manifests.yaml
    
  3. Preview changes against live objects:

    kubectl diff -f manifests.yaml
    

Server-side dry runs require cluster access and validate installed custom resources as well as built-in resources. A client-side dry run is useful when offline, but it cannot prove compatibility with the target cluster’s API versions, admission policies, or custom resource definitions.

Apply only after reviewing the validation and diff results:

kubectl apply -f manifests.yaml

How Kubernetes verifies a manifest

Verification is layered; no single check proves everything:

  1. A YAML parser checks document structure, indentation, scalar types, and multi-document separators. It does not understand Kubernetes fields.
  2. With --validate=strict, the API server decodes each document according to the schema for its apiVersion and kind. Strict field validation rejects duplicate or unknown fields instead of silently dropping them. The API server publishes its built-in and custom resource schemas using OpenAPI.
  3. --dry-run=server sends the normal create or update request through defaulting, schema validation, and applicable mutating and validating admission controls, but skips persistence. This catches cluster-specific policy and custom resource errors that a generic YAML tool cannot see.
  4. kubectl diff compares the proposed objects with live objects and shows the changes that an apply would make. This is a review step, not a substitute for validation.

A dry run can succeed while the eventual workload still fails at runtime. Image availability, permissions used after admission, external dependencies, scheduling capacity, and application behavior need separate tests or health checks.

Reuse the model for domain-specific YAML

The same verification mechanism can be implemented for an application’s own YAML format:

  1. Define and version a machine-readable schema, commonly JSON Schema or OpenAPI. Require a discriminator such as apiVersion plus kind when the application supports multiple document types or schema versions.
  2. Parse YAML with duplicate-key detection enabled. Convert the parsed data to the data model expected by the schema validator, then reject unknown fields unless forward compatibility deliberately requires them.
  3. Add semantic validation for rules a schema cannot express, such as unique names, valid cross-references, allowed state transitions, and environment policy.
  4. Provide a side-effect-free validate, plan, or --dry-run command that uses the same loading, defaulting, and validation code as the real apply operation. A separate implementation is likely to drift.
  5. Show a deterministic diff or plan, and run syntax, schema, semantic, and dry-run checks in CI before accepting a configuration change.

Keep schema versions backward-compatible where practical, report errors with the document and field path, and test invalid examples as well as valid ones. If applying configuration triggers plugins or external services, define a dry-run contract that prevents side effects throughout that call chain.

Reuse the resource envelope

The fields apiVersion, kind, metadata, and spec are Kubernetes API conventions, not reserved YAML keywords. Another application can use the same envelope for its own domain objects as long as it defines and implements their semantics. For example:

apiVersion: billing.example.com/v1
kind: PricingPolicy
metadata:
  name: standard-rates
  namespace: production
  labels:
    billing.example.com/region: eu
  annotations:
    billing.example.com/owner: finance-platform
spec:
  currency: EUR
  hourlyRate: 2.50

A domain application can interpret the fields as follows:

These names are useful because they form a familiar and extensible contract, but copying the names alone provides no behavior. The application must define identity, defaulting, schema selection, updates, reconciliation, and ownership. For a small configuration format with only one type, a simpler domain-specific structure may be clearer than adopting the entire envelope.

Multiple domain objects in one YAML file

The --- separator is a YAML feature rather than a Kubernetes feature. An application can accept a YAML stream and process every document as a separate domain object:

apiVersion: billing.example.com/v1
kind: PricingPolicy
metadata:
  name: standard-rates
spec:
  currency: EUR
  hourlyRate: 2.50
---
apiVersion: billing.example.com/v1
kind: Budget
metadata:
  name: production-limit
spec:
  pricingPolicy: standard-rates
  monthlyLimit: 10000

Supporting multiple documents requires more than calling a YAML parser:

Multi-document files work well for objects that share a lifecycle. Separate files are usually clearer when objects have different owners, permissions, release schedules, or failure boundaries. Applications must document whether they accept YAML streams; some YAML libraries and configuration loaders read only the first document.

Lessons for applications storing data as YAML

YAML is a serialization format, not an application’s data model, schema, or execution language. Applications that use YAML for CI, deployment, automation, or domain-specific configuration can adopt these Kubernetes practices:

A strong processing pipeline is:

YAML
  -> strict parser
  -> version selection
  -> schema validation
  -> defaults and migration
  -> semantic and policy validation
  -> plan or diff
  -> atomic reconciliation

Generic YAML validity proves only that the syntax can be parsed. Schema-aware validation detects unknown fields, wrong types, and unsupported values; semantic and policy validation establishes whether otherwise valid data makes sense for the application and its environment.

References