Intro
It was our most recent decision in managing the infrastructure: We’re going with Kubernetes!
At first, I thought — great, that’s exciting! But almost immediately, reality hit me. We didn’t have that many containers yet, and everything I knew about Kubernetes pointed to one thing: it’s powerful, but managing it is no walk in the park.
From deploying Kubernetes resources, managing RBAC, security, networking, Helm charts etc. I quickly realised that management of such a complex software is error-prone, might require manual intervention and hard to scale.
To make the matter worse — infrastructure consists of many environments usually — from test, staging, production, deployment previews — you name it. If we needed a Kubernetes setup for each one, the complexity would multiply quickly.
That’s when I started thinking: how can we automate all of this — from cluster setup to ongoing management — so it’s scalable, repeatable, and consistent across environments? This is why I reached out for Ansible and automated installation of K8s.
Today’s Focus: Automating Kubernetes Installation Deployment with Ansible. We will translate Bash procedure into Ansible playbook so we have replicable Kubernetes deployment script
1️⃣ Project’s Scenario

So what exactly are we supposed to do in this specific project?
Below is the list of all tasks from our project scenario:
✅ Install Docker
✅ Install Kubernetes ces such as Jenkins with Helm
✅ Deploy Kubernetes
This project is basically the re-write of the Kubernetes installation procedure from link where authors list all the Bash commands needed for the Kubernetes installation.
https://phoenixnap.com/kb/install-kubernetes-on-ubuntu
We have to remember two things while running the code I wrote:
✅ It is only to start Control Plane and to initiate the cluster. We do not install Worker Nodes. The focus here is simply the main node.
✅ I have fine-tuned the instruction so it fits the infrastructure I have (one Ubuntu 22.04 server). There is not 100% certainty that the code I wrote will configure a Control Plane Node in your infrastructure.
Our project will have three separate roles in Ansible — docker_install and kuberntetes_install and kuberntetes_deploy.The main playbook kube-deployment.yaml aggregates all roles and deploy the changes.
---- name: Install Kubernetes on Target Node hosts: all gather_facts: yes become: yes roles: - docker_install - kubernetes_install - kubernetes_deploy
---- name: Install Kubernetes on Target Node hosts: all gather_facts: yes become: yes roles: - docker_install - kubernetes_install - kubernetes_deploy
---- name: Install Kubernetes on Target Node hosts: all gather_facts: yes become: yes roles: - docker_install - kubernetes_install - kubernetes_deploy
The project structure looks the following:
.├── ansible.cfg├── inventory.ini├── kube-deployment.yaml└── roles ├── docker_install │ ├── README.md │ ├── defaults │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ ├── tests │ └── vars ├── kubernetes_deploy │ ├── README.md │ ├── defaults │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ ├── tests │ └── vars └── kubernetes_install ├── README.md ├── defaults ├── files ├── handlers ├── meta ├── tasks ├── templates ├── tests └── vars
.├── ansible.cfg├── inventory.ini├── kube-deployment.yaml└── roles ├── docker_install │ ├── README.md │ ├── defaults │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ ├── tests │ └── vars ├── kubernetes_deploy │ ├── README.md │ ├── defaults │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ ├── tests │ └── vars └── kubernetes_install ├── README.md ├── defaults ├── files ├── handlers ├── meta ├── tasks ├── templates ├── tests └── vars
.├── ansible.cfg├── inventory.ini├── kube-deployment.yaml└── roles ├── docker_install │ ├── README.md │ ├── defaults │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ ├── tests │ └── vars ├── kubernetes_deploy │ ├── README.md │ ├── defaults │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ ├── tests │ └── vars └── kubernetes_install ├── README.md ├── defaults ├── files ├── handlers ├── meta ├── tasks ├── templates ├── tests └── vars
Also, my inventory.ini file, where I have only one Control Plane Node:
[ControlPlane]prod-control01 ansible_host=172.31.65.58 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_ed25519
[ControlPlane]prod-control01 ansible_host=172.31.65.58 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_ed25519
[ControlPlane]prod-control01 ansible_host=172.31.65.58 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_ed25519
2️⃣ Install Docker Role
The first role is to install Docker. This is a soft start in this project.
---- name: Update repositories cache and install "docker.io" package apt: name: docker.io update_cache: yes- name: Ensure Docker is enabled and running service: name: docker state: started enabled: yes
---- name: Update repositories cache and install "docker.io" package apt: name: docker.io update_cache: yes- name: Ensure Docker is enabled and running service: name: docker state: started enabled: yes
---- name: Update repositories cache and install "docker.io" package apt: name: docker.io update_cache: yes- name: Ensure Docker is enabled and running service: name: docker state: started enabled: yes
What this code does?
1. Using apt module, we install docker.io
2. With servicewe enable and start docker.
3️⃣ Install Kubernetes Role
Then we move on to the next part — installing the Kubernetes role. In this step, we need to accomplish tasks such as downloading the Kubernetes GPG key, adding the APT repository, and installing Kubernetes tools (kubelet, kubeadm, and kubectl). It’s also important to keep these tools at a stable version. For that, I use the dpkg_selections module in Ansible.
---- name: Download Kubernetes GPG key ansible.builtin.get_url: url: https:
---- name: Download Kubernetes GPG key ansible.builtin.get_url: url: https:
---- name: Download Kubernetes GPG key ansible.builtin.get_url: url: https:
4️⃣ Deploy Kubernetes Role
In the end, I wrote a final role for this part of the installation process. This is the most advanced and complex one.
We use a wide array of different modules here — from shell, modprobe, blockinfile, setup, and systemd, to debug for verifying the installation process.
Among all of these tasks, the most important is:
- name: Initalize Kubernetes Control Plane become: yes command: > kubeadm init \ --control-plane-endpoint={{ ansible_hostname }} \ --upload-certs \ --pod-network-cidr=10.200.0.0/16- name: Initalize Kubernetes Control Plane become: yes command: > kubeadm init \ --control-plane-endpoint={{ ansible_hostname }} \ --upload-certs \ --pod-network-cidr=10.200.0.0/16- name: Initalize Kubernetes Control Plane become: yes command: > kubeadm init \ --control-plane-endpoint={{ ansible_hostname }} \ --upload-certs \ --pod-network-cidr=10.200.0.0/16Here, we are initializing the kuberntes Control Plane. It took ma a moment to draft the correct script as the system had issues with API sever that wasn’t responding.
Here, we are initializing the Kubernetes Control Plane. It took me a moment to draft the correct script, as the system had issues with the API server not responding.
The full script is below:
---- name: Disable Swap shell: | swapoff -a sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab #sed -i '/swap/d' /etc/fstab- name: Insert containerd modules blockinfile: path: /etc/m§ Q 3W £odules-load.d/containerd.conf create: yes block: | overlay br_netfilter- name: Add the Kernel modules modprobe: name: "{{ item }}" state: present loop: - overlay - br_netfilter- name: Configure Kubernetes Networking blockinfile: path: /etc/sysctl.d/kubernetes.conf create: yes block: | net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1- name: Reload Configuration shell: | sudo sysctl --system- name: Read the hostname setup: filter: ansible_hostname- name: Configure kublet blockinfile: path: /etc/default/kublet create: yes block: | KUBELET_EXTRA_ARGS="--cgroup-driver=cgroupfs"- name: Reload systemd and restart kubelet systemd: name: kubelet state: restarted daemon_reload: yes- name: Configure Docker copy: src: daemon.json dest: /etc/docker/daemon.json owner: root group: root mode: 0644 notify: Restart Docker- name: Initalize Kubernetes Control Plane become: yes command: > kubeadm init \ --control-plane-endpoint={{ ansible_hostname }} \ --upload-certs \ --pod-network-cidr=10.200.0.0/16 \- name: Ensure .kube directory exists with correct permissions file: path: /home/ubuntu/.kube state: directory owner: ubuntu group: ubuntu mode: '0700'- name: Copy admin.conf to user's kube config copy: src: /etc/kubernetes/admin.conf dest: /home/ubuntu/.kube/config owner: ubuntu group: ubuntu mode: '0600' remote_src: yes become: yes- name: verify the installation command: kubectl --kubeconfig=/home/ubuntu/.kube/config -n kube-system get pods register: kube_status- name: Debug the status debug: msg: "{{ kube_status.stdout }}"---- name: Disable Swap shell: | swapoff -a sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab #sed -i '/swap/d' /etc/fstab- name: Insert containerd modules blockinfile: path: /etc/m§ Q 3W £odules-load.d/containerd.conf create: yes block: | overlay br_netfilter- name: Add the Kernel modules modprobe: name: "{{ item }}" state: present loop: - overlay - br_netfilter- name: Configure Kubernetes Networking blockinfile: path: /etc/sysctl.d/kubernetes.conf create: yes block: | net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1- name: Reload Configuration shell: | sudo sysctl --system- name: Read the hostname setup: filter: ansible_hostname- name: Configure kublet blockinfile: path: /etc/default/kublet create: yes block: | KUBELET_EXTRA_ARGS="--cgroup-driver=cgroupfs"- name: Reload systemd and restart kubelet systemd: name: kubelet state: restarted daemon_reload: yes- name: Configure Docker copy: src: daemon.json dest: /etc/docker/daemon.json owner: root group: root mode: 0644 notify: Restart Docker- name: Initalize Kubernetes Control Plane become: yes command: > kubeadm init \ --control-plane-endpoint={{ ansible_hostname }} \ --upload-certs \ --pod-network-cidr=10.200.0.0/16 \- name: Ensure .kube directory exists with correct permissions file: path: /home/ubuntu/.kube state: directory owner: ubuntu group: ubuntu mode: '0700'- name: Copy admin.conf to user's kube config copy: src: /etc/kubernetes/admin.conf dest: /home/ubuntu/.kube/config owner: ubuntu group: ubuntu mode: '0600' remote_src: yes become: yes- name: verify the installation command: kubectl --kubeconfig=/home/ubuntu/.kube/config -n kube-system get pods register: kube_status- name: Debug the status debug: msg: "{{ kube_status.stdout }}"---- name: Disable Swap shell: | swapoff -a sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab #sed -i '/swap/d' /etc/fstab- name: Insert containerd modules blockinfile: path: /etc/m§ Q 3W £odules-load.d/containerd.conf create: yes block: | overlay br_netfilter- name: Add the Kernel modules modprobe: name: "{{ item }}" state: present loop: - overlay - br_netfilter- name: Configure Kubernetes Networking blockinfile: path: /etc/sysctl.d/kubernetes.conf create: yes block: | net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1- name: Reload Configuration shell: | sudo sysctl --system- name: Read the hostname setup: filter: ansible_hostname- name: Configure kublet blockinfile: path: /etc/default/kublet create: yes block: | KUBELET_EXTRA_ARGS="--cgroup-driver=cgroupfs"- name: Reload systemd and restart kubelet systemd: name: kubelet state: restarted daemon_reload: yes- name: Configure Docker copy: src: daemon.json dest: /etc/docker/daemon.json owner: root group: root mode: 0644 notify: Restart Docker- name: Initalize Kubernetes Control Plane become: yes command: > kubeadm init \ --control-plane-endpoint={{ ansible_hostname }} \ --upload-certs \ --pod-network-cidr=10.200.0.0/16 \- name: Ensure .kube directory exists with correct permissions file: path: /home/ubuntu/.kube state: directory owner: ubuntu group: ubuntu mode: '0700'- name: Copy admin.conf to user's kube config copy: src: /etc/kubernetes/admin.conf dest: /home/ubuntu/.kube/config owner: ubuntu group: ubuntu mode: '0600' remote_src: yes become: yes- name: verify the installation command: kubectl --kubeconfig=/home/ubuntu/.kube/config -n kube-system get pods register: kube_status- name: Debug the status debug: msg: "{{ kube_status.stdout }}"5️⃣ Running All Roles From Playbook
Finally, we deploy changes with our playbook to the target machine where we want to install full fledged Kubernetes Cluster.

The last task where I am debugging the kubectl get pods give me the following output:
msg: | NAME READY STATUS RESTARTS AGE etcd-prod-control01 0/1 Running 11 2s kube-apiserver-prod-control01 0/1 Running 11 2s kube-controller-manager-prod-control01 0/1 Running 11 2s kube-scheduler-prod-control01 0/1 Running 11 2s
msg: | NAME READY STATUS RESTARTS AGE etcd-prod-control01 0/1 Running 11 2s kube-apiserver-prod-control01 0/1 Running 11 2s kube-controller-manager-prod-control01 0/1 Running 11 2s kube-scheduler-prod-control01 0/1 Running 11 2s
msg: | NAME READY STATUS RESTARTS AGE etcd-prod-control01 0/1 Running 11 2s kube-apiserver-prod-control01 0/1 Running 11 2s kube-controller-manager-prod-control01 0/1 Running 11 2s kube-scheduler-prod-control01 0/1 Running 11 2s
I hope you liked it,
Simon Izydorek
CEO and Founder of BecomeDevOps
Reach out in case of questions/suggestion: https://www.linkedin.com/in/sizydorek/
