DEV Community

Cover image for Kubernetes at Home: Set Up Your Own Cluster Using Vagrant & Ansible
Houssam Bourkane
Houssam Bourkane

Posted on

4 3 2 3 4

Kubernetes at Home: Set Up Your Own Cluster Using Vagrant & Ansible

TL;DR: Learn how to build a lightweight Kubernetes cluster on your local machine using Vagrant, Ansible, and VMware Fusion. Perfect for ARM-based Mac users looking to experiment with Kubernetes in a reproducible environment.


Why This Guide?

Setting up a Kubernetes cluster manually can be complex and time-consuming. But with the power of Vagrant for managing virtual machines and Ansible for automating setup tasks, you can spin up a local cluster with minimal effort and maximum reproducibility. This tutorial walks you through building a two-node cluster on macOS with ARM chips (e.g., M1/M2), but it's adaptable to other setups.


Table of Contents


Prerequisites

Before we start, ensure you have the following installed:

For macOS ARM users, refer to this special setup guide: Run Vagrant VMs in a M1/M2/M3 Apple Sillicon Chip


Project Structure

Create a project directory and replicate the following structure:

.
├── ansible
│   ├── ansible.cfg
│   ├── inventory.ini
│   └── k8s-cluster-setup.yml
└── Vagrantfile
Enter fullscreen mode Exit fullscreen mode

Step 1: Configure Vagrant

Install the VMware Desktop plugin:

vagrant plugin install vagrant-vmware-desktop
Enter fullscreen mode Exit fullscreen mode

Next, reserve two static private IPs from your LAN. Scan your local network using:

nmap -sn 192.168.1.0/24
Enter fullscreen mode Exit fullscreen mode

Replace 192.168.1.0 with you LAN network IP

Update your Vagrantfile with something like this:

Vagrant.configure("2") do |config|
  config.vm.define "kubmaster" do |kub|
    kub.vm.box = "spox/ubuntu-arm"
    kub.vm.box_version = "1.0.0"
    kub.vm.hostname = 'kubmaster'
    kub.vm.provision "docker"
    kub.vm.network "public_network", ip: "192.168.1.101", bridge: "en0: Wifi"
    kub.vm.provider "vmware_desktop" do |v|
      v.allowlist_verified = true
      v.gui = false
      v.vmx["memsize"] = "4096"
      v.vmx["numvcpus"] = "2"
    end
  end

  config.vm.define "kubnode1" do |kubnode|
    kubnode.vm.box = "spox/ubuntu-arm"
    kubnode.vm.box_version = "1.0.0"
    kubnode.vm.hostname = 'kubnode1'
    kubnode.vm.provision "docker"
    kubnode.vm.network "public_network", ip: "192.168.1.102", bridge: "en0: Wifi"
    kubnode.vm.provider "vmware_desktop" do |v|
      v.allowlist_verified = true
      v.gui = false
      v.vmx["memsize"] = "4096"
      v.vmx["numvcpus"] = "2"
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Replace the IPs with ones that match your LAN.

Now bring up the VMs:

vagrant up
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Ansible

ansible/inventory.ini

[master]
kubmaster ansible_host=192.168.1.101 ansible_ssh_private_key_file=.vagrant/machines/kubmaster/vmware_desktop/private_key

[workers]
kubnode1 ansible_host=192.168.1.102 ansible_ssh_private_key_file=.vagrant/machines/kubnode1/vmware_desktop/private_key

[all:vars]
ansible_user=vagrant
Enter fullscreen mode Exit fullscreen mode

Make sure to replace the IP addresses

ansible/ansible.cfg

[defaults]
inventory = inventory.ini
host_key_checking = False
Enter fullscreen mode Exit fullscreen mode

Step 3: Ansible Playbook for Kubernetes Setup

ansible/k8s-cluster-setup.yml

This playbook performs the following:

  • Prepares all nodes: disables swap, installs required packages, configures kernel modules, adds K8s repositories, installs kubeadm, kubelet, and kubectl.
  • Initializes the master node and stores the cluster join command.
  • Sets up Flannel CNI for networking.
  • Joins worker nodes using the generated join command.
---
- name: Prepare Kubernetes Nodes
  hosts: all
  become: yes

  tasks:  

    - name: Disable swap (runtime)
      command: swapoff -a
      when: ansible_swaptotal_mb > 0

    - name: Comment out swap line in /etc/fstab
      lineinfile:
        path: /etc/fstab
        regexp: '^\s*([^#]\S*\s+\S+\s+swap\s+\S+)\s*$'
        line: '# \1'
        backrefs: yes

    - name: Apply mount changes
      command: mount -a

    - name: Stop AppArmor
      systemd:
        name: apparmor
        state: stopped
        enabled: no

    - name: Restart containerd
      systemd:
        name: containerd
        state: restarted

    - name: Configure sysctl for Kubernetes
      copy:
        dest: /etc/sysctl.d/kubernetes.conf
        content: |
          net.bridge.bridge-nf-call-ip6tables = 1
          net.bridge.bridge-nf-call-iptables = 1

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

    - name: Install transport and curl
      apt:
        name:
          - apt-transport-https
          - curl
        update_cache: yes
        state: present

    - name: Add Kubernetes APT key
      shell: |
        curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
      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.33/deb/ /"
        filename: "kubernetes"
        state: present

    - name: Install Kubernetes components
      apt:
        name:
          - kubelet
          - kubeadm
          - kubectl
          - kubernetes-cni
        update_cache: yes
        state: present

    - name: Enable kubelet service
      systemd:
        name: kubelet
        enabled: yes

- name: Initialize Kubernetes Master
  hosts: master
  become: yes
  vars:
    pod_cidr: "10.244.0.0/16"

  tasks:

    - name: Remove default containerd config
      file:
        path: /etc/containerd/config.toml
        state: absent

    - name: Restart containerd
      systemd:
        name: containerd
        state: restarted
        enabled: yes

    - name: Wait for containerd socket to be available
      wait_for:
        path: /run/containerd/containerd.sock
        state: present
        timeout: 20

    - name: Initialize Kubernetes control plane
      command: kubeadm init --apiserver-advertise-address={{ ansible_host }} --node-name {{ inventory_hostname }} --pod-network-cidr={{ pod_cidr }}
      register: kubeadm_output
      args:
        creates: /etc/kubernetes/admin.conf

    - name: Extract join command
      shell: |
        kubeadm token create --print-join-command
      register: join_command
      changed_when: false

    - name: Set join command fact
      set_fact:
        kube_join_command: "{{ join_command.stdout }}"

    - name: Create .kube directory for vagrant user
      become_user: vagrant
      file:
        path: /home/vagrant/.kube
        state: directory
        mode: 0755

    - name: Copy Kubernetes admin config to vagrant user
      copy:
        src: /etc/kubernetes/admin.conf
        dest: /home/vagrant/.kube/config
        remote_src: yes
        owner: vagrant
        group: vagrant
        mode: 0644

- name: Configure networking
  hosts: all
  become: yes

  tasks:

    - name: Ensure br_netfilter loads at boot
      copy:
        dest: /etc/modules-load.d/k8s.conf
        content: |
          br_netfilter

    - name: Load br_netfilter kernel module now
      command: modprobe br_netfilter

    - name: Configure sysctl for Kubernetes networking
      copy:
        dest: /etc/sysctl.d/k8s.conf
        content: |
          net.bridge.bridge-nf-call-iptables = 1
          net.bridge.bridge-nf-call-ip6tables = 1

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

- name: Configure flannel
  hosts: master
  become: yes

  tasks:

    - name: Apply Flannel CNI plugin
      become_user: vagrant
      command: kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
      environment:
        KUBECONFIG: /home/vagrant/.kube/config


- name: Join worker nodes to cluster
  hosts: workers
  become: yes
  vars:
    kube_join_command: "{{ hostvars['kubmaster']['kube_join_command'] }}"

  tasks:
    - name: Remove default containerd config
      file:
        path: /etc/containerd/config.toml
        state: absent

    - name: Restart containerd
      systemd:
        name: containerd
        state: restarted
        enabled: yes
    - name: Wait until the Kubernetes API server is reachable
      wait_for:
        host: "{{ hostvars['kubmaster']['ansible_host'] }}"
        port: 6443
        delay: 10
        timeout: 120
        state: started

    - name: Join the node to Kubernetes cluster
      command: "{{ kube_join_command }}"
      args:
        creates: /etc/kubernetes/kubelet.conf
Enter fullscreen mode Exit fullscreen mode

Step 4: Test and Deploy the Cluster

Test Ansible SSH connectivity:

ansible all -m ping -i ansible/inventory.ini
Enter fullscreen mode Exit fullscreen mode

Run the full cluster setup:

ansible-playbook -i ansible/inventory.ini ansible/k8s-cluster-setup.yml
Enter fullscreen mode Exit fullscreen mode

Retrieve the kubeconfig file locally:

vagrant ssh kubmaster -c "sudo cat /etc/kubernetes/admin.conf" > ~/kubeconfig-vagrant.yaml
Enter fullscreen mode Exit fullscreen mode

Test your cluster:

KUBECONFIG=~/kubeconfig-vagrant.yaml kubectl get nodes
Enter fullscreen mode Exit fullscreen mode

You should see both the master and worker nodes in Ready status.


What's Next?

You now have a fully functioning local Kubernetes cluster on ARM-based hardware, with everything automated and reproducible. You can now:

  • Experiment with Helm charts
  • Try GitOps with ArgoCD
  • Deploy sample apps

Stay tuned for the next part of this series!


Useful Links

Heroku

Amplify your impact where it matters most — building exceptional apps.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (2)

Collapse
 
kozmer_vip profile image
Kozmer Vip

Thanks for this tutorial!! I have been struggling doing it manually for so long, can't wait for upcoming sections

Collapse
 
thegeekfellow profile image
GeekGuy

I mean, I've always wanted to automate my K8s cluster deployment, and I looked it up on Google I found this. How lucky am i ???

ITRS image

See What Users Experience in The Browser — Anywhere, Anytime

Simulate logins, checkouts, and payments on SaaS, APIs, and internal apps. Catch issues early, baseline web performance, and stay ahead of incidents. Easily record user journeys right from your browser.

Start Free Trial

AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️