Kubernetes
10 min readAugust 6, 2026Updated August 19, 2026

Ansible for Kubernetes Automation: Where It Fits and Where It Doesn't

Part ofKubernetes
AJ
Ajeet Yadav
Platform & Cloud Engineer
Ansible for Kubernetes Automation: Where It Fits and Where It Doesn't

Quick answer

The kubernetes.core collection lets Ansible create, patch and query any Kubernetes object, run Helm, and exec into pods. That does not make it a deployment tool — Ansible has no reconcile loop. Here are the three jobs it genuinely wins, the one it should never take, and the modules that actually exist.

10 min read · Kubernetes

Ansible can drive Kubernetes completely — create any object, patch it, wait on it, run Helm, exec into pods, pull logs. It still should not be how you deploy your applications. The reason is not capability, it is model: Ansible runs when you invoke it and remembers nothing, while Kubernetes is built around controllers that reconcile continuously. Putting a stateless push tool in charge of a declarative reconciling system means you get the worst properties of both.

That said, "don't use Ansible for app delivery" is not "don't use Ansible with Kubernetes." There are three jobs where it is clearly the best available tool, and they are jobs that GitOps controllers handle badly or not at all.

The kubernetes.core collection

Everything runs through this collection, installed with ansible-galaxy:

bash
ansible-galaxy collection install kubernetes.core

On the control node — not the cluster, not the managed nodes — you need the Python dependencies the modules declare:

python >= 3.9
kubernetes >= 24.2.0
PyYAML >= 3.11
jsonpatch

This trips people up in CI: the Ansible container image needs the kubernetes Python client installed, and a missing client produces an import error that reads like an Ansible bug rather than a missing dependency.

The modules that exist

Worth listing precisely, because half the module names people guess at do not exist:

ModulePurpose
kubernetes.core.k8sCreate, patch or delete any object. The workhorse
kubernetes.core.k8s_infoQuery objects — the read counterpart
kubernetes.core.k8s_json_patchApply a JSON patch
kubernetes.core.k8s_scaleScale a workload and optionally wait
kubernetes.core.k8s_drainCordon and drain a node
kubernetes.core.k8s_taintManage node taints
kubernetes.core.k8s_execExecute a command in a pod
kubernetes.core.k8s_logRetrieve pod logs
kubernetes.core.k8s_cpCopy files to or from a pod
kubernetes.core.k8s_rollbackRoll back a Deployment or DaemonSet
kubernetes.core.k8s_serviceManage Services specifically
kubernetes.core.k8s_cluster_infoCluster version and API information
kubernetes.core.kubeconfigManage kubeconfig files
kubernetes.core.helmInstall, upgrade and uninstall releases
kubernetes.core.helm_infoQuery release state
kubernetes.core.helm_repositoryManage chart repositories
kubernetes.core.helm_templateRender a chart locally
kubernetes.core.helm_pullFetch a chart
kubernetes.core.helm_registry_authAuthenticate to an OCI registry

Plus a kubernetes.core.kubectl connection plugin (target a pod as if it were a host), a kubernetes.core.k8s lookup plugin (read cluster data into a variable at template time), and a kubernetes.core.kustomize lookup for rendering kustomizations.

Note there is no inventory plugin in this collection — if you have seen references to dynamic inventory from Kubernetes, that comes from elsewhere.

The k8s module

yaml
- name: Create a k8s namespace
  kubernetes.core.k8s:
    name: testing
    api_version: v1
    kind: Namespace
    state: present

state takes exactly three values: present (default), absent, and patched. The semantics are worth knowing precisely:

  • present creates the object if missing, and patches it if it exists and differs.
  • absent deletes it.
  • patched patches an existing object and silently does nothing if it does not exist — no error. That is occasionally exactly what you want and occasionally a very quiet bug.

Two options matter more than their documentation suggests. apply: true compares against the previously applied definition and ignores auto-generated fields — it behaves like kubectl apply and works considerably better with Services than force: true, which replaces the object outright and will happily blow away a Service's allocated node port. And merge_type handles CRDs that reject strategic merge patches; if you see "strategic merge patch format is not supported," set merge_type: merge. The old merge_type: json was removed in version 4.0.0 — use kubernetes.core.k8s_json_patch instead.

Job 1: Day-zero cluster bootstrap

This is the strongest case. Before a GitOps controller can reconcile anything, something has to install the GitOps controller.

The bootstrap sequence for a new cluster is inherently ordered and imperative: create the cluster, wait for the API to respond, install the CNI, wait for nodes to become ready, install cert-manager, wait for its webhook to be serving, install the CRDs that depend on it, install Argo CD, register the first Application, and then get out of the way.

That is a procedure, with real waits and real ordering. Ansible is good at procedures:

yaml
1- name: Bootstrap platform components
2  hosts: localhost
3  connection: local
4  gather_facts: false
5
6  tasks:
7    - name: Add the Jetstack chart repository
8      kubernetes.core.helm_repository:
9        name: jetstack
10        repo_url: https://charts.jetstack.io
11
12    - name: Install cert-manager
13      kubernetes.core.helm:
14        name: cert-manager
15        chart_ref: jetstack/cert-manager
16        release_namespace: cert-manager
17        create_namespace: true
18        wait: true
19        values:
20          crds:
21            enabled: true
22
23    - name: Wait for the cert-manager webhook to be available
24      kubernetes.core.k8s_info:
25        kind: Deployment
26        name: cert-manager-webhook
27        namespace: cert-manager
28        wait: true
29        wait_condition:
30          type: Available
31          status: "True"
32        wait_timeout: 300
33
34    - name: Create the cluster issuer
35      kubernetes.core.k8s:
36        state: present
37        src: manifests/cluster-issuer.yaml

That third task is the one you cannot express in a Helm chart or a GitOps Application without extra machinery. "Wait for this specific condition before continuing" is Ansible's natural idiom, and bootstrap sequences are full of it.

Once Argo CD is running and pointed at your repository, Ansible's job ends. Everything after that is GitOps.

Kubernetes Production Readiness Checklist

The pre-launch checks we run before calling a cluster production-ready — probes, resources, RBAC, upgrades, and backups. Plain Markdown you can commit to your repo.

Free. Instant download. You'll also get the occasional deep-dive from the newsletter — unsubscribe anytime.

Job 2: Operational runbooks

The second strong case is the operations that are deliberately one-off and should never be reconciled.

Draining a node for maintenance. Rotating a credential across twelve namespaces. Collecting diagnostics from every failing pod in a cluster. Scaling a workload down for a maintenance window and back up afterwards. Executing a database migration in a pod, once, in a controlled way.

yaml
1- name: Drain a node for maintenance
2  kubernetes.core.k8s_drain:
3    name: "{{ node_name }}"
4    state: drain
5    delete_options:
6      ignore_daemonsets: true
7      delete_emptydir_data: true
8      wait_timeout: 300

These are not desired-state declarations. A GitOps controller would fight you — you do not want a reconciler noticing that a node is cordoned and un-cordoning it. Runbooks want a tool that does the thing once, reports what happened, and forgets. That is exactly what Ansible is.

The k8s_exec, k8s_log, and k8s_cp modules make Ansible a decent incident-response harness too: gather logs from every pod matching a selector across three clusters into one directory, in one command. See the Kubernetes debugging guide for what you would be gathering.

Job 3: The hybrid estate

If Kubernetes is only part of your infrastructure — clusters plus VMs, network appliances, or on-premises databases — Ansible is the only tool that speaks to all of it with one vocabulary.

A change that has to touch a firewall rule, a legacy application server, and a Kubernetes ConfigMap in a specific order is genuinely awkward to coordinate across three tools. It is one playbook in Ansible. This is not a glamorous reason, and it is frequently the real one.

Ansible-based operators

There is a fourth case worth knowing exists. The Operator SDK supports building operators whose reconcile logic is an Ansible role rather than Go code: the CRD triggers a role, the role converges the cluster, and the operator handles the reconcile loop for you.

This is a legitimate way to package existing Ansible automation as a Kubernetes-native controller, and it is much less work than learning controller-runtime. The trade is performance and control — you are running an Ansible process per reconcile, which is heavier than a Go loop and harder to make fast. Reasonable for a low-frequency operator managing external systems; a poor fit for anything reconciling thousands of objects. If you want the real thing, see building operators with controller-runtime.

The job Ansible should not take

Continuous application delivery. This is worth being unambiguous about.

If Ansible deploys your applications, then:

  • Nothing corrects drift. Someone runs kubectl edit on a Deployment and it stays edited until the next playbook run — which might be next week, or never, because playbooks run when a human runs them.
  • There is no record of desired state. Your cluster's intended configuration is spread across playbooks, variable files, and whatever was passed on the command line. Git shows you the playbook, not the outcome.
  • Rollback is a new deployment. No revision history, no "sync to previous commit."
  • You have rebuilt a reconciler, badly. The end state of this path is a scheduled job that runs the playbook every fifteen minutes to fix drift — which is a worse Argo CD with no UI and no diff view.

Argo CD and Flux exist because this is a genuinely hard problem that deserves a purpose-built controller. Use one.

The clean split: Ansible up to the point where the GitOps controller is running. GitOps after that. Ansible again for out-of-band operations that should never be reconciled.

Frequently Asked Questions

Do I need Ansible if I already use Helm and Argo CD?

For application delivery, no. For bootstrapping the cluster to the point where Argo CD is running, and for operational runbooks that should not be reconciled, it earns its place. Many teams use it for exactly those two windows and nothing else, which is a healthy outcome.

Where do the Python dependencies need to be installed?

On the control node — the machine running ansible-playbook. The modules use the Kubernetes Python client locally to talk to the API server; nothing is installed on the cluster or on any managed node. In CI, this means your Ansible image needs kubernetes >= 24.2.0 alongside Ansible itself.

What is the difference between state: present and state: patched?

present creates the object if it does not exist and patches it if it does. patched only patches an existing object and silently succeeds if it is missing. Use present unless you specifically want the no-op-if-absent behaviour, because a typo'd name under patched fails silently.

Should I use the k8s module or the helm module?

helm when the thing you are installing is a chart, because you get release tracking, upgrade semantics, and helm rollback. k8s for individual manifests, CRs, and the small objects that surround a release — issuers, secrets, namespaces. Mixing them in one playbook is normal.

Can Ansible replace kubectl for day-to-day work?

It can, but it should not. For interactive work kubectl is faster and gives better feedback. Ansible earns its keep when the operation spans many objects, many namespaces, or many clusters, and when you want it recorded and repeatable — not when you want to look at one pod.

How does this compare to Crossplane?

They solve adjacent problems from opposite directions. Crossplane makes external infrastructure into Kubernetes resources reconciled by controllers in the cluster; Ansible reaches out from a control node to configure things, including Kubernetes. Crossplane gives you continuous reconciliation for cloud resources; Ansible gives you ordered procedures across heterogeneous targets. See Crossplane for Kubernetes infrastructure.

See also

Official References

Was this article helpful?

Be the first to rate this article

Related Topics

Ansible
Kubernetes
Automation
Helm
GitOps
Operators
Platform Engineering

Found this useful? Share it.

Practice this

Related tools

Read Next