Phase 1 — Network¶
Objective¶
Bridge br0 with static IP on the 3 nodes, ready for Incus, K3s (a lightweight Kubernetes distribution), and CAPN (Cluster API Provider for Incus).
What You Learn¶
Why the Homelab requires a fixed IP: Incus records cluster.https_address, K3s uses
the IP as the kubelet identity, and CAPN references https://<IP>:8443 in
secrets.
Learn this phase
In simple terms: a fixed IP is your postal address: if it changes every day, no one can find you. A bridge is like a virtual multi-outlet: it lets the machine and virtual machines share the same network connection.
Key Concepts
- Static or dynamic IP: A static IP stays the same; a dynamic one is assigned by the router (DHCP, Dynamic Host Configuration Protocol) and can change.
- Mask and CIDR (Classless Inter-Domain Routing): The notation
/22indicates how many addresses share the network. - Gateway: The device, usually the router, through which you exit to other networks.
- DNS (Domain Name System): The "phone book" that translates names to IP addresses.
- Bridge
br0: The virtual interface that the node, Incus, and K3s share.
Practical challenge (read-only): On a node, find its IP, its mask, and its gateway.
Why does the Homelab need a fixed IP on the nodes?
Because Incus stores the cluster address, K3s uses the IP as the node identity, and other components reference it: if it changes, the cluster will stop finding itself.
What is a bridge and what is it for here?
It is a virtual interface that connects several interfaces into a single network. It allows Incus instances and pods to use the node's physical network.
In 192.168.20.5/22, which part is the address and which is the mask?
192.168.20.5 is the address and /22 indicates that the first 22 bits are the network.
Questions? Use the Learn with AI button next to each title, or the page Learn.
Stack for this phase¶
flowchart TB
PHY[Interfaz_fisica] --> BR0[br0_L2]
BR0 --> IP[IP_estatica]
IP --> SSH[ansible_host]
IP --> Incus[Instancias_Incus]
IP --> K3s[kubelet_node_ip]
- Netplan — Declarative network configuration on Debian; Phase 1.
- Ansible — Applies Netplan idempotently via
playbook-set-static-ip.yml. - Role:
netplan_bridge
Further reading: Network — Netplan examples
Before You Start¶
- Phase 0 — Preparation completed (
ansible pingOK). - Know the physical interface per node (
eno1,eno2,enP4p65s0). - Target IPs defined in inventory.
| Node | Interface | IP on br0 |
|---|---|---|
| invincible | eno2 |
192.168.20.6 |
| oliver | eno1 |
192.168.20.7 |
| deborah | enP4p65s0 |
192.168.20.5 |
Confirm always with
ip -br linkon the node before assuming these values.
Execute¶
SSH Disconnect
If you change the IP, Ansible may lose the session. The playbook uses
serial: 1 — one node at a time. Update ansible_host after each node
if the connection IP changed.
cd ansible
# Un nodo a la vez si la IP de conexión cambia
ansible-playbook -i inventory.ini playbook-set-static-ip.yml \
--limit invincible
# Actualiza ansible_host en inventory.ini si hace falta, luego:
ansible-playbook -i inventory.ini playbook-set-static-ip.yml \
--limit oliver
ansible-playbook -i inventory.ini playbook-set-static-ip.yml \
--limit deborah
See YAML examples in Network:
Options¶
| Decision | Default Homelab |
|---|---|
| Node order | One at a time (serial: 1) |
All three nodes change IP upon applying
No shortcut: update ansible_host in inventory.ini after
each node (that's why serial: 1).
playbook-set-static-ip.yml)serial: 1Verify¶
ansible -i ansible/inventory.ini incus_cluster -m command -a "ip -br addr show br0"
# Cada nodo muestra su IP estática en br0
ping -c 2 192.168.20.5
ping -c 2 192.168.20.6
ping -c 2 192.168.20.7
If It Fails¶
| Symptom | Check |
|---|---|
| Lost SSH after apply | Connect by new IP; update inventory.ini |
No br0 |
Interface in bridge_iface; logs journalctl -u systemd-networkd |
DNS (BIND on deborah)¶
The three nodes use 192.168.20.5 (deborah) as the primary DNS and the gateway
192.168.20.1 as the secondary. With the fixed IP already applied on deborah, install
and configure BIND (Berkeley Internet Name Domain) there (runs only on deborah):
Until it runs, the nodes resolve by the gateway, with some delay in each query.
Two things that playbook-set-static-ip.yml leaves and that subsequent phases depend on:
- A fixed
/etc/resolv.confin each node, withsearch homelab.local mco.localand first the BIND (192.168.20.5). If the node has NetworkManager, install alsodns=noneto prevent it from rewriting with the router's DNS. Without this, a node stops resolving*.homelab.local(the nodes cannot validate the SSO —single sign-on— of the 4.12). - The BIND zone
homelab.localbrings a record for each node and each published service, without wildcard (see 4.8).
Check on each node: cat /etc/resolv.conf and getent hosts oliver.homelab.local. Details:
ansible/roles/bind_dns/README.md.
Hardening (recommended before proceeding)¶
With the IP already fixed, it's time to harden the host before it joins the cluster. This goes here in the sequence (see Post-incident hardening):
ansible-playbook -i inventory.ini playbook-hardening.yml --check --diff
ansible-playbook -i inventory.ini playbook-hardening.yml
Details:
ansible/roles/host_hardening/README.md.
Next¶
Complete bootstrap order (includes hardening, BIND, WiFi):
ansible/bootstrap/README.md.