Network Reference
Network configuration and addressing for the fzymgc-house cluster.
IP Addressing Summary
Section titled “IP Addressing Summary”| Network | CIDR | Purpose |
|---|---|---|
| LAN Supernet | 192.168.20.0/22 | Physical LAN — one flat L2 segment (.20.0-.23.255); general client devices (laptops, phones) are DHCP-assigned anywhere in this range, not just .20.0/24 |
| Node Network | 192.168.20.0/24 | Physical node IPs (a subset of the LAN supernet above) |
| Pod Network | 10.42.0.0/16 | Kubernetes pods |
| Service Network | 10.43.0.0/16 | Kubernetes services |
| MetalLB Pool 1 | 192.168.20.145-149 | LoadBalancer VIPs |
| MetalLB Pool 2 | 192.168.20.155-159 | LoadBalancer VIPs |
IPv6 ULA Addressing
Section titled “IPv6 ULA Addressing”Unique Local Addresses (ULA) provide stable internal IPv6 addressing that works without internet connectivity.
| Prefix | CIDR | Purpose |
|---|---|---|
| ULA Base | fddb:f665:73f7::/48 | Site-local IPv6 prefix |
VLAN Subnets
Section titled “VLAN Subnets”Each VLAN receives a /64 subnet from the ULA prefix:
| VLAN | Interface | IPv6 Subnet | Router Address |
|---|---|---|---|
| Main (3000) | bond0.3000 | fddb:f665:73f7:1::/64 | ::1 |
| Guest (3100) | bond0.3100 | fddb:f665:73f7:2::/64 | ::1 |
| IoT (3020) | bond0.3020 | fddb:f665:73f7:3::/64 | ::1 |
| Lab (1000) | bond0.1000 | fddb:f665:73f7:4::/64 | ::1 |
| Telework (3200) | bond0.3200 | fddb:f665:73f7:5::/64 | ::1 |
Configuration
Section titled “Configuration”ULA addresses are configured by the router-ipv6-ula Ansible role:
- Assigns ULA addresses to router interfaces at boot
- Configures dnsmasq for Router Advertisement (RA)
- Clients auto-configure via SLAAC
See: ansible/roles/router-ipv6-ula/defaults/main.yml
Node Addresses
Section titled “Node Addresses”Control Plane Nodes
Section titled “Control Plane Nodes”| Node | IP Address | Role |
|---|---|---|
| tpi-alpha-1 | 192.168.20.101 | Control plane (etcd) |
| tpi-alpha-2 | 192.168.20.102 | Control plane (etcd) |
| tpi-alpha-3 | 192.168.20.103 | Control plane (etcd) |
Worker Nodes
Section titled “Worker Nodes”| Node | IP Address | Board |
|---|---|---|
| tpi-alpha-4 | 192.168.20.104 | alpha |
| tpi-beta-1 | 192.168.20.111 | beta |
| tpi-beta-2 | 192.168.20.112 | beta |
| tpi-beta-3 | 192.168.20.113 | beta |
| tpi-beta-4 | 192.168.20.114 | beta |
Virtual IPs
Section titled “Virtual IPs”| Address | Purpose | Provider |
|---|---|---|
192.168.20.140 | Kubernetes API VIP | kube-vip |
192.168.20.145-149 | Service LoadBalancers | MetalLB |
192.168.20.155-159 | Service LoadBalancers | MetalLB |
DNS Configuration
Section titled “DNS Configuration”External DNS (Cloudflare)
Section titled “External DNS (Cloudflare)”Domain: fzymgc.house
| Record | Type | Target | Proxy |
|---|---|---|---|
vault | CNAME | Cloudflare Tunnel | No (TCP passthrough) |
auth | CNAME | Cloudflare Tunnel | Yes |
grafana | CNAME | Cloudflare Tunnel | Yes |
argocd | CNAME | Cloudflare Tunnel | Yes |
mealie | CNAME | Cloudflare Tunnel | Yes |
longhorn | CNAME | Cloudflare Tunnel | Yes |
status | CNAME | Cloudflare Tunnel | Yes |
Internal DNS Subdomain
Section titled “Internal DNS Subdomain”k8s.fzymgc.house - Direct cluster access (not via Cloudflare)
| Record | Type | Target |
|---|---|---|
*.k8s | A | MetalLB Traefik VIP |
doltdb | A | MetalLB Dolt VIP |
Kubernetes DNS (CoreDNS)
Section titled “Kubernetes DNS (CoreDNS)”| Pattern | Resolution |
|---|---|
<svc>.<ns>.svc.cluster.local | Service ClusterIP |
<pod-ip-dashed>.<ns>.pod.cluster.local | Pod IP |
<svc>.<ns>.svc | Short form (within cluster) |
Internal .house Resolver (Authoritative)
Section titled “Internal .house Resolver (Authoritative)”Client DNS for fzymgc.house follows a fixed chain, not a flat forward:
Client -> dnsmasq (192.168.20.1:53, interceptor only) -> unbound (127.0.0.1:8953, authoritative for .house) -> Control D over DoT (everything else)- unbound is authoritative for every
.housename and answers its configured RR types; any unlisted type (including type 65/HTTPS) gets NODATA, closing the split-horizon ECH leak that previously let clients see a public HTTPS record for an internal-only name. - dnsmasq is a pure interceptor: it forwards
.housequeries to unbound and carries no zone records of its own (the earlier per-namelocal=/address=dnsmasq overlay has been retired). router-hosts(the Firewalla-side gRPC daemon managed fromtf/router-hosts) regenerates the unbound zone (unbound_local/10-fzymgc-authoritative.conf) from the host inventory on startup and on every host-record change — the zone is generated, not hand-maintained.- A systemd
.pathunit watchesunbound_local/and reloads unbound whenever the file changes, so zone updates take effect without manual intervention.
See docs/operations/dns.md for operational commands and the verification runbook, and docs/engineering/specs/2026-07-07-authoritative-house-resolver-design.md for the full design.
Ingress Architecture
Section titled “Ingress Architecture”Traffic Flow - External
Section titled “Traffic Flow - External”Internet | vCloudflare Edge (WAF, DDoS protection) | vCloudflare Tunnel (encrypted) | vcloudflared pod (namespace: cloudflared) | vTraefik (namespace: traefik) | vBackend ServicesTraffic Flow - Internal
Section titled “Traffic Flow - Internal”Internal Client | vDNS: *.k8s.fzymgc.house -> MetalLB VIP | vTraefik (namespace: traefik) | vBackend ServicesTraefik Configuration
Section titled “Traefik Configuration”Entrypoints
Section titled “Entrypoints”| Name | Port | Protocol | Purpose |
|---|---|---|---|
| web | 80 | HTTP | Redirect to HTTPS |
| websecure | 443 | HTTPS | TLS termination |
Middleware
Section titled “Middleware”| Name | Namespace | Type | Purpose |
|---|---|---|---|
oauth-auth-<app> (one per dashboard: agentgateway, temporal, hubble, traefik-dashboard, longhorn) | oauth2-proxy | ForwardAuth | Per-route Keycloak-backed SSO, gated by ?allowed_groups=<app>-users (ADR hl-1fia) |
| oauth-errors | oauth2-proxy | Errors | Converts oauth2-proxy’s 401/403 into a redirect to /oauth2/sign_in |
| redirect-https | traefik | RedirectScheme | HTTP to HTTPS |
| admin-allowlist | keycloak | IPAllowList | Restricts id.fzymgc.house /admin + /realms/master to 192.168.20.0/22 (LAN supernet, not just the node /24) + 100.64.0.0/10 (Tailscale CGNAT) for traffic that reaches Traefik directly. Uses default RemoteAddrStrategy; Traefik’s Service runs externalTrafficPolicy: Local so this Middleware sees real client IPs, not a SNAT’d address (hl-ba3a). The Cloudflare-tunnel path never reaches this Middleware at all — it’s gated separately by a Cloudflare Access Application (tf/cloudflare/access.tf, keycloak_admin) — see argocd/app-configs/keycloak/ingress-route.yaml |
TLS Configuration
Section titled “TLS Configuration”| Certificate | Issuer | Domains |
|---|---|---|
| Wildcard | Let’s Encrypt | *.fzymgc.house |
| Per-service | Let’s Encrypt | Individual hostnames |
MetalLB Configuration
Section titled “MetalLB Configuration”MetalLB runs in the metallb namespace (argocd/app-configs/metallb/kustomization.yaml).
Address Pools
Section titled “Address Pools”apiVersion: metallb.io/v1beta1kind: IPAddressPoolmetadata: name: defaultspec: addresses: - "192.168.20.145-192.168.20.149" - "192.168.20.155-192.168.20.159"L2 Advertisement
Section titled “L2 Advertisement”All pools use L2 mode (ARP) for service advertisement.
apiVersion: metallb.io/v1beta1kind: L2Advertisementmetadata: name: defaultspec: ipAddressPools: - defaultNetwork Policies (Cilium)
Section titled “Network Policies (Cilium)”Policy Types
Section titled “Policy Types”The cluster uses Cilium for policy enforcement (policyEnforcementMode: default):
- Standard Kubernetes NetworkPolicy (
networking.k8s.io/v1) — Cilium honors these as L3/L4 rules - CiliumNetworkPolicy (
cilium.io/v2) — used when identity-aware rules (FQDN, ServiceAccount, etc.) or L7 awareness are needed - CiliumClusterwideNetworkPolicy (
cilium.io/v2) — cluster-scoped policies (not currently in use)
Cilium’s enforcer evaluates policies per pod identity. Pods with NO selecting policy default to allow-all (standard K8s NetworkPolicy semantics).
Active policies
Section titled “Active policies”No CiliumNetworkPolicies are currently in force. Crown-jewel namespace coverage is tracked under the post-migration hardening initiative (see docs/engineering/specs/2026-05-12-cilium-post-migration-hardening-design.md).
History note
Section titled “History note”The cluster previously used Calico for both CNI and policy enforcement. Migration to Cilium completed 2026-05-12 (PR3 #1017 + PR4). See docs/operations/cilium-migration.md for the full migration log.
Service Routing (Cilium KPR)
Section titled “Service Routing (Cilium KPR)”Production-mode posture (since 2026-05-13)
Section titled “Production-mode posture (since 2026-05-13)”The cluster runs Cilium kube-proxy replacement in Strict mode. kube-proxy is no longer running on any node.
| Setting | Value | Source |
|---|---|---|
kube-proxy-replacement | true (Strict mode + Socket LB) | cilium-config ConfigMap |
enable-host-legacy-routing | false (eBPF native routing for host net ns) | cilium-config |
enable-bpf-masquerade | true (preserves source-NAT for pod-to-external) | cilium-config |
KUBERNETES_SERVICE_HOST / _PORT | 192.168.20.140 (kube-vip VIP) / 6443 | Cilium agent pod env |
disable-kube-proxy: true | on all 3 control-plane nodes | /etc/rancher/k3s/config.yaml |
Service traffic path
Section titled “Service traffic path”- Pod opens a connection to a Service ClusterIP
- Cilium socketLB intercepts at the socket layer (BEFORE iptables) and rewrites the destination to a backend pod IP via the eBPF Service map
- Packet flows directly to the backend pod (across nodes via the existing tunnel/VXLAN datapath if needed)
There are no KUBE-SVC-* iptables chains. The only KUBE-* chain on each node is KUBE-KUBELET-CANARY, which kubelet manages for its own iptables health-check.
Debugging
Section titled “Debugging”- Service routing issues: see
docs/operations/cilium-kpr.md - Service map state:
cilium-dbg service listfrom any agent - Real-time policy verdicts:
cilium-dbg monitorfiltered by endpoint ID
Migration history
Section titled “Migration history”Pre-cutover state had kube-proxy-replacement: false with k3s embedded kube-proxy programming iptables KUBE-SVC-* chains for Service routing. Cutover landed via PR #1076 (KPR enable + bpf flips) and PR #1078 (correct k3s disable-kube-proxy flag). Spec at docs/engineering/specs/2026-05-13-cilium-production-mode-hardening-design.md; plan at docs/engineering/plans/2026-05-13-cilium-production-mode-hardening.md.
Cloudflare Tunnel
Section titled “Cloudflare Tunnel”Tunnel Configuration
Section titled “Tunnel Configuration”| Setting | Value |
|---|---|
| Tunnel Name | fzymgc-house-main |
| Namespace | cloudflared |
| Replicas | 2 |
| Protocol | QUIC (primary), HTTP/2 (fallback) |
Ingress Rules
Section titled “Ingress Rules”Configured in Cloudflare Zero Trust dashboard, routes to:
http://traefik.traefik.svc:80(most services)tcp://vault-active.vault.svc:8200(Vault - TCP passthrough)
Port Reference
Section titled “Port Reference”External Ports
Section titled “External Ports”| Port | Protocol | Service |
|---|---|---|
| 443 | HTTPS | All web services (via Cloudflare) |
| 6443 | HTTPS | Kubernetes API (internal only) |
Internal Service Ports
Section titled “Internal Service Ports”| Service | Port | Protocol |
|---|---|---|
| Traefik | 80/443 | HTTP/HTTPS |
| Vault | 8200 | HTTPS |
| Keycloak | 443 | HTTPS |
| Grafana | 3000 | HTTP |
| ArgoCD | 80/443 | HTTP/HTTPS |
| VictoriaMetrics | 8428 | HTTP |
| Loki | 3100 | HTTP |
| Dolt | 3306 | MySQL |
Troubleshooting
Section titled “Troubleshooting”Verify MetalLB
Section titled “Verify MetalLB”kubectl get ipaddresspools -n metallbkubectl get l2advertisements -n metallbkubectl get svc -A | grep LoadBalancerCheck Traefik Routes
Section titled “Check Traefik Routes”kubectl get ingressroutes -Akubectl get ingressroutetcps -Akubectl get middlewares -ADNS Resolution
Section titled “DNS Resolution”# From within clusterkubectl run -it --rm debug --image=busybox -- nslookup vault.vault.svc
# External resolutiondig vault.fzymgc.houseNetwork Policy Debugging
Section titled “Network Policy Debugging”# Standard Kubernetes NetworkPolicieskubectl get networkpolicies -Akubectl describe networkpolicy <name> -n <namespace>
# CiliumNetworkPolicies (cilium.io/v2)kubectl get ciliumnetworkpolicies -Akubectl describe ciliumnetworkpolicy <name> -n <namespace>
# Check policy verdicts via Hubble Relay (cilium-cli)hubble observe --verdict DROPPEDhubble observe --pod <namespace>/<pod>
# Check enforcement state on a specific node's cilium agentkubectl exec -n kube-system <cilium-pod> -c cilium-agent -- \ cilium-dbg endpoint list