How to Automate Kubernetes with Ansible? Project that (might) land you DevOps Job

How to Automate Kubernetes with Ansible? Project that (might) land you DevOps Job


A perfect DevOps project for anyone who want to become DevOps Press enter or click to view image in full size


Intro

Yes, hands-on DevOps projects are important. There’s a wide array of technologies that are — or might be — part of your daily work. That’s why I highly recommend spending your free time learning and expanding your portfolio as an engineer.

Is it easy? No, it’s not. Is it necessary? Hell yes.

I’ve been working with Ansible for a while now — both in my corporate environment and in my home lab. I’m constantly exploring new modules, integrations, technologies, and use cases that I can implement to refine my code.

Recently, I worked on a fun project with Docker. Once I had provisioned a container running Grafana, I thought: “Well, Ansible has fantastic integration with Docker… Does it also integrate with Kubernetes?”
Turns out — it does!

So in this article — we can see how you can manage your Kubernetes cluster with … Ansible modules!

🤔 Why Use Ansible with Kubernetes Anyway?

You might ask — “Why use Ansible when kubectl works just fine?”

Good questions. Working with kubectl in your cluster is ok and there is nothing wrong about that. The following project is an example of an interesting home-lab scenario — how you use K8s/Ansible in your production environment is you choice :)

The way I see why it (can) make sense to use Ansible for K8s is because DevOps is about automation, repeatability, and scaling. kubectl is manual. Ansible turns your tasks into code that runs anywhere. And once you get the hang of it, you can manage entire clusters with a single command.

Plus, in real-world DevOps jobs, tools like Ansible Tower, AWX, or Jenkins often run these playbooks in pipelines.


1️⃣ Project’s Requirements

Press enter or click to view image in full size


So what exactly are we supposed to do in this specific project?
Below is the list of all tasks in our Kubetnetes × Ansible integration:

✅ Get Cluster Namespaces Info — we want Ansible to extract namespaces from our cluster and print them

✅ Print All Pods Names — the second task is a similar one. Here, we also want to extract info byt this time all pods names.

✅ Create Kubernetes Namespace — the third task. We perform another CRUD operation type — creating Namespace with Ansible.

✅ Deploy Nginx via Deployment — another fun task. From Ansible’s playbook we deploy Nginx.

✅ Expose Nginx via NodePort Service — last but not least. We want to see our Nginx nicely running in our pod.

To build this project, we’ll rely on two key Ansible modules from the Kubernetes collection:

  • kubernetes.core.k8s_info – for gathering information like pods and namespaces

  • kubernetes.core.k8s – for creating, updating, or deleting Kubernetes resources

These two modules are more than enough to automate meaningful Kubernetes workflows.


This project focuses on automation development, so a few things are already set up:

  • Ansible is installed, and SSH key-based authentication is configured between the Ansible control node and the target host.

  • Kubernetes is installed on the target machine — this could be via Minikube, kubeadm, or any other setup.

2️⃣ Writing Deployment Playbook

The core development was done in on the manage_k8s_playbook.yml file. The first two tasks were about fetching info about cluster with the utilization of kubernetes.core.k8s_info :

- name: Get Cluster Namespaces Info    kubernetes.core.k8s_info:      api_version: v1      kind: Namespace      kubeconfig: /etc/kubernetes/admin.conf    register: namespaces  - name: Print all namespace names    debug:      msg: "{{ namespaces.resources | map(attribute='metadata.name') | list }}"  - name: Get Cluster Running Pods    kubernetes.core.k8s_info:      kind: Pod      kubeconfig: /etc/kubernetes/admin.conf      field_selectors:        - status.phase=Running    register: pods  - name: Print all pods    debug:      msg: "{{ pods.resources | map(attribute='metadata.name') | list }}"
- name: Get Cluster Namespaces Info    kubernetes.core.k8s_info:      api_version: v1      kind: Namespace      kubeconfig: /etc/kubernetes/admin.conf    register: namespaces  - name: Print all namespace names    debug:      msg: "{{ namespaces.resources | map(attribute='metadata.name') | list }}"  - name: Get Cluster Running Pods    kubernetes.core.k8s_info:      kind: Pod      kubeconfig: /etc/kubernetes/admin.conf      field_selectors:        - status.phase=Running    register: pods  - name: Print all pods    debug:      msg: "{{ pods.resources | map(attribute='metadata.name') | list }}"
- name: Get Cluster Namespaces Info    kubernetes.core.k8s_info:      api_version: v1      kind: Namespace      kubeconfig: /etc/kubernetes/admin.conf    register: namespaces  - name: Print all namespace names    debug:      msg: "{{ namespaces.resources | map(attribute='metadata.name') | list }}"  - name: Get Cluster Running Pods    kubernetes.core.k8s_info:      kind: Pod      kubeconfig: /etc/kubernetes/admin.conf      field_selectors:        - status.phase=Running    register: pods  - name: Print all pods    debug:      msg: "{{ pods.resources | map(attribute='metadata.name') | list }}"

This portion of the playbook focuses on gathering live data from your Kubernetes cluster and printing it in a human-readable way. The printing tasks use the debug module.

As mentioned, these tasks rely on the kubernetes.core.k8s_info module to query the cluster for all existing Namespaces. It authenticates using the provided kubeconfig file, and the output is stored in a variable called namespaces.

What’s being done here:

msg: "{{ namespaces.resources | map(attribute='metadata.name') | list }}"
msg: "{{ namespaces.resources | map(attribute='metadata.name') | list }}"
msg: "{{ namespaces.resources | map(attribute='metadata.name') | list }}"

is basically we just extract the namespaces names using Jinja filters and print them. It maps over namespaces.resources, picks metadata.name from each, and converts it to a list.



Next, we can move on to the next part. At this stage, the configuration goes beyond gathering info — we’re now actually modifying the cluster by creating Kubernetes resources. For this purpose, we’re using the kubernetes.core.k8s module to perform declarative actions, just like kubectl apply.

The following tasks are being performed:

1️⃣ task creates a namespace called web-demo in the cluster, if it doesn’t already exist. state: present ensures the resource is created and idempotent (re-running won't cause errors).

2️⃣nd task deploy Nginx via a Deployment. Here, I am defining a Deployment type, give it a name, put the deployment into web-demo namespace. As a replica, just selecting one instance. In the end, I also specific container with image, port and NodePort.

3️⃣rd task is to deploy a service. Once, I have a service, I need to be sure it can be accessible via my Chrome. Consequently, I deploy service with kind: Service. I make all nessesary adjustment with:

  • ports:
    port: 80 → the port the service exposes
    targetPort: 80 → forwards traffic to port 80 inside the container
    nodePort: 30080 → the external port exposed on each node (so you can access Nginx via NODE_IP:30080)

And our service should be accessible!

  - name: Create K8s Namespace    kubernetes.core.k8s:      name: web-demo      api_version: v1      kind: Namespace      state: present      kubeconfig: /etc/kubernetes/admin.conf  - name: Deploy Nginx via Deployment    kubernetes.core.k8s:      state: present      definition:        apiVersion: apps/v1        kind: Deployment        metadata:          name: nginx-deployment          namespace: web-demo        spec:          replicas: 1          selector:            matchLabels:              app: nginx          template:            metadata:              labels:                app: nginx            spec:              containers:                - name: nginx                  image: nginx                  ports:                    - containerPort: 80      kubeconfig: /etc/kubernetes/admin.conf  - name: Expose Nginx via NodePort Service    kubernetes.core.k8s:      state: present      definition:        apiVersion: v1        kind: Service        metadata:          name: nginx-service          namespace: web-demo        spec:          type: NodePort          selector:            app: nginx          ports:            - port: 80              targetPort: 80              nodePort: 30080      kubeconfig: /etc/kubernetes/admin.conf
  - name: Create K8s Namespace    kubernetes.core.k8s:      name: web-demo      api_version: v1      kind: Namespace      state: present      kubeconfig: /etc/kubernetes/admin.conf  - name: Deploy Nginx via Deployment    kubernetes.core.k8s:      state: present      definition:        apiVersion: apps/v1        kind: Deployment        metadata:          name: nginx-deployment          namespace: web-demo        spec:          replicas: 1          selector:            matchLabels:              app: nginx          template:            metadata:              labels:                app: nginx            spec:              containers:                - name: nginx                  image: nginx                  ports:                    - containerPort: 80      kubeconfig: /etc/kubernetes/admin.conf  - name: Expose Nginx via NodePort Service    kubernetes.core.k8s:      state: present      definition:        apiVersion: v1        kind: Service        metadata:          name: nginx-service          namespace: web-demo        spec:          type: NodePort          selector:            app: nginx          ports:            - port: 80              targetPort: 80              nodePort: 30080      kubeconfig: /etc/kubernetes/admin.conf
  - name: Create K8s Namespace    kubernetes.core.k8s:      name: web-demo      api_version: v1      kind: Namespace      state: present      kubeconfig: /etc/kubernetes/admin.conf  - name: Deploy Nginx via Deployment    kubernetes.core.k8s:      state: present      definition:        apiVersion: apps/v1        kind: Deployment        metadata:          name: nginx-deployment          namespace: web-demo        spec:          replicas: 1          selector:            matchLabels:              app: nginx          template:            metadata:              labels:                app: nginx            spec:              containers:                - name: nginx                  image: nginx                  ports:                    - containerPort: 80      kubeconfig: /etc/kubernetes/admin.conf  - name: Expose Nginx via NodePort Service    kubernetes.core.k8s:      state: present      definition:        apiVersion: v1        kind: Service        metadata:          name: nginx-service          namespace: web-demo        spec:          type: NodePort          selector:            app: nginx          ports:            - port: 80              targetPort: 80              nodePort: 30080      kubeconfig: /etc/kubernetes/admin.conf

3️⃣ Deploying Changes

Now, when we have the code written, we can deploy our changes with simple Ansible’s command:

ansible-playbook playbooks/deployment
ansible-playbook playbooks/deployment
ansible-playbook playbooks/deployment

As I’ve screen-shotted the playbook’s output after I deployed the changes, it doesn’t show any new changes.

We can see below in the screenshots that the playbook first fetches the info about the cluster (with registered variable namespaces), then it prints the running pods, creates a K8s namespace, deploys Nginx, and finally exposes the service via NodePort.

Press Enter or click to view image in full size


Press enter or click to view image in full size


4️⃣ Service Verification

We can run the verification of our serice under the public IP and the 30080 port:

Press enter or click to view image in full size


And that’s all for today. I hope you enjoyed!


Test us in action. Book a free
consultation for your team

We don't do hard sales pitches. Let's spend 30 minutes discussing your current DevOps bottlenecks, and we'll help you map out the most efficient upskilling path for your team.

Test us in action. Book a free consultation for your team

We don't do hard sales pitches. Let's spend 30 minutes discussing your current DevOps bottlenecks, and we'll help you map out the most efficient upskilling path for your team.