Back to Blog

Step-by-Step Guide: Install Kubernetes on 3 Nodes using Vagrant

Abhishek10 min read
Step-by-Step Guide: Install Kubernetes on 3 Nodes using Vagrant

Learn how to set up a complete Kubernetes cluster with 3 nodes using Vagrant. Includes master node setup, worker nodes, networking, and cluster validation.

Setting up a Kubernetes cluster for learning or development can be challenging. This comprehensive guide walks you through creating a 3-node Kubernetes cluster using Vagrant, providing you with a complete local development environment that mirrors production setups.

Table of Contents

  1. Prerequisites and Setup
  2. Vagrant Configuration
  3. Base System Configuration
  4. Kubernetes Installation
  5. Master Node Setup
  6. Worker Nodes Setup
  7. Network Configuration
  8. Cluster Validation
  9. Troubleshooting

Prerequisites and Setup

Required Software

Before starting, ensure you have the following installed:

  • VirtualBox: Version 6.1 or later
  • Vagrant: Version 2.2 or later
  • At least 8GB RAM and 50GB free disk space
  • Host OS: Windows, macOS, or Linux

Installation Commands

bash
# Install VirtualBox (Ubuntu/Debian)
sudo apt update
sudo apt install virtualbox virtualbox-ext-pack

# Install Vagrant (Ubuntu/Debian)
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com hashicorp main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vagrant

# Verify installations
vagrant --version
vboxmanage --version

Vagrant Configuration

Create Project Directory

bash
mkdir k8s-vagrant-cluster
cd k8s-vagrant-cluster

Vagrantfile Configuration

Create a comprehensive Vagrantfile for our 3-node cluster:

ruby
# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile for 3-node Kubernetes cluster
Vagrant.configure("2") do |config|
  # Define cluster configuration
  MASTER_NODE = "k8s-master"
  WORKER_NODES = ["k8s-worker1", "k8s-worker2"]
  
  # Network configuration
  NETWORK_BASE = "192.168.56"
  MASTER_IP = "#{NETWORK_BASE}.10"
  WORKER_IPS = ["#{NETWORK_BASE}.11", "#{NETWORK_BASE}.12"]
  
  # Resource allocation
  MASTER_MEMORY = 2048
  MASTER_CPUS = 2
  WORKER_MEMORY = 1536
  WORKER_CPUS = 2
  
  # Base box configuration
  config.vm.box = "ubuntu/focal64"
  config.vm.box_check_update = false
  
  # Disable default sync folder
  config.vm.synced_folder ".", "/vagrant", disabled: true
  
  # Master node configuration
  config.vm.define MASTER_NODE do |master|
    master.vm.hostname = MASTER_NODE
    master.vm.network "private_network", ip: MASTER_IP
    
    # Port forwarding for Kubernetes API
    master.vm.network "forwarded_port", guest: 6443, host: 6443, auto_correct: true
    master.vm.network "forwarded_port", guest: 8080, host: 8080, auto_correct: true
    
    master.vm.provider "virtualbox" do |vb|
      vb.name = MASTER_NODE
      vb.memory = MASTER_MEMORY
      vb.cpus = MASTER_CPUS
      vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
      vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
    end
    
    # Provisioning script for master
    master.vm.provision "shell", path: "scripts/common.sh"
    master.vm.provision "shell", path: "scripts/master.sh", args: [MASTER_IP]
  end
  
  # Worker nodes configuration
  WORKER_NODES.each_with_index do |worker_name, index|
    config.vm.define worker_name do |worker|
      worker.vm.hostname = worker_name
      worker.vm.network "private_network", ip: WORKER_IPS[index]
      
      worker.vm.provider "virtualbox" do |vb|
        vb.name = worker_name
        vb.memory = WORKER_MEMORY
        vb.cpus = WORKER_CPUS
        vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
        vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
      end
      
      # Provisioning script for workers
      worker.vm.provision "shell", path: "scripts/common.sh"
      worker.vm.provision "shell", path: "scripts/worker.sh", args: [MASTER_IP]
    end
  end
end

Create Scripts Directory

bash
mkdir scripts

Base System Configuration

Common Configuration Script

Create scripts/common.sh for all nodes:

bash
#!/bin/bash
set -e

echo "=== Starting Common Configuration ==="

# Update system
echo "Updating system packages..."
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get upgrade -y

# Install required packages
echo "Installing required packages..."
apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release \
    software-properties-common \
    net-tools \
    htop \
    vim

# Disable swap (required for Kubernetes)
echo "Disabling swap..."
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

# Load required kernel modules
echo "Loading kernel modules..."
cat <<EOF | tee /etc/modules-load.d/k8s.conf
br_netfilter
overlay
EOF

modprobe br_netfilter
modprobe overlay

# Set sysctl parameters for Kubernetes
echo "Setting sysctl parameters..."
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

sysctl --system

# Install containerd
echo "Installing containerd..."
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

apt-get update -y
apt-get install -y containerd.io

# Configure containerd
echo "Configuring containerd..."
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml

# Enable SystemdCgroup
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

systemctl restart containerd
systemctl enable containerd

# Install kubeadm, kubelet, and kubectl
echo "Installing Kubernetes components..."
curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg

echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | tee /etc/apt/sources.list.d/kubernetes.list

apt-get update -y
apt-get install -y kubelet=1.28.2-00 kubeadm=1.28.2-00 kubectl=1.28.2-00
apt-mark hold kubelet kubeadm kubectl

# Configure kubelet
echo "Configuring kubelet..."
systemctl enable kubelet

# Set up bash completion
echo "Setting up bash completion..."
echo 'source <(kubectl completion bash)' >> /home/vagrant/.bashrc
echo 'alias k=kubectl' >> /home/vagrant/.bashrc
echo 'complete -F __start_kubectl k' >> /home/vagrant/.bashrc

echo "=== Common Configuration Complete ==="

Kubernetes Installation

Master Node Setup Script

Create scripts/master.sh:

bash
#!/bin/bash
set -e

MASTER_IP=$1

echo "=== Starting Master Node Configuration ==="
echo "Master IP: $MASTER_IP"

# Pull required images
echo "Pulling Kubernetes images..."
kubeadm config images pull

# Initialize Kubernetes cluster
echo "Initializing Kubernetes cluster..."
kubeadm init \
    --apiserver-advertise-address=$MASTER_IP \
    --apiserver-cert-extra-sans=$MASTER_IP \
    --node-name=k8s-master \
    --pod-network-cidr=10.244.0.0/16 \
    --service-cidr=10.96.0.0/12 \
    --kubernetes-version=1.28.2

# Set up kubectl for vagrant user
echo "Setting up kubectl for vagrant user..."
mkdir -p /home/vagrant/.kube
cp -i /etc/kubernetes/admin.conf /home/vagrant/.kube/config
chown vagrant:vagrant /home/vagrant/.kube/config

# Set up kubectl for root user
mkdir -p /root/.kube
cp -i /etc/kubernetes/admin.conf /root/.kube/config

# Install Flannel CNI
echo "Installing Flannel CNI..."
export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

# Generate join command for worker nodes
echo "Generating join command..."
kubeadm token create --print-join-command > /vagrant/join-command.sh
chmod +x /vagrant/join-command.sh

# Wait for master node to be ready
echo "Waiting for master node to be ready..."
kubectl wait --for=condition=Ready node/k8s-master --timeout=300s

# Install Kubernetes Dashboard (optional)
echo "Installing Kubernetes Dashboard..."
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml

# Create admin user for dashboard
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
EOF

# Get dashboard token
echo "Creating dashboard access token..."
kubectl -n kubernetes-dashboard create token admin-user > /vagrant/dashboard-token.txt

echo "=== Master Node Configuration Complete ==="

# Display cluster information
echo "=== Cluster Information ==="
kubectl get nodes -o wide
kubectl get pods -A
echo "Dashboard token saved to: /vagrant/dashboard-token.txt"
echo "Access dashboard at: https://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/"

Worker Node Setup Script

Create scripts/worker.sh:

bash
#!/bin/bash
set -e

MASTER_IP=$1

echo "=== Starting Worker Node Configuration ==="
echo "Master IP: $MASTER_IP"

# Wait for master node to be ready and join command to be available
echo "Waiting for join command from master..."
while [ ! -f /vagrant/join-command.sh ]; do
    echo "Waiting for join command..."
    sleep 10
done

# Execute join command
echo "Joining cluster..."
bash /vagrant/join-command.sh

# Verify node joined successfully
echo "Verifying node status..."
sleep 30

echo "=== Worker Node Configuration Complete ==="

Master Node Setup

Start the Cluster

bash
# Start all VMs
vagrant up

# Check status
vagrant status

# SSH into master node
vagrant ssh k8s-master

Verify Master Node

bash
# Check node status
kubectl get nodes

# Check system pods
kubectl get pods -A

# Check cluster info
kubectl cluster-info

Worker Nodes Setup

Monitor Worker Node Join

bash
# SSH into worker nodes to check status
vagrant ssh k8s-worker1
vagrant ssh k8s-worker2

# From master node, verify all nodes
kubectl get nodes -o wide

Expected Output

bash
NAME          STATUS   ROLES           AGE   VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
k8s-master    Ready    control-plane   10m   v1.28.2   192.168.56.10   <none>        Ubuntu 20.04.6 LTS   5.4.0-174-generic   containerd://1.6.21
k8s-worker1   Ready    <none>          8m    v1.28.2   192.168.56.11   <none>        Ubuntu 20.04.6 LTS   5.4.0-174-generic   containerd://1.6.21
k8s-worker2   Ready    <none>          6m    v1.28.2   192.168.56.12   <none>        Ubuntu 20.04.6 LTS   5.4.0-174-generic   containerd://1.6.21

Network Configuration

Flannel CNI Setup

Flannel is automatically installed during master setup. Verify it's working:

bash
# Check Flannel pods
kubectl get pods -n kube-flannel

# Verify network connectivity
kubectl get nodes -o wide

Test Pod-to-Pod Communication

yaml
# Create test deployment
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
      - name: nginx
        image: nginx:1.20
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: test-service
spec:
  selector:
    app: test-app
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP
EOF
bash
# Verify pods are distributed across nodes
kubectl get pods -o wide

# Test service connectivity
kubectl run debug --image=busybox -it --rm -- wget -qO- test-service

Cluster Validation

Comprehensive Cluster Tests

bash
# Check all system components
kubectl get componentstatuses

# Verify all nodes are ready
kubectl get nodes

# Check all system pods
kubectl get pods -A

# Test DNS resolution
kubectl run dns-test --image=busybox -it --rm -- nslookup kubernetes.default

# Deploy a test application
kubectl create deployment nginx --image=nginx:1.20
kubectl expose deployment nginx --port=80 --type=NodePort

# Get service details
kubectl get svc nginx

Performance Testing

bash
# Create a resource-intensive pod
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: resource-test
spec:
  containers:
  - name: stress
    image: polinux/stress
    command: ["stress"]
    args: ["--cpu", "1", "--timeout", "60s"]
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 256Mi
EOF

# Monitor resource usage
kubectl top nodes
kubectl top pods

Load Balancing Verification

bash
# Scale deployment across nodes
kubectl scale deployment nginx --replicas=6

# Verify distribution
kubectl get pods -o wide | grep nginx

# Test load balancing
for i in {1..10}; do
  kubectl exec -it $(kubectl get pod -l app=nginx -o jsonpath='{.items[0].metadata.name}') -- hostname
done

Troubleshooting

Common Issues and Solutions

1. Node Not Ready

bash
# Check node status
kubectl describe node <node-name>

# Check kubelet logs
vagrant ssh <node-name>
sudo journalctl -u kubelet -f

# Restart kubelet if needed
sudo systemctl restart kubelet

2. Pod Network Issues

bash
# Check Flannel pods
kubectl get pods -n kube-flannel

# Restart Flannel if needed
kubectl delete pods -n kube-flannel -l app=flannel

# Check iptables rules
sudo iptables -L -n | grep FORWARD

3. DNS Resolution Problems

bash
# Check CoreDNS pods
kubectl get pods -n kube-system | grep coredns

# Test DNS resolution
kubectl run dns-debug --image=busybox -it --rm -- nslookup google.com

4. Resource Constraints

bash
# Check resource usage
kubectl top nodes
kubectl describe nodes

# Check for resource pressure
kubectl get events --sort-by='.lastTimestamp'

Useful Commands for Debugging

bash
# Get cluster information
kubectl cluster-info dump

# Check all events
kubectl get events -A --sort-by='.lastTimestamp'

# Describe problematic resources
kubectl describe pod <pod-name>
kubectl describe node <node-name>

# Access logs
kubectl logs <pod-name> -c <container-name>

Vagrant-Specific Troubleshooting

bash
# Restart VMs
vagrant reload

# Destroy and recreate
vagrant destroy -f
vagrant up

# Check VM status
vagrant status
vagrant global-status

# SSH debugging
vagrant ssh-config

Advanced Configuration

Installing Additional Tools

bash
# Install Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Install ingress controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/baremetal/deploy.yaml

# Install metrics server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

Persistent Storage Setup

yaml
# Create local storage class
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /tmp/data
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - k8s-worker1

Monitoring Setup

bash
# Install kube-prometheus-stack with Helm
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack

Cleanup and Management

Cluster Maintenance

bash
# Update node labels
kubectl label nodes k8s-worker1 role=worker
kubectl label nodes k8s-worker2 role=worker

# Drain node for maintenance
kubectl drain k8s-worker1 --ignore-daemonsets --delete-emptydir-data

# Uncordon node
kubectl uncordon k8s-worker1

Complete Cleanup

bash
# Destroy Vagrant environment
vagrant destroy -f

# Clean up VirtualBox
VBoxManage list vms
VBoxManage unregistervm <vm-name> --delete

Conclusion

You now have a fully functional 3-node Kubernetes cluster running on Vagrant! This setup provides:

Key Features:

  • Multi-node cluster with 1 master and 2 worker nodes
  • Container networking with Flannel CNI
  • Persistent storage capabilities
  • Dashboard access for web-based management
  • Production-like environment for learning and testing

Next Steps:

  • Deploy sample applications
  • Experiment with scaling and load balancing
  • Set up CI/CD pipelines
  • Practice disaster recovery scenarios
  • Explore advanced Kubernetes features

Use Cases:

  • Development environment for Kubernetes applications
  • Learning platform for container orchestration
  • Testing ground for deployment strategies
  • Prototype environment for production workloads

For expert Kubernetes consulting and enterprise cluster setup, our team provides comprehensive Kubernetes services and container orchestration solutions.


Need help with production Kubernetes deployments? Our certified Kubernetes administrators can help you design and implement scalable container orchestration solutions. Contact us for a free consultation.

Written by

Abhishek

KEEP LEARNING WITH THEDBADMIN

Turn this guide into a skill you can prove

Keep reading

Featured course · New batch

NextGen DBA + AI Engineer Bootcamp

Instructor-led, lab-first training for production DBAs: PostgreSQL, MongoDB, Docker, Kubernetes and AI.

  • Live labs on real execution plans
  • Backup, PITR & HA in production
  • pgvector, RAG & AI for DBAs

🛒 FROM THE SHOP

200 PostgreSQL DBA Interview Questions & Answers DBA · DevOps · SRE / DBRE Cloud DBA · Vector DBA · AI / ML Ops | A practitioner's preparation

PDF · 64 pages

₹199₹5,00096% off

View product