Tutorials > How to create a Kubernetes cluster with Kubeadm and Ansible on Ubuntu 20.04

How to create a Kubernetes cluster with Kubeadm and Ansible on Ubuntu 20.04

Published on: 08 December 2020

Kubernetes Ubuntu

Introduction

Kubernetes is an open source platform, actively developed by a community around the world, which enables the management and orchestration of large-scale application containers.

In the following tutorial its latest version is used, by configuring a Kubernetes cluster from scratch and using the Ansible software for the automation of the procedures and the Kubeadm tool for cluster creation on an Ubuntu 20.04 server.

Provided the servers in the cluster have enough CPU and RAM resources for the applications to run, by the end of this guide you will have a cluster ready to run containerized applications. Before proceeding with this tutorial, make sure you have at least two worker machines, and a master machine with at least 2 CPUs that can work properly with the worker machines.

First, connect to your server via SSH. If you haven't done so yet, following our guide is recommended to  connect securely with the SSH protocol . In case of a local server, go to the next step and open the terminal of your server.

Installing Ansible

First, update the system packages via the commands to proceed with the installation of Ansible:

sudo apt update
 

Then, proceed by installing the necessary software:

sudo apt install software-properties-common -y
sudo apt install ansible -y
 

After a few seconds, Ansible should be ready for use.

Configuring SSH login

To allow Ansibile to correctly work on remote nodes, add SSH keys on the target servers, in order to guarantee access without requiring a password.

Generate the two keys (private and public) first through the command:

# ssh-keygen -t rsa
 
 Generating public/private rsa key pair.
 Enter file in which to save the key (/root/.ssh/id_rsa):
 Enter passphrase (empty for no passphrase):
 Enter same passphrase again:
 

As shown above, you will be prompted for passwords, which you can leave blank.

Once completed, send the public key to the remote servers using the "ssh-copy-id" command:

# ssh-copy-id root@IP_WORKER_1
 
# ssh-copy-id root@IP_WORKER_2
 
# ssh-copy-id root@IP_MASTER
 

For each configured machine, enter the login credentials when required.

Configuring Master node

Create a directory called "kube-cluster" in the home directory of your master machine:

mkdir ~/kube-cluster
cd ~/kube-cluster
 

This will be the directory with all your Ansible playbooks for your workspace throughout the tutorial. In addition, under this directory all the local commands will be run.

Create an "inventory" file, called hosts, using nano or your favorite text editor:

nano ~/kube-cluster/hosts
 

Add the following text to the file specifying the remote machines information in your cluster:

[masters]
 master ansible_host=[IP_MASTER] ansible_user=root
 
[workers]
 worker1 ansible_host=[IP_WORKER_1] ansible_user=root
 worker2 ansible_host=[IP_WORKER_2] ansible_user=root
 
[all:vars]
 ansible_python_interpreter=/usr/bin/python3
 

The inventory files in Ansible are used to specify server information: IP addresses, remote users, and server groupings to be used as a single unit for executing commands. In this tutorial two Ansible groups (master and worker) have been added by specifying the logical structure of your cluster.

In the master group, there is a server entry named "master" that lists the IP of the master node (IP_MASTER) and specifies that Ansible should run remote commands as the system administrator (root user). In the workgroup, there are two entries for the worker servers (worker1 and worker2) which also specify ansible_user as root.

The last line of the file tells Ansible to use the remote servers' Python 3 interpreters for its management operations.

Save and close the file after adding the text.

Master Main user configuration

Now, create a non-root user with sudo privileges on all servers to manually log in via SSH as an unprivileged user.

Using an unprivileged user for typical tasks that occur while maintaining a cluster minimizes the risk of inadvertently performing dangerous operations or modifying or deleting important files.

Create a file called initial.yml in the workspace:

nano initial.yml
 

Then, add the following content:

- hosts: all
 become: yes
 tasks:
 - name: create the 'ubuntu' user
 user: name=ubuntu append=yes state=present createhome=yes shell=/bin/bash
 
 - name: allow 'ubuntu' to have passwordless sudo
 lineinfile:
  dest: /etc/sudoers
  line: 'ubuntu ALL=(ALL) NOPASSWD: ALL'
  validate: 'visudo -cf %s'
 
 - name: set up authorized keys for the ubuntu user
 authorized_key: user=ubuntu key="{{item}}"
 with_file:
  - ~/.ssh/id_rsa.pub
 

This playbook has the task of creating the non-root user "ubuntu", by configuring the "sudoers" file to allow the user "ubuntu" to execute sudo commands without a password prompt.

It is also responsible for adding the public key on your local machine (usually ~ / .ssh / id_rsa.pub) to the list of authorized keys of the remote "ubuntu" user. This will allow you to access each server via SSH as the "ubuntu" user.

Save and close the file after adding the text.

Next, run the playbook by running locally:

ansible-playbook -i hosts initial.yml
 

The command will be completed within a few minutes. When finished, outputs similar to the following will be shown:

Output
 PLAY [all] ****
 
 TASK [Gathering Facts] ****
 ok: [master]
 ok: [worker1]
 ok: [worker2]
 
 TASK [create the 'ubuntu' user] ****
 changed: [master]
 changed: [worker1]
 changed: [worker2]
 
 TASK [allow 'ubuntu' user to have passwordless sudo] ****
 changed: [master]
 changed: [worker1]
 changed: [worker2]
 
 TASK [set up authorized keys for the ubuntu user] ****
 changed: [worker1] => (item=ssh-rsa AAAAB3...)
 changed: [worker2] => (item=ssh-rsa AAAAB3...)
 changed: [master] => (item=ssh-rsa AAAAB3...)
 
 PLAY RECAP ****
 master  : ok=5 changed=4 unreachable=0 failed=0 
 worker1  : ok=5 changed=4 unreachable=0 failed=0 
 worker2  : ok=5 changed=4 unreachable=0 failed=0 
 

Preliminary setup is complete.

Creation of the Kubernetes cluster

Now, install the required packages by Kubernetes at the operating system level with Ubuntu's package manager.

Create a file called kube-dependencies.yml in the workspace:

nano ~/kube-cluster/kube-dependencies.yml
 

Add the following instructions to the file to install these packages on your servers:

- hosts: all
 become: yes
 tasks:
 - name: install Docker
 apt:
  name: docker.io
  state: present
  update_cache: true
 
 - name: install APT Transport HTTPS
 apt:
  name: apt-transport-https
  state: present
 
 - name: add Kubernetes apt-key
 apt_key:
  url: https://packages.cloud.google.com/apt/doc/apt-key.gpg
  state: present
 
 - name: add Kubernetes' APT repository
 apt_repository:
 repo: deb http://apt.kubernetes.io/ kubernetes-xenial main
 state: present
 filename: 'kubernetes'
 
 - name: install kubelet
 apt:
  name: kubelet
  state: present
  update_cache: true
 
 - name: install kubeadm
 apt:
  name: kubeadm
  state: present
 
 - hosts: master
 become: yes
 tasks:
 - name: install kubectl
 apt:
  name: kubectl
  state: present
 

The newly pasted playbook has the task of:

  • Installing Docker, the container runtime;
  • Installing apt-transport-https, allowing you to add external HTTPS sources to the APT source list;
  • Adding the apt key of the Kubernetes APT repository for key verification;
  • Adding the Kubernetes APT repository to the remote server APT sources list;
  • Installing Kubelet and Kubeadm.

Save and close the file when done.

Next, run the playbook by running locally:

ansible-playbook -i hosts kube-dependencies.yml
 

When finished, outputs similar to the following will be shown:

Output
 PLAY [all] ****
 
 TASK [Gathering Facts] ****
 ok: [worker1]
 ok: [worker2]
 ok: [master]
 
 TASK [install Docker] ****
 changed: [master]
 changed: [worker1]
 changed: [worker2]
 
 TASK [install APT Transport HTTPS] *****
 ok: [master]
 ok: [worker1]
 changed: [worker2]
 
 TASK [add Kubernetes apt-key] *****
 changed: [master]
 changed: [worker1]
 changed: [worker2]
 
 TASK [add Kubernetes' APT repository] *****
 changed: [master]
 changed: [worker1]
 changed: [worker2]
 
 TASK [install kubelet] *****
 changed: [master]
 changed: [worker1]
 changed: [worker2]
 
 TASK [install kubeadm] *****
 changed: [master]
 changed: [worker1]
 changed: [worker2]
 
 PLAY [master] *****
 
 TASK [Gathering Facts] *****
 ok: [master]
 
 TASK [install kubectl] ******
 ok: [master]
 
 PLAY RECAP ****
 master  : ok=9 changed=5 unreachable=0 failed=0 
 worker1  : ok=7 changed=5 unreachable=0 failed=0 
 worker2  : ok=7 changed=5 unreachable=0 failed=0 
 

After execution, Docker, Kubeadm and Kubelet will be installed on all the remote servers.

Kubectl is not a required component and is only needed to run cluster commands. Installing it on the master node is useful only in this context, as only Kubectl commands will be run from the master.

However, Kubectl commands can be run from any worker node or any machine on which it can be installed and configured to point to a cluster.

All system dependencies are now installed.

At this point, configure the master node and initialize the cluster, before creating any playbooks.

Create an Ansible playbook named master.yml on your local computer:

nano ~/kube-cluster/master.yml
 

Add the following playback to the file to initialize the cluster and install Flannel:

- hosts: master
 become: yes
 tasks:
 - name: remove swap
 shell: "swapoff -a"
 
 - name: initialize the cluster
 shell: kubeadm init --pod-network-cidr=10.244.0.0/16 >> cluster_initialized.txt
 args:
  chdir: $HOME
  creates: cluster_initialized.txt
 
 - name: create .kube directory
 become: yes
 become_user: ubuntu
 file:
  path: $HOME/.kube
  state: directory
  mode: 0755
 
 - name: copy admin.conf to user's kube config
 copy:
  src: /etc/kubernetes/admin.conf
  dest: /home/ubuntu/.kube/config
  remote_src: yes
  owner: ubuntu
 
 - name: install Pod network
 become: yes
 become_user: ubuntu
 shell: kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml >> pod_network_setup.txt
 args:
  chdir: $HOME
  creates: pod_network_setup.txt
 

In detail:

  • By running the first Kubeadm task "init" the cluster is initialized, passing the argument --pod-network-cidr = 10.244.0.0 / 16 and therefore specifying the private subnet from which the pod IPs will be assigned. Flannel uses the old subnet by default - Kubeadm is told to use the same subnet.
  • With the second task the creation of a .kube directory in / home / ubuntu is initialized, and it will contain all the configuration information, such as the admin key files, needed to connect to the cluster, and the cluster API address.
  • With the third task the /etc/kubernetes/admin.conf file generated by kubeadm init to the non-root user's home directory will be copied. This will allow you to use kubectl to access the newly created cluster.
  • With the fourth task, you are running Kubectl apply to install Flannel. kubectl apply -f descriptor. [yml | json] is the syntax for telling Kubectl to create the objects described in the descriptor file. [yml | json]. The kube-flannel.yml file contains descriptions of the objects needed to configure Flannel in the cluster.

Save and close the file at the end.

Run the playbook locally, by running the following command:

ansible-playbook -i hosts master.yml
 

When done, outputs similar to the followingwill be shown:

PLAY [master] ****
 
 TASK [Gathering Facts] ****
 ok: [master]
 
 TASK [initialize the cluster] ****
 changed: [master]
 
 TASK [create .kube directory] ****
 changed: [master]
 
 TASK [copy admin.conf to user's kube config] *****
 changed: [master]
 
 TASK [install Pod network] *****
 changed: [master]
 
 PLAY RECAP ****
 master  : ok=5 changed=4 unreachable=0 failed=0 
 

To check the status of the master node, use this command:

ssh ubuntu@master_ip

Once inside the master node, run:

kubectl get nodes
 

Now, the following output can be seen:

NAME STATUS ROLES AGE VERSION master Ready master 1d v1.14.0

The output indicates that all the initialization tasks have been completed by the master node, which is now in a ready state and therefore ready to begin accepting worker nodes and executing tasks sent to the API server.

Configure worker nodes

Adding workers to the cluster involves running a single command on each of them.

This command includes the necessary cluster information, such as IP address, master API Server port, and a secure token. Nodes that pass into the secure token will be able to join the cluster later.

Go back to your workspace and create a playbook called workers.yml:

nano ~/kube-cluster/workers.yml
 

To add workers to the cluster, add the following configuration to the file:

- hosts: master
 become: yes
 gather_facts: false
 tasks:
 - name: get join command
 shell: kubeadm token create --print-join-command
 register: join_command_raw
 
 - name: set join command
 set_fact:
  join_command: "{{ join_command_raw.stdout_lines[0] }}"
 
 - hosts: workers
 become: yes
 tasks:
 - name: remove swap
 shell: "swapoff -a"
 
 - name: join cluster	
 shell: "{{ hostvars['master'].join_command }} >> node_joined.txt"
 args:
  chdir: $HOME
  creates: node_joined.txt
 

This playbook contains the instructions that have to be executed on worker nodes.

With the second replay, a single task executes the join command on all worker nodes.

At the end of this activity, the two worker nodes will be part of the cluster.

Save and close the file when done and run the playbook by running locally:

ansible-playbook -i hosts workers.yml
 

When done, outputs similar to the following will be shown:

Output
 PLAY [master] ****
 
 TASK [get join command] ****
 changed: [master]
 
 TASK [set join command] *****
 ok: [master]
 
 PLAY [workers] *****
 
 TASK [Gathering Facts] *****
 ok: [worker1]
 ok: [worker2]
 
 TASK [join cluster] *****
 changed: [worker1]
 changed: [worker2]
 
 PLAY RECAP *****
 master  : ok=2 changed=1 unreachable=0 failed=0 
 worker1  : ok=2 changed=1 unreachable=0 failed=0 
 worker2  : ok=2 changed=1 unreachable=0 failed=0 
 

With the addition of worker nodes, your cluster is now fully configured and functional, with workers ready to run workloads.

Check the correct functioning of the newly added clusters, using the Kubectl tool, as previously done:

ssh ubuntu master_ip

Run the following command to get the cluster status:

kubectl get nodes

Outputs similar to the following will be shown: 

Output NAME STATUS ROLES AGE VERSION master Ready master 1d v1.14.0 worker1 Ready  1d v1.14.0 worker2 Ready  1d v1.14.0
 

However, if some nodes have the status "NotReady", this may imply that the worker nodes have not completed the configuration yet. Within ten minutes, re-run the "kubectl get" nodes command and inspect the new output.

If the status change from NotReady to Ready still does not occur, check if you have correctly executed the commands in the previous steps and repeat them if necessary.

Now that your cluster has been successfully verified, you are ready to schedule remote operations within the Kubernetes cluster on your Ubuntu 20.04 server.