Kubernetes is usually described as a declarative system. Most of the time you work with YAML that defines what the end state of the system should look like. Kubernetes supports imperative APIs too though, where you issue a command and get an immediate output.
In this article, we’ll explore the differences between these two forms of object management. The chances are you’ve already used both even if you don’t recognize the terms.
Declarative vs Imperative: Definitions
First it’s helpful to examine the terminology.
Something that’s declarative makes a statement of the end result, indicating intent but not the process to achieve it. In Kubernetes, this is saying “There should be a ReplicaSet with three Pods.”
An imperative acts as a command. Whereas a declarative is passive, imperatives are active and immediate: “Create a ReplicaSet with three Pods.”
The Kubernetes ecosystem provides mechanisms for interacting with your cluster in either of these forms. Imperative approaches are catered for by CLI commands and individual YAML files. Declarative configuration is facilitated using directories of files that are combined into the final resource representation.
Managing Objects Imperatively
Here’s an example of creating a Deployment imperatively:
kubectl create deployment my-deployment --image my-image:latest
You’re instructing Kubernetes to immediately add a new Deployment to your cluster. The command includes a single verb (
create) and the name of the resource type you’re working with (
You can also write a YAML file and apply it imperatively using the
apiVersion: apps/v1 kind: Deployment spec: replicas: 3 selector: matchLabels: app: example template: metadata: labels: app: example spec: # ...
kubectl create -f deployment.yml
As before, you’re issuing an immediate command via an active verb. Kubernetes will take the configuration from your file and create corresponding resources in the cluster. If you need to update a resource, you must modify your YAML and use the
replace command to effect the change:
kubectl replace -f deployment.yml
This operation will remove the spec of any existing resources and replace it with the version in your config file. This is conveyed by the name of the
replace command. It means you’ll lose any changes made to your live objects that aren’t present in your YAML.
When Kubernetes is consuming imperative commands, it needs to be told exactly what to do. Consequently there’s no way to selectively apply just the changed parts of your YAML. For that you’ll need to switch to declarative operations.
Trying Declarative Management
Declarative management is only available when you’re using YAML config files. There’s no such thing as a declarative command. When you’re using declarative operations, you don’t tell Kubernetes what to do by providing a verb (
replace). Instead, you use the single
apply command and trust Kubernetes to work out the actions to perform.
kubectl apply -f deployment.yml
Continuing the deployment example from above, applying the above YAML to your cluster would initially act the same as an imperative
create command. No matching resource will exist to begin with so Kubernetes must create a new one.
You could then change the
replicas field to
5 and repeat the
apply command. This time Kubernetes will match the existing resource, detect the change in your YAML, and scale the deployment without impacting any other fields.
Using the imperative approach, you’d need to use the
kubectl scale command to change the replica count of an existing deployment. If you modified the YAML you used with
kubectl create, you’d need to run
kubectl replace – but this would replace the deployment’s entire
spec, instead of simply scaling its replica count.
Declarative vs Imperative: Comparing The Trade-offs
Imperative operations are simple to understand and reason about. Each action is expressed as a verb with a clearly defined consequence. For this reason, most people will begin their earliest Kubernetes interactions using imperative commands that can be loosely mapped to other technologies such as Docker.
Declarative management exposes the real power of Kubernetes. You declare what the final state should look like, then let Kubernetes do the rest. Every command has the same imperative action –
apply this set of YAML files and progress the cluster to the state they define.
Declarative management is ideal for automated deployments. You don’t need to spend time crafting a set of migration instructions each time you update a resource. Instead, adjust your YAML so it would produce correctly configured objects if they were created anew at the present time. Kubernetes will handle updates of existing objects so they match the new state too.
Declarative YAML files are easy to version, review, and merge as part of your source control system. If you use imperative commands, you’ve got no way of tracking how your cluster has evolved and it’ll be trickier to rollback to an earlier state. Unlike imperative operations, declarative updates don’t overwrite the entire object so you’ll retain changes you made through other mechanisms, independently of your YAML files.
Nonetheless imperative management does retain some advantages. Declarative configuration adds layers of complexity and can be harder to debug, particularly when Kubernetes selects an unexpected course of action. Each change results in a merge and patch operation to bring your objects into alignment with your desired state. With the imperative model, what you ask for is what you’ll get, unless an error occurs.
As ever when two approaches are offered, both strategies are useful and which you choose should depend on the context. For production clusters hosting live apps with frequent changes, you probably want versioned declarative YAML files. If you’re quickly spinning up new containers in a development cluster, imperative commands will save time and be easier to work with.
Declarative and imperative management are two ways of interacting with your Kubernetes cluster and its resources. Kubectl has integrated support for both these strategies but the techniques shouldn’t be mixed on a per-object basis. If you create an object declaratively, it should be managed that way through its entire life – using imperative commands with it can lead to unexpected behavior.
Imperative operations affect live objects within your cluster. You define a verb, resource, and configuration via command arguments and flags. Declarative management is based on changes to local config files that Kubectl diffs and applies to the cluster via patches when you use the
kubectl diff and
kubectl apply commands.