Skip to content

OPS-NC/Vagrant-Talos

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

95 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

English Β· FranΓ§ais

🏠 🐧 Vagrant-Talos

Build a Talos Linux cluster (immutable, API-driven Kubernetes) on VirtualBox with vagrant up plus one script. Single control plane or HA with 3 CPs and a VIP, then a full application layer (Cilium, Envoy Gateway, Longhorn, Vault, PostgreSQL…).

Vagrant-Talos β€” the Vagrant logo next to the Talos Linux logo

Talos has no SSH and no package manager: the OS is immutable and driven entirely through the talosctl API from the host. Vagrant therefore only creates and starts the VMs; all the cluster configuration goes through talosctl.

The whole path, in three commands:

vagrant up                      # creates the VMs, they boot into maintenance mode
./talos/cluster-up.sh           # config + etcd bootstrap + kubeconfig + health
./_k8s/platform-up.sh           # application layer (assumes CNI=cilium, the default, see Β§9)
πŸ“– Browsable docs ops-nc.github.io/Vagrant-Talos β€” EN/FR switch, offline copy with make docs
πŸ“¦ Application layer _k8s/README.md
⬆️ Talos / K8s upgrades talos/UPGRADE.md
πŸš‘ Something broken? TROUBLESHOOTING.md

🧰 1. Prerequisites (on the host)

Tool Purpose Install
VirtualBox 7 hypervisor https://www.virtualbox.org/
Vagrant VM creation https://developer.hashicorp.com/vagrant
talosctl driving the Talos cluster curl -sL https://talos.dev/install | sh
kubectl using the cluster https://kubernetes.io/docs/tasks/tools/
helm _k8s/ addons https://helm.sh/docs/intro/install/
uv (optional) make docs https://docs.astral.sh/uv/

πŸ’‘ Keep talosctl aligned with TALOS_VERSION. The binary version is what decides the generated configuration schema; a mismatch with the ISO produces obscure errors.

To pin talosctl to a specific version instead of taking the latest:

curl -Lo /tmp/talosctl https://github.com/siderolabs/talos/releases/download/v1.13.7/talosctl-linux-amd64
sudo install -m 0755 /tmp/talosctl /usr/local/bin/talosctl

ℹ️ The Talos ISO (metal-amd64.iso) is downloaded automatically on the first vagrant up, into iso/. No Vagrant box or plugin to install: the "dummy communicator" (no SSH) and the empty pace/empty box are handled by the Vagrantfile.

⚠️ VirtualBox and KVM cannot share VT-x. If the KVM module is loaded, vagrant up dies on VERR_VMX_IN_VMX_ROOT_MODE β€” unload it first: TROUBLESHOOTING.md.


πŸ—ΊοΈ 2. IP plan (host-only network 192.168.56.0/24)

Item IP
Host (host-only) 192.168.56.1
Kubernetes API VIP 192.168.56.5
talos-cp1 / cp2 / cp3 192.168.56.10 / .20 / .30
talos-w1 / w2 / w3 … 192.168.56.101 / .102 / .103 …
LoadBalancer VIP (Cilium L2) 192.168.56.200

The IPs are deterministic: each VM has a fixed MAC and a DHCP reservation on the VirtualBox host-only network, created automatically by the Vagrantfile.

Every VM has 2 NICs: NIC1 = VirtualBox NAT (Internet) and NIC2 = host-only 192.168.56.x (cluster and API network).

ℹ️ Interface naming: since Talos 1.5, NICs get predictable names (enp0s3, enp0s8…), not eth0/eth1. The host-only NIC is therefore enp0s8 (VirtualBox NIC2 = PCI bus 0000:00:08.0). The patches never target by name: the VIP is set through busPath and the node IP through the 192.168.56.0/24 subnet β†’ robust whatever the naming scheme.

⚠️ The subnet is only half configurable. NETWORK drives the Vagrantfile and cluster-up.sh, but 192.168.56.x is hard-coded in talos/patch-all.yaml (validSubnets), talos/patch-cp.yaml (vip.ip, advertisedSubnets) and talos/cni-flannel.yaml (--iface-can-reach). Changing NETWORK without editing those three files produces a silently broken cluster.


βš™οΈ 3. Pick the topology β€” lab.env

The topology lives in lab.env, the single source read by the Vagrantfile and by talos/cluster-up.sh. Start from the versioned template (lab.env is gitignored):

cp lab.env.example lab.env
Variable Template default Purpose
TALOS_VERSION v1.13.7 boot ISO and installer image
INSTALLER_IMAGE Image Factory image installer with extensions (iscsi for Longhorn)
CONTROL_PLANES 3 1 = single, 3 = HA with a VIP
WORKERS 3 number of workers
CP_MEM / CP_CPU 4096 / 2 control plane resources (never below 3072: etcd)
WK_MEM / WK_CPU 2048 / 2 worker resources
CNI cilium cilium, calico, flannel or none (see Β§9)
LAB_DOMAIN talos.lab.example.io UI domain (*.<domain>: wildcard TLS + HTTPRoute)
SELF_SIGNED true TLS mode: true = wildcard signed by a local CA (openssl, no domain, no token), false = cert-manager + Let's Encrypt
LAB_DNS_ZONE (empty β†’ last 2 labels) DNS zone of the ACME DNS-01 solver β€” SELF_SIGNED=false only
LAB_ACME_EMAIL (empty β†’ admin@<zone>) Let's Encrypt account (expiry notices) β€” SELF_SIGNED=false only
LAB_ACME_ISSUER staging ACME issuer: staging (untrusted, huge quota) or prod (trusted, 5 certs/week) β€” SELF_SIGNED=false only
CLOUDFLARE_API_TOKEN (empty) cert-manager DNS-01 (_k8s/) β€” SELF_SIGNED=false only
NETWORK 192.168.56 host-only network
CP_IP_START / CP_IP_STEP 10 / 10 β†’ .10, .20, .30
WK_IP_START / WK_IP_STEP 101 / 1 β†’ .101, .102, .103
LB_POOL_START / LB_POOL_END 192.168.56.200 / .230 LoadBalancer IP range; the 1st one is the Gateway's, the wildcard DNS target

Variables read by cluster-up.sh but missing from the template (all have a default): VIP ($NETWORK.5), CLUSTER_NAME (talos-lab), INSTALL_DISK (/dev/sda), OUT (_out), FORCE.

πŸ’‘ Create lab.env anyway. Without it, the Vagrantfile and cluster-up.sh fall back to their internal defaults β€” both aligned on v1.13.7 and on CNI=cilium, but you lose the Image Factory installer image (iscsi extensions), and therefore Longhorn. Keep the CNI value in lab.env in sync with what you actually want: cluster-up.sh decides what Talos lays down, platform-up.sh decides what Helm installs afterwards, and two disagreeing values give you two competing CNIs β€” a broken pod network.

⚠️ Do not lower CP_MEM below 3072. 2 GB control planes starve etcd as soon as the _k8s/ addons stack up, and the cluster collapses under load β€” observability/ requires 4 GB explicitly. That is why the template ships 4096.

πŸ’° What the default topology costs: 3 Γ— 4 GB + 3 Γ— 2 GB = 18 GB of RAM, 12 vCPU and ~6 Γ— 20 GB of disk. A 16 GB host cannot run it β€” use the minimal lab below.

πŸ’‘ Minimal lab (2 VMs, ~6 GB). Enough for Talos itself and the base platform (platform-up.sh); the data addons expect the default 3 workers (Longhorn replicates Γ—3, observability/ wants 4 GB control planes). Edit lab.env:

CONTROL_PLANES=1
WORKERS=1

⚠️ Edit the file, do not just export the variable. CONTROL_PLANES=1 vagrant up only affects vagrant: cluster-up.sh re-reads lab.env and would wait for control planes .20/.30 that were never created. To override on the fly, pass the variable to both commands: CONTROL_PLANES=1 vagrant up && CONTROL_PLANES=1 ./talos/cluster-up.sh.

🌐 LAB_DOMAIN: the repo is public, so its default is neutral (talos.lab.example.io). The _k8s/ manifests carry that domain; the *-up.sh scripts replace it on the fly with LAB_DOMAIN (sed), without ever rewriting the versioned files. Put your domain in lab.env (see _k8s/README.md).

The 1st control plane is always talos-cp1 (192.168.56.10). The VirtualBox/Vagrant VM name is identical to the Talos hostname (see Β§8).


πŸš€ 4. Start the cluster

vagrant up                      # the VMs boot from the ISO, in maintenance mode
./talos/cluster-up.sh           # everything else

cluster-up.sh chains: config generation β†’ apply to the nodes (with deterministic hostnames) β†’ etcd bootstrap β†’ kubeconfig β†’ health wait. It prints the exports you need and a final kubectl get nodes.

export TALOSCONFIG="$PWD/_out/talosconfig"
export KUBECONFIG="$PWD/kubeconfig"
kubectl get nodes -o wide

For another topology, edit lab.env or override on the spot:

CONTROL_PLANES=1 WORKERS=2 ./talos/cluster-up.sh     # single
CNI=flannel      ./talos/cluster-up.sh               # CNI laid down by Talos, no LB IPs

⚠️ NEVER re-run cluster-up.sh on an already-installed cluster. Its maintenance mode wait polls the nodes with --insecure; an already-installed node (secure mode) never answers. The wait is bounded (WAIT_MAINTENANCE, 300 s) and then fails with an explicit message β€” but it still wasted five minutes and applied nothing. To grow a running cluster, see Β§6.1.

⚠️ NEVER regenerate _out/ (nor FORCE=1) on a running cluster: gen config produces new secrets and new CAs, which breaks the existing cluster. Do it only after a vagrant destroy.

πŸ” Understanding: the 6 steps by hand (what the script automates)

Useful to learn, to debug, or to resume halfway. The generation command is strictly the script's own: --install-image included.

4.1 Start the VMs

vagrant up

The VMs boot from the Talos ISO in maintenance mode and take their reserved IP. Check that a node answers:

talosctl -n 192.168.56.10 get disks --insecure   # must list /dev/sda

4.2 Generate the Talos configuration

set -a ; . ./lab.env ; set +a        # loads TALOS_VERSION, INSTALLER_IMAGE, CNI…

talosctl gen config talos-lab https://192.168.56.5:6443 \
  --install-disk /dev/sda \
  --install-image "$INSTALLER_IMAGE" \
  --additional-sans 192.168.56.5,192.168.56.10,192.168.56.20,192.168.56.30 \
  --config-patch               @talos/patch-all.yaml \
  --config-patch-control-plane @talos/patch-cp.yaml \
  --config-patch-control-plane "@talos/cni-${CNI:-cilium}.yaml" \
  --output-dir _out

export TALOSCONFIG="$PWD/_out/talosconfig"

Produces _out/controlplane.yaml, _out/worker.yaml and _out/talosconfig. The kube-apiserver endpoint is the VIP 192.168.56.5, in single as in HA.

⚠️ --install-image is not optional. Without it you install the classic installer, without the system extensions β€” and Longhorn fails later on iscsiadm: not found. The value comes from INSTALLER_IMAGE (lab.env).

⚠️ The CNI patch is not optional either. One file per intent β€” cni-cilium.yaml (the default), cni-calico.yaml, cni-flannel.yaml, cni-none.yaml β€” hence the ${CNI} above, read from lab.env like cluster-up.sh does. Omitting the patch leaves Talos' default CNI in place, without the host-only VXLAN fix (see Β§9).

ℹ️ The VIP serves only kube-apiserver (:6443). For the Talos API (-e/--endpoints, :50000) always target real node IPs (e.g. 192.168.56.10), never the VIP β€” that is the Talos recommendation.

4.3 Apply the configuration (maintenance mode β†’ --insecure)

# Control plane(s) β€” single: only .10; HA: .10, .20, .30
talosctl apply-config --insecure -n 192.168.56.10 --file _out/controlplane.yaml

# Workers (.101, .102, … β€” independent of the number of CPs)
talosctl apply-config --insecure -n 192.168.56.101 --file _out/worker.yaml
talosctl apply-config --insecure -n 192.168.56.102 --file _out/worker.yaml

Each node installs itself on /dev/sda, then reboots from disk.

ℹ️ These commands leave the auto-generated hostname (talos-xxxxx). For deterministic names, cluster-up.sh adds to every apply-config a --config-patch carrying a HostnameConfig document (auto: "off" + hostname:).

4.4 Point talosctl at the cluster

talosctl config endpoint 192.168.56.10        # HA: add .20 .30
talosctl config node     192.168.56.10

4.5 Bootstrap etcd (ONCE ONLY, on the 1st CP)

talosctl bootstrap -n 192.168.56.10

⚠️ bootstrap runs only once, on a single control plane. In HA, the other CPs join etcd automatically through discovery. If Talos answers "bootstrap is not available yet", etcd is still finishing its pre-state: retry.

4.6 Kubeconfig and health

talosctl kubeconfig -n 192.168.56.10 ./kubeconfig
export KUBECONFIG="$PWD/kubeconfig"

talosctl health --wait-timeout 10m -n 192.168.56.10 -e 192.168.56.10
talosctl -n 192.168.56.10 get members      # members seen by discovery
kubectl get nodes -o wide

πŸ“¦ 5. What comes next: the application layer

A bare cluster does nothing useful. Everything else lives in _k8s/: Cilium, Envoy Gateway, cert-manager, Longhorn, Vault, PostgreSQL, Prometheus/Loki, Kyverno, Trivy, MinIO…

./talos/cluster-up.sh              # 1. cluster (CNI=cilium by default: Talos installs nothing)
./_k8s/platform-up.sh              # 2. Cilium β†’ Envoy Gateway β†’ metrics-server β†’ wildcard TLS
./_k8s/argocd/argocd-up.sh         # 3. opt-in addons

After the bootstrap, the nodes stay NotReady until the CNI is installed β€” that is expected, platform-up.sh handles it. See _k8s/README.md for the full dependency chain and the list of addons.

⚠️ This layer requires CNI=cilium (the default). It relies on a LoadBalancer Service that actually gets an IP, which only Cilium's L2 announcement (ARP) provides here. With flannel, calico or none, the Gateway stays at EXTERNAL-IP <pending> and no UI is reachable. Details in §9.

5.1 DNS + TLS: the two manual prerequisites

ℹ️ This whole subsection is for SELF_SIGNED=false only. With the default (SELF_SIGNED=true), platform-up.sh signs the wildcard itself with openssl under a local CA: no public DNS record and no Cloudflare token are needed, and the domain never has to exist outside your machine. All you do is make the name resolve locally β€” an /etc/hosts line pointing your subdomains at 192.168.56.200 β€” and, optionally, import _out/self-signed/ca.crt to silence the browser warning. See _k8s/self-signed/README.md. Read on only if you own a real domain and want a publicly trusted certificate.

This is the part everyone forgets, and nothing works without it. Two things to do once, outside the cluster.

a) A wildcard DNS record pointing at the Gateway IP.

Every lab UI is served through a single entry point β€” Envoy's LoadBalancer Service, which takes the first IP of LB_POOL_START (192.168.56.200 by default). One record is therefore enough for all the subdomains:

Type Name Content Proxy
A *.talos.lab.example.io 192.168.56.200 DNS only (πŸ”˜ grey cloud)
# the IP actually assigned (use this if you changed LB_POOL_START)
kubectl -n envoy-gateway-system get svc -o wide | grep LoadBalancer

# check resolution
dig +short argo.talos.lab.example.io      # must answer 192.168.56.200

⚠️ The Cloudflare proxy (orange cloud) cannot work here. It would have to reach your origin from the Internet, but 192.168.56.200 is a private, non-routable IP. In orange you would get a 522 error. Stay on DNS-only: Envoy terminates TLS, not Cloudflare β€” hence the need for a publicly trusted certificate (Let's Encrypt, see point b).

ℹ️ The lab is therefore only reachable from the host, or through access to the host-only network (Tailscale β€” see _k8s/README.md). A public wildcard pointing at a private IP carries no exploitation risk, but it does publish the existence of the lab and its IP plan: your call.

πŸ’‘ With no DNS at all, you can test by short-circuiting resolution:

curl -sI --resolve argo.talos.lab.example.io:443:192.168.56.200 \
  https://argo.talos.lab.example.io/

b) A Cloudflare API token for the DNS-01 challenge.

A wildcard cannot be validated over HTTP-01 (Let's Encrypt cannot reach a private IP), so cert-manager uses DNS-01: it proves ownership by writing an _acme-challenge record, which takes a token scoped to Zone/DNS/Edit + Zone/Zone/Read on your zone only β€” an All zones token would let the lab rewrite the DNS of every domain you own. How to create it, and how the certificate is then issued: _k8s/cert-manager/README.md.

Then in lab.env (gitignored β€” never in lab.env.example):

SELF_SIGNED=false                      # leave the default (true) and none of this is read
LAB_DOMAIN=talos.lab.example.io        # your domain
LAB_DNS_ZONE=example.io                # the Cloudflare zone (derived if empty)
LAB_ACME_EMAIL=you@example.io          # Let's Encrypt expiry notices
LAB_ACME_ISSUER=staging                # staging (default) | prod β€” see the warning below
CLOUDFLARE_API_TOKEN=<your-token>

platform-up.sh creates the cloudflare-api-token Secret, substitutes the domain in the manifests and waits for the certificate. Follow it with kubectl -n envoy-gateway-system get certificate.

⚠️ prod costs a quota slot on every rebuild, and staging is the default on purpose. The wildcard lives only in etcd, so vagrant destroy burns it and the next platform-up.sh asks for a brand new one. Let's Encrypt production allows 5 certificates per week for the same *.<LAB_DOMAIN>: the 6th rebuild fails with 429 rateLimited and the lab stays without TLS until the 168 h window slides. Use prod on a stable lab, not while iterating β€” and back the wildcard up before a destroy:

kubectl -n envoy-gateway-system get secret wildcard-<your-domain-in-dashes>-tls \
  -o yaml > _out/wildcard-tls.backup.yaml     # contains the private key: _out/ is gitignored

♻️ 6. Lifecycle

vagrant status                 # VM status
vagrant halt                   # power off
vagrant up                     # power back on
vagrant destroy -f             # delete everything (dedicated disks included)

⚠️ After a destroy, also delete the local Talos state before starting over: rm -rf _out kubeconfig.

⚠️ VirtualBox 7.x does not always clean up after a destroy, and the next up then fails on VERR_ALREADY_EXISTS. Purge the leftovers with ./talos/virtualbox-cleanup.sh β€” details and precautions in TROUBLESHOOTING.md.

6.1 Adding workers (live, without breaking the cluster)

To grow an already running cluster, start the new VMs and apply the existing worker config to them (same secrets). Two rules:

  • Do not regenerate _out/ (nor FORCE=1): new secrets would break the cluster.
  • Do not re-run cluster-up.sh: it would wait for maintenance mode on already-installed nodes and hang.

Example β€” going from 3 to 5 workers (talos-w4=.104, talos-w5=.105):

  1. Raise WORKERS in lab.env (here WORKERS=5).
  2. Start only the new VMs:
    vagrant up talos-w4 talos-w5
  3. Apply the existing worker config while pinning the hostname (Nth worker = talos-w<N>):
    export TALOSCONFIG="$PWD/_out/talosconfig"
    WK_IP_START=101 ; WK_IP_STEP=1              # same values as lab.env
    for n in 4 5; do
      ip="192.168.56.$(( WK_IP_START + (n - 1) * WK_IP_STEP ))"
      until talosctl -n "$ip" get disks --insecure >/dev/null 2>&1; do sleep 5; done
      talosctl apply-config --insecure -n "$ip" --file _out/worker.yaml \
        --config-patch "$(printf 'apiVersion: v1alpha1\nkind: HostnameConfig\nauto: "off"\nhostname: talos-w%s\n' "$n")"
    done
  4. The workers join automatically (their config already points at the VIP). Check: kubectl get nodes -o wide β†’ talos-w4/talos-w5 turn Ready.

πŸ’‘ Removing a worker:

kubectl drain talos-w5 --ignore-daemonsets --delete-emptydir-data
vagrant destroy -f talos-w5
kubectl delete node talos-w5

then lower WORKERS in lab.env.

ℹ️ Adding control planes follows the same logic (VM + apply-config of controlplane.yaml, hostname talos-cp<N>); they join etcd through discovery, without re-running bootstrap.


πŸš‘ 7. Troubleshooting

Symptoms and fixes have their own page, so this one stays about installing: TROUBLESHOOTING.md β€” host and VirtualBox (VT-x/KVM conflict, leftovers after a destroy), addressing and DHCP (stale leases, unreachable VIP), Talos nodes (--insecure silence, KUBERNETES: n/a), cluster and pods (the NAT-NIC DNS trap, nodes staying NotReady).

Addon-specific problems live in the ⚠️ pitfalls and πŸš‘ troubleshooting sections of each _k8s/<addon>/README.md β€” index in _k8s/README.md.


πŸ” 8. How it works (under the hood)

  • No SSH β†’ a dummy communicator (in the Vagrantfile) reports "ready" immediately so that vagrant up does not hang.
  • No Talos box β†’ we start from the empty pace/empty box and boot the metal-amd64.iso ISO (SATA DVD drive, BIOS, boot disk then DVD).
  • Deterministic IPs β†’ fixed MAC per VM + host-only DHCP reservations (VBoxManage dhcpserver ... --fixed-address) created by a before :up trigger, stale leases purged β†’ the node takes its reserved IP on the 1st DHCP.
  • Deterministic hostnames β†’ cluster-up.sh applies one HostnameConfig document per node (auto: "off" + fixed hostname:) instead of the auto-generated name (talos-xxxxx). The VirtualBox VMs carry the same name.
  • VIP / HA β†’ talos/patch-cp.yaml sets a VIP shared between control planes (election through etcd): the kube-apiserver endpoint stays stable even if a CP goes down.
  • Online discovery β†’ talos/patch-all.yaml enables the discovery.talos.dev service and disables the kubernetes registry, deprecated and incompatible with Kubernetes β‰₯ 1.32.
  • Default route through the NAT β†’ deliberate (Internet access). What must be host-only is the node's identity (kubelet nodeIP, etcd, VIP), not the default route.

References: Talos Linux Β· siderolabs/talos Β· rgl/talos-vagrant Β· bjwschaap/vagrant-empty-box


🌐 9. CNI: Cilium, Calico or Flannel

Two ways to install a CNI, and a single variable

CNI (in lab.env) expresses an intent, read in two places:

  1. talos/cluster-up.sh applies the talos/cni-<CNI>.yaml patch, which fills in cluster.network.cni in the control plane config. Talos installs flannel itself, at bootstrap β€” without kubectl, by rendering an internal manifest.
  2. _k8s/platform-up.sh installs the CNI in every other case, through Helm.
CNI= Talos patch Who installs LoadBalancer IP
cilium (default) cni-cilium.yaml β†’ none platform-up.sh β†’ _k8s/cilium/ βœ… pool + L2 announcement (ARP)
calico cni-calico.yaml β†’ none platform-up.sh β†’ _k8s/calico/ ❌ BGP only
flannel cni-flannel.yaml Talos, at bootstrap time ❌
none cni-none.yaml you ❌
CNI=calico ./talos/cluster-up.sh && ./_k8s/platform-up.sh

Which one to choose?

Cilium Calico Flannel
Getting started one script after the bootstrap one script after the bootstrap immediate, Talos does it all
Pod network + NetworkPolicy βœ… βœ… ⚠️ no NetworkPolicy
LoadBalancer Services βœ… L2 announcement ❌ MetalLB required ❌
_k8s/ layer (Envoy, HTTPS UIs) βœ… ⚠️ after MetalLB ❌ unusable
kube-proxy replacement βœ… possible ❌ ❌
Network observability Hubble β€” β€”

In practice: keep cilium. It is the only choice that makes the lab usable end to end. calico is there to compare CNIs and to work on NetworkPolicy; flannel for a bare cluster, if you just want to explore Talos.

The Cilium install (chart pinned to 1.19.6, L2 pool, --set devices=enp0s8) is documented and scripted in _k8s/cilium/README.md β€” that is the source of truth, platform-up.sh calls it for you.

⚠️ Calico does not announce LoadBalancer Service IPs. It can only do it over BGP, which assumes a peer router β€” non-existent on a VirtualBox host-only network. With CNI=calico you therefore have to install MetalLB (L2 mode) and adjust _k8s/envoy-gateway/Envoy-Proxy.yml, which pins loadBalancerClass: io.cilium/l2-announcer (platform-up.sh strips that line outside Cilium). Full procedure: _k8s/calico/README.md.

⚠️ Switching CNI on an existing cluster is not supported: vagrant destroy, then rebuild. Two coexisting CNIs fight over the pod network.

⚠️ The key point on the Cilium side is the same as for flannel: pin the host-only interface (enp0s8). Otherwise Cilium picks the default route NIC (the NAT) and the VTEPs are broken.

ℹ️ The kubelet.nodeIP.validSubnets fix (talos/patch-all.yaml) still holds with Cilium: the nodes' INTERNAL-IP, the source of the VTEPs, is already on 192.168.56.0/24.


πŸ› οΈ 10. Validating a change

Everything can be validated without touching a cluster:

make validate       # script syntax + Vagrantfile + Talos config + doc links
make docs           # regenerates docs/index.html from every README (EN + FR)
make help           # lists the targets

make validate-talos generates the config in a temporary directory, then feeds it to talosctl validate --mode metal: no risk for _out/ nor for the cluster β€” unlike FORCE=1 ./talos/cluster-up.sh, which regenerates the secrets and breaks a running cluster. make validate-docs builds the docs into a throwaway directory and fails if a *.md link or a cross-page anchor no longer resolves. make validate-yaml parses every *.yaml / *.yml tracked by git.

On every pull request, the ci workflow re-runs three of these on a runner β€” shell syntax, YAML, and the Vagrantfile β€” by calling the very same make targets, so a check cannot pass in CI and fail on your machine. vagrant validate runs there with --ignore-provider, since a runner has no VirtualBox.

πŸ“„ 11. License

This project is licensed under the Apache License 2.0 β€” see LICENSE.

In short: use it, modify it, redistribute it, including commercially, as long as you keep the copyright notice and state your changes. It comes with no warranty: this is a lab, do not run it in production.

The license covers what this repo actually contains β€” the Vagrantfile, the talos/ and _k8s/ scripts, the manifests and the documentation. It does not extend to the third-party components those scripts download (Talos Linux, Cilium, Longhorn, Vault, Envoy Gateway, chaoskube…), each of which keeps its own license.