Table of Contents
Imperative vs Declarative in Kubernetes
In Kubernetes, the declarative configuration model means you describe what the desired state of the system should be, and the system figures out how to make it so.
This contrasts with imperative usage, where you directly tell Kubernetes what action to perform step by step.
- Imperative style examples (telling it what to do right now):
kubectl create deployment ...kubectl scale deployment myapp --replicas=5- Declarative style examples (describing the final state you want):
- A YAML file that says:
- there should be a Deployment named
myapp - it should have
5replicas - they should run a specific image
Kubernetes’ controllers then work continuously to match the actual state of the cluster to the desired state you declared.
Desired vs Actual State
The declarative model is built around the idea of desired state:
- You specify the desired state in resource manifests (YAML/JSON).
- Kubernetes stores this state in the API server.
- Controllers watch for changes and reconcile the real world back towards the desired state.
Conceptually:
- Desired state: “There should always be 3 replicas of this pod running.”
- Actual state: “Right now, there are only 2 pods running because one crashed.”
- Reconciliation loop: A controller notices the difference and schedules a new pod to restore 3 replicas.
This model makes Kubernetes:
- Self-healing: drifts from the desired state are corrected automatically.
- Predictable: you can inspect the stored configuration to see what the cluster should look like.
- Repeatable: you can apply the same manifests in different clusters and expect consistent results.
Resource Manifests as Source of Truth
In the declarative model, Kubernetes resources are usually defined in manifest files (often YAML):
- Manifests represent the source of truth for application and cluster configuration.
- They can be:
- checked into version control (e.g., Git)
- peer-reviewed
- versioned, rolled back, and audited
A Kubernetes manifest typically includes:
apiVersion: which version of the API this resource useskind: the type of resource (e.g.Deployment,Service)metadata: name, namespace, labels, annotationsspec: the desired configuration for that resource
Example (simplified) Deployment manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: nginx:1.27
ports:
- containerPort: 80In this file you’re not giving step-by-step instructions; you are declaring:
- there should be a Deployment called
myapp - it should manage 3 replicas
- pods should run the
nginx:1.27image
Idempotence and Repeated Application
A key property of the declarative model is idempotence:
- Applying the same manifest multiple times leads to the same resulting state.
- Re-applying a file:
- updates only the fields that changed
- leaves others untouched
- does not duplicate the resource
This enables workflows like:
- Safely running
kubectl apply -f .many times during development. - Automated systems (e.g., GitOps tools) continuously reconciling manifests with the cluster without harming running workloads.
Using `kubectl` Declaratively
Kubernetes supports a declarative workflow through commands like:
kubectl apply -f file.yamlkubectl apply -f ./directory/kubectl delete -f file.yaml
kubectl apply:
- Sends your desired configuration to the API server.
- Stores a “last applied” configuration as an annotation (by default).
- Computes the diff between current and desired configuration to perform minimal changes.
Common patterns:
- Maintain one or more directories of YAML manifests for your application.
- Use
kubectl apply -fon those directories for: - initial deployment
- configuration updates
- sync after changes in version control
Partial vs Full Specifications
In declarative manifests, you typically:
- Specify the fields you care about under
spec. - Rely on Kubernetes to fill in defaults or status fields.
Over-specifying everything can:
- Make manifests verbose and harder to maintain.
- Increase chances of conflicts with fields managed by controllers or the cluster itself.
Common practice:
- Define only the critical settings in
spec(e.g., replicas, containers, resources). - Avoid modifying fields that are clearly managed by controllers (for example, most of
status).
Patching and Declarative Updates
While kubectl apply is the main declarative tool, Kubernetes also supports patching resources:
kubectl patchallows you to adjust specific parts of a resource without replacing the entire spec.- This can be done in a declarative-like way using strategic merge or JSON patches, but is often used for one-off changes.
Example:
kubectl patch deployment myapp \
-p '{"spec":{"replicas":5}}'This is somewhat between imperative and declarative:
- You’re not scripting every step.
- But you are not maintaining the full desired state in a file either.
In a strictly declarative practice, you’d usually:
- Update the
replicasfield in the YAML file. - Re-apply the manifest to keep the file as the source of truth.
Declarative Model and GitOps
The declarative configuration model is a foundation for GitOps practices:
- All cluster and application configuration is declared in version-controlled manifests.
- A Git repository becomes the canonical desired state.
- Automated agents (such as Argo CD or Flux) continuously:
- compare cluster state with the Git repo
- apply changes to converge the cluster to what’s declared in Git
This builds on Kubernetes’ native reconciliation:
- Kubernetes reconciles its own internal objects to match their manifests.
- GitOps tools reconcile the cluster objects to match what’s in Git.
Advantages and Trade-Offs
Advantages:
- Consistency: same manifests deployed in many environments yield consistent results.
- Auditability: history of changes is tracked in version control.
- Recovery: you can recreate cluster state from manifests after a failure.
- Automation-friendly: tools can safely re-apply configuration without side effects.
Trade-offs:
- Learning curve: requires understanding resource schemas and YAML.
- State drift without discipline: imperative ad-hoc changes (
kubectl edit,kubectl scale) can move the cluster away from what’s in your manifests. - Merge conflicts: many people editing the same manifests can lead to version control conflicts.
Typical mitigation:
- Adopt a workflow where all intentional changes go through manifests (and usually Git).
- Minimize direct imperative changes in production clusters, or treat them as temporary/debug-only.
Practical Guidelines
When working with Kubernetes in a declarative way:
- Prefer
kubectl apply -f(or equivalent tooling) over imperativekubectl create/kubectl scalefor ongoing management. - Keep manifests:
- small and focused (group related resources together)
- named clearly (e.g.,
deployment-myapp.yaml,service-myapp.yaml) - Store all manifests in version control and treat them as code.
- Avoid manual edits to running resources that are not reflected back into the manifests.
- Use labels and annotations consistently in manifests to support automation and organization.
These practices let you fully benefit from Kubernetes’ declarative configuration model and its reconciliation-driven behavior.