Title 6

Title 6


Automate K8s Installation with Ansible Playbook. A story from the real-life production environment.

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://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key    dest: /tmp/kubernetes-release.key    mode: '0644'- name: Convert GPG key to dearmored format  ansible.builtin.command: >    gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg /tmp/kubernetes-release.key  args:    creates: /etc/apt/keyrings/kubernetes-apt-keyring.gpg- name: Add Kubernetes APT repository  apt_repository:    repo: "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /"    state: present    update_cache: yes- name: Install Kubernetes tools  apt:    name:      - kubelet      - kubeadm      - kubectl    state: present- name: Prevent Kubernetes tools from being upgraded  dpkg_selections:    name: "{{ item }}"    selection: hold  loop:    - kubelet    - kubeadm    - kubectl
---- name: Download Kubernetes GPG key  ansible.builtin.get_url:    url: https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key    dest: /tmp/kubernetes-release.key    mode: '0644'- name: Convert GPG key to dearmored format  ansible.builtin.command: >    gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg /tmp/kubernetes-release.key  args:    creates: /etc/apt/keyrings/kubernetes-apt-keyring.gpg- name: Add Kubernetes APT repository  apt_repository:    repo: "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /"    state: present    update_cache: yes- name: Install Kubernetes tools  apt:    name:      - kubelet      - kubeadm      - kubectl    state: present- name: Prevent Kubernetes tools from being upgraded  dpkg_selections:    name: "{{ item }}"    selection: hold  loop:    - kubelet    - kubeadm    - kubectl
---- name: Download Kubernetes GPG key  ansible.builtin.get_url:    url: https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key    dest: /tmp/kubernetes-release.key    mode: '0644'- name: Convert GPG key to dearmored format  ansible.builtin.command: >    gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg /tmp/kubernetes-release.key  args:    creates: /etc/apt/keyrings/kubernetes-apt-keyring.gpg- name: Add Kubernetes APT repository  apt_repository:    repo: "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /"    state: present    update_cache: yes- name: Install Kubernetes tools  apt:    name:      - kubelet      - kubeadm      - kubectl    state: present- name: Prevent Kubernetes tools from being upgraded  dpkg_selections:    name: "{{ item }}"    selection: hold  loop:    - kubelet    - kubeadm    - kubectl

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/16

Here, 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.

Press enter or click to view image in full size


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/

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.