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.
- The kubernetes.core collection
- Job 1: Day-zero cluster bootstrap
- Job 2: Operational runbooks
- Job 3: The hybrid estate
- Ansible-based operators
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:
ansible-galaxy collection install kubernetes.coreOn 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:
| Module | Purpose |
|---|---|
kubernetes.core.k8s | Create, patch or delete any object. The workhorse |
kubernetes.core.k8s_info | Query objects — the read counterpart |
kubernetes.core.k8s_json_patch | Apply a JSON patch |
kubernetes.core.k8s_scale | Scale a workload and optionally wait |
kubernetes.core.k8s_drain | Cordon and drain a node |
kubernetes.core.k8s_taint | Manage node taints |
kubernetes.core.k8s_exec | Execute a command in a pod |
kubernetes.core.k8s_log | Retrieve pod logs |
kubernetes.core.k8s_cp | Copy files to or from a pod |
kubernetes.core.k8s_rollback | Roll back a Deployment or DaemonSet |
kubernetes.core.k8s_service | Manage Services specifically |
kubernetes.core.k8s_cluster_info | Cluster version and API information |
kubernetes.core.kubeconfig | Manage kubeconfig files |
kubernetes.core.helm | Install, upgrade and uninstall releases |
kubernetes.core.helm_info | Query release state |
kubernetes.core.helm_repository | Manage chart repositories |
kubernetes.core.helm_template | Render a chart locally |
kubernetes.core.helm_pull | Fetch a chart |
kubernetes.core.helm_registry_auth | Authenticate 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
- name: Create a k8s namespace
kubernetes.core.k8s:
name: testing
api_version: v1
kind: Namespace
state: presentstate takes exactly three values: present (default), absent, and patched. The semantics are worth knowing precisely:
presentcreates the object if missing, and patches it if it exists and differs.absentdeletes it.patchedpatches 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:
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.yamlThat 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.
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: 300These 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 editon 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
- What Is Ansible? — the execution model behind all of the above
- Ansible vs Terraform — the provisioning layer underneath the cluster
- What Is GitOps? — the model that should own app delivery
- Argo CD GitOps on Kubernetes — what to hand off to after bootstrap
- Helm Best Practices for Production — for the chart side of the
helmmodule
Official References
- Ansible playbook guide — tasks, handlers, roles and execution order
- Helm chart template guide — templates, values and the sprig function set
- Helm charts — chart structure, dependencies and hooks
Was this article helpful?
Be the first to rate this article
Related Topics
Found this useful? Share it.


