Ansible Lesson 33 – Ansible with Kubernetes | Dataplexa
Section III · Lesson 33

Ansible with Kubernetes

In this lesson

Ansible vs Kubernetes kubernetes.core modules Deploying resources Secrets & ConfigMaps Helm integration

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.

Ansible handles
Provisioning nodes and installing kubelet + container runtime
Bootstrapping the cluster (kubeadm init / join)
Applying manifests and Helm charts in CI/CD pipelines
Injecting Vault secrets into Kubernetes Secrets at deploy time
Kubernetes handles
Scheduling pods to nodes
Self-healing — restarting failed containers
Rolling updates via Deployment strategy
Service discovery and internal load balancing

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

Target localhost with connection: local — modules talk directly to the API server via kubeconfig; no SSH to cluster nodes needed.
Inject Vault secrets into Kubernetes Secrets at deploy time — secrets never live in Git unencrypted and never appear in manifest files. Always add no_log: true.
Use k8s_info with wait: true before smoke tests — applying a Deployment does not block. Explicitly wait before post-deployment verification.
The helm module accepts values: as a dict — inject per-environment variables directly, removing the need for separate values files.
Let Kubernetes handle scheduling, scaling, and self-healing — declare desired state and let the control plane reconcile. Do not manage individual pods from Ansible loops.

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.