Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3eb69ee20 | ||
|
|
c6cb6b3aec | ||
|
|
29c299c0fe | ||
|
|
a3efc511c8 | ||
|
|
924b6e9e88 | ||
|
|
5bfa957f29 |
@@ -95,9 +95,8 @@ the VM enrolled as `claude-ws-190-112.nb.renner.uno` / `100.96.190.112` (+ IPv6)
|
||||
> inventory targets the tunnel DHCP IP.
|
||||
|
||||
> **DNS trap.** `claude-ws.obg.renner.uno` resolves to the VM's lease **only at
|
||||
> the off-site DNS** (`@10.3.40.1`). The home resolver catch-alls `*.renner.uno`
|
||||
> to the public proxy (`162.55.50.97`, the Netbird VPS) — so from home the FQDN
|
||||
> lands on `root@netbird`, **not** the VM. Never use it as `ansible_host`.
|
||||
> the off-site DNS** (`@10.3.40.1`). From home this FQDN does not resolve (it
|
||||
> exists only at the off-site DNS @10.3.40.1). Never use it as `ansible_host`.
|
||||
|
||||
**Optional — mesh route to the k8s API (redundant read path):** to reach the API
|
||||
over the mesh too, add a Netbird dashboard **network route** advertising the
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Enforces the decisions the in-cluster CrowdSec already detects, at the point
|
||||
# where public traffic actually enters: these hosts terminate TLS for every
|
||||
# *.renner.uno name. A drop here never reaches the mesh, the relay or the
|
||||
# public renner.uno name. A drop here never reaches the mesh, the relay or the
|
||||
# cluster.
|
||||
#
|
||||
# Detection stays in the cluster. The reverse-proxy produces no parseable
|
||||
|
||||
@@ -157,6 +157,29 @@
|
||||
ansible.builtin.pause:
|
||||
seconds: 10
|
||||
|
||||
# The host's mesh client must not manage DNS: containers here resolve through
|
||||
# the host stub, and the renner.uno nameserver group is unreachable from it.
|
||||
- name: Check for the host NetBird client
|
||||
ansible.builtin.stat:
|
||||
path: /usr/bin/netbird
|
||||
register: _netbird_client
|
||||
|
||||
- name: Read the host NetBird client config
|
||||
ansible.builtin.command: netbird debug config
|
||||
register: _netbird_client_config
|
||||
changed_when: false
|
||||
when: _netbird_client.stat.exists
|
||||
|
||||
# disable-dns is only applied on a fresh `up`, not to a connected client.
|
||||
- name: Reconnect the host NetBird client with DNS management disabled
|
||||
ansible.builtin.shell: |
|
||||
set -eu
|
||||
netbird down
|
||||
netbird up --disable-dns
|
||||
when:
|
||||
- _netbird_client.stat.exists
|
||||
- not (_netbird_client_config.stdout | from_json).disableDns
|
||||
|
||||
- name: Display deployment status
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
|
||||
Binary file not shown.
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""netbird-service-sync: reconcile NetBird reverse-proxy services from Ingresses.
|
||||
|
||||
Source of truth: every Ingress carrying one of the public labels.
|
||||
dns.renner.uno/public also takes the host out of the LAN DNS (external-dns-unifi
|
||||
skips it); netbird.renner.uno/public publishes on the proxy only and leaves the
|
||||
LAN record in place.
|
||||
Source of truth: every Ingress labeled netbird.renner.uno/public=true. DNS is
|
||||
independent of it: dns.renner.uno/internal (UDM) and dns.renner.uno/public
|
||||
(Cloudflare) are read by the two external-dns instances.
|
||||
For each host on such an Ingress the NetBird management gets one reverse-proxy
|
||||
service in TLS-passthrough mode on :443, targeting Traefik's ClusterIP through a
|
||||
subnet resource that covers the cluster's service CIDR. SNI and Host reach
|
||||
@@ -45,7 +44,7 @@ NETWORK_NAME = os.environ.get("NETWORK_NAME", "k8s public ingress")
|
||||
ROUTER_GROUP = os.environ.get("ROUTER_GROUP_NAME", "k8s-ingress-router")
|
||||
SOURCE_GROUP = os.environ.get("SOURCE_GROUP_NAME", "All")
|
||||
TRAEFIK_NS, TRAEFIK_SVC = os.environ.get("TRAEFIK_SERVICE", "ingress/traefik").split("/", 1)
|
||||
PUBLIC_LABELS = os.environ.get("PUBLIC_LABELS", "dns.renner.uno/public,netbird.renner.uno/public").split(",")
|
||||
PUBLIC_LABELS = os.environ.get("PUBLIC_LABELS", "netbird.renner.uno/public").split(",")
|
||||
LISTEN_PORT = int(os.environ.get("LISTEN_PORT", "443"))
|
||||
INTERVAL = int(os.environ.get("INTERVAL_SECONDS", "60"))
|
||||
HTTP_TIMEOUT = 20
|
||||
|
||||
@@ -4,8 +4,8 @@ metadata:
|
||||
name: audiobookshelf
|
||||
namespace: audiobookshelf
|
||||
labels:
|
||||
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
|
||||
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
|
||||
# Registered publicly (explicit CNAME -> NetBird proxy). No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: audiobooks.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -161,8 +161,8 @@ spec:
|
||||
enabled: true
|
||||
ingressClassName: traefik
|
||||
labels:
|
||||
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the
|
||||
# UDM external-dns instance skips it, LAN uses the hairpin NAT.
|
||||
# Registered publicly (explicit CNAME -> NetBird proxy). No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: auth.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -43,16 +43,16 @@ spec:
|
||||
name: cloudflare-api-token
|
||||
key: api-token
|
||||
extraArgs:
|
||||
- --cloudflare-proxied
|
||||
- --cloudflare-dns-records-per-page=5000
|
||||
- --default-targets=d2386ccd-a665-41e7-8631-603fdafcb006.cfargotunnel.com
|
||||
# Every public host is a DNS-only CNAME to the proxy load balancer (kubernetes/apps/cloudflare-lb-sync).
|
||||
- --default-targets=proxy.renner.uno
|
||||
- --force-default-targets
|
||||
domainFilters:
|
||||
- renner.uno
|
||||
sources:
|
||||
- ingress
|
||||
txtOwnerId: k8s-homelab
|
||||
policy: upsert-only
|
||||
policy: sync
|
||||
# Filter by label, not annotation. external-dns translates annotations
|
||||
# in its prefix namespace into ProviderSpecific properties on desired
|
||||
# endpoints; the UniFi webhook does not implement AdjustEndpoints, so
|
||||
@@ -60,4 +60,4 @@ spec:
|
||||
# records and cause every reconcile to delete-and-recreate the same
|
||||
# values. Labels are not translated, so they are safe to use as a
|
||||
# selector without leaking provider-specific state to the UniFi side.
|
||||
labelFilter: external-dns/cloudflare=true
|
||||
labelFilter: dns.renner.uno/public=true
|
||||
|
||||
@@ -4,8 +4,8 @@ metadata:
|
||||
name: gatus
|
||||
namespace: apps
|
||||
labels:
|
||||
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
|
||||
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
|
||||
# Registered publicly (explicit CNAME -> NetBird proxy). No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: status.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata:
|
||||
# Published on the NetBird proxy; LAN DNS stays on the Traefik VIP.
|
||||
netbird.renner.uno/public: "true"
|
||||
dns.renner.uno/internal: "true"
|
||||
dns.renner.uno/public: "true"
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-cluster
|
||||
spec:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: gitea-mcp.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -9,6 +9,7 @@ metadata:
|
||||
# SSH (gitea-ssh) and node image pulls stay local.
|
||||
netbird.renner.uno/public: "true"
|
||||
dns.renner.uno/internal: "true"
|
||||
dns.renner.uno/public: "true"
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-cluster
|
||||
# Long-running git fetch/push and container-registry blob uploads:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: git.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
@@ -90,7 +90,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: gitea.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
is a TLS-passthrough service on :443 with a PROXY v2 header, going to Traefik `websecure`.
|
||||
There are no UDM port forwards and no other public L4 path. LB VIPs (`bgp-pool`,
|
||||
`fd99::/64`, `10.2.99.0/24`) are LAN/mesh-only.
|
||||
- DNS labels on an Ingress: `dns.renner.uno/internal` writes the UDM (LAN) record
|
||||
(opt-in), `dns.renner.uno/public` writes the Cloudflare CNAME to `proxy.renner.uno`, and
|
||||
`netbird.renner.uno/public` makes `netbird-service-sync` publish the proxy service. A
|
||||
public host needs both public labels.
|
||||
- `netbird-service-sync` publishes **only hosts on Ingresses** labelled
|
||||
`dns.renner.uno/public` (or `netbird.renner.uno/public`), and it reads no other kind.
|
||||
`netbird.renner.uno/public`, and it reads no other kind.
|
||||
To publish a TLS host served by an `IngressRouteTCP` (TLS terminated by Traefik), put the
|
||||
host on its **own** labelled Ingress annotated
|
||||
`traefik.ingress.kubernetes.io/router.entrypoints: web` with **no** `tls:` section, and
|
||||
|
||||
@@ -16,15 +16,15 @@ monitor, and its history starts over.
|
||||
the KumaMonitor `healthCheckExprs` entry with `current: "true"` (see SKILL.md pitfalls).
|
||||
|
||||
## Which hosts are public
|
||||
The public set is exactly the hosts on Ingresses labelled `dns.renner.uno/public` or
|
||||
`netbird.renner.uno/public`. To confirm it, do a TLS handshake against the proxy-1 and
|
||||
A public host needs both labels on its Ingress: `netbird.renner.uno/public` for the proxy
|
||||
service and `dns.renner.uno/public` for the Cloudflare CNAME. To confirm it, do a TLS handshake against the proxy-1 and
|
||||
proxy-2 addresses (resolved via DoH) with each candidate hostname as the SNI. Only public hosts
|
||||
return a CA-verified certificate. The proxies do not answer for their own names, so a
|
||||
handshake with `proxy-N.renner.uno` as the SNI fails.
|
||||
|
||||
## Checks per host (owner's spec: DNS + HTTP reachability + response validation)
|
||||
- **DNS.** A cluster host has no record of its own and resolves through the `*.renner.uno`
|
||||
wildcard. Check `resolveType: CNAME` with `conditions: [{operator: equals, value:
|
||||
- **DNS.** A cluster host's public record is a CNAME to `proxy.renner.uno` created by
|
||||
external-dns from `dns.renner.uno/public`. Check `resolveType: CNAME` with `conditions: [{operator: equals, value:
|
||||
proxy.renner.uno}]`. An external host checks its own AAAA record, or A when it has no
|
||||
AAAA. Use `resolveServer: "2606:4700:4700::1111,1.1.1.1"` so IPv6 comes first; Kuma
|
||||
fails over through the list in order.
|
||||
@@ -71,7 +71,7 @@ reached Kuma.
|
||||
parent exists and is a group.
|
||||
- Emulate each http/keyword/json-query check the way Kuma does it: axios, Kuma's
|
||||
`evaluateJsonQuery` logic via `jsonata`, and a custom `lookup` that resolves through DoH,
|
||||
because this pod's DNS is split-horizon. Run each wildcard host pinned to proxy-1 and to
|
||||
because this pod's DNS is split-horizon. Run each public host pinned to proxy-1 and to
|
||||
proxy-2.
|
||||
- DNS monitors can't be checked from the cluster, because outbound :53 to external
|
||||
resolvers is blocked. After merge, read `monitor_status` from Prometheus instead
|
||||
|
||||
@@ -90,8 +90,7 @@ spec:
|
||||
# BLE radios: ESP32 esphome bluetooth_proxy nodes (e.g. the M5 Atom
|
||||
# Echo) are AUTO-DISCOVERED on the IoT VLAN via mDNS
|
||||
# (_esphomelib._tcp) over the same net1 macvlan route as the Shellys —
|
||||
# no static hostname/DNS (which the *.renner.uno wildcard would poison
|
||||
# over IPv6). The shared fleet Noise key (= the dashboard's
|
||||
# no static hostname/DNS. The shared fleet Noise key (= the dashboard's
|
||||
# shelly_ble_proxy_api_key) lets shelly-manager complete the encrypted
|
||||
# native-API handshake to each discovered proxy: DeviceInfo on
|
||||
# /ble-radios today, the BLE scan/GATT provisioning path next.
|
||||
|
||||
@@ -4,8 +4,8 @@ metadata:
|
||||
name: immich
|
||||
namespace: immich
|
||||
labels:
|
||||
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
|
||||
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
|
||||
# Registered publicly (explicit CNAME -> NetBird proxy). No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
@@ -28,9 +28,8 @@ spec:
|
||||
hosts:
|
||||
- immich.renner.uno
|
||||
---
|
||||
# photos.renner.uno is LAN-registered (no proxy tunnel), so it lives on its
|
||||
# own unlabeled Ingress: the dns.renner.uno/public label-filter is
|
||||
# per-resource, and this name must keep its UDM record. It was the LXC alias
|
||||
# photos.renner.uno is LAN-only (UDM record, no proxy), so it lives on its
|
||||
# own Ingress: DNS label filters are per-resource. It was the LXC alias
|
||||
# served by Caddy; preserved so external-dns-unifi can take over once the
|
||||
# tofu CNAME on lxc_225 is removed.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: immich.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -15,9 +15,8 @@ metadata:
|
||||
name: llama-server
|
||||
namespace: ai
|
||||
labels:
|
||||
# Public: Cloudflare wildcard → tunnel → NetBird proxy → Traefik. The UDM
|
||||
# external-dns instance skips anything carrying this, and LAN clients reach
|
||||
# the same hostname through the hairpin NAT.
|
||||
# Public: Cloudflare CNAME → NetBird proxy → Traefik. No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: llm.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Public (REN-663): each Ingress carries dns.renner.uno/public, so
|
||||
# netbird-service-sync publishes its host through the NetBird proxy
|
||||
# (Cloudflare wildcard -> proxy.renner.uno -> TLS passthrough to Traefik) and
|
||||
# external-dns-unifi skips it; LAN clients follow the public answer and the UDM
|
||||
# hairpins back in. Same path as audiobooks/immich. Federation stays off.
|
||||
# (explicit Cloudflare CNAME -> proxy.renner.uno -> TLS passthrough to Traefik)
|
||||
# and there is no dns.renner.uno/internal label, so external-dns-unifi writes
|
||||
# no UDM record; LAN clients follow the public answer and the UDM hairpins
|
||||
# back in. Same path as audiobooks/immich. Federation stays off.
|
||||
#
|
||||
# Synapse: only the client-facing paths. /_synapse/admin stays off the
|
||||
# Ingress (in-cluster only); /_matrix/federation and /_matrix/key are not
|
||||
@@ -13,8 +14,8 @@ metadata:
|
||||
name: synapse
|
||||
namespace: matrix
|
||||
labels:
|
||||
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
|
||||
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
|
||||
# Registered publicly (explicit CNAME -> NetBird proxy). No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
@@ -123,8 +124,8 @@ metadata:
|
||||
name: element-web
|
||||
namespace: matrix
|
||||
labels:
|
||||
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
|
||||
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
|
||||
# Registered publicly (explicit CNAME -> NetBird proxy). No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
@@ -154,8 +155,8 @@ metadata:
|
||||
name: mas
|
||||
namespace: matrix
|
||||
labels:
|
||||
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
|
||||
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
|
||||
# Registered publicly (explicit CNAME -> NetBird proxy). No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: matrix.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
@@ -136,7 +136,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: account.matrix.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
@@ -202,7 +202,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: element.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
@@ -289,7 +289,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: rtc.matrix.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
@@ -354,7 +354,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: turn.matrix.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -25,8 +25,8 @@ spec:
|
||||
summary: "{{ $labels.dns_name }} no longer resolves to its LAN address"
|
||||
description: >-
|
||||
The UDM stopped answering for a name external-dns publishes from
|
||||
a LoadBalancer Service, or answered with the public
|
||||
*.renner.uno wildcard instead of the record. Clients that use
|
||||
a LoadBalancer Service, or answered without the
|
||||
record (no answer, or a public CNAME). Clients that use
|
||||
the name rather than the address lose the service while
|
||||
everything in the cluster stays healthy — nothing else in the
|
||||
stack reports this. Check the external-dns alerts first, then
|
||||
|
||||
@@ -31,8 +31,9 @@ spec:
|
||||
{{ $value | humanizePercentage }} of probes in the last 10m got
|
||||
the Ingress address for {{ $labels.dns_name }} ({{ $labels.record }})
|
||||
from k8s-gateway. Pods then resolve Ingress names through the UDM
|
||||
and the public wildcard (CNAME proxy.renner.uno) and leave the
|
||||
cluster. Check the rendered Corefile in the k8s-gateway ConfigMap
|
||||
and the public zone (CNAME proxy.renner.uno for public hosts)
|
||||
and leave the cluster. Check the rendered Corefile in the
|
||||
k8s-gateway ConfigMap
|
||||
(kube-system) for the renner.uno zone, the k8s-gateway logs, and
|
||||
whether the Traefik VIP in infrastructure/traefik/helmrelease.yaml
|
||||
still matches the dns_k8s_gateway_auth_* modules in
|
||||
|
||||
@@ -30,7 +30,7 @@ tests:
|
||||
record: A
|
||||
exp_annotations:
|
||||
summary: "k8s-gateway does not answer auth.renner.uno A with the Traefik VIP"
|
||||
description: "20% of probes in the last 10m got the Ingress address for auth.renner.uno (A) from k8s-gateway. Pods then resolve Ingress names through the UDM and the public wildcard (CNAME proxy.renner.uno) and leave the cluster. Check the rendered Corefile in the k8s-gateway ConfigMap (kube-system) for the renner.uno zone, the k8s-gateway logs, and whether the Traefik VIP in infrastructure/traefik/helmrelease.yaml still matches the dns_k8s_gateway_auth_* modules in apps/monitoring/helmrelease-blackbox-exporter.yaml. It also fails when the auth.renner.uno Ingress (authentik) is gone."
|
||||
description: "20% of probes in the last 10m got the Ingress address for auth.renner.uno (A) from k8s-gateway. Pods then resolve Ingress names through the UDM and the public zone (CNAME proxy.renner.uno for public hosts) and leave the cluster. Check the rendered Corefile in the k8s-gateway ConfigMap (kube-system) for the renner.uno zone, the k8s-gateway logs, and whether the Traefik VIP in infrastructure/traefik/helmrelease.yaml still matches the dns_k8s_gateway_auth_* modules in apps/monitoring/helmrelease-blackbox-exporter.yaml. It also fails when the auth.renner.uno Ingress (authentik) is gone."
|
||||
- eval_time: 17m
|
||||
alertname: K8sGatewayProbesAbsent
|
||||
exp_alerts: []
|
||||
@@ -53,7 +53,7 @@ tests:
|
||||
record: AAAA
|
||||
exp_annotations:
|
||||
summary: "k8s-gateway does not answer auth.renner.uno AAAA with the Traefik VIP"
|
||||
description: "50% of probes in the last 10m got the Ingress address for auth.renner.uno (AAAA) from k8s-gateway. Pods then resolve Ingress names through the UDM and the public wildcard (CNAME proxy.renner.uno) and leave the cluster. Check the rendered Corefile in the k8s-gateway ConfigMap (kube-system) for the renner.uno zone, the k8s-gateway logs, and whether the Traefik VIP in infrastructure/traefik/helmrelease.yaml still matches the dns_k8s_gateway_auth_* modules in apps/monitoring/helmrelease-blackbox-exporter.yaml. It also fails when the auth.renner.uno Ingress (authentik) is gone."
|
||||
description: "50% of probes in the last 10m got the Ingress address for auth.renner.uno (AAAA) from k8s-gateway. Pods then resolve Ingress names through the UDM and the public zone (CNAME proxy.renner.uno for public hosts) and leave the cluster. Check the rendered Corefile in the k8s-gateway ConfigMap (kube-system) for the renner.uno zone, the k8s-gateway logs, and whether the Traefik VIP in infrastructure/traefik/helmrelease.yaml still matches the dns_k8s_gateway_auth_* modules in apps/monitoring/helmrelease-blackbox-exporter.yaml. It also fails when the auth.renner.uno Ingress (authentik) is gone."
|
||||
|
||||
# Two consecutive failures in an otherwise healthy hour stay silent
|
||||
# (average exactly 0.8).
|
||||
|
||||
@@ -78,11 +78,10 @@ spec:
|
||||
# passed per scrape. The Probes in monitoring-rules/probes-dns.yaml
|
||||
# point these at the UDM.
|
||||
#
|
||||
# Both answer checks are load-bearing. valid_rcodes alone passes a
|
||||
# deleted record two labels deep (NOERROR, no answer), and the public
|
||||
# *.renner.uno wildcard answers one label deep with a CNAME to
|
||||
# proxy.renner.uno — NOERROR with the WAN proxy behind it. Requiring
|
||||
# an A and rejecting a CNAME separates a real record from both.
|
||||
# Both answer checks are load-bearing: valid_rcodes alone passes a
|
||||
# deleted record (NOERROR, no answer, any depth). Requiring an A
|
||||
# separates a real record from that; a CNAME means the UDM answered with a
|
||||
# public-zone target instead of the LAN address.
|
||||
dns_lan_ha:
|
||||
prober: dns
|
||||
timeout: 5s
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# target of the *.renner.uno wildcard
|
||||
# target of every public CNAME (NetBird proxy load balancer)
|
||||
hostname: proxy.renner.uno
|
||||
resolveType: AAAA
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
@@ -228,7 +228,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# Cloudflare-proxied apex, not the wildcard
|
||||
# Cloudflare-proxied apex
|
||||
hostname: renner.uno
|
||||
resolveType: AAAA
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -4,8 +4,8 @@ metadata:
|
||||
name: music-assistant
|
||||
namespace: music-assistant
|
||||
labels:
|
||||
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
|
||||
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
|
||||
# Registered publicly (explicit CNAME -> NetBird proxy). No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: music.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: navidrome.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
@@ -101,7 +101,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: lidarr.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -24,8 +24,8 @@ metadata:
|
||||
name: docs
|
||||
namespace: paperless
|
||||
labels:
|
||||
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
|
||||
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
|
||||
# Registered publicly (explicit CNAME -> NetBird proxy). No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: docs.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
@@ -88,7 +88,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: paperless.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -354,8 +354,8 @@ metadata:
|
||||
name: paperless
|
||||
namespace: paperless
|
||||
labels:
|
||||
# Registered publicly (Cloudflare wildcard -> NetBird proxy); the UDM
|
||||
# external-dns instance skips it, LAN reaches it via the hairpin NAT.
|
||||
# Registered publicly (explicit CNAME -> NetBird proxy). No
|
||||
# dns.renner.uno/internal label: external-dns-unifi writes no UDM record.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
|
||||
@@ -51,9 +51,6 @@ metadata:
|
||||
name: wordpress-admin
|
||||
namespace: wordpress
|
||||
labels:
|
||||
# external-dns-unifi runs with --label-filter=dns.renner.uno/public!=true,
|
||||
# so an unlabelled Ingress on this host would gain a UDM A-record pointing
|
||||
# LAN clients at the Traefik VIP.
|
||||
dns.renner.uno/public: "true"
|
||||
netbird.renner.uno/public: "true"
|
||||
annotations:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: www.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Reconciles NetBird reverse-proxy services from Ingresses that carry
|
||||
# dns.renner.uno/public or netbird.renner.uno/public (proxy only, LAN DNS
|
||||
# untouched): one TLS-passthrough service per host on :443,
|
||||
# netbird.renner.uno/public: one TLS-passthrough service per host on :443,
|
||||
# targeting Traefik's ClusterIP through a subnet resource over the service
|
||||
# CIDR (ci/images/netbird-service-sync/controller.py).
|
||||
#
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
retryInterval: 60
|
||||
maxRetries: 1
|
||||
dns:
|
||||
# cluster services have no own record; they resolve through the *.renner.uno wildcard
|
||||
# public CNAME to the NetBird proxy, created by external-dns (dns.renner.uno/public)
|
||||
hostname: crowdsec.renner.uno
|
||||
resolveType: CNAME
|
||||
resolveServer: "2606:4700:4700::1111,1.1.1.1"
|
||||
|
||||
@@ -82,25 +82,12 @@ spec:
|
||||
# once via the UDM UI instead.
|
||||
domainFilters:
|
||||
- renner.uno
|
||||
# The label controls where a domain is registered: resources labeled
|
||||
# dns.renner.uno/public=true are publicly-served (Cloudflare wildcard ->
|
||||
# NetBird proxy) and must NOT get a LAN-only A/AAAA on the UDM — a phone
|
||||
# that caches 10.2.99.86 / fd99::86 on wifi and then roams to cellular
|
||||
# blackholes on it (RFC1918/ULA gives no RST and no ICMP, so the app
|
||||
# hangs until TCP timeout). This instance therefore skips labeled
|
||||
# resources; LAN clients follow the public answer and the UDM hairpins
|
||||
# the proxy addresses back to the cluster (opentofu/unifi_nat_hairpin.tf).
|
||||
#
|
||||
# The filter is per-RESOURCE: every Ingress/IngressRoute/Service that
|
||||
# carries a public host must be labeled, and a public host must not share
|
||||
# a resource with a LAN-registered host (split it, see immich-photos).
|
||||
#
|
||||
# Unlike a domain filter, the label filter applies only to the SOURCE
|
||||
# side: records already on the UDM stay visible through the TXT registry,
|
||||
# so labeling a resource drops its names from desired state and
|
||||
# policy: sync deletes the now-stale records automatically. No manual
|
||||
# cleanup step exists in this design.
|
||||
labelFilter: dns.renner.uno/public!=true
|
||||
# Opt-in: only resources labeled dns.renner.uno/internal=true get a UDM
|
||||
# record. Public hosts without it resolve on-LAN via the public answer and
|
||||
# the UDM hairpin (opentofu/unifi_nat_hairpin.tf). The filter is
|
||||
# per-resource; policy: sync deletes the records of resources that lose
|
||||
# the label.
|
||||
labelFilter: dns.renner.uno/internal=true
|
||||
sources:
|
||||
- ingress
|
||||
- service
|
||||
|
||||
@@ -67,22 +67,10 @@ spec:
|
||||
parameters: ":8181"
|
||||
# Survive the UDM's intermittent DNS outages (issue #393).
|
||||
#
|
||||
# The UDM's dnsmasq refuses connections in bursts — 330 "connection
|
||||
# refused" errors in the last 3000 log lines here, on BOTH 10.2.40.1 and
|
||||
# fd00::1. That alone would only be a blip, but it combines with a
|
||||
# wildcard trap: the public renner.uno zone has a *.renner.uno CNAME to
|
||||
# proxy.renner.uno that matches at EVERY depth, so renner.uno never
|
||||
# returns NXDOMAIN. Any name that fails locally resolves publicly to the
|
||||
# WAN proxy instead of failing honestly.
|
||||
#
|
||||
# The TTLs make it stick: the correct answer is a dnsmasq lease record
|
||||
# with TTL 5, the wrong wildcard answer has TTL 300 — 60x more durable.
|
||||
# One 5-second UDM blip therefore pins a wrong answer for 5 minutes.
|
||||
#
|
||||
# `success ... 60` clamps that 5s TTL up to a 60s floor, and
|
||||
# `serve_stale 1h immediate` keeps answering from cache while the
|
||||
# upstream is refusing connections, so a blip stops becoming a lookup
|
||||
# failure that the wildcard can capture.
|
||||
# The UDM's dnsmasq refuses connections in bursts and answers with
|
||||
# 5s-TTL lease records; `success ... 60` raises the TTL to a 60s
|
||||
# floor and `serve_stale 1h immediate` keeps serving through an
|
||||
# upstream outage.
|
||||
- name: cache
|
||||
parameters: "300"
|
||||
configBlock: |-
|
||||
|
||||
+10
-16
@@ -7,9 +7,9 @@ locals {
|
||||
# www.renner.uno returns the public answer (proxy.renner.uno) — so this
|
||||
# block was drift waiting to be re-applied.
|
||||
#
|
||||
# The correct end state is exactly this absence: www.renner.uno is labelled
|
||||
# dns.renner.uno/public, so external-dns-unifi deliberately writes no UDM
|
||||
# record and LAN clients follow the public path (wildcard -> proxy) and
|
||||
# The correct end state is exactly this absence: www.renner.uno has no
|
||||
# dns.renner.uno/internal label, so external-dns-unifi writes no UDM record
|
||||
# and LAN clients follow the public path (explicit CNAME -> proxy) and
|
||||
# hairpin back in. Re-adding an A record here would point the house at a
|
||||
# server that is being decommissioned.
|
||||
#
|
||||
@@ -27,21 +27,15 @@ locals {
|
||||
|
||||
# AAAA Records
|
||||
# UniFi auto-creates only the A record for the UDM's own hostname
|
||||
# (udm -> 192.168.8.1) and forwards AAAA queries upstream, where the
|
||||
# *.renner.uno wildcard answers with proxy.renner.uno (the public
|
||||
# reverse proxy). IPv6-first clients — notably Home Assistant's UniFi
|
||||
# Network integration — then connect to the proxy, which rejects the
|
||||
# unknown SNI with `tlsv1 alert internal error`, flapping the
|
||||
# integration into setup_error. Pin the AAAA to the UDM's own
|
||||
# management ULA (fd00::1, serves the controller cert CN=udm.renner.uno
|
||||
# and is reachable from the cluster via its v6 default route) so v6
|
||||
# resolution stays local instead of falling through to the proxy.
|
||||
# (udm -> 192.168.8.1). Pin the AAAA to the UDM's management ULA
|
||||
# (fd00::1, serves the controller cert CN=udm.renner.uno) so IPv6-first
|
||||
# clients such as Home Assistant's UniFi Network integration connect
|
||||
# over v6.
|
||||
"udm" = { type = "AAAA", value = "fd00::1" }
|
||||
|
||||
# Same failure mode as `udm` above: the UniFi captive-portal hostname
|
||||
# auto-resolves to the UDM console (captive -> 192.168.8.1) over v4 but
|
||||
# has no AAAA, so v6 queries fall through to the *.renner.uno wildcard
|
||||
# (proxy.renner.uno). Pin the AAAA to the same UDM management ULA.
|
||||
# The captive-portal hostname auto-resolves to the UDM console
|
||||
# (captive -> 192.168.8.1) over v4 but has no AAAA; pin the AAAA to the
|
||||
# same management ULA.
|
||||
"captive" = { type = "AAAA", value = "fd00::1" }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user