Compare commits

..
Author SHA1 Message Date
claude-botandClaude Opus 5.5 5bfa957f29 feat(dns): one consumer per label; public DNS as CNAME to proxy.renner.uno
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
Step 2 of 2 of the label split (owner decision 2026-10-22). Requires
#1425 (step 1) to be applied first.

- external-dns-unifi: labelFilter dns.renner.uno/internal=true (opt-in).
  Every object it publishes today carries the label since step 1, so the
  UDM record set is unchanged.
- external-dns-cloudflare: labelFilter dns.renner.uno/public=true, default
  target proxy.renner.uno (the cloudflare-lb-sync load balancer) instead of
  the retired tunnel, no --cloudflare-proxied, policy sync. Each public
  host gets a DNS-only CNAME to proxy.renner.uno plus its TXT registry
  record; this is the prerequisite for removing the *.renner.uno wildcard.
- netbird-service-sync: reads netbird.renner.uno/public only. Every
  dns-public Ingress carries it since step 1, so the 21 proxy services are
  unchanged.
- git/gitea/gitea-mcp: add dns.renner.uno/public (public today via the
  wildcard; keep their LAN record via dns.renner.uno/internal).
- Drop comments describing the old public!=true filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:45:20 +02:00
9 changed files with 19 additions and 36 deletions
+4 -5
View File
@@ -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
@@ -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
+1
View File
@@ -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:
+1
View File
@@ -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:
+2 -3
View File
@@ -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
-3
View File
@@ -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:
@@ -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).
#
@@ -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