Ansible Course
Ansible with Kubernetes
In this lesson
Ansible and Kubernetes occupy different but
complementary positions in the infrastructure stack. Kubernetes
schedules and manages containerised workloads — it is the runtime platform.
Ansible provisions the infrastructure Kubernetes runs on, manages
the cluster lifecycle, and applies manifests and Helm charts as part of a deployment
pipeline. The kubernetes.core collection gives Ansible idempotent modules
for every Kubernetes operation — creating namespaces, deploying workloads, managing
Secrets, applying manifests from Jinja2 templates, and invoking Helm — all with
Ansible's variable injection and Vault integration that native
kubectl apply cannot provide.
Division of Responsibility
The key to using these tools together is knowing where each one's responsibility ends. Trying to use Ansible to replicate what Kubernetes already does — self-healing, scheduling, autoscaling — leads to fragile automation that fights the platform.
The kubernetes.core Collection
Install with
ansible-galaxy collection install kubernetes.core. All modules
communicate with the Kubernetes API server via kubeconfig — no SSH to cluster nodes needed.
kubernetes.core.k8s
Create, update, or delete any Kubernetes resource — inline definition or from a manifest file. Idempotent equivalent of kubectl apply with full Ansible variable injection.
kubernetes.core.k8s_info
Query the API for resource information. Use with wait: true to block until a Deployment's pods are ready before proceeding to smoke tests.
kubernetes.core.helm
Install and upgrade Helm releases. Accepts values: as a dict — inject per-environment Ansible variables directly, eliminating separate values files per environment.
kubernetes.core.helm_repository
Register and manage Helm chart repositories. Run before helm tasks to ensure required repos are available.
The Airport Control Tower Analogy
Kubernetes is the air traffic control system — it schedules planes (pods) to runways (nodes) and keeps them flying. Ansible is the construction crew — it builds runways (provisions nodes), installs radar equipment (cluster components), and delivers aircraft to the tarmac (deploys applications). The tower does not build runways; the crew does not schedule flights.
Deploying Kubernetes Resources
Kubernetes playbooks target localhost with connection: local — the modules call the API server directly, no SSH needed.
---
- name: Deploy application to Kubernetes
hosts: localhost
connection: local
gather_facts: false
vars:
namespace: "myapp-{{ environment }}"
app_image: "registry.example.com/myapp:{{ app_version }}"
replicas: "{{ 3 if environment == 'production' else 1 }}"
tasks:
- name: Create application namespace
kubernetes.core.k8s:
api_version: v1
kind: Namespace
name: "{{ namespace }}"
state: present
- name: Deploy application Deployment
kubernetes.core.k8s:
state: present
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: "{{ namespace }}"
labels:
app: myapp
version: "{{ app_version }}"
spec:
replicas: "{{ replicas | int }}"
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: "{{ app_image }}"
ports:
- containerPort: 8000
envFrom:
- secretRef:
name: myapp-secrets
resources:
requests: { memory: "128Mi", cpu: "100m" }
limits: { memory: "512Mi", cpu: "500m" }
- name: Expose via Service
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: "{{ namespace }}"
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8000
type: ClusterIP
Injecting Vault Secrets into Kubernetes
The most secure pattern: Ansible Vault stores credentials; Ansible injects them into Kubernetes Secrets at deploy time. Secrets never live in Git unencrypted and never appear in manifest files.
- name: Create Kubernetes Secret from Vault variables
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
namespace: "{{ namespace }}"
type: Opaque
stringData:
DATABASE_URL: "postgresql://{{ vault_db_user }}:{{ vault_db_password }}@postgres:5432/appdb"
SECRET_KEY: "{{ vault_app_secret_key }}"
API_KEY: "{{ vault_api_key }}"
no_log: true # prevent decrypted values appearing in output
- name: Create ConfigMap for non-sensitive config
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
namespace: "{{ namespace }}"
data:
ENVIRONMENT: "{{ environment }}"
LOG_LEVEL: "{{ log_level | default('info') }}"
TASK [Create Kubernetes Secret from Vault variables] ************************** changed: [localhost] <-- Secret created (values suppressed by no_log) TASK [Create ConfigMap for non-sensitive config] ****************************** ok: [localhost] <-- ConfigMap already matches desired state
What just happened?
Ansible decrypted the Vault variables and patched the Kubernetes Secret — but no_log: true suppressed all output. Kubernetes stores Secret values base64-encoded internally. The ConfigMap was already up to date so it reported ok.
Waiting for Rollout + Helm
Applying a Deployment returns immediately — Kubernetes reconciles asynchronously. Always wait for pods to be ready before running smoke tests.
- name: Wait for Deployment rollout to complete
kubernetes.core.k8s_info:
api_version: apps/v1
kind: Deployment
name: myapp
namespace: "{{ namespace }}"
wait: true
wait_timeout: 300
wait_condition:
type: Available
status: "True"
register: deployment_status
- name: Install ingress-nginx via Helm
kubernetes.core.helm:
name: ingress-nginx
chart_ref: ingress-nginx/ingress-nginx
chart_version: "4.8.3"
release_namespace: ingress-nginx
create_namespace: true
state: present
values:
controller:
replicaCount: "{{ 2 if environment == 'production' else 1 }}"
service:
type: LoadBalancer
- name: Deploy application Helm chart
kubernetes.core.helm:
name: "myapp-{{ environment }}"
chart_ref: oci://registry.example.com/charts/myapp
chart_version: "{{ app_chart_version }}"
release_namespace: "{{ namespace }}"
create_namespace: true
state: present
values:
image:
tag: "{{ app_version }}"
replicaCount: "{{ replicas }}"
existingSecret: myapp-secrets
Connection Parameters
kubernetes.core connection parameters
kubeconfig
Path to kubeconfig. Defaults to ~/.kube/config. Set per-task to target different clusters in the same playbook.
context
Named kubeconfig context — select between clusters without switching the active context.
host
API server URL. Use with api_key for service account authentication — ideal for CI/CD where no kubeconfig file is available.
api_key
Bearer token for service account auth. Store in Ansible Vault and always use no_log: true on tasks that reference it.
Do Not Use Ansible to Replicate What Kubernetes Already Does
Never use Ansible loops to restart individual pods, manage rolling updates, or scale replicas — Kubernetes does these declaratively and more reliably. Declare desired state via the k8s module and let the control plane reconcile. Fighting the platform with Ansible task loops produces fragile, hard-to-debug automation.
Key Takeaways
localhost with connection: local — modules talk directly to the API server via kubeconfig; no SSH to cluster nodes needed.
no_log: true.
k8s_info with wait: true before smoke tests — applying a Deployment does not block. Explicitly wait before post-deployment verification.
helm module accepts values: as a dict — inject per-environment variables directly, removing the need for separate values files.
Teacher's Note
Replace a kubectl apply -f deployment.yml line in a CI script with a kubernetes.core.k8s task. Then add a Vault-encrypted Secret task below it. The difference is immediate: variable injection, idempotency reporting, and no plaintext credentials in any file.
Practice Questions
1. Kubernetes playbooks using kubernetes.core modules target which host, since they communicate directly with the API server rather than SSH-ing into cluster nodes?
2. Which module, used with wait: true, blocks the playbook until a Deployment's pods are available before proceeding?
3. A k8s task creates a Kubernetes Secret containing a decrypted Vault variable. Which task attribute prevents the values from appearing in output?
Quiz
1. Your application Helm chart needs different replica counts and log levels for staging vs production. What is the cleanest Ansible approach?
2. Database passwords are in Ansible Vault and need to reach application pods as environment variables. What is the most secure pipeline?
3. After applying a Deployment, the smoke test fails because pods are not yet ready. What is the correct fix?
Up Next · Lesson 34
Troubleshooting Ansible
Master the full Ansible debugging toolkit — verbosity levels, the debug module, step mode, check mode with diff, common error patterns, and strategies for diagnosing failures on unreachable hosts.