Adetayo Akinsanya unkletayo.dev

Declarative Desired State vs Imperative Commands: The Kubernetes Operating Philosophy

Understanding spec vs status fields, idempotency, and reconciliation loop philosophy.

Part 5 in Series — Catch up on the previous article: The Kubelet and Container Runtime Interface (CRI): How Nodes Execute Pods (Part 4) before diving into this post.

A DevOps engineer writes a deployment shell script to launch 5 replicas of an API gateway across a cluster of servers:

#!/bin/bash
for i in {1..5}; do
  ssh node-$i "docker run -d --name api-gateway -p 8080:8080 my-api:v1.0"
done

The script runs sequentially.

node-1 and node-2 execute successfully. However, when the script attempts to connect to node-3, a temporary 10-second network packet drop causes the SSH connection to time out. The script halts with an error.

The engineer re-runs the shell script.

Immediately, node-1 and node-2 return fatal errors:

Error response from daemon: Conflict. The container name "/api-gateway" is already in use.

The imperative script failed because it was non-idempotent. It possessed no awareness of current cluster state, no error recovery logic, and no mechanism to reconcile partial failures.

When the team converts their deployment to a Kubernetes Declarative Manifest:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-gateway
spec:
  replicas: 5
  template:
    spec:
      containers:
      - name: api-gateway
        image: my-api:v1.0

Executing kubectl apply -f deployment.yaml 10 times in a row produces the exact same result: 5 active replicas, 0 errors, and complete state convergence.

How does the declarative operating model eliminate configuration drift and manual operational overhead?


1. Imperative vs Declarative Paradigms

To understand Kubernetes architecture, you must understand the distinction between Imperative and Declarative state management:

IMPERATIVE PARADIGM ("HOW to do it")          DECLARATIVE PARADIGM ("WHAT it should be")
+---------------------------------------+     +---------------------------------------+
|  1. Check if container exists         |     |  apiVersion: apps/v1                  |
|  2. If not, run container             | --> |  kind: Deployment                     |
|  3. Bind port 8080                    |     |  spec:                                |
|  (Script describes action steps)      |     |    replicas: 5                        |
+---------------------------------------+     |  (Manifest describes target state)    |
                                              +---------------------------------------+

Imperative Approach

  • Describes the Actions: Tells the system how to perform a sequence of state transitions (create, add, delete).
  • Non-Idempotent: Re-running an imperative command often fails because resources created by step 1 already exist.
  • Brittle: Lacks self-healing. If a container crashes after creation, the imperative script exits and leaves the system unmonitored.

Declarative Approach

  • Describes the Target Outcome: Defines the desired final state of the cluster regardless of current conditions.
  • Strictly Idempotent: Applying a declarative document 1 time or 1,000 times yields the identical, predictable system state.
  • Continuous Convergence: A control loop constantly compares actual state to declared state, automatically correcting discrepancies.

2. Anatomy of a Kubernetes Resource Object

Every resource stored in etcd and exposed by kube-apiserver adheres to a strict 4-part JSON/YAML structural schema:

apiVersion: apps/v1     # 1. API Group and Version
kind: Deployment        # 2. Resource Kind Type
metadata:               # 3. Object Identification Metadata
  name: payment-api
  namespace: production
  labels:
    tier: backend
spec:                   # 4. DESIRED STATE (Written by User / CI/CD)
  replicas: 3
  selector:
    matchLabels:
      app: payment
status:                 # 5. ACTUAL STATE (Written ONLY by Controllers!)
  availableReplicas: 3
  readyReplicas: 3
  updatedReplicas: 3

3. The Separation of Concerns: spec vs status

The most important architectural pattern inside a Kubernetes manifest is the strict separation between spec and status:

+-------------------------------------------------------------------+
|                        KUBERNETES MANIFEST                        |
+-------------------------------------------------------------------+
|  spec: { replicas: 3, image: "nginx:1.25" }                       |
|  --> DESIRED STATE (User Intent)                                  |
|  --> WRITTEN BY: Human Engineers / CI/CD Pipelines                |
+-------------------------------------------------------------------+
                                  |
                                  |  Control Loop Reconciliation
                                  v
+-------------------------------------------------------------------+
|  status: { readyReplicas: 2, phase: "Running" }                   |
|  --> ACTUAL STATE (Current Reality)                               |
|  --> WRITTEN BY: System Controllers ONLY                          |
+-------------------------------------------------------------------+

The System Contract:

  • Users modify spec: When you edit a deployment or run kubectl apply, you update the spec block in etcd.
  • Controllers update status: Background control loops read the actual state of worker nodes and write operational metrics into the status block.
  • Reconciliation Engine Target: The system’s sole job is to drive the status block until it matches the spec block:

System Goal: statusspec\text{System Goal: } \text{status} \equiv \text{spec}


Imperative vs Declarative Matrix

Metric / PropertyImperative Strategy (kubectl create)Declarative Strategy (kubectl apply)
Command Formatkubectl run web --image=nginxkubectl apply -f web.yaml
Source of TruthMemory / Transient Shell historyVersion-Controlled Git Repository (GitOps)
Execution PropertyNon-Idempotent (Fails on duplicate names)Idempotent (Safely re-applies changes)
State Drift HandlingIgnores state drift after initial runDetects & reverses unauthorized state drift
AuditabilityPoor (Hard to track manual CLI options)High (git diff tracks every spec change)

Summary & Next Steps

Declarative desired state management forms the core philosophy of Kubernetes:

  • Imperative scripts execute fixed steps, creating non-idempotent setups prone to failures.
  • Declarative manifests specify target outcomes, supporting safe, idempotent kubectl apply operations.
  • spec represents the user’s desired intent.
  • status represents current system reality updated by controllers.
  • The core job of Kubernetes is to run continuous control loops that force statusspec\text{status} \to \text{spec}.

In the next article, we inspect The Reconciliation Loop Engine: Observe, Diff, and Act Mechanics.

References & Further Reading

  1. Cloud Native Computing Foundation. Pods Lifecycle and Phase Transitions. CNCF Docs.
  2. CNCF. Container Runtime Interface (CRI) Specification. CNCF GitHub.
  3. Burns, B., et al. (2022). Kubernetes: Up and Running (Chapter 5: Pods). O’Reilly Media.

Up Next in Series →

Part 6: The Reconciliation Loop Engine: Observe, Diff, and Act Mechanics

Continue to Part 6 →