Installing Kubernetes with Kubespray and Adding Worker Nodes (2025V.)

A comprehensive guide to automated Kubernetes cluster deployment and scaling using Kubespray with Cilium CNI

Featured image

Reference: Kubespray Deployment Guide



Overview

In this post, we’ll explore how to automatically install and configure a Kubernetes cluster using Kubespray, and further expand it by adding Worker Nodes through hands-on practice. Kubespray is an Ansible-based provisioning tool that allows flexible installation of Kubernetes clusters including HA configurations across various environments (GCP, AWS, on-premises, etc.) by simply defining YAML-based inventory files.

This practical guide covers the following key areas:

Additionally, we’ll cover essential Kubernetes operational settings including kubectl, bashrc, autocompletion, and context configuration.

What is Kubespray?

Kubespray is a composition of Ansible playbooks, inventory, provisioning tools, and domain knowledge for deploying a production-ready Kubernetes cluster. It allows for highly customizable deployments and supports multiple cloud providers, bare metal installations, and virtualized environments.

Key features include:

  • Composable attributes
  • Multiple network plugin support (Calico, Flannel, Cilium, etc.)
  • HA cluster setup
  • Configurable addons
  • Support for most popular Linux distributions


System Configuration

Based on July 30, 2025 standards.


Environment Details

Component Specification
Operating System Ubuntu 24.04 LTS (Noble)
Infrastructure On-premises Server
Kubernetes Version v1.33.3 (deployed by Kubespray)
CNI Plugin Cilium
Container Runtime containerd v2.1.3
Python Version 3.10


Node Specifications

Node Hostname IP Address CPU Memory Role
Control Plane Node k8s-control-01 10.10.10.17 4 cores 24GB Control Plane + etcd
Worker Node 1 k8s-compute-01 10.10.10.18 12 cores 48GB Worker (application)
Worker Node 2 k8s-compute-02 10.10.10.19 12 cores 48GB Worker (application)


Prerequisites

Before starting the Kubespray installation, you need to prepare your environment.


VM Creation

VMs were created using Cockpit for this deployment.


SSH Key Setup

# Generate SSH key
ssh-keygen

# Update /etc/hosts for easier node access
sudo vi /etc/hosts
10.10.10.17 k8s-control-01
10.10.10.18 k8s-compute-01
10.10.10.19 k8s-compute-02

# Copy SSH key to all nodes
ssh-copy-id k8s-control-01
ssh-copy-id k8s-compute-01
ssh-copy-id k8s-compute-02

# Verify SSH connections
ssh k8s-control-01
ssh k8s-compute-01
ssh k8s-compute-02


Package Installation

# Update package lists and install required packages
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get -y update
sudo apt install -y python3.10 python3-pip git python3.10-venv

# Verify Python version
python3.10 --version


Kubespray Installation and Setup


1. Clone Repository and Setup Environment

# Clone the Kubespray repository
git clone https://github.com/kubernetes-sigs/kubespray.git
cd kubespray

# Copy sample inventory
cp -rfp inventory/sample inventory/somaz-cluster

# Create and activate virtual environment
python3.10 -m venv venv
source venv/bin/activate

# Install dependencies
pip install -U -r requirements.txt


2. Configure Inventory

First, we’ll deploy only k8s-compute-01, then later add k8s-compute-02 using scale.yml.

inventory/somaz-cluster/inventory.ini

[kube_control_plane]
k8s-control-01 ansible_host=10.10.10.17 ip=10.10.10.17 etcd_member_name=etcd1

[etcd:children]
kube_control_plane

[kube_node]
k8s-compute-01 ansible_host=10.10.10.18 ip=10.10.10.18


3. Customize Cluster Configuration

inventory/somaz-cluster/group_vars/k8s_cluster/k8s-cluster.yml

# Choose network plugin (cilium, calico, kube-ovn, weave or flannel)
kube_network_plugin: cilium

# Cluster name
cluster_name: somaz-cluster.local

# Ownership configuration (important for permission issues)
kube_owner: root  # Change from default 'kube' to 'root' to avoid permission issues

inventory/somaz-cluster/group_vars/k8s_cluster/addons.yml

# Helm deployment
helm_enabled: true

# Metrics Server deployment
metrics_server_enabled: true

# The plugin manager for kubectl
krew_enabled: true
krew_root_dir: "/usr/local/krew"


4. Verify Ansible Connectivity

# Test connection to all nodes (run from ~/kubespray directory)
ansible all -i inventory/somaz-cluster/inventory.ini -m ping


5. Deploy Kubernetes Cluster

# Deploy the cluster (run from ~/kubespray directory)
ansible-playbook -i inventory/somaz-cluster/inventory.ini cluster.yml --become

# For background execution
nohup ansible-playbook -i inventory/somaz-cluster/inventory.ini cluster.yml --become &


6. Configure kubectl

# Create kubectl config directory and copy credentials
mkdir ~/.kube
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
sudo chown $USER:$USER ~/.kube/config


7. Setup kubectl Completion and Aliases

# Add kubectl completion and aliases to bashrc
echo '# kubectl completion and alias' >> ~/.bashrc
echo 'source <(kubectl completion bash)' >> ~/.bashrc
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -F __start_kubectl k' >> ~/.bashrc

# Apply bashrc changes
source ~/.bashrc


8. Verify Installation

# Check nodes
k get nodes
NAME             STATUS   ROLES           AGE    VERSION
k8s-compute-01   Ready    <none>          2d4h   v1.33.3
k8s-control-01   Ready    control-plane   2d4h   v1.33.3

# Check versions
k version
containerd --version

# Check Cilium image
k get ds cilium -n kube-system -o=jsonpath='{.spec.template.spec.containers[0].image}'


Troubleshooting Cilium Permission Issues

Currently, there’s a known permission error with Cilium. Here’s the fix:

# Fix CNI binary permissions
chown -R root:root /opt/cni/bin

For more information about this issue, see: Cilium GitHub Issue #23838

Alternatively, you can modify the configuration:

inventory/somaz-cluster/group_vars/k8s_cluster/k8s-cluster.yml

# Change from default
# kube_owner: kube

# To
kube_owner: root


Adding Worker Nodes

One of the key advantages of Kubernetes is its scalability. Let’s add k8s-compute-02 to our cluster using the scale.yml playbook.

Reference: Kubespray Scaling Documentation


1. Update Inventory Configuration

inventory/somaz-cluster/inventory.ini

# This inventory describes a HA topology with stacked etcd
[kube_control_plane]
k8s-control-01 ansible_host=10.10.10.17 ip=10.10.10.17 etcd_member_name=etcd1

[etcd:children]
kube_control_plane

[kube_node]
k8s-compute-01 ansible_host=10.10.10.18 ip=10.10.10.18
k8s-compute-02 ansible_host=10.10.10.19 ip=10.10.10.19

[add_node]
k8s-compute-02


2. Run Scale Playbook

# Add new nodes to the cluster using scale.yml
nohup ansible-playbook -i inventory/somaz-cluster/inventory.ini scale.yml --limit add_node --become &


3. Verify Scaled Cluster

# Check all nodes
k get nodes
NAME             STATUS   ROLES           AGE    VERSION
k8s-compute-01   Ready    <none>          2d5h   v1.33.3
k8s-compute-02   Ready    <none>          2d4h   v1.33.3
k8s-control-01   Ready    control-plane   2d5h   v1.33.3

Error Recovery

If you encounter errors during scaling, run the facts playbook first, then retry:

# Run facts playbook to refresh node information
ansible-playbook -i inventory/somaz-cluster/inventory.ini playbooks/facts.yml --become

# Then retry the scale operation
nohup ansible-playbook -i inventory/somaz-cluster/inventory.ini scale.yml --limit add_node --become &


Advanced Operations


Scaling Options

# Scale with all new nodes
nohup ansible-playbook -i inventory/somaz-cluster/inventory.ini scale.yml --become &

# Scale with specific node limit
nohup ansible-playbook -i inventory/somaz-cluster/inventory.ini scale.yml --limit add_node --become &


Node Removal

# Remove specific node
ansible-playbook -i inventory/somaz-cluster/inventory.ini remove-node.yml -b --extra-vars='node=node3' --extra-vars reset_nodes=true


Verification and Monitoring


Cluster Status Verification

# Check all nodes with detailed information
kubectl get nodes -o wide

# Check system pods
kubectl get pods -n kube-system

# Check Cilium status
kubectl get pods -n kube-system -l k8s-app=cilium

# Verify Cilium connectivity
kubectl exec -n kube-system ds/cilium -- cilium status


Sample Application Deployment

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

# Check deployment
kubectl get pods
kubectl get services


Conclusion: “Production-Ready Kubernetes Installation Automation with Kubespray”

Kubespray goes beyond simple installation automation to enable production-level Kubernetes cluster configuration. It’s particularly useful in the following scenarios:

Through this hands-on practice, we’ve learned how to control clusters using declarative inventory files and Ansible playbooks instead of manually repeating Kubernetes installation processes.


Recommended Advanced Features

For future advanced functionality practice, consider:

  • Multi Control Plane configuration (HA setup)
  • External etcd cluster integration
  • GitOps tools (ArgoCD, Flux) integration
  • Monitoring integration with Prometheus/Grafana, Loki
  • Worker Node auto-scaling configuration

Establishing the flow of Ansible-based automation → Cluster construction → GitOps-based app deployment can elevate operational automation to the next level.



References