<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ali Soltaninasab</title>
    <description>The latest articles on DEV Community by Ali Soltaninasab (@alisoltani255).</description>
    <link>https://dev.to/alisoltani255</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3632271%2F8b5eb928-c7b4-428c-a7b5-c97db63cb907.jpg</url>
      <title>DEV Community: Ali Soltaninasab</title>
      <link>https://dev.to/alisoltani255</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alisoltani255"/>
    <language>en</language>
    <item>
      <title>Building an Air-gapped Hardened Kubernetes Cluster with Kubespray</title>
      <dc:creator>Ali Soltaninasab</dc:creator>
      <pubDate>Thu, 27 Nov 2025 09:16:12 +0000</pubDate>
      <link>https://dev.to/alisoltani255/building-an-air-gapped-hardened-kubernetes-cluster-with-kubespray-4405</link>
      <guid>https://dev.to/alisoltani255/building-an-air-gapped-hardened-kubernetes-cluster-with-kubespray-4405</guid>
      <description>&lt;h2&gt;
  
  
  Building an Air-gapped, Hardened Kubernetes Cluster with Kubespray
&lt;/h2&gt;

&lt;p&gt;Most Kubernetes examples assume you have full Internet access and can just &lt;code&gt;curl | bash&lt;/code&gt; your way into a cluster.&lt;br&gt;&lt;br&gt;
In many real environments, that's not the case.&lt;/p&gt;

&lt;p&gt;In this project I built and documented a &lt;strong&gt;production-style, air-gapped, hardened, highly available Kubernetes cluster&lt;/strong&gt; using &lt;strong&gt;Kubespray&lt;/strong&gt;. This post is a high-level walkthrough of the approach, the constraints, and how the pieces fit together.&lt;/p&gt;

&lt;p&gt;👉 Full runbook and supporting material are available on GitHub:&lt;br&gt;&lt;br&gt;
&lt;a href="https://a-soltani255.github.io/Kubespray/" rel="noopener noreferrer"&gt;https://a-soltani255.github.io/Kubespray/&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Why air-gapped + hardened?
&lt;/h3&gt;

&lt;p&gt;The scenario behind this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No direct Internet access&lt;/strong&gt; from the Kubernetes nodes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strict security requirements&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Need for a &lt;strong&gt;repeatable, documented process&lt;/strong&gt; to create new clusters&lt;/li&gt;
&lt;li&gt;Desire for &lt;strong&gt;high availability&lt;/strong&gt; (multi-control-plane) rather than a single-master lab&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination is common in regulated environments, but it's rarely covered in simple tutorials.&lt;/p&gt;




&lt;h3&gt;
  
  
  High-level architecture
&lt;/h3&gt;

&lt;p&gt;At a very high level, the setup looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Several Linux nodes (for control plane and workers)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;bastion / deploy node&lt;/strong&gt; where Kubespray runs Ansible&lt;/li&gt;
&lt;li&gt;Internal infrastructure:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local OS package repositories&lt;/strong&gt; (mirrors of upstream repos)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private container registry&lt;/strong&gt; for all required images&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Kubernetes deployed by &lt;strong&gt;Kubespray&lt;/strong&gt; with:

&lt;ul&gt;
&lt;li&gt;Multi-node control plane (HA)&lt;/li&gt;
&lt;li&gt;Custom CNI&lt;/li&gt;
&lt;li&gt;Opinionated hardening&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The GitHub repo contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A full Markdown runbook:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Installing-Airgapped-Hardened-Kubernetes-Cluster-Using-Kubespray.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;A &lt;code&gt;Scripts, appendices and Configurations/&lt;/code&gt; folder with:

&lt;ul&gt;
&lt;li&gt;Example &lt;strong&gt;inventories&lt;/strong&gt; and &lt;strong&gt;group vars&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Helper &lt;strong&gt;scripts&lt;/strong&gt; and one-liners&lt;/li&gt;
&lt;li&gt;Diagrams and troubleshooting appendices&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 1 – Preparing the environment
&lt;/h3&gt;

&lt;p&gt;The runbook starts with the basics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choosing and preparing the OS for all nodes&lt;/li&gt;
&lt;li&gt;Setting up &lt;strong&gt;hostnames, IPs, and SSH access&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Ensuring consistent &lt;strong&gt;time sync&lt;/strong&gt; and basic hardening at the OS level&lt;/li&gt;
&lt;li&gt;Making sure the deploy node can reach all cluster nodes via SSH&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to have a clean, predictable base before Kubespray comes into play.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2 – Building offline OS repositories and registry
&lt;/h3&gt;

&lt;p&gt;Because the cluster is &lt;strong&gt;air-gapped&lt;/strong&gt;, public repos and registries are not available directly.&lt;/p&gt;

&lt;p&gt;The project walks through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mirroring OS repositories (e.g. BaseOS/AppStream/EPEL equivalents) into an internal repo&lt;/li&gt;
&lt;li&gt;Setting up a &lt;strong&gt;private container registry&lt;/strong&gt; and pushing required images into it&lt;/li&gt;
&lt;li&gt;Wiring Kubespray and the nodes to use these internal sources instead of the Internet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the foundation that allows you to fully install and operate Kubernetes without external network dependency.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3 – Designing the Kubespray inventory
&lt;/h3&gt;

&lt;p&gt;Kubespray uses an &lt;strong&gt;Ansible inventory&lt;/strong&gt; plus &lt;strong&gt;group vars&lt;/strong&gt; to describe the cluster.&lt;/p&gt;

&lt;p&gt;In the repo you’ll find examples showing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separation of &lt;strong&gt;control plane&lt;/strong&gt; and &lt;strong&gt;worker&lt;/strong&gt; nodes&lt;/li&gt;
&lt;li&gt;How to describe the HA layout in the inventory&lt;/li&gt;
&lt;li&gt;How group vars are tuned for:

&lt;ul&gt;
&lt;li&gt;The chosen OS&lt;/li&gt;
&lt;li&gt;The air-gapped environment&lt;/li&gt;
&lt;li&gt;The selected CNI and other components&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The idea is to move away from one-off &lt;code&gt;kubeadm&lt;/code&gt; experiments and into a &lt;strong&gt;declarative, repeatable&lt;/strong&gt; cluster definition.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4 – Running Kubespray and bringing the cluster up
&lt;/h3&gt;

&lt;p&gt;With the inventory designed and the registry/repos in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubespray is used to:

&lt;ul&gt;
&lt;li&gt;Bootstrap etcd and the control plane&lt;/li&gt;
&lt;li&gt;Configure worker nodes&lt;/li&gt;
&lt;li&gt;Install networking, DNS, and core add-ons&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The runbook includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The exact commands to run from the deploy node&lt;/li&gt;
&lt;li&gt;Common pitfalls and validation checks (e.g. verifying nodes, pods, and core services)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 5 – Hardening and validation
&lt;/h3&gt;

&lt;p&gt;After the cluster is up, the project focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardening steps&lt;/strong&gt; that make sense in this environment&lt;/li&gt;
&lt;li&gt;Verifying:

&lt;ul&gt;
&lt;li&gt;Node status and core components&lt;/li&gt;
&lt;li&gt;DNS and basic networking&lt;/li&gt;
&lt;li&gt;Access to the private registry from inside the cluster&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;There is also a troubleshooting section with diagrams to help understand where things can break and how to debug them.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why I built this and what it demonstrates
&lt;/h3&gt;

&lt;p&gt;From a skills point of view, this project showcases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes &lt;strong&gt;cluster provisioning&lt;/strong&gt; with Kubespray&lt;/li&gt;
&lt;li&gt;Designing for &lt;strong&gt;high availability&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Working in &lt;strong&gt;air-gapped / restricted&lt;/strong&gt; environments&lt;/li&gt;
&lt;li&gt;Building internal &lt;strong&gt;package repositories&lt;/strong&gt; and &lt;strong&gt;container registries&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Applying &lt;strong&gt;basic hardening&lt;/strong&gt; and doing structured validation&lt;/li&gt;
&lt;li&gt;Writing a &lt;strong&gt;runbook&lt;/strong&gt; that other engineers can follow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re interested in the full details, you can explore the GitHub repo here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/A-Soltani255/Kubespray" rel="noopener noreferrer"&gt;https://github.com/A-Soltani255/Kubespray&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;My goal with this work was to go beyond “local single-node clusters” and document something closer to a realistic production-style setup, including all the operational pieces around it.&lt;/p&gt;

&lt;p&gt;If you work with Kubernetes in constrained environments, or you’re moving towards platform engineering / SRE roles, projects like this are a great way to practise &lt;strong&gt;end-to-end thinking&lt;/strong&gt;: from OS and networking, to provisioning, to security and operations.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>kubespray</category>
      <category>devops</category>
      <category>sre</category>
    </item>
  </channel>
</rss>
