Introduction

Ansible and Kubernetes are complementary: Kubernetes orchestrates containers at runtime, while Ansible automates the infrastructure around it — cluster provisioning, application deployment, config management, and Day 2 operations. The kubernetes.core collection provides modules to manage any Kubernetes resource directly from Ansible playbooks.

Prerequisites

Install the kubernetes.core collection and Python dependencies:

# Install the collection
ansible-galaxy collection install kubernetes.core

# Install Python dependencies
pip install kubernetes PyYAML jsonpatch

Verify:

ansible-galaxy collection list | grep kubernetes
# kubernetes.core  3.2.0

Authentication

Ansible connects to your cluster using the same kubeconfig as kubectl:

---
- name: K8s tasks using default kubeconfig
  hosts: localhost
  connection: local
  tasks:
    - name: Get all pods (uses ~/.kube/config)
      kubernetes.core.k8s_info:
        kind: Pod
        namespace: default
      register: pods

Explicit kubeconfig

- name: Use specific kubeconfig
  kubernetes.core.k8s:
    kubeconfig: /path/to/kubeconfig
    state: present
    definition: "{{ lookup('file', 'deployment.yml') }}"

# Or via environment variable
- name: Set kubeconfig via env
  hosts: localhost
  environment:
    K8SCONFIG: /path/to/kubeconfig
  tasks:
    - kubernetes.core.k8s:
        state: present
        src: deployment.yml

In-cluster Authentication (Running Inside K8s)

- name: Use service account token (in-pod)
  kubernetes.core.k8s:
    host: "https://kubernetes.default.svc"
    api_key: "{{ lookup('file', '/var/run/secrets/kubernetes.io/serviceaccount/token') }}"
    validate_certs: false
    state: present
    definition: "{{ deployment }}"

kubernetes.core.k8s Module

The main module — create, update, and delete any Kubernetes resource:

Create a Namespace

- name: Create namespace
  kubernetes.core.k8s:
    state: present
    definition:
      apiVersion: v1
      kind: Namespace
      metadata:
        name: myapp
        labels:
          team: backend
          env: production

Deploy an Application

---
- name: Deploy web application to Kubernetes
  hosts: localhost
  connection: local
  vars:
    app_name: mywebapp
    app_namespace: production
    app_image: nginx:1.25
    app_replicas: 3

  tasks:
    - name: Create namespace
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: "{{ app_namespace }}"

    - name: Create ConfigMap
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: "{{ app_name }}-config"
            namespace: "{{ app_namespace }}"
          data:
            APP_ENV: production
            LOG_LEVEL: info

    - name: Deploy application
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: "{{ app_name }}"
            namespace: "{{ app_namespace }}"
            labels:
              app: "{{ app_name }}"
          spec:
            replicas: "{{ app_replicas }}"
            selector:
              matchLabels:
                app: "{{ app_name }}"
            template:
              metadata:
                labels:
                  app: "{{ app_name }}"
              spec:
                containers:
                  - name: "{{ app_name }}"
                    image: "{{ app_image }}"
                    ports:
                      - containerPort: 80
                    envFrom:
                      - configMapRef:
                          name: "{{ app_name }}-config"
                    resources:
                      requests:
                        cpu: "100m"
                        memory: "128Mi"
                      limits:
                        cpu: "500m"
                        memory: "256Mi"

    - name: Create Service
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Service
          metadata:
            name: "{{ app_name }}"
            namespace: "{{ app_namespace }}"
          spec:
            selector:
              app: "{{ app_name }}"
            ports:
              - port: 80
                targetPort: 80
            type: ClusterIP

    - name: Create Ingress
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: networking.k8s.io/v1
          kind: Ingress
          metadata:
            name: "{{ app_name }}"
            namespace: "{{ app_namespace }}"
            annotations:
              nginx.ingress.kubernetes.io/rewrite-target: /
          spec:
            rules:
              - host: "{{ app_name }}.example.com"
                http:
                  paths:
                    - path: /
                      pathType: Prefix
                      backend:
                        service:
                          name: "{{ app_name }}"
                          port:
                            number: 80

Load from YAML Files

# From a single file
- name: Apply manifest file
  kubernetes.core.k8s:
    state: present
    src: manifests/deployment.yml

# From a directory (all YAML files)
- name: Apply all manifests
  kubernetes.core.k8s:
    state: present
    src: "{{ item }}"
  loop: "{{ lookup('fileglob', 'manifests/*.yml', wantlist=True) }}"

Query Resources with k8s_info

---
- name: Gather Kubernetes information
  hosts: localhost
  connection: local
  tasks:
    - name: Get all pods in namespace
      kubernetes.core.k8s_info:
        kind: Pod
        namespace: production
      register: pods

    - name: Show running pods
      ansible.builtin.debug:
        msg: "{{ item.metadata.name }} — {{ item.status.phase }}"
      loop: "{{ pods.resources }}"
      when: item.status.phase == 'Running'

    - name: Get specific deployment
      kubernetes.core.k8s_info:
        api_version: apps/v1
        kind: Deployment
        name: mywebapp
        namespace: production
      register: deployment

    - name: Show replica count
      ansible.builtin.debug:
        msg: "Replicas: {{ deployment.resources[0].status.readyReplicas | default(0) }}/{{ deployment.resources[0].spec.replicas }}"

    - name: Get nodes
      kubernetes.core.k8s_info:
        kind: Node
      register: nodes

    - name: Show node names and versions
      ansible.builtin.debug:
        msg: "{{ item.metadata.name }} — {{ item.status.nodeInfo.kubeletVersion }}"
      loop: "{{ nodes.resources }}"

    - name: Find pods by label
      kubernetes.core.k8s_info:
        kind: Pod
        namespace: production
        label_selectors:
          - app=mywebapp
          - tier=frontend
      register: labeled_pods

Manage Secrets

- name: Create secret from variables
  kubernetes.core.k8s:
    state: present
    definition:
      apiVersion: v1
      kind: Secret
      metadata:
        name: db-credentials
        namespace: production
      type: Opaque
      stringData:
        DB_HOST: "{{ vault_db_host }}"
        DB_USER: "{{ vault_db_user }}"
        DB_PASS: "{{ vault_db_pass }}"

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

Helm Charts with Ansible

---
- name: Deploy with Helm
  hosts: localhost
  connection: local
  tasks:
    - name: Add Helm repository
      kubernetes.core.helm_repository:
        name: bitnami
        repo_url: https://charts.bitnami.com/bitnami

    - name: Deploy PostgreSQL via Helm
      kubernetes.core.helm:
        name: my-postgres
        namespace: databases
        create_namespace: true
        chart_ref: bitnami/postgresql
        chart_version: "15.5.0"
        values:
          auth:
            postgresPassword: "{{ vault_pg_password }}"
            database: myapp
          primary:
            persistence:
              size: 20Gi
          metrics:
            enabled: true

    - name: Upgrade with new values
      kubernetes.core.helm:
        name: my-postgres
        namespace: databases
        chart_ref: bitnami/postgresql
        values:
          primary:
            resources:
              requests:
                cpu: "500m"
                memory: "512Mi"

    - name: Uninstall chart
      kubernetes.core.helm:
        name: old-release
        namespace: default
        state: absent

Wait for Resources

- name: Deploy and wait for readiness
  kubernetes.core.k8s:
    state: present
    definition: "{{ lookup('file', 'deployment.yml') }}"
    wait: true
    wait_timeout: 300
    wait_condition:
      type: Available
      status: "True"

- name: Wait for pod to be running
  kubernetes.core.k8s_info:
    kind: Pod
    namespace: production
    label_selectors:
      - app=mywebapp
    wait: true
    wait_timeout: 120
    wait_condition:
      type: Ready
      status: "True"

Rolling Updates

---
- name: Rolling update deployment
  hosts: localhost
  connection: local
  vars:
    new_image: mywebapp:v2.1.0

  tasks:
    - name: Update deployment image
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: mywebapp
            namespace: production
          spec:
            template:
              spec:
                containers:
                  - name: mywebapp
                    image: "{{ new_image }}"
        wait: true
        wait_timeout: 300

    - name: Verify rollout
      kubernetes.core.k8s_info:
        api_version: apps/v1
        kind: Deployment
        name: mywebapp
        namespace: production
      register: deploy

    - name: Check all replicas ready
      ansible.builtin.assert:
        that:
          - deploy.resources[0].status.readyReplicas == deploy.resources[0].spec.replicas
        fail_msg: "Rolling update incomplete — {{ deploy.resources[0].status.readyReplicas | default(0) }}/{{ deploy.resources[0].spec.replicas }} ready"

Delete Resources

- name: Delete deployment
  kubernetes.core.k8s:
    state: absent
    api_version: apps/v1
    kind: Deployment
    name: mywebapp
    namespace: production

- name: Delete namespace (and everything in it)
  kubernetes.core.k8s:
    state: absent
    definition:
      apiVersion: v1
      kind: Namespace
      metadata:
        name: staging

Ansible vs kubectl vs Helm

FeatureAnsible + k8skubectlHelm
Idempotent✅ Built-in❌ apply only✅
Templating✅ Jinja2 + vars❌ Basic✅ Go templates
Multi-cluster✅ Easy with varsManual context switchManual
Non-K8s resources✅ Full Ansible❌ K8s only❌ K8s only
Secrets management✅ Ansible Vault❌ Plain YAML❌ Values files
RollbackManual✅ rollout undo✅ helm rollback

When to use Ansible for K8s:

  • You already manage infra with Ansible
  • You need to combine K8s + non-K8s tasks (DNS, LB, DB)
  • You want Ansible Vault for secrets
  • Multi-cluster deployments with environment-specific vars

Troubleshooting

"Failed to import kubernetes Python library"

pip install kubernetes PyYAML jsonpatch
# Or for a specific Python
pip3 install kubernetes

"Connection refused" or "Unable to connect to server"

Your kubeconfig isn't set or the cluster is unreachable:

# Verify kubectl works first
kubectl cluster-info

# Check kubeconfig location
echo $KUBECONFIG
ls ~/.kube/config

"Forbidden: User cannot create resource"

RBAC permissions issue. Check your service account or user has the right ClusterRole:

# Create a ClusterRoleBinding for your service account
- kubernetes.core.k8s:
    state: present
    definition:
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRoleBinding
      metadata:
        name: ansible-admin
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: ClusterRole
        name: cluster-admin
      subjects:
        - kind: ServiceAccount
          name: ansible
          namespace: default

Conclusion

The kubernetes.core collection makes Ansible a powerful K8s management tool. Use k8s for creating/updating any resource, k8s_info for querying, and helm for chart-based deployments. The biggest advantage over raw kubectl: you can combine K8s operations with non-K8s tasks (DNS, load balancers, monitoring) in a single playbook, use Ansible Vault for secrets, and manage multiple clusters with environment-specific variables.

Further reading

To go deeper, Ansible for Kubernetes by Example expands on these patterns in production.