feat(ingress): NetBird mesh ingress (routing peer) alongside cloudflared #27
@@ -14,6 +14,7 @@ resources:
|
||||
- matter-server.yaml
|
||||
- mmwave-configurator.yaml
|
||||
- music-assistant.yaml
|
||||
- netbird-ingress.yaml
|
||||
- paperless-ngx.yaml
|
||||
- protonmail-bridge.yaml
|
||||
- pve-exporter.yaml
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: netbird-ingress
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: netbirdio/netbird
|
||||
interval: 1h
|
||||
---
|
||||
# Semver. filterTags restricts candidates to bare X.Y.Z release tags so
|
||||
# pre-release (-rc.N) and the -rootless variant can't slip through; the
|
||||
# -rootless image is userspace/netstack and can't act as a routing peer.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: netbird-ingress
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: netbird-ingress
|
||||
filterTags:
|
||||
pattern: '^(?P<ver>\d+\.\d+\.\d+)$'
|
||||
extract: '$ver'
|
||||
policy:
|
||||
semver:
|
||||
range: ">=0.73.0"
|
||||
@@ -16,6 +16,7 @@ resources:
|
||||
- ceph-csi-drivers/
|
||||
- cloudnative-pg/
|
||||
- cloudflared/
|
||||
- netbird-ingress/
|
||||
- coredns-config/
|
||||
- k8s-gateway/
|
||||
- external-dns-unifi/
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# NetBird mesh ingress — a second, parallel ingress path alongside
|
||||
# cloudflared. This pod enrolls as a NetBird *routing peer* with the
|
||||
# self-hosted management server (netbird.renner.uno) and advertises the
|
||||
# Traefik VIP (10.2.99.86) into the mesh, so mesh peers can reach every
|
||||
# Traefik-fronted service. Mesh DNS (10.2.40.53) already resolves
|
||||
# <app>.renner.uno → 10.2.99.86; the only missing piece was making that
|
||||
# VIP routable over the overlay, which this peer provides.
|
||||
#
|
||||
# Flow: mesh peer (100.96.x.x) --wg--> this pod --(masq + fwd)--> 10.2.99.86 (Traefik) --> service
|
||||
#
|
||||
# Unlike the upstream NetBird k8s operator, this pod is NOT privileged.
|
||||
# The NET_ADMIN/SYS_ADMIN/SYS_RESOURCE caps are the irreducible minimum
|
||||
# for an L3 WireGuard routing peer (the netbird entrypoint mknods
|
||||
# /dev/net/tun and programs the wg interface + masquerade itself). Its
|
||||
# egress is locked down by the netbird-ingress-egress CiliumNetworkPolicy
|
||||
# (network-policies/ingress-isolation.yaml), mirroring cloudflared.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: netbird-ingress
|
||||
namespace: ingress
|
||||
labels:
|
||||
app: netbird-ingress
|
||||
spec:
|
||||
# Single routing peer = single advertised next-hop into the mesh.
|
||||
# HA (multiple peers + distribution) is a later iteration.
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: netbird-ingress
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: netbird-ingress
|
||||
spec:
|
||||
containers:
|
||||
- name: netbird
|
||||
image: netbirdio/netbird:0.73.2 # {"$imagepolicy": "flux-system:netbird-ingress"}
|
||||
env:
|
||||
- name: NB_SETUP_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: netbird-ingress-setup-key
|
||||
key: setup-key
|
||||
- name: NB_MANAGEMENT_URL
|
||||
value: "https://netbird.renner.uno"
|
||||
- name: NB_HOSTNAME
|
||||
value: "netbird-ingress"
|
||||
- name: NB_LOG_LEVEL
|
||||
value: "info"
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- NET_ADMIN
|
||||
- SYS_RESOURCE
|
||||
- SYS_ADMIN
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["netbird", "status", "--check", "live"]
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["netbird", "status", "--check", "ready"]
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
exec:
|
||||
command: ["netbird", "status", "--check", "startup"]
|
||||
periodSeconds: 2
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 30
|
||||
volumeMounts:
|
||||
- name: state
|
||||
mountPath: /var/lib/netbird
|
||||
volumes:
|
||||
# Ephemeral state: the setup key is reusable, so a pod restart
|
||||
# cleanly re-enrolls a fresh peer (old one is reaped as ephemeral).
|
||||
- name: state
|
||||
emptyDir: {}
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- secret.sops.yaml
|
||||
- deployment.yaml
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: ENC[AES256_GCM,data:n5g=,iv:AS2cmrgB/sOiONg/DoPRT6ZmPdLOPbWjaQqilYTJpUg=,tag:ulxs3Ez1IixOUIRDktDtew==,type:str]
|
||||
kind: ENC[AES256_GCM,data:G2FirIAV,iv:xiJ8bkbXCRC8diE4UwYP3rnTBYG1BTszYOKcPTcMgCU=,tag:/vMwZg4Tu0NHwpzxX8RJ0g==,type:str]
|
||||
metadata:
|
||||
name: ENC[AES256_GCM,data:HTnb3axigMpnO7jTqK3XQ+V7TehSfDPH+Q==,iv:8TEl6Yvgpj54ZtY1aF1dJ6Hkb9M0bVjJR7fUhA0+RUk=,tag:u5QEgWAc4udwwwKMLB+F2g==,type:str]
|
||||
namespace: ENC[AES256_GCM,data:BMF0U6V7GA==,iv:cImq31ZJLipyvkHOVcixDKyUakWKFTTQposOS6NDYqM=,tag:RGfZgiX9w4nEX2C9YXytPQ==,type:str]
|
||||
type: ENC[AES256_GCM,data:EtCwuXbU,iv:jmjZOjGDXhK+GZ/kjd7cynRYfp+BxCMU6IuvS+2p5AU=,tag:Stc7GZZf6UjdR4svy2+M6Q==,type:str]
|
||||
stringData:
|
||||
setup-key: ENC[AES256_GCM,data:+ivBRI8oBZaiKT8CYIIT4ECQk4tpX17BmCbncMJ3we3kmCq+,iv:7q51hlOukOAxUFEjWw3el0x6tTyJQdvelMXUxA0T5Ks=,tag:JoDZRAri8QO47Ouc5sJK0w==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- recipient: age1yqjvqkwwwxdj7mr546zfs5ckzuwndp69ya2n2drd4vypwns8w4ys04qfy3
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvOWNLQXg3NUpiZFQrQmx5
|
||||
bUxkYlZ5U1ZuU0VGN1ZiSit1QjNtVTNxelRNCkRoYW1Kb0lDZlFXWmlOcjRETUw2
|
||||
SzV2OGlVZXNPUzlISkEwc3crcWdTNlEKLS0tIEVjeFFHakZWOUJBdVdQMGJXN1kr
|
||||
c1gybVVEOVV5b1dwR2V6VEpqbUtPSlkKdrtsKN4ExpeRfT8tk+s0yUVlBmD20Tbw
|
||||
zSGHaQvuFeAG4HJW6QBlFcJ9c2GGhlvYhAH2E31tHKzwPaqYLQYPww==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-06-25T17:46:34Z"
|
||||
mac: ENC[AES256_GCM,data:gxxN4X4m3YTxL2nqMhG6eTrbOIhr2bMIhUdVZs1SeDldIgO+iXu8S8ComrEFHb7IeYh55OaCZjFz9CG9uR36JzAX7o2d7F7/HM7g3kzkoA7vV0PGX7BgGLWJsT5LxVuU7/300KEdaiqPXPubwAhHOEbVkVdyn8IXnrQRCHbkkzs=,iv:inS8+v2tbGYZAbD3zG3Bf5uVIckTTFMK8Tk0fPsVXXg=,tag:7JMmk6irsBCfM/za2CdkaQ==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.10.2
|
||||
@@ -42,6 +42,53 @@ spec:
|
||||
- port: "443"
|
||||
protocol: TCP
|
||||
---
|
||||
# NetBird mesh-ingress routing peer: same egress-lockdown discipline as
|
||||
# cloudflared above. It may reach (a) traefik (by label selector — Cilium
|
||||
# applies LB/DNAT before egress policy, so forwarded mesh→VIP traffic is
|
||||
# enforced on traefik's identity, no static VIP needed); (b) DNS; (c) the
|
||||
# NetBird control/data plane on netbird.renner.uno — mgmt/signal/relay
|
||||
# over TCP/443, STUN/TURN over UDP/3478, and WireGuard over UDP/51820.
|
||||
# Relay-over-443 is the guaranteed fallback when direct UDP P2P is blocked.
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: netbird-ingress-egress
|
||||
namespace: ingress
|
||||
spec:
|
||||
endpointSelector:
|
||||
matchLabels:
|
||||
app: netbird-ingress
|
||||
egress:
|
||||
- toEndpoints:
|
||||
- matchLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "8443"
|
||||
protocol: TCP
|
||||
- port: "8000"
|
||||
protocol: TCP
|
||||
# Allow DNS resolution
|
||||
- toEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": kube-system
|
||||
k8s-app: kube-dns
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "53"
|
||||
protocol: UDP
|
||||
# NetBird control + data plane (netbird.renner.uno, external)
|
||||
- toEntities:
|
||||
- world
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "443"
|
||||
protocol: TCP
|
||||
- port: "3478"
|
||||
protocol: UDP
|
||||
- port: "51820"
|
||||
protocol: UDP
|
||||
---
|
||||
# Default deny ingress to ingress namespace from external sources only.
|
||||
# Any in-cluster pod may reach traefik (required for east-west OIDC
|
||||
# flows via k8s_gateway — audiobookshelf → Authentik etc.); we don't
|
||||
|
||||
Reference in New Issue
Block a user