Deploy Kubernetes with Kubespray

Khimananda Oli 8 min read Virtualization
Deploy Kubernetes with Kubespray

By Khimananda Oli | Last reviewed: August 2026

You need full control over your infrastructure, but managed services like EKS or GKE are too expensive or restricted by compliance requirements. To deploy Kubernetes with Kubespray effectively, you must treat it as an infrastructure-as-code project rather than a simple script execution. This approach ensures your cluster is reproducible, auditable, and aligned with security standards like ISO 27001 or SOC 2.

Why should you deploy Kubernetes with Kubespray instead of managed services?

Managed Kubernetes is excellent for reducing operational overhead, but it isn't always the right fit. In my work with Nepali fintech companies and government projects, data residency laws often prohibit storing data outside specific physical borders where no managed K8s exists. Similarly, organizations pursuing strict compliance certifications sometimes require direct access to the control plane configuration that managed providers abstract away.

Kubespray fills this gap by providing a battle-tested set of Ansible playbooks that produce upstream-compliant clusters. Unlike kubeadm alone, which handles node bootstrapping but leaves networking and high-availability architecture to you, Kubespray integrates these concerns into a single declarative workflow. If you are already managing servers with Ansible playbooks for server automation, adopting Kubespray feels like a natural extension rather than a new toolchain. It supports air-gapped deployments, multiple CNIs (Calico, Cilium, Flannel), and all major Linux distributions, making it the de facto standard for self-hosted enterprise Kubernetes.

Ansible ControllerKubespray PlaybooksInventory & VarsSSH KeysControl Plane (HA)etcd + API ServerController ManagerControl Plane (HA)etcd + API ServerSchedulerControl Plane (HA)etcd + API ServerLoad Balancer VIPWorker Node 1Containerd + KubeletCNI PluginWorker Node NContainerd + KubeletApplication Pods
High-level architecture when you deploy Kubernetes with Kubespray: Ansible orchestrates control plane HA and worker node provisioning via SSH.

How do you prepare infrastructure before running Kubespray?

The most common failure mode I see isn't the playbook itself—it's the underlying infrastructure. Kubespray assumes a clean, consistent base. Before you even clone the repo, verify these prerequisites across every target node.

Operating system and resource baselines

Kubespray supports Ubuntu 22.04/24.04, Debian 12, Rocky Linux 9, and AlmaLinux 9. Stick to LTS releases. Each control plane node needs at least 2 vCPUs and 4GB RAM; workers need 2 vCPUs and 8GB RAM minimum for production workloads. Disk I/O matters more than capacity: use SSD-backed storage for etcd directories (/var/lib/etcd) specifically. Spinning disks here will cause leader elections and cluster instability under load.

Network and firewall requirements

All nodes must have unique hostnames, MAC addresses, and product UUIDs. Disable swap permanently (swapoff -a and remove from /etc/fstab). Ensure these ports are open between nodes:

  • TCP 6443: Kubernetes API server
  • TCP 2379-2380: etcd client and peer communication
  • TCP 10250: Kubelet API
  • TCP 179: Calico BGP (if using Calico CNI)
  • UDP 4789: VXLAN overlay (if using Flannel/VXLAN)

If you're deploying in a restricted environment, review firewall configuration best practices to avoid blocking critical inter-node traffic. Time synchronization via chrony or systemd-timesyncd is non-negotiable; certificate validation fails with clock skew greater than five minutes.

SSH and user access

Kubespray requires passwordless SSH from your Ansible controller to all targets. Use a dedicated user with sudo privileges rather than root. Configure ssh-agent forwarding if you're jumping through a bastion host. Test connectivity with ansible all -m ping -i inventory/mycluster/hosts.yaml before proceeding.

How do you configure inventory and group variables correctly?

Configuration determines whether your cluster survives its first node failure. Never edit files directly in the Kubespray source tree. Instead, generate a separate inventory directory to keep your configuration version-controlled and upgrade-safe.

# Generate inventory skeleton
cp -rfp inventory/sample inventory/mycluster

# Build hosts file dynamically from IP list
declare -a IPS=(10.10.1.10 10.10.1.11 10.10.1.12 10.10.1.20 10.10.1.21)
CONFIG_FILE=inventory/mycluster/hosts.yaml python3 contrib/inventory_builder/inventory.py ${IPS[@]}

Defining high availability topology

Edit inventory/mycluster/hosts.yaml to assign roles explicitly. For production, always use three or five control plane nodes (odd numbers prevent split-brain). Etcd can be colocated on control plane nodes for smaller clusters or deployed on dedicated nodes for large-scale deployments.

all:
  hosts:
    node1:
      ansible_host: 10.10.1.10
      ip: 10.10.1.10
      access_ip: 10.10.1.10
    node2:
      ansible_host: 10.10.1.11
      ip: 10.10.1.11
      access_ip: 10.10.1.11
    node3:
      ansible_host: 10.10.1.12
      ip: 10.10.1.12
      access_ip: 10.10.1.12
  children:
    kube_control_plane:
      hosts:
        node1:
        node2:
        node3:
    kube_node:
      hosts:
        node1:
        node2:
        node3:
        node4:
        node5:
    etcd:
      hosts:
        node1:
        node2:
        node3:

Critical group_vars settings

Override defaults in inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml. These three settings matter most:

  1. Container runtime: Set container_manager: containerd. Docker was removed as a runtime in Kubernetes 1.24+; containerd is now the stable default.
  2. CNI plugin: Choose kube_network_plugin: calico for most on-prem deployments. Calico provides network policies out-of-the-box, essential for multi-tenant security. Use Cilium if you need eBPF-based observability.
  3. API server load balancer: Set loadbalancer_apiserver_localhost: true and loadbalancer_apiserver_type: nginx. This deploys a local NGINX load balancer on each node pointing to all control planes, eliminating the need for an external LB in bare-metal setups.
1. Inventoryhosts.yamlRole AssignmentIP Mapping2. Variablesk8s-cluster.ymlCNI SelectionRuntime Config3. Executioncluster.ymlIdempotent RunRetry Logic4. Validationkubectl get nodesComponent CheckDNS Resolution5. ReadyWorkloadsMonitoringBackups
Five-stage workflow to deploy Kubernetes with Kubespray: inventory definition, variable configuration, playbook execution, validation, and production readiness.

What are the key differences between Kubespray, kubeadm, and managed Kubernetes?

Choosing the right deployment method depends on your team's expertise, budget, and compliance constraints. This comparison reflects real trade-offs I've navigated across dozens of production deployments.

CriteriaKubespraykubeadm (Manual)Managed (EKS/GKE/AKS)
Setup ComplexityModerate (Ansible knowledge required)High (manual component integration)Low (API-driven provisioning)
Control Plane AccessFull (etcd, API server flags, certs)FullLimited (no etcd access, restricted flags)
Upgrade PathAutomated via playbook versionsManual, error-proneOne-click, managed rollback
Air-Gap SupportNative (offline registry + artifacts)Possible but manualNot available
Operational OverheadHigh (you own everything)Very HighLow (provider manages control plane)
Cost ModelInfrastructure only (no management fee)Infrastructure onlyManagement fee + infrastructure
Compliance FlexibilityMaximum (full audit trail, custom hardening)MaximumConstrained by provider controls

Choose Kubespray when you need managed-service-like reproducibility without surrendering control. Choose kubeadm only for learning or highly customized single-cluster scenarios. Choose managed services when operational headcount is limited and compliance allows it.

How do you execute the deployment and validate cluster health?

With inventory and variables configured, run the cluster playbook. Always use the --become flag and specify your inventory path explicitly.

ansible-playbook -i inventory/mycluster/hosts.yaml \
  --become --become-user=root \
  cluster.yml

This takes 15–30 minutes depending on node count and network speed. Kubespray is idempotent: safe to re-run if interrupted. After completion, copy the kubeconfig:

# Copy admin kubeconfig to local machine
scp root@node1:~/.kube/config ~/.kube/mycluster-config
export KUBECONFIG=~/.kube/mycluster-config

# Validate all nodes are Ready
kubectl get nodes -o wide

# Verify core components
kubectl get pods -n kube-system
kubectl cluster-info dump | grep -E 'etcd|apiserver|controller'

Post-deployment hardening checklist

A fresh Kubespray cluster isn't production-ready until you address these items:

  • Enable Pod Security Standards: Enforce restricted policy on namespaces to prevent privileged containers.
  • Configure etcd backups: Schedule automated snapshots to object storage. Test restoration quarterly.
  • Install monitoring: Deploy Prometheus stack via Helm. Alert on etcd latency, API server errors, and node pressure.
  • Rotate certificates: Kubespray generates certs valid for one year. Document rotation procedures before expiry.
  • Restrict RBAC: Remove cluster-admin bindings from default users. Implement least-privilege service accounts.

For teams integrating AI-assisted operations, consider how AIOps practices can enhance monitoring and anomaly detection on self-hosted clusters without sending telemetry to third parties.

Kubespray (Self-Hosted)Hardware / VM ProvisioningOS Hardening & Patchingetcd Backup & RecoveryControl Plane UpgradesNetwork Policy EnforcementApplication WorkloadsYOU OWN ALL LAYERSManaged Kubernetes (EKS/GKE)Hardware / VM ProvisioningOS Hardening & Patchingetcd Backup & RecoveryControl Plane UpgradesNetwork Policy EnforcementApplication WorkloadsPROVIDER OWNS GRAY LAYERS
Operational responsibility comparison: Kubespray requires ownership of all layers while managed services abstract infrastructure and control plane maintenance.

Deploy Kubernetes with Kubespray as a long-term platform strategy

When you deploy Kubernetes with Kubespray, you're choosing operational sovereignty over convenience. This trade-off pays dividends in regulated industries, cost-sensitive environments, and regions where cloud providers lack local presence. The initial investment in Ansible proficiency and infrastructure discipline yields clusters that survive audits, outlast vendor lock-in, and scale on your terms.

Start with a three-node test cluster to build muscle memory before touching production. Version-control your inventory and group_vars alongside application code. Treat upgrades as planned events, not emergencies. And remember: automation without observability is just faster failure. Pair every deployment with comprehensive monitoring from day one.

If you're evaluating self-hosted Kubernetes for a compliance-sensitive project or need guidance on production hardening, reach out to discuss your infrastructure requirements. I help teams architect clusters that pass audits and handle real traffic—not just demo environments.

Frequently Asked Questions

Kubespray is an Ansible-based tool for deploying production-grade Kubernetes clusters on bare metal or VMs. It automates complex setup tasks like etcd, CNI, and control plane configuration without requiring managed cloud services.

Kubespray uses kubeadm internally but adds Ansible automation for multi-node setups, certificate management, and add-ons. Kubeadm alone requires manual scripting for scaling and upgrades across multiple nodes.

Control plane nodes need 2 vCPUs and 4GB RAM minimum. Worker nodes require 2 vCPUs and 8GB RAM. Etcd performance depends heavily on disk IOPS, so use SSDs for production etcd storage.

Supported distributions include Ubuntu 22.04/24.04, Debian 12/13, Rocky Linux 9, AlmaLinux 9, and Fedora CoreOS. Always check the official compatibility matrix before starting deployment.

Yes. Use the aws-ec2 inventory plugin or Terraform to provision instances first. Kubespray handles OS-level configuration, Kubernetes installation, and networking regardless of underlying infrastructure provider.

Set container_manager in group_vars/all.yml to containerd, crio, or docker. Containerd is default and recommended for 2026 deployments. Docker support exists but adds unnecessary complexity via dockershim.

Calico provides network policies and BGP peering for bare metal. Cilium offers eBPF-based observability and security. Flannel suits simple overlay networks. Choose based on policy enforcement needs and performance requirements.

A three-node cluster deploys in 15-25 minutes on fast hardware. Larger clusters scale linearly. Network latency between nodes and package mirror speed significantly impact total provisioning time.

Yes. Run upgrade-cluster.yml playbook with target version specified. Kubespray performs rolling upgrades, drains nodes properly, and validates health between steps. Always backup etcd before upgrading production clusters.

Add new hosts to inventory under kube_node group. Run scale.yml playbook targeting only new nodes. Existing cluster state remains untouched while new workers join and receive certificates automatically.

Firewall blocking required ports, insufficient disk space, mismatched OS versions, and DNS resolution failures cause most issues. Check preflight logs carefully and validate SSH connectivity before running cluster.yml.

Yes. Configure at least three control plane nodes and set kube_apiserver_count accordingly. Kubespray configures stacked etcd or external etcd automatically based on your inventory and variables.

Default configs enable RBAC, encrypt etcd, and disable anonymous access. Harden further by enabling audit logging, restricting pod security standards, and rotating certificates regularly using built-in playbooks.

Enable kube_prometheus_stack in addons.yml to deploy Prometheus, Grafana, and alertmanager automatically. Metrics-server installs by default for HPA support. Disable unused add-ons to reduce resource overhead.

Yes. Pre-download all container images and binaries using fetch-images.sh script. Configure local registry and set offline mode variables. Kubespray skips internet downloads entirely when configured correctly.