← Back to Blog

Writing Idempotent Ansible Playbooks for Kubernetes Clusters

How I built a reliable WhatsApp AI shopping assistant for Clickmothercare that survives hallucinated products, silent save failures, and multi-agent handoff bugs.

Anas Rhimi
Anas Rhimi August 2026 • 8 min read

The Idempotency Challenge in Kubernetes with Ansible

While Kubernetes is inherently declarative, using Ansible to manage external configurations, secrets, or complex orchestrations around a cluster often leads to imperative anti-patterns. A common pitfall is using the command or shell modules with kubectl, which breaks idempotency by executing every time the playbook runs, regardless of the cluster's state.

Moving to the kubernetes.core.k8s Module

To achieve true idempotence, we must leverage the kubernetes.core.k8s module. This module speaks directly to the Kubernetes API, allowing Ansible to compare the desired state (defined in your YAML or dict) against the live state in the cluster, only making changes when they differ.

- name: Ensure Nginx deployment is in the desired state
  kubernetes.core.k8s:
    state: present
    definition:
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: nginx-deployment
        namespace: production
      spec:
        replicas: 3
        selector:
          matchLabels:
            app: nginx
        template:
          metadata:
            labels:
              app: nginx
          spec:
            containers:
            - name: nginx
              image: nginx:1.24.0 # Specific version pinning for idempotence
              ports:
              - containerPort: 80

Notice how we explicitly pin the image: nginx:1.24.0. Using latest is an idempotency killer; the playbook cannot guarantee the underlying image hash hasn't changed, leading to unpredictable updates.

Handling Secrets Intelligently

Generating secrets on the fly often triggers constant changes. Let's look at an idempotent approach to handling a TLS certificate secret, ensuring the secret is only regenerated if the certificate file actually changes on disk.

- name: Read TLS certificate contents
  ansible.builtin.slurp:
    src: /etc/ssl/certs/app.crt
  register: tls_cert

- name: Read TLS key contents
  ansible.builtin.slurp:
    src: /etc/ssl/private/app.key
  register: tls_key

- name: Apply TLS Secret to Kubernetes
  kubernetes.core.k8s:
    state: present
    definition:
      apiVersion: v1
      kind: Secret
      type: kubernetes.io/tls
      metadata:
        name: app-tls
        namespace: production
      data:
        tls.crt: "{{ tls_cert['content'] }}" # Slurp returns b64 encoded data, perfect for k8s secrets
        tls.key: "{{ tls_key['content'] }}"

Because slurp returns Base64 encoded content and Kubernetes expects Base64, we inject it directly. The kubernetes.core.k8s module computes the hash of the live secret and the desired secret. If the disk files haven't changed, the playbook reports ok rather than changed.

Is your AI agent's infrastructure secure and reliable?

Book a Free 15-Min Technical Audit
Hire Me