revert(vpn-ingress): withdraw the L2 lease-collision fix — a node CAN reach a VIP it announces
PR #69 claimed a node cannot reach a LoadBalancer VIP whose Cilium L2 announcement
lease it holds, and excluded the VPN ingress node from announcing on that basis.
The claim makes a sharp prediction; today's power-cycle put ALL EIGHT
cilium-l2announce-* leases on k3s-prod-1, and the prediction failed.
Measured 2026-10-17 from the wireguard pod's host netns on each node: the holder
reaches 192.168.2.20/.22/.23/.24 normally (curl 200 in 18ms on .20) while its
ip neigh for every one of them sits at FAILED. The asserted cause is present
without the asserted effect, which falsifies it. .21 (unifi) fails from all three
nodes and is a separate pre-existing fault.
It is inert because no path resolves a VIP by ARP: cilium-dbg reports
KubeProxyReplacement on [eno1, wg0] and programs 192.168.2.20:443 =>
127.0.0.1:13606, so VIPs are DNAT'd in BPF before any neighbour lookup, on the
forwarded path as well as the host one.
Both probes behind the original diagnosis were bad. `ip neigh` FAILED is
permanent and harmless, and a ping artefact. `curl --interface wg0` — the control
used to rule out a host-netns artefact — fails on all three nodes, including ones
whose ARP resolves, so it discriminates nothing. The deeper error: a MASQUERADEd
client is FORWARDED while every pod-runnable probe is socket-originated. Sharing
a netns is not sharing a hook.
Reverted the policy nodeSelector, the Ansible node label, both alerts, their
promtool tests and the kube-state-metrics allowlist entry. Kept documentation
only: the measurements, which probes lie, and the fact that the forwarded path
remains unmeasured without a connected client.
Also recorded, for the reported "LAN and cluster work, internet does not":
new-peer.sh has emitted a FULL tunnel (0.0.0.0/0) by default since f92707f, so
there is no full-tunnel option to add and no code change is made. The server side
is capable — ip_forward=1, the 10.90.0.0/24 MASQUERADE present with an empty
CILIUM_POST_nat ahead of it, FORWARD accepts, default route intact, nodes reach
1.1.1.1. A full tunnel egresses the HOUSE WAN, not PIA: the egress policy selects
pods by egress-class=vpn, this pod is labelled internet, and tun0 exists on
k3s-prod-3 only. Root cause NOT found; no client has connected since the
power-cycle (0 forwarded packets, no handshake), so it is not reproducible here.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
1 parent
4656b07178
commit
e82dab5df5
13 files changed
+338
-871
No files matched your search
@@ -1109,45 +1109,3 @@
|
||||
state: "{{ 'present' if node_baseline_hosts_pins | length > 0 else 'absent' }}"
|
||||
backup: true
|
||||
tags: [dns, hosts]
|
||||
|
||||
# ── vpn-ingress node label — tag `vpn-ingress` ────────────────────────────────────────────────
|
||||
# Marks the node terminating remote-access WireGuard so Cilium's L2 announcement policy can EXCLUDE
|
||||
# it from announcing LoadBalancer VIPs (a node cannot reach a VIP it announces itself -- see
|
||||
# defaults/main.yml). Same shape as roles/vpn_egress_gateway's egress-gateway label, and for the
|
||||
# same reason: the label is the one place BOTH a nodeSelector and a PromQL alert can read it from.
|
||||
#
|
||||
# Skipped entirely when node_baseline_vpn_ingress_node is empty (dev, or before the forward exists),
|
||||
# so this adds nothing to an environment that has no WAN forward.
|
||||
- name: Label this node as the vpn-ingress node
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
k3s kubectl label node {{ inventory_hostname }}
|
||||
{{ node_baseline_vpn_ingress_label_key }}=true --overwrite
|
||||
environment:
|
||||
KUBECONFIG: "{{ node_baseline_vpn_ingress_kubeconfig }}"
|
||||
register: node_baseline_vpn_ingress_label_apply
|
||||
changed_when: "'not labeled' not in node_baseline_vpn_ingress_label_apply.stdout"
|
||||
when:
|
||||
- node_baseline_vpn_ingress_node | length > 0
|
||||
- inventory_hostname == node_baseline_vpn_ingress_node
|
||||
tags: [vpn-ingress]
|
||||
|
||||
# The other half: moving the knob must MOVE the label, not add a second one. Two labelled nodes
|
||||
# would remove two of three nodes from announcement candidacy; three would leave no announcer at
|
||||
# all and take every VIP off the LAN.
|
||||
- name: Remove the vpn-ingress label from a node that no longer holds the forward
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
k3s kubectl label node {{ inventory_hostname }}
|
||||
{{ node_baseline_vpn_ingress_label_key }}-
|
||||
environment:
|
||||
KUBECONFIG: "{{ node_baseline_vpn_ingress_kubeconfig }}"
|
||||
register: node_baseline_vpn_ingress_label_remove
|
||||
changed_when: "'not labeled' not in node_baseline_vpn_ingress_label_remove.stdout"
|
||||
failed_when:
|
||||
- node_baseline_vpn_ingress_label_remove.rc != 0
|
||||
- "'not found' not in node_baseline_vpn_ingress_label_remove.stderr"
|
||||
when:
|
||||
- node_baseline_vpn_ingress_node | length > 0
|
||||
- inventory_hostname != node_baseline_vpn_ingress_node
|
||||
tags: [vpn-ingress]
|
||||
Reference in new issue
Block a user