
August 19, 2026
9 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
If you need full control over your infrastructure without cloud vendor lock-in, you likely want to deploy Kubernetes with Kubespray. This Ansible-based toolkit provisions production-grade clusters on bare metal or virtual machines, handling the complex plumbing of etcd, container runtimes, and networking that manual installs get wrong. For teams building CI/CD pipeline automation or self-hosted platforms in Nepal or abroad, Kubespray offers a repeatable, auditable alternative to managed services.
inventory_builder.py, configure cluster variables in group_vars, and run the cluster.yml Ansible playbook. It automates etcd, container runtime, CNI, and control plane setup across Linux nodes.Why deploy Kubernetes with Kubespray instead of kubeadm?
Kubeadm is excellent for learning and single-node setups, but it stops at the bootstrap phase. You still need to manually configure CNI plugins, set up high-availability etcd, manage certificate rotation, and harden security policies. Kubespray wraps all of this into a single, idempotent Ansible workflow that treats infrastructure as code.
In my experience working on production infrastructure for legal-tech portals and eCommerce systems, the gap between "kubeadm init succeeded" and "production-ready cluster" is where most projects stall. Kubespray bridges that gap by baking in best practices for HA etcd, node labeling, taints, and network policies from day one.
When Kubespray is the right choice
- Bare metal or VM environments where cloud controllers are unavailable
- Air-gapped or restricted-network deployments requiring offline artifact mirroring
- Multi-cloud hybrid setups needing consistent configuration across providers
- Teams already standardized on Ansible for configuration management
- Regulated environments requiring auditable, version-controlled infrastructure
When to reconsider
If you're running a single development node, kubeadm or kind is faster. If you're fully committed to AWS/GCP/Azure and don't need portability, managed EKS/GKE/AKS removes operational burden. Kubespray shines when you own the infrastructure and need reproducibility.
How do you prepare nodes before you deploy Kubernetes with Kubespray?
Kubespray expects clean, minimally configured Linux hosts. Skipping preparation causes cryptic Ansible failures mid-playbook. On Ubuntu 22.04/24.04 LTS (the most common target in 2026), follow these steps on every node.
Base system requirements
<!-- Run on ALL target nodes -->
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip curl apt-transport-https ca-certificates gnupg lsb-release
# Disable swap (Kubernetes requirement)
sudo swapoff -a
sudo sed -i '/swap/d' /etc/fstab
# Load required kernel modules
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay br_netfilter
# Set sysctl parameters
cat <<EOF | sudo 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
sudo sysctl --system SSH and user configuration
Kubespray connects via SSH. Ensure passwordless sudo works for your Ansible user. Create a dedicated k8s-deploy user rather than reusing root:
# On each node
sudo useradd -m -s /bin/bash k8s-deploy
echo 'k8s-deploy ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/k8s-deploy
sudo mkdir -p /home/k8s-deploy/.ssh
sudo cp ~/.ssh/authorized_keys /home/k8s-deploy/.ssh/
sudo chown -R k8s-deploy:k8s-deploy /home/k8s-deploy/.ssh Firewall rules
Open only what Kubernetes needs. UFW example for control plane nodes:
sudo ufw allow 22/tcp # SSH
sudo ufw allow 6443/tcp # kube-apiserver
sudo ufw allow 2379:2380/tcp # etcd
sudo ufw allow 10250/tcp # kubelet
sudo ufw allow 10259/tcp # kube-scheduler
sudo ufw allow 10257/tcp # kube-controller-manager
sudo ufw reload Worker nodes need ports 22, 10250, and the NodePort range (default 30000-32767) if you expose services that way. Document your firewall policy alongside your server security hardening documentation.
How do you configure inventory and variables to deploy Kubernetes with Kubespray?
The inventory defines your cluster topology. Variables control everything from CNI selection to container runtime. Getting these right prevents re-running the playbook multiple times.
Generate the inventory
# Clone specific release tag (never use master in production)
git clone --branch v2.26.0 https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
# Generate inventory from IP list
declare -a IPS=(10.0.1.10 10.0.1.11 10.0.1.12 10.0.1.20 10.0.1.21)
CONFIG_FILE=inventory/mycluster/hosts.yaml python3 contrib/inventory_builder/inventory_builder.py ${IPS[@]} This creates inventory/mycluster/hosts.yaml with sensible defaults: first three IPs become control plane + etcd nodes, remaining become workers. Edit it to match your actual topology.
Critical group_vars settings
Edit inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml. These are the variables I adjust on nearly every project:
# Container runtime (containerd is default and recommended in 2026)
container_manager: containerd
# Kubernetes version (pin explicitly, never rely on defaults)
kube_version: v1.31.0
# CNI plugin selection
kube_network_plugin: calico
# Alternatives: cilium, flannel, weave
# Calico is safest default for bare metal; Cilium for eBPF features
# Service CIDR and Pod CIDR (must not overlap host networks)
kube_service_addresses: 10.233.0.0/18
kube_pods_subnet: 10.233.64.0/18
# Enable audit logging for compliance
kubernetes_audit: true
# Disable dashboard in production (security risk)
dashboard_enabled: false Etcd configuration
For production, always use external etcd or kubeadm-managed etcd with at least three nodes. Edit group_vars/etcd.yml:
etcd_deployment_type: kubeadm
etcd_metrics: basic
# Enable TLS between etcd peers (default true, never disable)
etcd_peer_client_auth: true What networking and CNI options work best when you deploy Kubernetes with Kubespray?
CNI choice determines your cluster's networking capabilities, performance characteristics, and operational complexity. There is no universal best option — only the right trade-off for your workload.
| CNI Plugin | Best For | Complexity | Key Trade-offs |
|---|---|---|---|
| Calico | General-purpose bare metal, network policies | Low | Mature, well-documented, BGP routing optional |
| Cilium | eBPF observability, advanced security policies | Medium | Higher resource usage, kernel ≥5.8 required |
| Flannel | Simple overlays, learning environments | Very Low | No native network policy, limited scalability |
| Weave | Encrypted multi-cloud, legacy compatibility | Low | Slower performance, smaller community in 2026 |
For most Nepal-based deployments I've worked on — legal-tech portals, eCommerce backends, booking systems — Calico is the pragmatic default. It handles network policies, works reliably on Ubuntu 22.04/24.04, and doesn't require exotic kernel features. Choose Cilium only if you specifically need eBPF-based observability or L7-aware policies and have verified kernel compatibility.
Load balancer strategy for HA control plane
Kubespray supports multiple approaches for distributing API server traffic across control plane nodes:
- kube-vip (recommended): Lightweight, runs as static pod, provides VIP + ARP/NDP announcement
- HAProxy + Keepalived: Traditional, well-understood, separate from K8s lifecycle
- External LB: Hardware F5, cloud LB, or MetalLB for bare metal service exposure
Set loadbalancer_apiserver.type: kube-vip in k8s-cluster.yml for the simplest HA setup. Define apiserver_loadbalancer_domain_name and loadbalancer_apiserver.address with your chosen VIP.
How do you execute the playbook and validate after you deploy Kubernetes with Kubespray?
With inventory and variables configured, run the cluster playbook. Use a virtual environment and pin dependencies to avoid breakage.
Install dependencies and run
# Create isolated Python environment
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Dry-run first to catch configuration errors
ansible-playbook -i inventory/mycluster/hosts.yaml cluster.yml --check --diff
# Actual deployment (expect 15-45 minutes depending on node count/network)
ansible-playbook -i inventory/mycluster/hosts.yaml cluster.yml -b -v The -b flag enables privilege escalation. The -v flag provides visibility into task execution. Never skip the dry-run on a fresh inventory — it catches misconfigured IPs, unreachable hosts, and variable typos before they waste 30 minutes of playbook time.
Post-deployment validation
Copy the admin kubeconfig and verify cluster health immediately:
# Copy kubeconfig (adjust path based on inventory)
scp root@10.0.1.10:/root/.kube/config ~/.kube/kubespray-cluster
export KUBECONFIG=~/.kube/kubespray-cluster
# Verify all nodes are Ready
kubectl get nodes -o wide
# Check system pods (all should be Running)
kubectl get pods -n kube-system
# Validate etcd health
ETCDCTL_API=3 etcdctl --endpoints=https://10.0.1.10:2379 \
--cacert=/etc/ssl/etcd/ssl/ca.pem \
--cert=/etc/ssl/etcd/ssl/admin-node1.pem \
--key=/etc/ssl/etcd/ssl/admin-node1-key.pem \
endpoint health --cluster
# Test DNS resolution
kubectl run dns-test --rm -it --image=busybox:1.36 --restart=Never -- nslookup kubernetes.default Troubleshooting common failures
If nodes stay NotReady, check kubelet logs: journalctl -u kubelet -f. The most frequent causes are swap still enabled, missing kernel modules, or CNI pods failing to start. If etcd health checks fail, verify time synchronization across all nodes — even 500ms drift breaks raft consensus. Use chronyc sources or timedatectl status to confirm NTP is active.
For persistent issues, re-run with increased verbosity: ansible-playbook cluster.yml -vvv. Kubespray is idempotent; safe re-runs won't corrupt a partially deployed cluster. If you need to reset entirely, use reset.yml before re-running cluster.yml.
Conclusion
Learning to deploy Kubernetes with Kubespray gives you portable, auditable cluster provisioning that survives cloud vendor changes and budget constraints. Pin your versions, validate node preparation thoroughly, choose Calico unless you have specific eBPF requirements, and always run post-deploy health checks before scheduling workloads. The upfront investment in proper configuration pays off during upgrades, incident response, and compliance audits. If you're evaluating whether self-managed Kubernetes makes sense for your Nepal-based or global infrastructure, reach out to discuss your specific architecture before committing to a platform.

