Hybrid Talos cluster with KubeSpan. Phase 3 - Applying config files

Hybrid Talos cluster with KubeSpan

In this blog series I explore how I deploy hybrid Talos cluster with KubeSpan.

Previous articles:

Introduction post on hybrid cluster deployment

Phase 0 - provisioning control plane nodes with Terraform

Phase 1 - preparing environment

Phase 2 - generating config files

Phase 3 - applying configuration

In this walkthrough we’ll apply generating configuration for our cluster. Configuration is applied to each node individually.

generated folder will contain configuration for each node separately. Configuration looks the same for all nodes except for IP, hostname and, maybe, disk configuration.

To apply the configuration for control plane nodes we’ll run talos apply-confg command:

INSECURE_FLAG=${INSECURE:-"yes"}
INSECURE_OPTION=""
if [ "$INSECURE_FLAG" = "yes" ]; then
  echo "Using --insecure flag for initial configuration (maintenance service)"
  INSECURE_OPTION="--insecure"
else
  echo "Not using --insecure flag (connecting to configured Talos API)"
fi

for node in $CONTROLPLANE_NODES; do
  ip_var="CONTROLPLANE_${node}_IP"
  name_var="CONTROLPLANE_${node}_NAME"
  ip="${!ip_var}"
  name="${!name_var}"

  if [ -n "$ip" ]; then
    echo "Applying controlplane-${node} configuration to $ip ($name)..."
    if talosctl apply-config $INSECURE_OPTION --nodes $ip --file generated/controlplane-${node}.yaml; then
      echo "Successfully applied controlplane-${node} configuration to $ip"
    else
      echo "ERROR: Failed to apply controlplane-${node} configuration to $ip"
    fi
  else
    echo "WARNING: IP address for controlplane-${node} not found"
  fi
done

For initial configuration, we need —insecure flag set. Current state of nodes does not have CA set, so this option is required. Any config change past initial apply will not need this option as trust will be configured.

Similar loop is used to apply worker configuration.

# Apply worker configurations
echo "Applying worker configurations..."
for node in $WORKER_NODES; do
  ip_var="WORKER_${node}_IP"
  ip="${!ip_var}"

  if [ -n "$ip" ]; then
    echo "Applying worker-${node} configuration to $ip..."
    if talosctl apply-config $INSECURE_OPTION --nodes $ip --file generated/worker-${node}.yaml; then
      echo "Successfully applied worker-${node} configuration to $ip"
    else
      echo "ERROR: Failed to apply worker-${node} configuration to $ip"
    fi
  else
    echo "WARNING: IP address for worker-${node} not found"
  fi
done

Bootstrapping

Bootstrapping is where etcd initialization takes place. As of 2026, Talos does not allow BYO etcd cluster, so this step is required.

Only one control plane node is needed to bootsrap etcd. Let’s take the first node to do that:

# Select first node as the bootstrap node
BOOTSTRAP_NODE=$(echo $NODE_IPS | awk '{print $1}')
echo "Using control plane node $BOOTSTRAP_NODE as bootstrap target"

# Bootstrap the cluster
echo "Bootstrapping the cluster on node $BOOTSTRAP_NODE"
if talosctl bootstrap --nodes $BOOTSTRAP_NODE -e $BOOTSTRAP_NODE; then
  echo "Successfully bootstrapped the cluster"
else
  echo "ERROR: Failed to bootstrap the cluster"
  exit 1
fi

This will take some time, but after this process is done you should get a healthy, boostrapped Kubernetes cluster. We’ll provision networking next.

Cilium

I chose Cilium for it’s comprehensive stack. It never failed me. I use Cilium as a replacement of networking fabric, MetalLB L2 ingress configuration and kube-proxy. It also includes GatewayAPI support out of the box.

In the following subsections I’ll describe some values from values.yaml file.

L2 Announcement

For L2 announcement to work, you need to supply list of interface regex’s to supply announcement to. This is to ensure only your data plane interfaces get ingress traffic.

I have a mix of nodes with interface names like enp5s01, eno1, etc. My devices field looks like this:

devices: "eth+ ens+ enp+ eno+"

GatewayAPI

GatewayAPI configuration is enabled via the following section:

gatewayAPI:
  enabled: true
  hostNetwork:
    enabled: false
  enableAlpn: true
  enableProxyProtocol: false

set enableProxyProtocol to true in the case you are running DMZ ingress in a different VLAN. I chose Caddy with L4 plugin to setup ingress in layer 4 and route traffic to various gateways.

I run separate ingress instances – private and public. Each in their own /28 VLAN. Via firewall they are allowed to connect to their respective GatewayAPI bound IP address. This way, isolation between public and private zones is enforced on multiple layers.

A really clean defence in depth approach to expose your workloads to the public internet, and keep your private endpoints isolated.

Final words

I’ll leave specific bootstrapping of Cilium and ArgoCD steps out of this series. You can take inspiration from the Github repository accompanying this blog. The process closely resembles one described in Talos blog on Deploying Cilium CNI.

I use the same approach to deploy ArgoCD. My bootstrapped cluster comes with networking and gitops out of the box so they’re ready to sync all my applications from Git.

Code

The code used to deploy the cluster is available via Github - sashkachan/talos-kubespan-bootstrap. I will use this code for the walkthrough of all phases and configuration required to make it succeed.