Using Ansible to Create a Kubernetes Cluster on a Virtual Lab

Automation Kubernetes Ansible

This post will show you how to install a three node Kubernetes cluster running on VirtualBox Virtual Machines by running a single command.

Introduction

I’ve just started learning Kubernetes and wanted to test it on a cluster of VMs on my MacBook Pro. In this post, I’ll explain how I managed to get the cluster running by using Vagrant to create the VMs, and Ansible to automate the Kubernetes installation.

Before you begin

Before you start this tutorial, you’ll need to download and install VirtualBox, Vagrant and Ansible on your local machine. The following steps have been tested on a MacBook Pro (2017 macOS Mojave) with the following versions:

VirtualBox and Vagrant were both installed from DMG files and Ansible was installed using pip.

pip install ansible

The following steps might work on Windows or Linux but I haven’t tested them.

Once you’ve installed the dependancies above, we’ll begin by creating the Vagrant file.

Step 1: Create Vagrant File

First, create a folder on your machine to store the Vagrant file and Playbooks in.

mkdir kubernetes
cd kubernetes

Create the Vagrantfile.

vim Vagrantfile

Add the following contents.

IMAGE_NAME = "ubuntu/bionic64"
N = 2

Vagrant.configure("2") do |config|
    config.ssh.insert_key = false

    config.vm.provider "virtualbox" do |v|
        v.memory = 1024
        v.cpus = 2
    end
      
    config.vm.define "master" do |master|
        master.vm.box = IMAGE_NAME
        master.vm.network "private_network", ip: "192.168.50.10"
        master.vm.hostname = "master"
        master.vm.provision "ansible" do |ansible|
            ansible.playbook = "master-playbook.yml"
            ansible.extra_vars = {
                node_ip: "192.168.50.10",
            }
        end
    end

    (1..N).each do |i|
        config.vm.define "node-#{i}" do |node|
            node.vm.box = IMAGE_NAME
            node.vm.network "private_network", ip: "192.168.50.#{i + 10}"
            node.vm.hostname = "node-#{i}"
            node.vm.provision "ansible" do |ansible|
                ansible.playbook = "node-playbook.yml"
                ansible.extra_vars = {
                    node_ip: "192.168.50.#{i + 10}",
                }
            end
        end
    end
end

When running the vagrant up command from the kubernetes folder, the contents of the file above will create one VM for the master, and two VMs for workers. As you can see from the IMAGE_NAME variable, Ubuntu 18.04 will be the OS installed.

Once the master VM has been created, Vagrant will run the Ansible playbook called master-playbook.yml and when the worker nodes are created, node-playbook.yml will be run.

Step 2: Create Ansible Playbooks

Let’s create the playbooks that will install all the dependancies on the VMs like Docker and Kubernetes etc.

master-playbook.yml

vim master-playbook.yml

Contents of master-playbook.yml.

---
- hosts: all
  become: true
  tasks:
  - name: Install packages that allow apt to be used over HTTPS
    apt:
      name: "{{ packages }}"
      state: present
      update_cache: yes
    vars:
      packages:
      - apt-transport-https
      - ca-certificates
      - curl
      - gnupg-agent
      - software-properties-common

  - name: Add an apt signing key for Docker
    apt_key:
      url: https://download.docker.com/linux/ubuntu/gpg
      state: present

  - name: Add apt repository for stable version
    apt_repository:
      repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable
      state: present

  - name: Install docker and its dependecies
    apt: 
      name: "{{ packages }}"
      state: present
      update_cache: yes
    vars:
      packages:
      - docker-ce 
      - docker-ce-cli 
      - containerd.io
    notify:
      - docker status

  - name: Add vagrant user to docker group
    user:
      name: vagrant
      group: docker

  - name: Disable swap
    command: swapoff -a
  
  - name: Add an apt signing key for Kubernetes
    apt_key:
      url: https://packages.cloud.google.com/apt/doc/apt-key.gpg
      state: present

  - name: Adding apt repository for Kubernetes
    apt_repository:
      repo: deb https://apt.kubernetes.io/ kubernetes-xenial main
      state: present
      filename: kubernetes.list

  - name: Install Kubernetes binaries
    apt: 
      name: "{{ packages }}"
      state: present
      update_cache: yes
    vars:
      packages:
        - kubelet 
        - kubeadm 
        - kubectl
    register: installed

  - name: Restart kubelet
    service:
      name: kubelet
      daemon_reload: yes
      state: restarted
  
  - name: Initialize the Kubernetes cluster using kubeadm
    command: kubeadm init --apiserver-advertise-address="192.168.50.10" --apiserver-cert-extra-sans="192.168.50.10"  --node-name master --pod-network-cidr=192.168.0.0/16
    when: installed is changed

  - name: Create .kube folder
    become: false
    file:
      path: /home/vagrant/.kube
      state: directory

  - name: Copy admin.conf file
    copy: remote_src=True src=/etc/kubernetes/admin.conf dest=/home/vagrant/.kube/config

  - name: Change admin.conf owner
    file:
      path: /home/vagrant/.kube/config
      owner: vagrant
      group: vagrant
    
  - name: Install calico pod network
    become: false
    command: kubectl apply -f https://docs.projectcalico.org/v3.10/manifests/calico.yaml

  - name: Generate join command
    command: kubeadm token create --print-join-command
    register: join_command

  - name: Copy join command to local file
    copy: 
      dest: "join" 
      content: "{{ join_command.stdout_lines[0] }}"
    become: false
    delegate_to: localhost

  handlers:
      - name: docker status
        service: name=docker state=started

node-playbook.yml

vim node-playbook.yml

Contents of node-playbook.yml

---
- hosts: all
  become: true
  tasks:
  - name: Install packages that allow apt to be used over HTTPS
    apt:
      name: "{{ packages }}"
      state: present
      update_cache: yes
    vars:
      packages:
      - apt-transport-https
      - ca-certificates
      - curl
      - gnupg-agent
      - software-properties-common

  - name: Add an apt signing key for Docker
    apt_key:
      url: https://download.docker.com/linux/ubuntu/gpg
      state: present

  - name: Add apt repository for stable version
    apt_repository:
      repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable
      state: present

  - name: Install docker and its dependecies
    apt: 
      name: "{{ packages }}"
      state: present
      update_cache: yes
    vars:
      packages:
      - docker-ce 
      - docker-ce-cli 
      - containerd.io
    notify:
      - docker status

  - name: Add vagrant user to docker group
    user:
      name: vagrant
      group: docker

  - name: Disable swap
    command: swapoff -a

  - name: Add an apt signing key for Kubernetes
    apt_key:
      url: https://packages.cloud.google.com/apt/doc/apt-key.gpg
      state: present

  - name: Adding apt repository for Kubernetes
    apt_repository:
      repo: deb https://apt.kubernetes.io/ kubernetes-xenial main
      state: present
      filename: kubernetes.list

  - name: Install Kubernetes binaries
    apt: 
      name: "{{ packages }}"
      state: present
      update_cache: yes
    vars:
      packages:
        - kubelet 
        - kubeadm 
        - kubectl

  - name: Restart kubelet
    service:
      name: kubelet
      daemon_reload: yes
      state: restarted
  
  - name: Copy the join command to server location
    copy:
      src: "join" 
      dest: /tmp/join-command.sh
      mode: 0777
    become: false

  - name: Join the node to cluster
    command: sh /tmp/join-command.sh

  handlers:
      - name: docker status
        service: name=docker state=started

After creating the playbooks, the directory structure should look like this:

MacBook-Pro:kubernetes $ tree
.
├── Vagrantfile
├── master-playbook.yml
└── node-playbook.yml

0 directories, 3 files

Step 3: Deploy Kubernetes

Now we should be able to deploy the VMs and configure the cluster by running the following command:

vagrant up

Once the deployment and configuration completes, you should be able to login to the master via ssh by running:

vagrant ssh master
MacBook-Pro:kubernetes $ vagrant ssh master
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-72-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Fri Dec  6 11:13:31 UTC 2019

  System load:  0.15              Users logged in:        0
  Usage of /:   31.7% of 9.63GB   IP address for enp0s3:  10.0.2.15
  Memory usage: 69%               IP address for enp0s8:  192.168.50.10
  Swap usage:   0%                IP address for docker0: 172.17.0.1
  Processes:    145               IP address for tunl0:   192.168.219.64


0 packages can be updated.
0 updates are security updates.


Last login: Fri Dec  6 01:36:04 2019 from 10.0.2.2
vagrant@master:~$

We can test the Kubernetes cluster by running the following command:

kubectl get nodes
vagrant@master:~$ kubectl get nodes
NAME     STATUS   ROLES    AGE   VERSION
master   Ready    master   10h   v1.16.3
node-1   Ready    <none>   9h    v1.16.3
node-2   Ready    <none>   9h    v1.16.3

That’s it. We’re now ready to deploy apps on Kubernetes.

Summary

In this tutorial, we used Vagrant and Ansible to automate the deployment and configuration of a Kubernetes test cluster on a MacBook Pro.

Written by: Tony Mackay