K8s & Ansible: Deploying a K8s Cluster using Ansible

K8s & Ansible: Deploying a K8s Cluster using Ansible

On our previous article, we looked at using Terraform to create a K8s cluster. Let us today look at creating that cluster but this time with Ansible. I was tasked recently with this and I decided to share it here so we can all learn it.

For the purposes of this article, we will use Ansible to deploy a small Kubernetes cluster — with one master node, used to manage the cluster, and two worker nodes, which will be used to run our container applications. To achieve this, we will use four Ansible playbooks. These will do the following:

  • Create a new User Account for use with Kubernetes on each node

  • Install Kubernetes and containered on each node

  • Configure the Master node

  • Join the Worker nodes to the new cluster

If you are considering using Ansible to deploy Kubernetes already, I will assume you’re already somewhat familiar with both technologies. So, with that said, let’s get straight into the detail.

Before we Deploy Kubernetes using Ansible

Before we can get started, we need a few prerequisites to be in place. This is what we are going to need:

  1. A host with Ansible installed. I’ve written previously about how to install Ansible — also, check out the online documentation! You should also set up an SSH key pair, which will be used to authenticate to the Kubernetes nodes without using a password, allowing Ansible to do it’s thing.

  2. Three servers/hosts to which we will use as our targets to deploy Kubernetes. I am using Ubuntu 18.04, and my servers each have 2GB ram and 2vCPUs. This is fine for my lab purposes, which I use to try out new things using Kubernetes. You need to be able to SSH into each of these nodes as root using the SSH key pair I mentioned above.

With that lot all in place we should be ready to go!

Setting up Ansible to Deploy Kubernetes

Before we start to look at the Ansible Playbooks, we need to set up Ansible to communicate with the Kubernetes nodes. First of all, on our Ansible host, lets set up a new directory from which we we run our playbooks.

$ mkdir kubernetes  
$ cd kubernetes

With that done, we now need to create a hosts file, to tell Ansible how to communicate with the Kubernetes master and worker nodes.

$ vi hosts

The content of the hosts file should look something like the following:

[masters]  
master ansible_host=172.31.117.172 ansible_user=root  

[workers]  
worker1 ansible_host=172.31.120.66 ansible_user=root  
worker2 ansible_host=172.31.118.25 ansible_user=root

Listing the master node and the worker nodes in different sections in the hosts file will allow us to target the playbooks at the specfic node type later on.

Finally, with that done, we can test it’s working by doing a Ansible ping:

$ ansible -i hosts all -m ping  
master | SUCCESS => {  
    "changed": false,  
    "ping": "pong"  
}  
worker2 | SUCCESS => {  
    "changed": false,  
    "ping": "pong"  
}  
worker1 | SUCCESS => {  
    "changed": false,  
    "ping": "pong"  
}

All good! Lets move onto the first playbook.

Creating a Kubernetes user with Ansible Playbook

Our first task in setting up the Kubernetes cluster is to create a new user on each node. This will be a non-root user, that has sudo privileges. It’s a good idea not to use the root account for day to day operations, of course. We can use Ansible to set the account up on all three nodes, quickly and easily. First, create a file in the working directory:

$ vi users.yml

Then add the following to the playbook:

- hosts: 'workers, masters'  
  become: yes  

  tasks:  
    - name: create the kube user account  
      user: name=kube append=yes state=present createhome=yes shell=/bin/bash  

    - name: allow 'kube' to use sudo without needing a password  
      lineinfile:  
        dest: /etc/sudoers  
        line: 'kube ALL=(ALL) NOPASSWD: ALL'  
        validate: 'visudo -cf %s'  

    - name: set up authorized keys for the kube user  
      authorized_key: user=kube key="{{item}}"  
      with_file:  
        - ~/.ssh/id_rsa.pub

We’re now ready to run our first playbook. To do so:

$ ansible-playbook -i hosts users.yml

Once done you should see:

Install Kubernetes with Ansible Playbook

Now we’re getting to the fun part! With our user now created, we can move on to installing Kubernetes. Lets dive straight in and have a look at the playbook, which I have named install-k8s.yml:

---  
- hosts: "masters, workers"  
  remote_user: ubuntu  
  become: yes  
  become_method: sudo  
  become_user: root  
  gather_facts: yes  
  connection: ssh  

  tasks:  
     - name: Create containerd config file  
       file:  
         path: "/etc/modules-load.d/containerd.conf"  
         state: "touch"  

     - name: Add conf for containerd  
       blockinfile:  
         path: "/etc/modules-load.d/containerd.conf"  
         block: |  
               overlay  
               br_netfilter  

     - name: modprobe  
       shell: |  
               sudo modprobe overlay  
               sudo modprobe br_netfilter  


     - name: Set system configurations for Kubernetes networking  
       file:  
         path: "/etc/sysctl.d/99-kubernetes-cri.conf"  
         state: "touch"  

     - name: Add conf for containerd  
       blockinfile:  
         path: "/etc/sysctl.d/99-kubernetes-cri.conf"  
         block: |  
                net.bridge.bridge-nf-call-iptables = 1  
                net.ipv4.ip_forward = 1  
                net.bridge.bridge-nf-call-ip6tables = 1  

     - name: Apply new settings  
       command: sudo sysctl --system  

     - name: install containerd  
       shell: |  
               sudo apt-get update && sudo apt-get install -y containerd  
               sudo mkdir -p /etc/containerd  
               sudo containerd config default | sudo tee /etc/containerd/config.toml  
               sudo systemctl restart containerd  

     - name: disable swap  
       shell: |  
               sudo swapoff -a  
               sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab  

     - name: install and configure dependencies  
       shell: |  
               sudo apt-get update && sudo apt-get install -y apt-transport-https curl  
               curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -  

     - name: Create kubernetes repo file  
       file:  
         path: "/etc/apt/sources.list.d/kubernetes.list"  
         state: "touch"  

     - name: Add K8s Source  
       blockinfile:  
         path: "/etc/apt/sources.list.d/kubernetes.list"  
         block: |  
               deb https://apt.kubernetes.io/ kubernetes-xenial main  

     - name: install kubernetes  
       shell: |  
               sudo apt-get update  
               sudo apt-get install -y kubelet=1.20.1-00 kubeadm=1.20.1-00 kubectl=1.20.1-00  
               sudo apt-mark hold kubelet kubeadm kubectl

This playbook will run against all three nodes, and will install the containerd runtime (including some pre-requisite configuration, then go onto install Kubernetes, which includes kubelet, kubeadm and kubectl. Run the playbook using the following syntax:

$  ansible-playbook -i hosts install-k8s.yml

There’s quite a lot going on here, so this one will take a little while to run whist the necessary packages are installed on each node. Once done you should see:

Now we’re halfway there!

Creating a Kubernetes Cluster Master Node using Ansible Playbook

Now we should have containerd and Kubernetes installed on all our nodes. The next step is to create the cluster on the master node. This is the master.yml file, which will initialize the Kubernetes cluster on my master node and set up the pod network, using Calico:

Note, towards the end of the playbook we generate the worker join command and save it to a local file on the Ansible host. We will use this file later to join the worker nodes to the cluster. Before then, execute the masters.yml playbook:

$  ansible-playbook -i hosts master.yml

Once the playbook has finished, we can check the outcome by connecting to the cluster master node using SSH to check the status of the master node:

From the output, we can see that the master node’s status is Ready, showing that the cluster has been initialized successfully.

Join Worker Nodes to Kubernetes Cluster using Ansible Playbook

Now we have a Kubernetes cluster initialized, the final step is to join our worker nodes to the cluster. To do so, the final playbook — join-workers.yml — contains the following:

- hosts: workers  
  become: yes  
  gather_facts: yes  

  tasks:  
   - name: Copy join command from Ansiblehost to the worker nodes.  
     become: yes  
     copy:  
       src: /tmp/kubernetes_join_command  
       dest: /tmp/kubernetes_join_command  
       mode: 0777  

   - name: Join the Worker nodes to the cluster.  
     become: yes  
     command: sh /tmp/kubernetes_join_command  
     register: joined_or_not

This works by copying the file containing the worker join command saved locally earlier to the worker nodes, then it runs the command. Run the playbook with:

$  ansible-playbook -i hosts join-workers.yml

Once the playbook has complete we can check the status of the cluster nodes by again running the following on the cluster master node:

Note: It may take a short while for all the nodes to transition to the Ready status.

Conclusion

In this article you have seen how it is possible to deploy a Kubernetes cluster using Ansible playbooks. You can find the playbook examples I have used here. I think you will agree, using Ansible to deploy a Kubernetes cluster is a great way to get up and running quickly and easily. As always, be sure to check out the official documentation if you are considering using Kubernetes in a production environment.

Please stay tuned and subscribe If you’d like to learn more about Infrastructure as Code, or other modern technology approaches and study materials on DevOps, K8s, DevSecOps and App Development.

#YouAreAwesome #StayAwesome

Please if this is helpful, do click here to buy me a coffee.