<?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: Gaganpreet Singh</title>
    <description>The latest articles on DEV Community by Gaganpreet Singh (@willeysingh).</description>
    <link>https://dev.to/willeysingh</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F387686%2F33ae1247-e556-4db9-bbcb-64a136821265.jpg</url>
      <title>DEV Community: Gaganpreet Singh</title>
      <link>https://dev.to/willeysingh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/willeysingh"/>
    <language>en</language>
    <item>
      <title>Autonomous Cyber Deception Grid: Designing Honeypots That Adapt</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Sat, 31 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/autonomous-cyber-deception-grid-designing-honeypots-that-adapt-9m8</link>
      <guid>https://dev.to/willeysingh/autonomous-cyber-deception-grid-designing-honeypots-that-adapt-9m8</guid>
      <description>&lt;p&gt;A honeypot that never changes gets fingerprinted quickly. The Autonomous Cyber Deception Grid is my design for a multi-level deception system that starts as a set of static decoys and grows into an environment that adapts to the attacker in front of it, so defenders learn more and the attacker wastes more time.&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/Willey2003/cyber-deception-grid" rel="noopener noreferrer"&gt;github.com/Willey2003/cyber-deception-grid&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Four levels
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Level 1: Static deception.&lt;/strong&gt; Containerised mock services (SSH, HTTP and a MySQL-style database) that log the attacker’s source address, timestamps and every command they type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 2: Behavioural adaptation.&lt;/strong&gt; A local LLM generates believable command output, fake files and system errors, tailored to the attacker’s technique, so the decoy holds up beyond the first few commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 3: Autonomous landscape evolution.&lt;/strong&gt; A reinforcement learning controller changes the fake network topology, moves high-value decoys, and expands the network footprint around aggressive actors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 4: Threat intelligence.&lt;/strong&gt; Captured payloads go to a malware sandbox, observed behaviour is mapped to MITRE ATT&amp;amp;CK, and the results feed a central dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Services
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ssh-honeypot&lt;/td&gt;
&lt;td&gt;2222&lt;/td&gt;
&lt;td&gt;Fake SSH server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;http-honeypot&lt;/td&gt;
&lt;td&gt;8080&lt;/td&gt;
&lt;td&gt;Fake web server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;db-honeypot&lt;/td&gt;
&lt;td&gt;3306&lt;/td&gt;
&lt;td&gt;Fake MySQL database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deception-api&lt;/td&gt;
&lt;td&gt;8000&lt;/td&gt;
&lt;td&gt;Orchestration API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;llm-adapter&lt;/td&gt;
&lt;td&gt;8001&lt;/td&gt;
&lt;td&gt;Response generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rl-controller&lt;/td&gt;
&lt;td&gt;8002&lt;/td&gt;
&lt;td&gt;Topology controller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;intel-processor&lt;/td&gt;
&lt;td&gt;8003&lt;/td&gt;
&lt;td&gt;Threat intelligence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dashboard&lt;/td&gt;
&lt;td&gt;3000&lt;/td&gt;
&lt;td&gt;Web dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What is built so far
&lt;/h2&gt;

&lt;p&gt;Level 1 is the starting point. The SSH honeypot is written in Python with Paramiko and asyncio: it accepts password logins, records each credential pair and command in an attacker session, and hands the session to an orchestrator that later levels will use to decide how the environment responds. A CI workflow runs on every push.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design principles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Containment first.&lt;/strong&gt; Decoys run in isolated containers with no route to real systems. A honeypot that becomes a pivot point is worse than none.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Local models only.&lt;/strong&gt; Attacker input never goes to a hosted AI service, and the model only ever generates fake output.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Everything maps to ATT&amp;amp;CK,&lt;/strong&gt; so what the grid learns is usable by the SOC and not just interesting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;HTTP and database decoys, the LLM adapter for Level 2, and a dashboard that shows live sessions and ATT&amp;amp;CK techniques. Run honeypots only on networks you own or are authorised to use, and keep them isolated from production.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>ai</category>
      <category>opensource</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Classifying Malware Variants with Graph Neural Networks</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/classifying-malware-variants-with-graph-neural-networks-4mmm</link>
      <guid>https://dev.to/willeysingh/classifying-malware-variants-with-graph-neural-networks-4mmm</guid>
      <description>&lt;p&gt;Malware authors rarely write a new family from scratch. They recompile, repack and patch, so the bytes change while the program’s structure stays much the same. Hash-based detection misses those variants. Structure-based classification can catch them. This project is my build of that idea: a Graph Neural Network that classifies malware variants from their control flow graphs and call graphs.&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/Willey2003/malware-variant-classifier" rel="noopener noreferrer"&gt;github.com/Willey2003/malware-variant-classifier&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Feature extraction.&lt;/strong&gt; Each binary is disassembled with Capstone, and its control flow graph and call graph are built with NetworkX. Each node gets a small feature vector describing its basic block.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Graph dataset.&lt;/strong&gt; The NetworkX graphs are converted to PyTorch Geometric &lt;code&gt;Data&lt;/code&gt; objects, labelled by family.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Model.&lt;/strong&gt; A configurable GNN encoder (GCN, GAT or GraphSAGE, 3 layers, 128 hidden units, 64-dimensional embedding) with pooling and a classification head over 10 families.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Training.&lt;/strong&gt; A training loop with validation split, checkpoints and MLflow experiment tracking.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Inference API.&lt;/strong&gt; A FastAPI service with &lt;code&gt;/health&lt;/code&gt;, &lt;code&gt;/predict&lt;/code&gt; and &lt;code&gt;/predict/batch&lt;/code&gt; endpoints that accept uploaded binaries and return the predicted family with a confidence score.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;The service ships with a Dockerfile, a docker-compose file and Kubernetes manifests, so the same image runs on a laptop or a cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python src/training/train.py &lt;span class="nt"&gt;--config&lt;/span&gt; configs/train_config.yaml

docker-compose up &lt;span class="nt"&gt;--build&lt;/span&gt;     &lt;span class="c"&gt;# local stack&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s/         &lt;span class="c"&gt;# Kubernetes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why graphs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  A repacked or recompiled variant changes its hash and many of its bytes, but its control flow tends to survive.&lt;/li&gt;
&lt;li&gt;  GNNs learn from the shape of the graph, so two samples with similar structure land close together in embedding space even when their bytes differ.&lt;/li&gt;
&lt;li&gt;  The embedding is also useful on its own: nearest-neighbour search over embeddings groups unknown samples with their closest known family.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Status and next steps
&lt;/h2&gt;

&lt;p&gt;This is an early build. The pipeline, model, API and deployment files are in place. Next is training on a public labelled dataset, publishing accuracy per family, and comparing the three GNN architectures. Only handle live malware samples in an isolated analysis environment.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>What Survives a VMware to OpenShift Migration (and What to Check)</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Tue, 27 Oct 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/what-survives-a-vmware-to-openshift-migration-and-what-to-check-597d</link>
      <guid>https://dev.to/willeysingh/what-survives-a-vmware-to-openshift-migration-and-what-to-check-597d</guid>
      <description>&lt;p&gt;"Will it still be the same VM?" is the first question every application owner asks about a migration. Licences are keyed to serials, DHCP reservations to MACs, and fstab entries to disk IDs.&lt;/p&gt;

&lt;p&gt;So after migrating a VM from vSphere to OpenShift Virtualization with MTV, I read the new &lt;code&gt;VirtualMachine&lt;/code&gt; and traced every field back to the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was kept
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field on the OpenShift VM&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Where it came from&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interfaces[net-0].macAddress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;00:50:56:aa:00:01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The source NIC's VMware MAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;firmware.serial&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VMware-42 00 00 …&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The VMware BIOS serial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disks[vol-0].serial&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;6000C290-…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The VMDK's UUID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2 sockets × 1 core&lt;/td&gt;
&lt;td&gt;The source vCPU topology&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory.guest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1Gi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The source RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;firmware.bootloader&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bios&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The source firmware (BIOS, not EFI)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(Values are documentation placeholders. In the lab, the real values matched the source exactly.)&lt;/p&gt;

&lt;p&gt;Why each one matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MAC address:&lt;/strong&gt; DHCP reservations and MAC-bound licences keep working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BIOS serial:&lt;/strong&gt; licence checks keyed to the hardware serial still pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk serial:&lt;/strong&gt; &lt;code&gt;/dev/disk/by-id&lt;/code&gt; paths inside the guest don't change, so fstab and app configs that use them keep working.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Check it on your own VM
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;VM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ubuntu-noble-24.04-cloudimg&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;NS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mtv-test
oc get vm &lt;span class="nv"&gt;$VM&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$NS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.spec.template.spec.domain.devices.interfaces[0].macAddress}{"\n"}'&lt;/span&gt;
oc get vm &lt;span class="nv"&gt;$VM&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$NS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.spec.template.spec.domain.firmware.serial}{"\n"}'&lt;/span&gt;
oc get vm &lt;span class="nv"&gt;$VM&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$NS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.spec.template.spec.domain.devices.disks[0].serial}{"\n"}'&lt;/span&gt;
oc get vm &lt;span class="nv"&gt;$VM&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$NS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.spec.template.spec.domain.cpu}{"\n"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare them with the source VM in the vSphere Client (network adapter MAC under &lt;em&gt;Edit Settings&lt;/em&gt;) and, inside the guest before and after, with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nb"&gt;link&lt;/span&gt;                      &lt;span class="c"&gt;# MAC&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dmidecode &lt;span class="nt"&gt;-s&lt;/span&gt; system-serial-number
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/disk/by-id/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I did not verify
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Static IPs.&lt;/strong&gt; My test VM used DHCP, so I can't claim static IP preservation from this lab. MTV has a setting for it, but test it on one of your own VMs with a static address before you promise it to anyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The network path also changes.&lt;/strong&gt; The VM now reaches its VLAN through a NetworkAttachmentDefinition on a Linux bridge, not a port group. Same VLAN, new plumbing, so test connectivity, not just the MAC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson
&lt;/h2&gt;

&lt;p&gt;Prove it on one VM, write down the before and after, and attach that to your migration runbook. It turns "trust me" into evidence.&lt;/p&gt;

&lt;p&gt;The full worked example, with every field of the migrated VM explained, is in the free &lt;a href="https://github.com/Willey2003/openshift-vmware-migration-sop" rel="noopener noreferrer"&gt;Lab Manual&lt;/a&gt;. Next in this series: five gotchas from building the lab.&lt;/p&gt;

</description>
      <category>openshift</category>
      <category>vmware</category>
      <category>kubernetes</category>
      <category>redhat</category>
    </item>
    <item>
      <title>Modelling Worm Outbreaks in Python to See Which Defences Actually Help</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Sun, 25 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/modelling-worm-outbreaks-in-python-to-see-which-defences-actually-help-db0</link>
      <guid>https://dev.to/willeysingh/modelling-worm-outbreaks-in-python-to-see-which-defences-actually-help-db0</guid>
      <description>&lt;p&gt;Every postmortem of a fast-moving outbreak ends with the same questions. Would segmentation have contained it? How much did the patch backlog matter? Would quarantining infected hosts at hour two have changed anything? In a real environment you cannot rerun the incident to find out. In a model, you can rerun it as many times as you like.&lt;/p&gt;

&lt;p&gt;That is why I built the Worm Propagation Simulator. It is a small Python project that models how a self-replicating worm spreads across a network graph, and how defensive measures change the outcome. It is built for defensive analysis and teaching. It contains no exploit code, sends no network traffic and touches no real systems. Every “host” is a node in an in-memory graph, and every “infection” is a random number compared against a probability.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Willey2003/worm-propagation-simulator" rel="noopener noreferrer"&gt;github.com/Willey2003/worm-propagation-simulator&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the model works
&lt;/h2&gt;

&lt;p&gt;The simulation runs in ticks. The engine builds a network graph with NetworkX and gives every node a state: vulnerable, infected, patched or quarantined. Each node gets a security level of low, medium or high, which sets its chance of being compromised on a single attempt to 0.75, 0.5 or 0.25. A configurable share of nodes starts out already patched. One or more “patient zero” nodes start out infected.&lt;/p&gt;

&lt;p&gt;On each tick, every infected node picks targets according to the chosen propagation model. For each target, the engine checks three things in order: whether the target is still susceptible, whether a firewall rule between their subnets blocks or rate-limits the attempt, and finally the probability roll. The engine records per-tick metrics as it goes: node counts by state, new infections, attempts, and attempts blocked by firewall rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can vary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Options in the code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Topology&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;random&lt;/code&gt; (Erdős–Rényi), &lt;code&gt;scale_free&lt;/code&gt; (Barabási–Albert), &lt;code&gt;small_world&lt;/code&gt; (Watts–Strogatz), &lt;code&gt;hierarchical&lt;/code&gt; (balanced tree), &lt;code&gt;datacenter&lt;/code&gt; (fully meshed racks linked to each other)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Propagation model&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;random_scan&lt;/code&gt;, &lt;code&gt;hitlist&lt;/code&gt;, &lt;code&gt;topological&lt;/code&gt; (graph neighbours only), &lt;code&gt;permutation&lt;/code&gt;, &lt;code&gt;local_preference&lt;/code&gt; (biased towards the node’s own subnet)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Baseline defence&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;patch_rate&lt;/code&gt;: the fraction of nodes patched before the outbreak starts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Segmentation&lt;/td&gt;
&lt;td&gt;Subnet-to-subnet firewall rules with &lt;code&gt;deny&lt;/code&gt; or &lt;code&gt;rate_limit&lt;/code&gt; (percentage of attempts allowed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mid-outbreak response&lt;/td&gt;
&lt;td&gt;One scheduled intervention: &lt;code&gt;patch_all&lt;/code&gt;, &lt;code&gt;patch_subnet&lt;/code&gt;, &lt;code&gt;quarantine_infected&lt;/code&gt; or &lt;code&gt;rate_limit&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reproducibility&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;seed&lt;/code&gt; value fixes the random state, so runs are repeatable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nodes are assigned round-robin to subnets of about 20 nodes each. This is what gives the segmentation rules and the local-preference model something to act on.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;configs/&lt;/code&gt; folder ships six YAML scenarios: five are loosely inspired by well-known worms (Code Red, SQL Slammer, Conficker, WannaCry, Mirai), plus a &lt;code&gt;defensive_analysis&lt;/code&gt; scenario. Treat them as stylised parameter sets for comparison, not calibrated reproductions of history. A 500-node graph says nothing precise about the 2001 internet. What they do give you is a fair way to compare propagation behaviour and defensive choices against each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;The core engine needs only NetworkX, NumPy and PyYAML. The test suite (17 tests covering nodes, topologies, propagation models, patching and firewall blocking) also runs in GitHub Actions on every push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Willey2003/worm-propagation-simulator.git
&lt;span class="nb"&gt;cd &lt;/span&gt;worm-propagation-simulator
pip &lt;span class="nb"&gt;install &lt;/span&gt;networkx numpy pyyaml pytest
python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest tests/ &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run a scenario and print the summary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.simulator.engine&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_simulator_from_config&lt;/span&gt;

&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configs/defensive_analysis.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;sim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_simulator_from_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_summary&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;infection_curve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The summary includes the peak infected count, the tick at which it peaked, total infections and attempts, and how many attempts were blocked. &lt;code&gt;infection_curve&lt;/code&gt; holds the full per-tick history if you want to plot it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson that surprised me: timing beats thoroughness
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;defensive_analysis&lt;/code&gt; scenario puts 500 nodes on a scale-free graph and 20% of them start patched. It applies two firewall rules between subnets and schedules a &lt;code&gt;patch_all&lt;/code&gt; intervention at tick 30. With seed 42, that intervention never fires. The worm reaches every vulnerable node by tick 5 and the run ends, with 396 nodes infected.&lt;/p&gt;

&lt;p&gt;Change one line, &lt;code&gt;intervention_tick: 2&lt;/code&gt;, and the same &lt;code&gt;patch_all&lt;/code&gt; caps the outbreak at 7 infected nodes out of 500.&lt;/p&gt;

&lt;p&gt;The model is simple, but this is the same lesson real incidents keep teaching. Against a fast scanner, a complete response that arrives late is worth almost nothing. A response that arrives early is worth almost everything. That is the case for pre-approved containment playbooks and for patching exposed services before an outbreak, not during one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other entry points
&lt;/h2&gt;

&lt;p&gt;The repo also includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;A Typer CLI&lt;/strong&gt; with &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;compare&lt;/code&gt; and &lt;code&gt;scenarios&lt;/code&gt; commands, using Rich tables for output&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;A FastAPI service&lt;/strong&gt; to create simulations, step them, apply interventions mid-run, fetch state over REST, and stream per-tick metrics over a WebSocket&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;A Plotly visualisation module&lt;/strong&gt; that writes the infection curve, network graph and a combined dashboard to HTML&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Docker, Docker Compose and Kubernetes manifests&lt;/strong&gt; for running the API in a container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the command line, the same runs are one command each:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; src.simulator.main scenarios
python &lt;span class="nt"&gt;-m&lt;/span&gt; src.simulator.main run &lt;span class="nt"&gt;--scenario&lt;/span&gt; code_red
python &lt;span class="nt"&gt;-m&lt;/span&gt; src.simulator.main compare &lt;span class="nt"&gt;--scenarios&lt;/span&gt; code_red,slammer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;compare&lt;/code&gt; command prints a side-by-side table of infections, peak timing and blocked attempts for each scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ideas for extending it
&lt;/h2&gt;

&lt;p&gt;Ideas I would like to explore next: per-node detection delay before quarantine, more than one intervention per run, and calibrating scan rates against published outbreak data. If you teach network security, the topology comparison on its own makes a good lab exercise. Run the same worm on a random graph and on a scale-free graph and look at what the highly connected hubs do to the curve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;

&lt;p&gt;The source, scenario files and tests are at &lt;a href="https://github.com/Willey2003/worm-propagation-simulator" rel="noopener noreferrer"&gt;github.com/Willey2003/worm-propagation-simulator&lt;/a&gt;. Clone it, change one parameter at a time, and see which defences change the curve. If you build a scenario that teaches something useful, open a pull request.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>python</category>
      <category>simulation</category>
      <category>opensource</category>
    </item>
    <item>
      <title>VDDK vs No VDDK: Why Your First MTV Migration Is Slow</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Fri, 23 Oct 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/vddk-vs-no-vddk-why-your-first-mtv-migration-is-slow-1p3n</link>
      <guid>https://dev.to/willeysingh/vddk-vs-no-vddk-why-your-first-mtv-migration-is-slow-1p3n</guid>
      <description>&lt;p&gt;My first migration from VMware to OpenShift Virtualization worked. It was also painfully slow: one 10 GiB Ubuntu VM took &lt;strong&gt;1 hour 24 minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ImageConversion (virt-v2v)&lt;/td&gt;
&lt;td&gt;23 min 26 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DiskTransfer, about 2.8 MB/s&lt;/td&gt;
&lt;td&gt;60 min 42 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1 h 24 min&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At that rate a 500 GiB production disk would take more than two days. Here's why, and the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;While the copy ran, the migration Pod's log said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nbdkit: curl[4]: ...
virt-v2v monitoring: Progress update, completed 17 %
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nbdkit curl&lt;/code&gt; means the disk was being read over &lt;strong&gt;HTTPS through vCenter&lt;/strong&gt; (port 443). That path works anywhere, but it's slow, and it puts the load on vCenter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cause
&lt;/h2&gt;

&lt;p&gt;The vSphere provider had no &lt;code&gt;vddkInitImage&lt;/code&gt; in its settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oc get provider vcenter-lab &lt;span class="nt"&gt;-n&lt;/span&gt; openshift-mtv &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.spec.settings}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;VDDK&lt;/strong&gt; is VMware's Virtual Disk Development Kit, the same library backup products use. With it, MTV reads disk data straight from the ESXi host over &lt;strong&gt;NBD on port 902&lt;/strong&gt;, without going through vCenter. Red Hat isn't allowed to redistribute VDDK, so MTV can't ship it. You download it from Broadcom and wrap it in a small container image yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: build the VDDK image
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on a machine with podman and access to your registry&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/vddk
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; ~/VMware-vix-disklib-8.0.&lt;span class="k"&gt;*&lt;/span&gt;.x86_64.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; ~/vddk
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/vddk/Containerfile &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;'
FROM registry.access.redhat.com/ubi9/ubi-minimal
USER 1001
COPY vmware-vix-disklib-distrib /vmware-vix-disklib-distrib
RUN mkdir -p /opt
ENTRYPOINT ["cp", "-r", "/vmware-vix-disklib-distrib", "/opt"]
&lt;/span&gt;&lt;span class="no"&gt;EOT
&lt;/span&gt;podman build &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;REGISTRY&amp;gt;/openshift-mtv/vddk:8.0 ~/vddk
podman push &amp;lt;REGISTRY&amp;gt;/openshift-mtv/vddk:8.0

&lt;span class="c"&gt;# point the provider at it&lt;/span&gt;
oc patch provider vcenter-lab &lt;span class="nt"&gt;-n&lt;/span&gt; openshift-mtv &lt;span class="nt"&gt;--type&lt;/span&gt; merge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"spec":{"settings":{"vddkInitImage":"&amp;lt;REGISTRY&amp;gt;/openshift-mtv/vddk:8.0"}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Before your first wave, check
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The OpenShift nodes can reach &lt;strong&gt;every ESXi host on TCP 902&lt;/strong&gt;. A quick test from a node: &lt;code&gt;timeout 4 bash -c "&amp;lt;/dev/tcp/&amp;lt;esxi&amp;gt;/902" &amp;amp;&amp;amp; echo open&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The provider shows the VDDK image and is Ready: &lt;code&gt;oc get provider -n openshift-mtv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You've timed one test VM with VDDK, so your wave plan uses your own throughput number, not a guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lesson
&lt;/h2&gt;

&lt;p&gt;Build the VDDK image before wave 1. Without it, MTV still works, which is exactly why it's easy to miss until a production disk takes days.&lt;/p&gt;

&lt;p&gt;The full chapter, with the provider setup and real output, is in the free &lt;a href="https://github.com/Willey2003/openshift-vmware-migration-sop" rel="noopener noreferrer"&gt;Lab Manual&lt;/a&gt;. Next in this series: what actually survives a migration.&lt;/p&gt;

</description>
      <category>openshift</category>
      <category>vmware</category>
      <category>kubernetes</category>
      <category>redhat</category>
    </item>
    <item>
      <title>Red Hat Astronaut 2026: a self-hosted OpenShift and Ansible practice platform</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Wed, 21 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/red-hat-astronaut-2026-a-self-hosted-openshift-and-ansible-practice-platform-40pc</link>
      <guid>https://dev.to/willeysingh/red-hat-astronaut-2026-a-self-hosted-openshift-and-ansible-practice-platform-40pc</guid>
      <description>&lt;p&gt;I’m preparing for the OpenShift administration exam (DO280/EX280), and I kept running into the same problem. Red Hat exams are fully hands-on, but most free practice material is either a static PDF or a quiz with no explanations. I wanted something that would run against a real cluster and tell me, objectively, whether the thing I built actually works. So I wrote one and published it: &lt;strong&gt;Red Hat Astronaut 2026&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is an independent practice tool. It is not affiliated with or endorsed by Red Hat. All questions are original material written to the published objectives. None of it is taken from, or claims to reproduce, real exam content.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Willey2003/redhat-astronaut-2026" rel="noopener noreferrer"&gt;github.com/Willey2003/redhat-astronaut-2026&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;The platform has four parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Engine.&lt;/strong&gt; A Python web UI and API (the &lt;em&gt;facilitator&lt;/em&gt;) plus a separate grader (the &lt;em&gt;conductor&lt;/em&gt;), packaged with Docker Compose. Kubernetes manifests are included if you want to host it in a lab cluster.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Question banks.&lt;/strong&gt; Plain YAML, one file per question, with an &lt;code&gt;exam.yaml&lt;/code&gt; per bank for duration, pass mark and domain weights. A validator checks every bank, and it runs in CI on every push.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Curriculum.&lt;/strong&gt; Six top-level modules, from containers and Linux up to capstones. There are also separate step-by-step tracks for the admin path (five modules) and the developer path (four modules).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cluster tooling.&lt;/strong&gt; Helper scripts for Red Hat OpenShift Local (CRC) and OKD, plus a preflight script that checks CPU, RAM, disk, virtualisation support and the &lt;code&gt;oc&lt;/code&gt; client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The banks
&lt;/h2&gt;

&lt;p&gt;There are 15 study banks with 280 questions, plus a 4-question smoke-test bank. 25 questions are hands-on, all of them in the EX280 and EX288 banks. Everything else is single-answer multiple choice, with an explanation for every question.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bank ID&lt;/th&gt;
&lt;th&gt;What it covers&lt;/th&gt;
&lt;th&gt;Questions&lt;/th&gt;
&lt;th&gt;Hands-on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex280-admin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenShift administration&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex288-developer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenShift application development&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex229&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Core cluster admin: nodes, RBAC, SCCs, routes, storage&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex282-developer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Developer basics: projects, builds, ImageStreams, Tekton&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex480-developer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Advanced delivery: GitOps, serverless, mesh, rollouts&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex380-admin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Advanced admin: MachineConfig, autoscaling, remediation&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex432&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cluster installation (IPI/UPI, bootstrap, SNO)&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex316&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenShift Virtualization&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex370-storage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenShift Data Foundation&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex180&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Podman and container basics&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ex430-ansible&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ansible and Automation Platform&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rhce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ansible fundamentals&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rhca&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Architecture and hybrid cloud concepts&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rhcoa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenStack concepts&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;knowledge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes and OpenShift concepts&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A note on the bank IDs. Treat them as labels, not as official exam mappings. Red Hat renames and retires exams regularly, and a few of my labels don’t match the current catalogue. For example, &lt;code&gt;ex432&lt;/code&gt; holds installation material. Check the current objectives on Red Hat’s site and use the “What it covers” column to pick a bank.&lt;/p&gt;

&lt;p&gt;Also, real Red Hat exams have no multiple-choice section. The multiple-choice banks here are concept checks to find gaps, not an imitation of the exam format. The hands-on banks are the closer rehearsal.&lt;/p&gt;

&lt;h2&gt;
  
  
  How grading works
&lt;/h2&gt;

&lt;p&gt;Each hands-on question carries a list of checks. A check is an &lt;code&gt;oc&lt;/code&gt; or shell command plus an expectation, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;equals&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;contains&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  a regex &lt;code&gt;matches&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;exit-success&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;non-empty&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The conductor runs the checks against your cluster and splits the points evenly across them, so a half-finished task earns partial credit. A Route task, for example, checks the host, the target service and the TLS termination type separately. Because the checks look at cluster state rather than at your YAML, any valid way of solving the task passes.&lt;/p&gt;

&lt;p&gt;There are three modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Training&lt;/strong&gt; shows the whole bank untimed, with solutions visible.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Mastery&lt;/strong&gt; and &lt;strong&gt;Exam&lt;/strong&gt; are timed and hide answers until grading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Results include a per-domain score, weakest first.&lt;/p&gt;

&lt;p&gt;There are also three written labs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  SCCs, RBAC and quotas for EX280&lt;/li&gt;
&lt;li&gt;  two application labs for EX288&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each lab ends with verification commands you can run yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;You need Docker with Compose. For hands-on work you also need an OpenShift cluster. CRC on a machine with at least 8 GB of RAM is the simplest option, and the helper script asks for your own Red Hat pull secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Willey2003/redhat-astronaut-2026.git
&lt;span class="nb"&gt;cd &lt;/span&gt;redhat-astronaut-2026
./ga doctor                    &lt;span class="c"&gt;# preflight&lt;/span&gt;
./cluster/preflight.sh         &lt;span class="c"&gt;# can this host run CRC/OKD?&lt;/span&gt;
./ga up                        &lt;span class="c"&gt;# build and start the platform&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;http://127.0.0.1:8903&lt;/code&gt;. There is no separate install step: &lt;code&gt;ga up&lt;/code&gt; builds the images and starts the stack. To start a timed attempt from the terminal instead, run &lt;code&gt;./ga exam ex280-admin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The conductor mounts your &lt;code&gt;~/.kube&lt;/code&gt; read-only and runs &lt;code&gt;oc&lt;/code&gt; and &lt;code&gt;kubectl&lt;/code&gt; from inside its container. Use a practice cluster only, and make sure its API is reachable from that container. The platform has no login, so keep it bound to localhost unless you are on a private network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status
&lt;/h2&gt;

&lt;p&gt;This is v0.1 and it is not finished. Most banks are knowledge-only, and the roadmap lists what is planned but not built yet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  setup steps that reset a task’s starting state before each attempt&lt;/li&gt;
&lt;li&gt;  multi-answer questions&lt;/li&gt;
&lt;li&gt;  a grading backend that talks to the OpenShift API instead of a shell&lt;/li&gt;
&lt;li&gt;  progress tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Repo: &lt;a href="https://github.com/Willey2003/redhat-astronaut-2026" rel="noopener noreferrer"&gt;github.com/Willey2003/redhat-astronaut-2026&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Licence: Apache-2.0&lt;/li&gt;
&lt;li&gt;  Independent project, not affiliated with or endorsed by Red Hat. Product and certification names are trademarks of their owners.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re working towards an OpenShift certification and want to add hands-on tasks for another bank, pull requests are welcome. The question format is documented in &lt;code&gt;docs/bank-spec.md&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>openshift</category>
      <category>redhat</category>
      <category>certification</category>
      <category>ansible</category>
    </item>
    <item>
      <title>VMware Concepts Mapped to OpenShift Virtualization: VMDK, Port Groups, vMotion and More</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Tue, 20 Oct 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/vmware-concepts-mapped-to-openshift-virtualization-vmdk-port-groups-vmotion-and-more-3g3f</link>
      <guid>https://dev.to/willeysingh/vmware-concepts-mapped-to-openshift-virtualization-vmdk-port-groups-vmotion-and-more-3g3f</guid>
      <description>&lt;p&gt;If you've run vSphere for years, OpenShift Virtualization looks foreign for about a day. Then you notice that almost every VMware concept has a direct counterpart. It just has a Kubernetes name and lives in YAML instead of a vCenter database.&lt;/p&gt;

&lt;p&gt;This is the translation table I wish I'd had on day one, taken from the lab I built for &lt;a href="https://github.com/Willey2003/openshift-vmware-migration-sop" rel="noopener noreferrer"&gt;The Lab Manual: Migration Edition&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;vSphere&lt;/th&gt;
&lt;th&gt;OpenShift Virtualization&lt;/th&gt;
&lt;th&gt;What to know&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ESXi host&lt;/td&gt;
&lt;td&gt;Node with &lt;code&gt;/dev/kvm&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;KVM is built into the RHCOS kernel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VM (the &lt;code&gt;.vmx&lt;/code&gt; definition)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;VirtualMachine&lt;/code&gt; (VM)&lt;/td&gt;
&lt;td&gt;Exists even when powered off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Running VM process&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;VirtualMachineInstance&lt;/code&gt; (VMI) + &lt;code&gt;virt-launcher&lt;/code&gt; Pod&lt;/td&gt;
&lt;td&gt;Exists only while the VM runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VMDK&lt;/td&gt;
&lt;td&gt;PVC / DataVolume&lt;/td&gt;
&lt;td&gt;The disk is a PersistentVolumeClaim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Datastore&lt;/td&gt;
&lt;td&gt;StorageClass&lt;/td&gt;
&lt;td&gt;Backed by a CSI driver (NFS, FC, iSCSI, Ceph…)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port group&lt;/td&gt;
&lt;td&gt;NetworkAttachmentDefinition (NAD)&lt;/td&gt;
&lt;td&gt;Carries the VLAN ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vSwitch / VDS&lt;/td&gt;
&lt;td&gt;Linux bridge built by NMState&lt;/td&gt;
&lt;td&gt;Declared once, applied on every node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vMotion&lt;/td&gt;
&lt;td&gt;Live migration (&lt;code&gt;VirtualMachineInstanceMigration&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Needs RWX storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VM template / content library&lt;/td&gt;
&lt;td&gt;Instance types + preferences + boot sources&lt;/td&gt;
&lt;td&gt;"How big", "which OS", "which image"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VMware Tools&lt;/td&gt;
&lt;td&gt;QEMU guest agent + virtio drivers&lt;/td&gt;
&lt;td&gt;MTV installs them during conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vCenter&lt;/td&gt;
&lt;td&gt;The OpenShift API + the console's Virtualization pages&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The three that trip people up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. VM vs VMI.&lt;/strong&gt; In vSphere a VM is one thing. Here it's two: the &lt;code&gt;VirtualMachine&lt;/code&gt; is the definition (like the &lt;code&gt;.vmx&lt;/code&gt;), and the &lt;code&gt;VirtualMachineInstance&lt;/code&gt; only exists while it's running. Powering off deletes the VMI and its Pod. The VM object stays.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oc get vm,vmi &lt;span class="nt"&gt;-n&lt;/span&gt; mtv-test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Port groups become NADs.&lt;/strong&gt; A NAD is a small JSON config: which bridge, which VLAN. Put shared networks in the &lt;code&gt;default&lt;/code&gt; namespace and any project can use them as &lt;code&gt;default/vlan-110&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oc get net-attach-def &lt;span class="nt"&gt;-A&lt;/span&gt;
oc get net-attach-def vlan-110 &lt;span class="nt"&gt;-n&lt;/span&gt; default &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{.spec.config}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. vMotion needs RWX.&lt;/strong&gt; Live migration moves the VM's memory to another node, but both nodes need the disk at the same time. That means a StorageClass with &lt;code&gt;ReadWriteMany&lt;/code&gt;. In my lab, OpenShift Virtualization didn't recognise the NFS StorageClass and would have created RWO disks. One StorageProfile patch fixed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oc patch storageprofile nfs-csi &lt;span class="nt"&gt;--type&lt;/span&gt; merge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"spec":{"claimPropertySets":[{"accessModes":["ReadWriteMany"],"volumeMode":"Filesystem"}],"cloneStrategy":"copy"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where vSwitches went
&lt;/h2&gt;

&lt;p&gt;On each node, an NMState policy (&lt;code&gt;NodeNetworkConfigurationPolicy&lt;/code&gt;) builds a Linux bridge, &lt;code&gt;br-vm&lt;/code&gt;, on a second NIC that carries a VLAN trunk. NADs then tag each VM's traffic with its VLAN on that bridge. It's the same idea as a distributed switch with port groups, declared as code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oc get nncp           &lt;span class="c"&gt;# the policy&lt;/span&gt;
oc get nnce           &lt;span class="c"&gt;# did it apply on each node?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/Willey2003/openshift-vmware-migration-sop" rel="noopener noreferrer"&gt;architecture diagram&lt;/a&gt; shows all of these objects in one picture, and the free manual walks through creating each one with real output. Next in this series: why my first migration ran at 2.8 MB/s.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>vmware</category>
      <category>openshift</category>
      <category>redhat</category>
    </item>
    <item>
      <title>Golden Kubestronaut 2026: a self-hosted practice simulator for the CNCF path</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/golden-kubestronaut-2026-a-self-hosted-practice-simulator-for-the-cncf-path-45fo</link>
      <guid>https://dev.to/willeysingh/golden-kubestronaut-2026-a-self-hosted-practice-simulator-for-the-cncf-path-45fo</guid>
      <description>&lt;p&gt;After passing the CKA again this year, I wanted one place to practise for the rest of the CNCF list without juggling a dozen browser tabs and paid mock-exam sites. So I built a small, self-hosted simulator and put it on GitHub: &lt;strong&gt;Golden Kubestronaut 2026&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is a practice tool, not a shortcut. Every question is original practice material written against the published exam objectives, and the project is independent: it is not affiliated with or endorsed by the Linux Foundation or the CNCF, and the questions are not taken from, and do not claim to mirror, the real exams.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Willey2003/golden-kubestronaut" rel="noopener noreferrer"&gt;github.com/Willey2003/golden-kubestronaut&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is in the box
&lt;/h2&gt;

&lt;p&gt;The project has four parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Engine.&lt;/strong&gt; A small Python service split into a &lt;em&gt;facilitator&lt;/em&gt; (web UI and JSON API) and a &lt;em&gt;conductor&lt;/em&gt; (the grader), plus a bank loader, a validator and a &lt;code&gt;ga&lt;/code&gt; command-line wrapper.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Question banks.&lt;/strong&gt; One folder per certification: an &lt;code&gt;exam.yaml&lt;/code&gt; with duration, pass mark and domain weights, and one YAML file per question.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Curriculum.&lt;/strong&gt; Seven Markdown modules, from foundations to networking and cost, with exercises to run on your own cluster.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Deployment.&lt;/strong&gt; A Docker Compose file for running it locally, and Kubernetes manifests if you prefer to host it in a lab cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The banks
&lt;/h2&gt;

&lt;p&gt;There are 16 banks and 274 questions in total. 48 of them are hands-on tasks; the rest are single-answer multiple choice with a written explanation for every option.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bank&lt;/th&gt;
&lt;th&gt;Focus&lt;/th&gt;
&lt;th&gt;Questions&lt;/th&gt;
&lt;th&gt;Hands-on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cka&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cluster administration&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ckad&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Application development&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kcna&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cloud native fundamentals&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kcsa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Security concepts&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lfcs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Linux system administration&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pca&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prometheus and PromQL&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;otca&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenTelemetry&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ica&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Istio service mesh&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;capa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Argo CD, Workflows, Events, Rollouts&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cgoa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GitOps principles and practice&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;cba&lt;/code&gt;, &lt;code&gt;cnpa&lt;/code&gt;, &lt;code&gt;cnpe&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Cilium networking, basic to advanced&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cca&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;General cloud concepts&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kca&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes cost and FinOps&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Known gaps
&lt;/h2&gt;

&lt;p&gt;This is version one and I would rather be upfront about it. Several bank IDs do not yet match the content of the certification that carries that code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The Cilium material currently lives under &lt;code&gt;cba&lt;/code&gt;, &lt;code&gt;cnpa&lt;/code&gt; and &lt;code&gt;cnpe&lt;/code&gt;. The real Cilium certification is CCA, but the &lt;code&gt;cca&lt;/code&gt; bank holds general cloud-computing questions instead.&lt;/li&gt;
&lt;li&gt;  There is no Backstage (CBA), Kyverno (KCA) or platform engineering (CNPA/CNPE) content yet. The &lt;code&gt;kca&lt;/code&gt; bank covers Kubernetes cost allocation instead.&lt;/li&gt;
&lt;li&gt;  The &lt;code&gt;labs/&lt;/code&gt; folder is empty. The hands-on practice currently lives in the curriculum exercises and in the hands-on bank questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So today, the CKA, CKAD, CKS, KCNA, KCSA, LFCS, PCA, OTCA, ICA, CAPA and CGOA banks line up with their syllabi, and Cilium is well covered under the wrong labels. Fixing the labels and writing the missing banks is next on my list.&lt;/p&gt;

&lt;h2&gt;
  
  
  How grading works
&lt;/h2&gt;

&lt;p&gt;Multiple-choice questions are marked right or wrong against the answer key.&lt;/p&gt;

&lt;p&gt;Hands-on questions are where the project earns its keep. Each one has a list of checks: a shell command plus an expected result (&lt;code&gt;equals&lt;/code&gt;, &lt;code&gt;contains&lt;/code&gt;, a regex &lt;code&gt;matches&lt;/code&gt;, &lt;code&gt;exit-success&lt;/code&gt; and so on). The conductor runs those commands against your cluster and splits the points evenly across the checks, so you get partial credit. For example, the etcd backup task checks that the snapshot file exists at the required path and is not empty. It doesn’t look at what you typed.&lt;/p&gt;

&lt;p&gt;There are three modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Training&lt;/strong&gt; shows every question in the bank with no timer, and reveals answers and solutions straight away.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Mastery&lt;/strong&gt; and &lt;strong&gt;Exam&lt;/strong&gt; draw a timed attempt and hide answers until grading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After each attempt you get a per-domain breakdown, weakest domain first, which tells you what to study next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;You need Docker with Compose. For local validation you also need Python 3 and PyYAML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Willey2003/golden-kubestronaut.git
&lt;span class="nb"&gt;cd &lt;/span&gt;golden-kubestronaut
./ga validate      &lt;span class="c"&gt;# check every bank parses and is well-formed&lt;/span&gt;
./ga doctor        &lt;span class="c"&gt;# preflight (python, docker, oc, kubectl, RAM)&lt;/span&gt;
./ga up            &lt;span class="c"&gt;# build and start the containers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;http://127.0.0.1:8902&lt;/code&gt;. By default it binds to localhost only. There is no authentication, so if you use &lt;code&gt;./ga expose&lt;/code&gt; to reach it from your LAN, keep it on a private network.&lt;/p&gt;

&lt;p&gt;For hands-on grading, the conductor container mounts your &lt;code&gt;~/.kube&lt;/code&gt; read-only (override with &lt;code&gt;KUBECONFIG_DIR&lt;/code&gt;) and runs &lt;code&gt;kubectl&lt;/code&gt; from inside the container. Two practical notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Point it at a &lt;strong&gt;throwaway practice cluster&lt;/strong&gt;, never a real one, because the checks execute commands with that kubeconfig.&lt;/li&gt;
&lt;li&gt;  The cluster’s API address must be reachable from inside the container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One rough edge: &lt;code&gt;doctor&lt;/code&gt; shares preflight code with my OpenShift project, so it flags a missing &lt;code&gt;oc&lt;/code&gt; client and less than 8 GB of RAM. Ignore those on a plain Kubernetes setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smaller sibling: kubestronaut
&lt;/h2&gt;

&lt;p&gt;There is also an earlier repo, &lt;a href="https://github.com/Willey2003/kubestronaut" rel="noopener noreferrer"&gt;Willey2003/kubestronaut&lt;/a&gt;. It runs the same engine and shares six identical banks with this one: CKA, CKAD, CKS, KCNA, KCSA and PCA. It adds two banks you won’t find here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  a Grafana bank (dashboards, data sources, alerting, provisioning)&lt;/li&gt;
&lt;li&gt;  a combined Prometheus-plus-Grafana monitoring track (&lt;code&gt;cpna&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither of those is a Linux Foundation certification; they are skill tracks. Use it if you want that Grafana practice. For everything else, use the golden repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Repo: &lt;a href="https://github.com/Willey2003/golden-kubestronaut" rel="noopener noreferrer"&gt;github.com/Willey2003/golden-kubestronaut&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Licence: Apache-2.0&lt;/li&gt;
&lt;li&gt;  Not affiliated with or endorsed by the Linux Foundation or CNCF. Certification names are trademarks of their owners.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you spot a wrong answer or want to help write the missing banks, open an issue or a pull request. The bank format is plain YAML, and &lt;code&gt;./ga validate&lt;/code&gt; runs in CI on every push.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>certification</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Zero to Senior Cloud-Native Engineer: A Free, Hands-On Lab Path on GitHub</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Fri, 16 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/zero-to-senior-cloud-native-engineer-a-free-hands-on-lab-path-on-github-4bc2</link>
      <guid>https://dev.to/willeysingh/zero-to-senior-cloud-native-engineer-a-free-hands-on-lab-path-on-github-4bc2</guid>
      <description>&lt;p&gt;People regularly ask me where to start in Kubernetes, DevOps or platform engineering with no Linux, networking or coding background. A list of links and course names doesn’t help much when you don’t yet know what a process, a subnet or a YAML file is.&lt;/p&gt;

&lt;p&gt;So I wrote the path down properly, as a GitHub repo you can clone and work through in order: &lt;strong&gt;&lt;a href="https://github.com/Willey2003/zero-to-senior-cloud-native-engineer" rel="noopener noreferrer"&gt;zero-to-senior-cloud-native-engineer&lt;/a&gt;&lt;/strong&gt;. It covers a roadmap, a certification order, numbered labs, on-call style break/fix tickets and a capstone project spec. This post explains what is in it, how it is meant to be used, and which parts are still unfinished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who it is for
&lt;/h2&gt;

&lt;p&gt;The repo assumes you are starting from nothing. Each lab follows the same structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Why this matters&lt;/strong&gt;: what the topic is and where you will meet it at work&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Concepts&lt;/strong&gt;: the few ideas you need before typing anything&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Hands-on steps&lt;/strong&gt;: exact commands and what you should see&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Real-world scenario&lt;/strong&gt;: a broken system to fix, written like an on-call ticket&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Check yourself&lt;/strong&gt;: questions and tasks that show you actually learned it&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cert mapping&lt;/strong&gt;: which exam objectives the lab covers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The README’s golden rules: type every command yourself, break things on purpose, spend two hours in the lab for every hour of reading, and explain each topic out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  The roadmap: 30 months, about 25 hours a week
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ROADMAP.md&lt;/code&gt; lays out 20 phases (0 to 19) over roughly 30 months, assuming about 25 hours a week. The order goes from fundamentals to specialisation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Exam at the end&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1-4&lt;/td&gt;
&lt;td&gt;Linux, networking, Bash/Git/Python, RHEL admin&lt;/td&gt;
&lt;td&gt;RHCSA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5-6&lt;/td&gt;
&lt;td&gt;Docker, AWS, Terraform, CI/CD&lt;/td&gt;
&lt;td&gt;AWS SAA, Terraform Associate (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7-10&lt;/td&gt;
&lt;td&gt;Kubernetes from fundamentals to security&lt;/td&gt;
&lt;td&gt;KCNA, CKAD, CKA, KCSA, CKS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11-15&lt;/td&gt;
&lt;td&gt;Ansible, observability, GitOps, service mesh, platform engineering&lt;/td&gt;
&lt;td&gt;RHCE, PCA, OTCA, CGOA, CAPA, ICA, CCA, KCA, CBA, CNPA, CNPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16-18&lt;/td&gt;
&lt;td&gt;OpenShift, cloud security, AI/MLOps&lt;/td&gt;
&lt;td&gt;EX280, EX316, AWS Security Specialty&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The roadmap also tells you to start applying for your first job around month 8 to 10 instead of waiting until the end, and to keep studying while you work.&lt;/p&gt;

&lt;p&gt;It is also direct about salary. Certifications alone will not get you to the top pay bands. Skill you can show live in an interview, production and on-call experience, public visibility and a specialisation will. The career ladder gives indicative India ranges per stage and makes clear they vary widely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The labs
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;labs/&lt;/code&gt; folder has 19 numbered labs, from &lt;code&gt;00-setup&lt;/code&gt; to &lt;code&gt;18-ai-security&lt;/code&gt;. Lab 01 is Linux from absolute zero. Lab 15 is OpenShift and OpenShift Virtualization. Most labs come with real files, not just notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;02-networking&lt;/strong&gt;: &lt;code&gt;netns-lab.sh&lt;/code&gt; builds container networking by hand with two network namespaces, a bridge, veth pairs and NAT, plus a subnet quiz in Python.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;03-bash-git-python&lt;/strong&gt;: a health-check script, a log analyser, and a small FastAPI service (&lt;code&gt;lab-api&lt;/code&gt;) with pytest tests. Later labs reuse it for Docker, Kubernetes, Istio and observability.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;07-kubernetes-core&lt;/strong&gt;: a 3-node kind cluster config, a hardened &lt;code&gt;lab-api.yaml&lt;/code&gt;, and a kubeadm install guide for CKA practice.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;09-kubernetes-security&lt;/strong&gt;: audit policy, encryption-at-rest config, read-only RBAC, Falco custom rules and Kyverno policies.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;10-redhat-ansible&lt;/strong&gt;: an Ansible role and a mock RHCSA exam.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;18-ai-security&lt;/strong&gt;: a prompt-injection lab that needs no real model. A naive bot leaks a secret, and your job is to write a safer version that blocks every listed attack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To start, set up a local cluster with the lab’s own kind config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Willey2003/zero-to-senior-cloud-native-engineer.git
&lt;span class="nb"&gt;cd &lt;/span&gt;zero-to-senior-cloud-native-engineer/labs/07-kubernetes-core
kind create cluster &lt;span class="nt"&gt;--config&lt;/span&gt; manifests/kind-3node.yaml &lt;span class="nt"&gt;--name&lt;/span&gt; lab
kubectl get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  On-call scenarios
&lt;/h2&gt;

&lt;p&gt;My years in VMware support taught me that you learn troubleshooting under pressure, not from slides. So &lt;code&gt;scenarios/README.md&lt;/code&gt; lists 30 tickets across Linux, networking, Docker, cloud, Kubernetes, security, observability, GitOps, service mesh, OpenShift and AI. Examples include “Disk full but files not visible”, “Service has no endpoints”, “etcd data loss” and “mTLS broke legacy client”.&lt;/p&gt;

&lt;p&gt;To be clear about what exists today: five of the Kubernetes tickets (14, 15, 16, 20 and 21) have a script that breaks a kind cluster on purpose. The other 25 are written ticket descriptions with the skills they test, and you set up the failure yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; labs/07-kubernetes-core/manifests/lab-api.yaml
./scenarios/k8s/break.sh 16   &lt;span class="c"&gt;# "curl api.shop hangs but pods are Running"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a 30-minute timer, write down your hypothesis before each command, and finish with the post-mortem template in the same file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Certifications and the capstone
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CERTIFICATIONS.md&lt;/code&gt; lists 24 exams in a suggested order, from RHCSA through Kubestronaut and Golden Kubestronaut to RHCA, with a short reason for each position. It also includes exam-day tips for the hands-on exams. Formats and prices change, so check the official page before you book.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;capstone/README.md&lt;/code&gt; describes “ShopPlatform”, an internal platform project: Terraform or kubeadm infrastructure, Argo CD app-of-apps, Kyverno and Falco, Istio canaries with automatic rollback, an SLO dashboard, a Backstage template and a RAG assistant with tenant isolation. &lt;strong&gt;It is a specification with eight milestones, not a finished build.&lt;/strong&gt; It is meant to be the project you build yourself and walk interviewers through.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is still in progress
&lt;/h2&gt;

&lt;p&gt;This is an early, single-commit release, so here is where it stands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The roadmap, certification guide, scenarios list and labs 00 to 18 are written.&lt;/li&gt;
&lt;li&gt;  The later labs (11 onwards) are shorter and point you to more self-directed work than the early ones.&lt;/li&gt;
&lt;li&gt;  The weekly &lt;code&gt;journal/&lt;/code&gt; has a template and a stub for week 1. It is meant to be filled in by whoever follows the path.&lt;/li&gt;
&lt;li&gt;  Only five scenarios are scripted so far.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I plan to add more scripted scenarios and expand the later labs. If you spot a mistake or want a specific scenario, open an issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;

&lt;p&gt;Repo: &lt;strong&gt;&lt;a href="https://github.com/Willey2003/zero-to-senior-cloud-native-engineer" rel="noopener noreferrer"&gt;github.com/Willey2003/zero-to-senior-cloud-native-engineer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Read &lt;code&gt;ROADMAP.md&lt;/code&gt; once from start to finish, then start at &lt;code&gt;labs/00-setup&lt;/code&gt; and go in number order. Commit after every lab, because your Git history becomes your proof of work. If it helps, star the repo and share it with someone just starting out.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>career</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Highly Available HAProxy and keepalived Load Balancers for OpenShift UPI: A Lab Repo</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/highly-available-haproxy-and-keepalived-load-balancers-for-openshift-upi-a-lab-repo-5e93</link>
      <guid>https://dev.to/willeysingh/highly-available-haproxy-and-keepalived-load-balancers-for-openshift-upi-a-lab-repo-5e93</guid>
      <description>&lt;p&gt;If you install OpenShift with UPI, or agent-based on platform &lt;code&gt;none&lt;/code&gt;, you provide the load balancer. Most guides show a single HAProxy VM and stop there. That VM then becomes the one thing that can take down your API, node ignition and every route at once.&lt;/p&gt;

&lt;p&gt;I wanted a lab that treats the load balancer as a real HA component. That means two nodes, a floating VIP, proper health checks, and a way to &lt;em&gt;prove&lt;/em&gt; failover works instead of assuming it does. The result is &lt;strong&gt;&lt;a href="https://github.com/Willey2003/ocp-haproxy-ha-lab" rel="noopener noreferrer"&gt;ocp-haproxy-ha-lab&lt;/a&gt;&lt;/strong&gt;. It covers prep, load balancers, ingress placement, failover testing and monitoring, in that order.&lt;/p&gt;

&lt;h2&gt;
  
  
  The topology
&lt;/h2&gt;

&lt;p&gt;The lab targets a cluster with 3 masters and 2 infra nodes. Two RHEL 9 VMs (&lt;code&gt;lb1&lt;/code&gt;, &lt;code&gt;lb2&lt;/code&gt;) run identical HAProxy configs and share one virtual IP through keepalived (VRRP). DNS for &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;api-int&lt;/code&gt; and &lt;code&gt;*.apps&lt;/code&gt; all points at the VIP, never at a single LB.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Frontend&lt;/th&gt;
&lt;th&gt;Backends&lt;/th&gt;
&lt;th&gt;Health check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;6443&lt;/td&gt;
&lt;td&gt;Kubernetes API&lt;/td&gt;
&lt;td&gt;3 masters (+ bootstrap at install)&lt;/td&gt;
&lt;td&gt;HTTPS &lt;code&gt;GET /readyz&lt;/code&gt;, expects 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22623&lt;/td&gt;
&lt;td&gt;Machine Config Server&lt;/td&gt;
&lt;td&gt;3 masters (+ bootstrap)&lt;/td&gt;
&lt;td&gt;TCP connect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;443 / 80&lt;/td&gt;
&lt;td&gt;Ingress&lt;/td&gt;
&lt;td&gt;2 infra nodes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GET /healthz/ready&lt;/code&gt; on router port 1936&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9000&lt;/td&gt;
&lt;td&gt;Stats page&lt;/td&gt;
&lt;td&gt;local&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/stats&lt;/code&gt; (basic auth), &lt;code&gt;/healthz&lt;/code&gt; for keepalived&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8405&lt;/td&gt;
&lt;td&gt;Prometheus metrics&lt;/td&gt;
&lt;td&gt;local&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/metrics&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every address in the repo is a placeholder on one lab subnet, and &lt;code&gt;prep/inventory.env&lt;/code&gt; is the single list of them. Change it once and the prep scripts follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design choices that matter
&lt;/h2&gt;

&lt;p&gt;Most of the value is in the details, and each one is commented in the config:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;/readyz&lt;/code&gt; over TLS for the API, not a TCP check.&lt;/strong&gt; The kube-apiserver starts failing &lt;code&gt;/readyz&lt;/code&gt; before it shuts down, so HAProxy drains a master that is rebooting gracefully. A plain TCP check keeps sending requests to a dying API server.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Router health on port 1936.&lt;/strong&gt; A TCP check on 443 stays green when the node is up but the router pod is gone.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;keepalived checks HAProxy, not just the process.&lt;/strong&gt; &lt;code&gt;check_haproxy.sh&lt;/code&gt; curls the local &lt;code&gt;/healthz&lt;/code&gt; monitor URI, which catches a hung HAProxy as well as a dead one. With &lt;code&gt;interval 2&lt;/code&gt; and &lt;code&gt;fall 2&lt;/code&gt;, the VIP moves in about 4 seconds.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Unicast VRRP&lt;/strong&gt; for networks that filter multicast, and periodic gratuitous ARP so switches don’t keep a stale MAC for the VIP.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;TLS passthrough with source affinity&lt;/strong&gt; on ingress, and 1-hour tunnel timeouts so &lt;code&gt;oc&lt;/code&gt; watches and websockets are not cut.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;SELinux stays enforcing.&lt;/strong&gt; The prep scripts label ports and scripts rather than disabling it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is in the repo
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Directory&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prep/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inventory, BIND zone templates for api/api-int/*.apps with A and PTR records, firewalld rules, sysctl (&lt;code&gt;ip_nonlocal_bind&lt;/code&gt;), SELinux settings, and DNS and host verification scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;haproxy/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;haproxy.cfg&lt;/code&gt;, keepalived configs for lb1 (MASTER) and lb2 (BACKUP), check and notify scripts, offline validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ingress/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Infra MachineConfigPool, IngressController placement patch, setup, verify and rollback scripts, plus an install-time manifest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failover/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Failover test runbook and scripts, LB metrics, alert rules, and two Prometheus setups&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Before copying anything to &lt;code&gt;/etc&lt;/code&gt;, you can syntax-check every config from a workstation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Willey2003/ocp-haproxy-ha-lab.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ocp-haproxy-ha-lab
./haproxy/validate.sh    &lt;span class="c"&gt;# haproxy -c plus keepalived -t on both LB configs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ingress setup script follows a safe order. The infra taint includes &lt;code&gt;NoExecute&lt;/code&gt;, so the routers are moved onto the infra nodes and rolled out with tolerations &lt;em&gt;before&lt;/em&gt; the taint is applied. Run it with &lt;code&gt;DRY_RUN=true&lt;/code&gt; first to preview every command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving failover: ten tests
&lt;/h2&gt;

&lt;p&gt;From a support background, my view is simple: an HA design you haven’t broken on purpose is just a diagram. &lt;code&gt;failover/RUNBOOK.md&lt;/code&gt; defines ten tests (T0 to T9). Each one breaks a single thing, measures what a client sees through the VIP, and puts it back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;T0&lt;/strong&gt; baseline, zero failed requests for 60 seconds&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;T1 to T3&lt;/strong&gt; HAProxy stopped, keepalived stopped, and a hard crash of the VIP holder&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;T4&lt;/strong&gt; VRRP blocked between the LBs to force split brain&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;T5 to T8&lt;/strong&gt; master and infra node loss, graceful and hard&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;T9&lt;/strong&gt; router pod deleted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;scripts/failover-test.sh&lt;/code&gt; runs each test end to end. It prechecks the cluster and refuses to run on a degraded one, starts a probe pinned to the VIP with &lt;code&gt;curl --resolve&lt;/code&gt;, breaks something, restores it, and grades the longest outage per endpoint against configurable limits (10 seconds by default). Results are saved as a raw &lt;code&gt;probe.csv&lt;/code&gt; plus a summary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;failover/lab.env.example failover/lab.env   &lt;span class="c"&gt;# edit for your lab&lt;/span&gt;
failover/scripts/failover-test.sh baseline
failover/scripts/failover-test.sh lb-haproxy-stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runbook lists the outage I expect from each setting, for example about 4 to 6 seconds for T1 and 0 seconds for a graceful master reboot. These are design targets. The results table in the runbook is a blank template for you to fill in from your own runs, and the repo does not publish measured numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring and alerts
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;failover/monitoring/&lt;/code&gt; adds node_exporter and a VRRP-state hook on each LB, then defines alerts including &lt;code&gt;KeepalivedNoMaster&lt;/code&gt;, &lt;code&gt;KeepalivedSplitBrain&lt;/code&gt;, &lt;code&gt;HAProxyBackendDown&lt;/code&gt;, &lt;code&gt;LBServiceNotActive&lt;/code&gt; and &lt;code&gt;VIPEndpointDown&lt;/code&gt;. A “live MASTER” only counts while &lt;code&gt;keepalived.service&lt;/code&gt; is actually active, so a stale state file from a killed keepalived cannot hide a missing VIP holder.&lt;/p&gt;

&lt;p&gt;There are two Prometheus setups on purpose. One uses OpenShift user workload monitoring, so LB alerts show up in the console. But it sits behind the VIP it is watching. The other runs Prometheus, Alertmanager and blackbox_exporter in podman on a bastion, probing from outside, so it keeps alerting when the cluster is unreachable. For a lab, one is enough. For production, the repo recommends both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits to know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  There are no dedicated worker nodes, so with infra nodes tainted, application pods land on the masters. Fine for a lab. For real workloads, add workers or set &lt;code&gt;TAINT=false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Replace every placeholder before deploying, including the VRRP password and stats credentials, which are marked &lt;code&gt;CHANGE ME&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  It assumes OpenShift 4.x with a user-managed load balancer and DNS you control.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;

&lt;p&gt;Repo: &lt;strong&gt;&lt;a href="https://github.com/Willey2003/ocp-haproxy-ha-lab" rel="noopener noreferrer"&gt;github.com/Willey2003/ocp-haproxy-ha-lab&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start with &lt;code&gt;prep/inventory.env&lt;/code&gt;, then work through &lt;code&gt;prep/&lt;/code&gt;, &lt;code&gt;haproxy/&lt;/code&gt;, &lt;code&gt;ingress/&lt;/code&gt; and &lt;code&gt;failover/&lt;/code&gt; in order. If you run the failover tests in your own lab, I would like to see your numbers. Open an issue with your results table.&lt;/p&gt;

</description>
      <category>openshift</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>linux</category>
    </item>
    <item>
      <title>VMware to OpenShift Virtualization Migration SOP (Free)</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Tue, 13 Oct 2026 13:10:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/vmware-to-openshift-virtualization-migration-sop-free-2dcg</link>
      <guid>https://dev.to/willeysingh/vmware-to-openshift-virtualization-migration-sop-free-2dcg</guid>
      <description>&lt;p&gt;Every VMware shop I talk to is asking the same question this year: where do our VMs go next? For many of them, the answer is Red Hat OpenShift Virtualization. It runs virtual machines on Kubernetes using KubeVirt, and Red Hat ships a tool, the Migration Toolkit for Virtualization (MTV), that moves them across from vSphere.&lt;/p&gt;

&lt;p&gt;The documentation is good, but it is written for people who already think in Kubernetes. A VMware engineer opening it for the first time meets Pods, PVCs, NADs and operators all at once. So I built a lab, migrated a VM end to end, and wrote down everything, including the parts that went wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwonmwove8gv164zyptl3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwonmwove8gv164zyptl3.png" alt="Migration architecture" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The concepts translate
&lt;/h3&gt;

&lt;p&gt;Most of what you know still applies. It just has a new name.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;vSphere&lt;/th&gt;
&lt;th&gt;OpenShift Virtualization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VMDK on a datastore&lt;/td&gt;
&lt;td&gt;PersistentVolumeClaim (PVC) on a StorageClass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port group&lt;/td&gt;
&lt;td&gt;NetworkAttachmentDefinition (NAD)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vSwitch / VDS&lt;/td&gt;
&lt;td&gt;Linux bridge built by NMState&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vMotion&lt;/td&gt;
&lt;td&gt;Live migration (needs RWX storage)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VMware Tools&lt;/td&gt;
&lt;td&gt;QEMU guest agent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once that table clicks, the rest is detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  What MTV actually does
&lt;/h3&gt;

&lt;p&gt;When you press Start on a migration plan, MTV runs a short pipeline. It validates the plan, creates the target disks, converts the guest with virt-v2v (which installs virtio drivers and the guest agent), copies the blocks, and creates a VirtualMachine with the same CPU, memory and MAC address. A cold migration only reads from the source, so the original VM stays where it is as your rollback.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I learned the hard way
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The read path decides your speed.&lt;/strong&gt; Without VDDK, MTV reads disks over HTTPS through vCenter on port 443. My 10 GiB test VM took 1 h 24 min, most of it disk transfer at about 2.8 MB/s. With VDDK, it reads from the ESXi hosts over NBD on port 902, which is much faster. Build the VDDK image before your first real wave.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS is everything.&lt;/strong&gt; The agent-based installer, the console and MTV's connection to vCenter all fail in confusing ways when a record is missing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage needs RWX for live migration.&lt;/strong&gt; If your StorageClass is ReadWriteOnce only, VMs work but cannot move between nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check what was kept.&lt;/strong&gt; In my test the VM kept its MAC address, disk serial and BIOS serial, so licensing and DHCP reservations survived. Verify this on your own VMs before you promise it to anyone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The commands I used most
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oc get providers,plan,migration &lt;span class="nt"&gt;-n&lt;/span&gt; openshift-mtv     &lt;span class="c"&gt;# is MTV connected, what is running&lt;/span&gt;
oc get migration &lt;span class="nt"&gt;-n&lt;/span&gt; openshift-mtv &lt;span class="nt"&gt;-w&lt;/span&gt;                  &lt;span class="c"&gt;# watch a run move through the pipeline&lt;/span&gt;
oc get vm,vmi &lt;span class="nt"&gt;-n&lt;/span&gt; mtv-test                             &lt;span class="c"&gt;# the migrated VM, defined and running&lt;/span&gt;
oc get dv,pvc &lt;span class="nt"&gt;-n&lt;/span&gt; mtv-test                             &lt;span class="c"&gt;# disk import progress and the claim&lt;/span&gt;
virtctl console ubuntu-noble-24.04-cloudimg &lt;span class="nt"&gt;-n&lt;/span&gt; mtv-test &lt;span class="c"&gt;# serial console into the guest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full 53-command cheat sheet is in the repo as a PDF.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's in the manual
&lt;/h3&gt;

&lt;p&gt;The Lab Manual: Migration Edition has ten chapters, from Linux and networking basics through the agent-based install, storage, VM networking and MTV, to an end-to-end migration and a troubleshooting chapter. Every command block shows real output from the lab, with hostnames and IPs replaced by placeholders. There is also a 53-command cheat sheet and an exam map for EX316.&lt;/p&gt;

&lt;p&gt;It is MIT-licensed and free: github.com/Willey2003/openshift-vmware-migration-sop&lt;/p&gt;

&lt;p&gt;If you are planning a migration, I'd like to hear what you are running into. Corrections and production lessons are welcome as issues or pull requests.&lt;/p&gt;

</description>
      <category>openshift</category>
      <category>redhat</category>
      <category>vmware</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>From Single-Master Lab to Production-Style Kubernetes: HA, Backups, Active Directory and Gateway API</title>
      <dc:creator>Gaganpreet Singh</dc:creator>
      <pubDate>Sun, 11 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/willeysingh/from-single-master-lab-to-production-style-kubernetes-ha-backups-active-directory-and-gateway-api-349</link>
      <guid>https://dev.to/willeysingh/from-single-master-lab-to-production-style-kubernetes-ha-backups-active-directory-and-gateway-api-349</guid>
      <description>&lt;p&gt;Our on-prem lab cluster was a typical kubeadm build on VMware: one control-plane node, no backups, local accounts on every host, and clocks drifting by up to six minutes. It worked until it didn’t. Over a few weeks I rebuilt it into a production-style platform that a whole team can use safely, on plain VMware VMs with no cloud load balancers.&lt;/p&gt;

&lt;p&gt;Every step is scripted and published here: &lt;a href="https://github.com/Willey2003/k8s-ha-ad-gateway-lab" rel="noopener noreferrer"&gt;github.com/Willey2003/k8s-ha-ad-gateway-lab&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was built
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;High availability&lt;/td&gt;
&lt;td&gt;3 control-plane nodes behind a floating API VIP (kube-vip, ARP mode). The &lt;em&gt;running&lt;/em&gt; cluster was migrated from a single-node endpoint to the VIP, with automatic rollback. Measured failover: about 1 second.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External etcd&lt;/td&gt;
&lt;td&gt;3-member TLS etcd cluster with weekly snapshots from the bastion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backups and DR&lt;/td&gt;
&lt;td&gt;Velero (including volume data) to a self-hosted S3 store, plus etcd snapshots, on a weekly systemd timer. A scripted restore drill proves the backups actually restore.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time and DNS&lt;/td&gt;
&lt;td&gt;All hosts sync time from the domain controllers; A/PTR records and an &lt;code&gt;*.apps&lt;/code&gt; wildcard.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active Directory for Linux&lt;/td&gt;
&lt;td&gt;Every node joined to AD (realmd/SSSD), AD-group-based SSH and sudo, Kerberos SSO between nodes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active Directory for kubectl&lt;/td&gt;
&lt;td&gt;LDAPS on the domain controllers, Dex as the OIDC provider, and the API server’s structured authentication config (CEL claim mappings). RBAC comes from AD groups.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-service&lt;/td&gt;
&lt;td&gt;Every AD user can read the cluster and deploy into a quota-limited &lt;code&gt;playground&lt;/code&gt; namespace (Pod Security baseline, no NodePorts or LoadBalancers).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingress&lt;/td&gt;
&lt;td&gt;Ingress-NGINX is retired, so: MetalLB (L2) plus HAProxy Unified Gateway with Gateway API. One shared Gateway, wildcard TLS, and users publish apps with a single &lt;code&gt;HTTPRoute&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Kubernetes 1.32 (kubeadm), CRI-O, Calico, kube-vip, etcd 3.5, Velero 1.18, Dex 2.45, MetalLB 0.16, HAProxy Unified Gateway 1.0, Gateway API 1.3, Active Directory (SSSD, Kerberos, LDAPS), chrony.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access model
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Who&lt;/th&gt;
&lt;th&gt;SSH&lt;/th&gt;
&lt;th&gt;Kubernetes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lab user&lt;/td&gt;
&lt;td&gt;Every enabled AD account&lt;/td&gt;
&lt;td&gt;Bastion only, no sudo&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;view&lt;/code&gt; cluster-wide (no Secrets) and &lt;code&gt;edit&lt;/code&gt; in &lt;code&gt;playground&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lab administrator&lt;/td&gt;
&lt;td&gt;AD group &lt;code&gt;K8s-Admins&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;All hosts with sudo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cluster-admin&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emergency&lt;/td&gt;
&lt;td&gt;Local accounts&lt;/td&gt;
&lt;td&gt;All hosts&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;admin.conf&lt;/code&gt; on the control plane&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a user it is simple: SSH to the bastion with an AD account, and kubectl is already configured. The first &lt;code&gt;kubectl&lt;/code&gt; command asks for the AD password once, and the token refreshes for up to seven days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run order
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;01-access&lt;/code&gt;: a least-privilege deployer ServiceAccount and kubeconfig.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;02-ha-control-plane&lt;/code&gt;: prepare the new managers, migrate to the VIP in three steps, test failover, roll back if needed.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;03-time-sync&lt;/code&gt;: chrony against the domain controllers.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;04-backups&lt;/code&gt;: backup disk, S3 gateway, Velero, weekly timer and the restore drill.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;05-dns&lt;/code&gt;: PowerShell for A and PTR records, with conflict detection and &lt;code&gt;-WhatIf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;06-active-directory&lt;/code&gt;: DNS through the DCs, realm join, Kerberos SSH, then Dex, the API-server auth config and RBAC for kubectl.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;07-gateway&lt;/code&gt;: MetalLB, HAProxy Unified Gateway, the shared Gateway and a demo app with route RBAC.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Lessons learned: the things that actually bit
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Check the router’s subnet, not just the hosts’.&lt;/strong&gt; Every VM used a /24, but the gateway routed only a /27. Load-balancer IPs above .31 worked from inside the subnet and silently timed out from everywhere else.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Validate API-server auth config before a rolling restart.&lt;/strong&gt; &lt;code&gt;claims.groups.map(...)&lt;/code&gt; fails CEL type-checking; it needs &lt;code&gt;dyn(claims.groups)&lt;/code&gt;. A local &lt;code&gt;kube-apiserver&lt;/code&gt; binary reproduces the error in seconds, before two managers crash-loop.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;A health check must prove the new container is healthy.&lt;/strong&gt; Waiting for &lt;code&gt;/readyz&lt;/code&gt; right after editing a static pod manifest can hit the old process. Wait for a new container ID, then require sustained health.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;HAProxy sizes its memory budget from the container memory limit.&lt;/strong&gt; Set the limit too low and TLS workers refuse to start with the default &lt;code&gt;maxconn&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Helm post-install CRD jobs can race the controller.&lt;/strong&gt; Restart the controller once after the first install.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dex expands &lt;code&gt;$VAR&lt;/code&gt; in config values.&lt;/strong&gt; A &lt;code&gt;$&lt;/code&gt; in the bind password silently broke LDAP; set &lt;code&gt;DEX_EXPAND_ENV=false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;realm join&lt;/code&gt; with a short hostname registers only short SPNs,&lt;/strong&gt; so Kerberos SSO fails with “Server not found in Kerberos database”. Fix &lt;code&gt;/etc/hosts&lt;/code&gt; and register &lt;code&gt;HOST/&amp;lt;fqdn&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Never make a repair tool depend on the thing it repairs.&lt;/strong&gt; The Dex password rotation writes through the control plane’s local admin config, not through Dex-based kubectl.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Prove the restore, not the backup.&lt;/strong&gt; My first restore drill “passed” for the wrong reason: the proof string lived in the pod spec. Writing it only into the volume made the test honest.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;

&lt;p&gt;All IPs, hostnames and the domain in the repository are example values, and no secrets are stored: keys and passwords are generated at run time. Read the scripts before you run them, and run them in a lab first: &lt;a href="https://github.com/Willey2003/k8s-ha-ad-gateway-lab" rel="noopener noreferrer"&gt;github.com/Willey2003/k8s-ha-ad-gateway-lab&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>opensource</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
