Introduction

Kubernetes handles container orchestration. Ansible handles everything around it: provisioning clusters, bootstrapping namespaces, deploying Helm charts, managing RBAC, rotating secrets, and coordinating multi-cluster operations. While kubectl and Helm work for single-cluster manual operations, Ansible provides the automation layer for managing Kubernetes at enterprise scale — across dev, staging, and production clusters with consistent, auditable, repeatable deployments.

Collections and Prerequisites

# Install required collections
ansible-galaxy collection install kubernetes.core
ansible-galaxy collection install cloud.common

# Python dependencies on Ansible control node
pip install kubernetes openshift PyYAML
CollectionKey ModulesPurpose
kubernetes.corek8s, k8s_info, helm, k8s_execCore K8s resource management
amazon.awseks_cluster, eks_nodegroupAWS EKS provisioning
azure.azcollectionazure_rm_aksAzure AKS provisioning
google.cloudgcp_container_clusterGCP GKE provisioning

Kubeconfig Management

# group_vars/k8s_clusters.yml
k8s_clusters:
  dev:
    kubeconfig: "~/.kube/configs/dev-cluster"
    context: "dev-context"
  staging:
    kubeconfig: "~/.kube/configs/staging-cluster"
    context: "staging-context"
  production:
    kubeconfig: "~/.kube/configs/prod-cluster"
    context: "prod-context"

Pattern 1: Namespace and RBAC Bootstrap

---
- name: Bootstrap Kubernetes namespaces and RBAC
  hosts: localhost
  connection: local
  vars:
    target_cluster: production
    namespaces:
      - name: app-frontend
        team: frontend
        resource_quota:
          cpu: "8"
          memory: "16Gi"
      - name: app-backend
        team: backend
        resource_quota:
          cpu: "16"
          memory: "32Gi"
      - name: app-data
        team: data-engineering
        resource_quota:
          cpu: "32"
          memory: "64Gi"

  tasks:
    - name: Create namespaces
      kubernetes.core.k8s:
        kubeconfig: "{{ k8s_clusters[target_cluster].kubeconfig }}"
        state: present
        definition:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: "{{ item.name }}"
            labels:
              team: "{{ item.team }}"
              managed-by: ansible
      loop: "{{ namespaces }}"

    - name: Apply resource quotas
      kubernetes.core.k8s:
        kubeconfig: "{{ k8s_clusters[target_cluster].kubeconfig }}"
        state: present
        definition:
          apiVersion: v1
          kind: ResourceQuota
          metadata:
            name: "{{ item.name }}-quota"
            namespace: "{{ item.name }}"
          spec:
            hard:
              requests.cpu: "{{ item.resource_quota.cpu }}"
              requests.memory: "{{ item.resource_quota.memory }}"
              limits.cpu: "{{ item.resource_quota.cpu }}"
              limits.memory: "{{ item.resource_quota.memory }}"
      loop: "{{ namespaces }}"

    - name: Create team RBAC roles
      kubernetes.core.k8s:
        kubeconfig: "{{ k8s_clusters[target_cluster].kubeconfig }}"
        state: present
        definition:
          apiVersion: rbac.authorization.k8s.io/v1
          kind: RoleBinding
          metadata:
            name: "{{ item.team }}-edit"
            namespace: "{{ item.name }}"
          roleRef:
            apiGroup: rbac.authorization.k8s.io
            kind: ClusterRole
            name: edit
          subjects:
            - kind: Group
              name: "{{ item.team }}"
              apiGroup: rbac.authorization.k8s.io
      loop: "{{ namespaces }}"

    - name: Apply network policies (namespace isolation)
      kubernetes.core.k8s:
        kubeconfig: "{{ k8s_clusters[target_cluster].kubeconfig }}"
        state: present
        definition:
          apiVersion: networking.k8s.io/v1
          kind: NetworkPolicy
          metadata:
            name: default-deny-ingress
            namespace: "{{ item.name }}"
          spec:
            podSelector: {}
            policyTypes:
              - Ingress
      loop: "{{ namespaces }}"

Pattern 2: Helm Chart Deployment

---
- name: Deploy application stack via Helm
  hosts: localhost
  connection: local
  vars:
    cluster: production
    app_version: "2.5.0"

  tasks:
    - name: Add Helm repositories
      kubernetes.core.helm_repository:
        name: "{{ item.name }}"
        repo_url: "{{ item.url }}"
      loop:
        - { name: bitnami, url: "https://charts.bitnami.com/bitnami" }
        - { name: ingress-nginx, url: "https://kubernetes.github.io/ingress-nginx" }
        - { name: prometheus-community, url: "https://prometheus-community.github.io/helm-charts" }

    - name: Deploy NGINX Ingress Controller
      kubernetes.core.helm:
        kubeconfig: "{{ k8s_clusters[cluster].kubeconfig }}"
        name: ingress-nginx
        chart_ref: ingress-nginx/ingress-nginx
        release_namespace: ingress-system
        create_namespace: true
        values:
          controller:
            replicaCount: 3
            resources:
              requests:
                cpu: 100m
                memory: 128Mi
            service:
              annotations:
                service.beta.kubernetes.io/aws-load-balancer-type: nlb
        wait: true
        timeout: 10m0s

    - name: Deploy application
      kubernetes.core.helm:
        kubeconfig: "{{ k8s_clusters[cluster].kubeconfig }}"
        name: myapp
        chart_ref: ./charts/myapp
        release_namespace: app-backend
        values:
          image:
            repository: "registry.internal/myapp"
            tag: "{{ app_version }}"
          replicas: 3
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
          ingress:
            enabled: true
            hostname: app.example.com
          database:
            host: "{{ db_endpoint }}"
            name: appdb
            existingSecret: app-db-credentials
        wait: true
        timeout: 5m0s

    - name: Deploy monitoring stack
      kubernetes.core.helm:
        kubeconfig: "{{ k8s_clusters[cluster].kubeconfig }}"
        name: kube-prometheus
        chart_ref: prometheus-community/kube-prometheus-stack
        release_namespace: monitoring
        create_namespace: true
        values:
          grafana:
            enabled: true
            adminPassword: "{{ grafana_admin_password }}"
          prometheus:
            prometheusSpec:
              retention: 30d
              storageSpec:
                volumeClaimTemplate:
                  spec:
                    resources:
                      requests:
                        storage: 100Gi

Pattern 3: Rolling Deployment with Validation

---
- name: Rolling application deployment with health checks
  hosts: localhost
  connection: local
  vars:
    cluster: production
    namespace: app-backend
    app_name: myapp
    new_version: "{{ deploy_version }}"

  tasks:
    - name: Get current deployment info
      kubernetes.core.k8s_info:
        kubeconfig: "{{ k8s_clusters[cluster].kubeconfig }}"
        kind: Deployment
        name: "{{ app_name }}"
        namespace: "{{ namespace }}"
      register: current_deployment

    - name: Store current version for rollback
      ansible.builtin.set_fact:
        previous_version: "{{ current_deployment.resources[0].spec.template.spec.containers[0].image.split(':')[1] }}"

    - name: Update deployment image
      kubernetes.core.k8s:
        kubeconfig: "{{ k8s_clusters[cluster].kubeconfig }}"
        state: present
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: "{{ app_name }}"
            namespace: "{{ namespace }}"
          spec:
            template:
              spec:
                containers:
                  - name: "{{ app_name }}"
                    image: "registry.internal/{{ app_name }}:{{ new_version }}"
        wait: true
        wait_timeout: 300
        wait_condition:
          type: Available
          status: "True"
      register: deploy_result

    - name: Wait for rollout to complete
      kubernetes.core.k8s_info:
        kubeconfig: "{{ k8s_clusters[cluster].kubeconfig }}"
        kind: Deployment
        name: "{{ app_name }}"
        namespace: "{{ namespace }}"
      register: rollout_status
      until: >
        rollout_status.resources[0].status.readyReplicas | default(0)
        == rollout_status.resources[0].spec.replicas
      retries: 30
      delay: 10

    - name: Run smoke tests
      ansible.builtin.uri:
        url: "https://app.example.com/healthz"
        status_code: 200
        timeout: 10
      retries: 5
      delay: 5
      register: health_check
      ignore_errors: true

    - name: Rollback on failure
      kubernetes.core.k8s:
        kubeconfig: "{{ k8s_clusters[cluster].kubeconfig }}"
        state: present
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: "{{ app_name }}"
            namespace: "{{ namespace }}"
          spec:
            template:
              spec:
                containers:
                  - name: "{{ app_name }}"
                    image: "registry.internal/{{ app_name }}:{{ previous_version }}"
      when: health_check is failed

Pattern 4: Secret Management

---
- name: Manage Kubernetes secrets from Vault
  hosts: localhost
  connection: local
  tasks:
    - name: Create database credentials secret
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Secret
          metadata:
            name: app-db-credentials
            namespace: app-backend
            labels:
              managed-by: ansible
          type: Opaque
          stringData:
            DB_HOST: "{{ vault_db_host }}"
            DB_USER: "{{ vault_db_user }}"
            DB_PASSWORD: "{{ vault_db_password }}"
            DB_NAME: "appdb"

    - name: Create TLS secret from certificates
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Secret
          metadata:
            name: app-tls
            namespace: app-backend
          type: kubernetes.io/tls
          data:
            tls.crt: "{{ lookup('file', 'certs/app.crt') | b64encode }}"
            tls.key: "{{ lookup('file', 'certs/app.key') | b64encode }}"

Pattern 5: Multi-Cluster Operations

---
- name: Deploy across all clusters
  hosts: localhost
  connection: local
  vars:
    clusters:
      - { name: us-east, kubeconfig: "~/.kube/us-east" }
      - { name: eu-west, kubeconfig: "~/.kube/eu-west" }
      - { name: ap-south, kubeconfig: "~/.kube/ap-south" }

  tasks:
    - name: Deploy to each cluster
      kubernetes.core.helm:
        kubeconfig: "{{ item.kubeconfig }}"
        name: myapp
        chart_ref: ./charts/myapp
        release_namespace: app-backend
        values_files:
          - "values/{{ item.name }}.yml"
        wait: true
      loop: "{{ clusters }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Verify deployment in all clusters
      kubernetes.core.k8s_info:
        kubeconfig: "{{ item.kubeconfig }}"
        kind: Deployment
        name: myapp
        namespace: app-backend
      loop: "{{ clusters }}"
      loop_control:
        label: "{{ item.name }}"
      register: cluster_status

    - name: Report status
      ansible.builtin.debug:
        msg: "{{ item.item.name }}: {{ item.resources[0].status.readyReplicas | default(0) }}/{{ item.resources[0].spec.replicas }} ready"
      loop: "{{ cluster_status.results }}"
      loop_control:
        label: "{{ item.item.name }}"

Troubleshooting

Module Can't Find kubeconfig

# Explicitly set in environment
- name: Deploy with explicit kubeconfig
  kubernetes.core.k8s:
    state: present
    src: deployment.yml
  environment:
    K8SCONFIG: "{{ kubeconfig_path }}"
    KUBECONFIG: "{{ kubeconfig_path }}"

Timeout During Deployment

# Increase wait timeout for large deployments
- name: Deploy with extended timeout
  kubernetes.core.helm:
    name: large-app
    chart_ref: ./charts/large-app
    wait: true
    timeout: 15m0s  # Default is 5m

RBAC Permission Denied

# Check service account permissions
kubectl auth can-i create deployments --namespace app-backend --as system:serviceaccount:ansible:ansible-sa

Conclusion

Use Ansible to automate the Kubernetes lifecycle beyond kubectl apply: namespace bootstrapping with RBAC and network policies, Helm chart deployments with kubernetes.core.helm, rolling updates with automated health checks and rollback, secret management integrated with external vaults, and multi-cluster operations from a single playbook. The kubernetes.core collection provides all the modules needed — k8s for raw manifests, helm for chart deployments, and k8s_info for status queries. For enterprise environments, combine with AAP workflows for approval gates and audit trails.

For a production-focused walkthrough, see Luca Berton's guide on Ansible Kubernetes day-2 operations.