Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b14bf0cc5e | ||
|
|
3b3ed4fbc4 | ||
|
|
c40466a3e6 | ||
|
|
ff0b3434c1 | ||
|
|
2cfebc2f8b | ||
|
|
3481e43032 | ||
|
|
98475a47e2 | ||
|
|
6f20955e3b | ||
|
|
92ac021931 | ||
|
|
5bbca6a162 | ||
|
|
edb07a344e | ||
|
|
260f548e67 | ||
|
|
ef6bc24491 | ||
|
|
779f557624 | ||
|
|
1fbefb3c39 | ||
|
|
f97aa077f0 | ||
|
|
8aa662e997 | ||
|
|
45012470c1 | ||
|
|
688f84dc29 | ||
|
|
6335954d6c |
@@ -26,8 +26,16 @@ edge_open3e_poll_seconds: 30
|
||||
# entries when added), so the multi-frame read is cheap at a 30 s poll.
|
||||
# Records faults/info events only (NormalOperation, LagDeviceDtcReported,
|
||||
# LegionellaProtectionTemperatureReached) — NOT setpoint or program changes.
|
||||
# onebase.*: the 29 paired ZigBee slots on the One Base gateway (ECU 0x6c3) --
|
||||
# 8 radiator valves, 9 climate sensors, 9 underfloor actuators, 3 floor
|
||||
# thermostats. Per-device room temperature, active setpoint, humidity, valve
|
||||
# opening/demand, battery and RSSI; the local replacement for the ViCare cloud
|
||||
# room sensors. Needs the onebase overlay (files/open3e-onebase.py) -- the stock
|
||||
# codec declares 57 bytes and a paired slot answers with 68.
|
||||
# Only CurrentValues are polled. The room and device registries (1884.., 2084..)
|
||||
# are static configuration and are read on demand, not every 30 s.
|
||||
edge_open3e_datapoints: >-
|
||||
vitocal.259,vitocal.260,vitocal.268,vitocal.269,vitocal.271,vitocal.274,vitocal.286,vitocal.288,vitocal.318,vitocal.321,vitocal.324,vitocal.335,vitocal.336,vitocal.396,vitocal.402,vitocal.403,vitocal.476,vitocal.477,vitocal.543,vitocal.548,vitocal.565,vitocal.1007,vitocal.1043,vitocal.1190,vitocal.1211,vitocal.1391,vitocal.1416,vitocal.1417,vitocal.2320,vitocal.2346,vitocal.2350,vitocal.2351,vitocal.2352,vitocal.2369,vitocal.2370,vitocal.2486,vitocal.2487,vitocal.2488,vitocal.2543,vitocal.2560,vitocal.2622,vitocal.2624,vitocal.2625,vitocal.2735,vitocal.3016,vx3.266,vx3.378,vx3.379,vx3.380,vx3.535,vx3.1603,vx3.1664,vx3.1690,vx3.1801,vx3.1802,vx3.1810,vx3.1828,vx3.1830,vx3.1577,vx3.1578,vx3.1579,vx3.1580,vx3.1581,vx3.1582,vx3.1834,vx3.1836,vx3.2214,vx3.2240,vx3.3085,vx3.3086,vx3.3087,vx3.3088,vx3.3089,vx3.3090,vcmu.320,vcmu.322,vcmu.325,vcmu.355,vcmu.389,vcmu.391,vcmu.1772,vcmu.1773,vcmu.1775,vcmu.1776,vcmu.2256,vcmu.2259,vcmu.2333,vcmu.2334,vcmu.2569,vcmu.2571,vitocal.2791
|
||||
vitocal.259,vitocal.260,vitocal.268,vitocal.269,vitocal.271,vitocal.274,vitocal.286,vitocal.288,vitocal.318,vitocal.321,vitocal.324,vitocal.335,vitocal.336,vitocal.396,vitocal.402,vitocal.403,vitocal.476,vitocal.477,vitocal.543,vitocal.548,vitocal.565,vitocal.1007,vitocal.1043,vitocal.1190,vitocal.1211,vitocal.1391,vitocal.1416,vitocal.1417,vitocal.2320,vitocal.2346,vitocal.2350,vitocal.2351,vitocal.2352,vitocal.2369,vitocal.2370,vitocal.2486,vitocal.2487,vitocal.2488,vitocal.2543,vitocal.2560,vitocal.2622,vitocal.2624,vitocal.2625,vitocal.2735,vitocal.3016,vx3.266,vx3.378,vx3.379,vx3.380,vx3.535,vx3.1603,vx3.1664,vx3.1690,vx3.1801,vx3.1802,vx3.1810,vx3.1828,vx3.1830,vx3.1577,vx3.1578,vx3.1579,vx3.1580,vx3.1581,vx3.1582,vx3.1834,vx3.1836,vx3.2214,vx3.2240,vx3.3085,vx3.3086,vx3.3087,vx3.3088,vx3.3089,vx3.3090,vcmu.320,vcmu.322,vcmu.325,vcmu.355,vcmu.389,vcmu.391,vcmu.1772,vcmu.1773,vcmu.1775,vcmu.1776,vcmu.2256,vcmu.2259,vcmu.2333,vcmu.2334,vcmu.2569,vcmu.2571,vitocal.2791,onebase.2086,onebase.2089,onebase.2092,onebase.2095,onebase.2098,onebase.2101,onebase.2104,onebase.2107,onebase.2110,onebase.2113,onebase.2116,onebase.2119,onebase.2122,onebase.2125,onebase.2128,onebase.2131,onebase.2134,onebase.2137,onebase.2140,onebase.2143,onebase.2262,onebase.2265,onebase.2268,onebase.2271,onebase.2274,onebase.2277,onebase.2280,onebase.2283,onebase.2286
|
||||
# ECU addresses; -dev selects codec set, tx selects the UDS address.
|
||||
# vcmu (refrigerant-circuit controller) has no dedicated codec set —
|
||||
# omitting dpList makes open3e fall back to the generic datapoint list,
|
||||
@@ -38,6 +46,9 @@ edge_open3e_devices:
|
||||
# files/open3e-vx3plus.py) — die Stock-vx3-Liste filtert sie sonst weg.
|
||||
vx3: { tx: "0x6a1", dpList: "/config/vx3plus.py" }
|
||||
vcmu: { tx: "0x68c" }
|
||||
# One Base ZigBee gateway. Overlay re-declares the paired ZigBee slots at
|
||||
# their real 68-byte length (see files/open3e-onebase.py).
|
||||
onebase: { tx: "0x6c3", dpList: "/config/onebase.py" }
|
||||
|
||||
edge_e3oncan_gap_seconds: 10
|
||||
edge_e3oncan_mqtt_format: "{device}_{didNumber:04d}_{didName}"
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Managed by ansible (roles/heating-can). Datapoint overlay for the One Base
|
||||
# ZigBee gateway on ECU 0x6c3 (BACKENDGATEWAY).
|
||||
#
|
||||
# Upstream declares ZigBee<N>DeviceCurrentValues as 57 bytes. A PAIRED device
|
||||
# slot on this gateway answers with 68: the 57 known bytes plus the parent
|
||||
# device serial and three trailing bytes. udsoncan reads only the declared
|
||||
# length and then treats the next two bytes -- the first half of that serial --
|
||||
# as a foreign data identifier, so every paired slot fails to decode with
|
||||
# "Server returned values for data identifier 0x.... that was not requested".
|
||||
# The bogus identifier differs per device because the serial does.
|
||||
#
|
||||
# This overlay re-declares the 29 paired slots at 68 bytes, reusing upstream's
|
||||
# sub-type list so the per-device-type O3ESwitch (climate sensor / TRV / floor
|
||||
# thermostat / actuator, incl. DeviceTemperatureSetpoint) is inherited rather
|
||||
# than copied.
|
||||
#
|
||||
# EMPTY slots answer with 57 and decode against the stock codec, so they are
|
||||
# left alone and are not polled. A slot that is later unpaired therefore starts
|
||||
# reporting a decode error on its topic instead of a value.
|
||||
#
|
||||
# Verified on ECU 0x6c3, 2026-09-17: 29 paired slots, all 68 bytes.
|
||||
from open3e.Open3Ecodecs import O3EComplexType, RawCodec
|
||||
from open3e.Open3Edatapoints import dataIdentifiers as _master
|
||||
|
||||
# ZigBee<N>DeviceCurrentValues of the paired slots: devices 1-20 run 2086..2143
|
||||
# in steps of 3, devices 21-29 continue at 2262..2286.
|
||||
_PAIRED_CURRENT_VALUES = [2086 + 3 * n for n in range(20)] + [
|
||||
2262 + 3 * n for n in range(9)
|
||||
]
|
||||
|
||||
|
||||
def _with_parent_serial(did):
|
||||
stock = _master["dids"][did]
|
||||
return O3EComplexType(
|
||||
68,
|
||||
stock.id,
|
||||
list(stock.subTypes)
|
||||
+ [
|
||||
RawCodec(8, "ParentDeviceSerialnumber"),
|
||||
RawCodec(2, "Unknown_65_66"),
|
||||
RawCodec(1, "Unknown_67"),
|
||||
],
|
||||
desc=stock.desc,
|
||||
info=stock.info,
|
||||
acc=stock.acc,
|
||||
)
|
||||
|
||||
|
||||
dataIdentifiers = {
|
||||
"dids": {
|
||||
**_master["dids"],
|
||||
**{did: _with_parent_serial(did) for did in _PAIRED_CURRENT_VALUES},
|
||||
}
|
||||
}
|
||||
@@ -129,6 +129,13 @@
|
||||
mode: "0644"
|
||||
notify: Restart heating-can
|
||||
|
||||
- name: Deploy open3e One Base datapoint overlay (68-byte ZigBee slots)
|
||||
ansible.builtin.copy:
|
||||
src: open3e-onebase.py
|
||||
dest: "{{ heating_can_base_dir }}/onebase.py"
|
||||
mode: "0644"
|
||||
notify: Restart heating-can
|
||||
|
||||
# --- Compose project ---------------------------------------------------------
|
||||
- name: Deploy the broker credential env file
|
||||
ansible.builtin.template:
|
||||
|
||||
@@ -17,6 +17,7 @@ services:
|
||||
volumes:
|
||||
- ./devices.json:/config/devices.json:ro
|
||||
- ./vx3plus.py:/config/vx3plus.py:ro
|
||||
- ./onebase.py:/config/onebase.py:ro
|
||||
env_file:
|
||||
# Broker credential, 0600 -- see heating-can.env.j2.
|
||||
- heating-can.env
|
||||
|
||||
@@ -70,7 +70,7 @@ spec:
|
||||
mountPath: /ca
|
||||
containers:
|
||||
- name: audiobookshelf
|
||||
image: ghcr.io/advplyr/audiobookshelf:2.36.0 # {"$imagepolicy": "flux-system:audiobookshelf"}
|
||||
image: ghcr.io/advplyr/audiobookshelf:2.36.1 # {"$imagepolicy": "flux-system:audiobookshelf"}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
|
||||
@@ -42,9 +42,11 @@ spec:
|
||||
type: RuntimeDefault
|
||||
initContainers:
|
||||
# Seed the libation-config PVC from the bootstrap secret on
|
||||
# first run only — sentinel file gates re-runs so token
|
||||
# refreshes in AccountsSettings.json and DB updates from
|
||||
# subsequent liberate passes are preserved.
|
||||
# first run only — sentinel file gates re-runs so DB updates
|
||||
# from subsequent liberate passes are preserved. Refreshed
|
||||
# Audible tokens are preserved by the container args, not by
|
||||
# this sentinel: the entrypoint copies AccountsSettings.json
|
||||
# into ephemeral /config-internal rather than symlinking it.
|
||||
- name: seed-config
|
||||
image: busybox:1.37.0
|
||||
command: ["/bin/sh", "-eu", "-c"]
|
||||
@@ -66,6 +68,11 @@ spec:
|
||||
install -m 660 /bootstrap/AccountsSettings.json /libation-state/AccountsSettings.json
|
||||
install -m 660 /bootstrap/Settings.json /libation-state/Settings.json
|
||||
install -m 660 /bootstrap/LibationContext.db /libation-state/LibationContext.db
|
||||
# Without this key the seeded tokens cannot be decrypted:
|
||||
# TokenStorageMethod is Encrypted and the container has no
|
||||
# OS secret store, so a missing key file makes the
|
||||
# entrypoint mint a throwaway one on every run.
|
||||
install -m 660 /bootstrap/libation-master.key /libation-state/libation-master.key
|
||||
touch /libation-state/.bootstrapped
|
||||
ls -la /libation-state
|
||||
volumeMounts:
|
||||
@@ -85,7 +92,23 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: libation
|
||||
image: docker.io/rmcrackan/libation:latest
|
||||
# Pinned by digest: Spegel caches mutable tag resolutions, so
|
||||
# :latest stayed on 13.7.11 despite imagePullPolicy Always.
|
||||
image: docker.io/rmcrackan/libation:14.2.1@sha256:6f9d8ba19dc3803dd6ba295ab5a7abfb6ff3bc06286eebf4ecd8ccbe43245888
|
||||
# The entrypoint stages config into ephemeral /config-internal
|
||||
# by copy (only the DB is symlinked to the PVC), so Audible
|
||||
# tokens refreshed during a run are lost at container exit.
|
||||
# Passing args makes the entrypoint set up the environment and
|
||||
# then exec this instead of its own loop; the trap copies the
|
||||
# refreshed file back to the PVC on every exit path.
|
||||
args:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
trap 'cp /config-internal/AccountsSettings.json /libation-state/AccountsSettings.json' EXIT
|
||||
set -e
|
||||
/libation/LibationCli scan
|
||||
/libation/LibationCli liberate
|
||||
env:
|
||||
- name: LIBATION_CONFIG_DIR
|
||||
value: /libation-state
|
||||
|
||||
@@ -43,7 +43,7 @@ spec:
|
||||
mountPath: /consume
|
||||
containers:
|
||||
- name: vsftpd
|
||||
image: gitea.renner.uno/k.renner/vsftpd:build.1784731469 # {"$imagepolicy": "flux-system:vsftpd"}
|
||||
image: gitea.renner.uno/k.renner/vsftpd:build.1789630783 # {"$imagepolicy": "flux-system:vsftpd"}
|
||||
ports:
|
||||
- name: ftp-ctrl
|
||||
containerPort: 21
|
||||
@@ -70,24 +70,19 @@ spec:
|
||||
- name: consume
|
||||
mountPath: /home/scanner
|
||||
subPath: scanner
|
||||
# FTP has no health verb, so the control port accepting a connection
|
||||
# is what can be asserted. It is enough for the failure that matters:
|
||||
# if vsftpd dies the scanner cannot deposit anything and nothing else
|
||||
# in the chain notices, because paperless only ever sees an empty
|
||||
# consume directory — which is indistinguishable from nobody having
|
||||
# scanned.
|
||||
#
|
||||
# Only the control port is probed. The passive range is negotiated
|
||||
# per transfer and is not listening at rest.
|
||||
# Asserts the control socket is listening without opening an FTP
|
||||
# connection: vsftpd 3.0.5 segfaults its listener on a fraction of
|
||||
# the connections it accepts. The passive range is negotiated per
|
||||
# transfer and is not listening at rest, so it is not probed.
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: ftp-ctrl
|
||||
exec:
|
||||
command: ["sh", "-c", "netstat -lnt | grep -q ':21 '"]
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: ftp-ctrl
|
||||
exec:
|
||||
command: ["sh", "-c", "netstat -lnt | grep -q ':21 '"]
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
|
||||
@@ -25,18 +25,15 @@ resources:
|
||||
configMapGenerator:
|
||||
# vsftpd config for the ftp-scanner deployment. The content-hash name
|
||||
# suffix is intentionally LEFT ENABLED (unlike home-assistant's nginx
|
||||
# config): vsftpd templates pasv_address from PASV_ADDRESS at container
|
||||
# start, so a changed value must roll the pod. With the hash suffix,
|
||||
# editing any value here renames the generated ConfigMap, which mutates
|
||||
# the Deployment's pod template and triggers an automatic rolling
|
||||
# restart. Without it, an IP change (e.g. an LB re-allocation) silently
|
||||
# leaves the running pod advertising the stale PASV address, which
|
||||
# breaks classic-PASV clients (older Brother scanners) while EPSV
|
||||
# clients keep working.
|
||||
# config): vsftpd reads these into vsftpd.conf at container start, so a
|
||||
# changed value must roll the pod, which the renamed ConfigMap does.
|
||||
#
|
||||
# No PASV_ADDRESS: the LB address is declared once, on the Service
|
||||
# (io.cilium/lb-ipam-ips). The image default 0.0.0.0 tells PASV clients
|
||||
# to reuse the control-connection address.
|
||||
- name: ftp-scanner-config
|
||||
namespace: paperless
|
||||
literals:
|
||||
- PASV_ADDRESS=10.2.99.82
|
||||
- PASV_MIN_PORT=21100
|
||||
- PASV_MAX_PORT=21104
|
||||
- FTP_USER=scanner
|
||||
|
||||
@@ -134,7 +134,7 @@ spec:
|
||||
# is a device-bricking class of risk. Bumps are a reviewed, manual
|
||||
# act — see kubernetes/infrastructure/image-automation/README.md.
|
||||
- name: terminus
|
||||
image: ghcr.io/usetrmnl/terminus:0.73.0 # {"$imagepolicy": "flux-system:terminus"}
|
||||
image: ghcr.io/usetrmnl/terminus:0.74.0 # {"$imagepolicy": "flux-system:terminus"}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 2345
|
||||
@@ -197,7 +197,7 @@ spec:
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
- name: sidekiq
|
||||
image: ghcr.io/usetrmnl/terminus:0.73.0 # {"$imagepolicy": "flux-system:terminus"}
|
||||
image: ghcr.io/usetrmnl/terminus:0.74.0 # {"$imagepolicy": "flux-system:terminus"}
|
||||
command: ["bundle", "exec", "sidekiq", "-r", "./config/sidekiq.rb"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
# Service; nothing on this path needs the host's network namespace.
|
||||
containers:
|
||||
- name: zone-configurator
|
||||
image: everythingsmarthome/everything-presence-mmwave-configurator:2.2.4 # {"$imagepolicy": "flux-system:mmwave-configurator"}
|
||||
image: everythingsmarthome/everything-presence-mmwave-configurator:2.2.5 # {"$imagepolicy": "flux-system:mmwave-configurator"}
|
||||
env:
|
||||
- name: HA_BASE_URL
|
||||
value: "http://home-assistant:80"
|
||||
|
||||
@@ -8,39 +8,64 @@ log stderr all;
|
||||
|
||||
protocol device { scan time 10; }
|
||||
|
||||
# This node's own pod CIDR, originated rather than read back out of the kernel.
|
||||
# Reading it back made BIRD's view depend on the shape of Cilium's route, and
|
||||
# the IPv4 one is rejected outright: its next hop sits inside the prefix it
|
||||
# describes, which KRT refuses with "strange next-hop". The prefix comes from
|
||||
# the Node object either way, so there is nothing to learn.
|
||||
# Everything a pod may need to reach, in one table, resolved by longest prefix:
|
||||
# the node's attached networks, the peers' pod CIDRs, and a default for the
|
||||
# rest. No rule has to carve out exceptions, because the specific routes win on
|
||||
# their own.
|
||||
#
|
||||
# blackhole because this route is only ever advertised. Nothing installs it:
|
||||
# the only kernel protocol exporting from these tables takes BGP routes.
|
||||
# The table is BIRD's alone. If this daemon stops, it empties, the rule finds
|
||||
# nothing and falls through to main — pod traffic returns to VLAN 40,
|
||||
# unseparated rather than misrouted. That is why the default route lives here
|
||||
# and not in the Talos config: a default that outlived the specific routes
|
||||
# would send pod traffic to the UDM.
|
||||
|
||||
# The node's attached networks, taken from the interfaces rather than written
|
||||
# down. The usual advice is not to export these to the kernel because it
|
||||
# maintains them itself — true of main, not of a table the kernel never fills.
|
||||
protocol direct attached {
|
||||
interface "mgmt0", "ingress0", "pod0";
|
||||
ipv4 { table master4; };
|
||||
ipv6 { table master6; };
|
||||
}
|
||||
|
||||
# Pod egress. GW4/GW6 are the UDM on the ingress segment, published as node
|
||||
# annotations beside the address that defines them.
|
||||
protocol static egress4 {
|
||||
ipv4 { table master4; };
|
||||
route 0.0.0.0/0 via GW4;
|
||||
}
|
||||
protocol static egress6 {
|
||||
ipv6 { table master6; };
|
||||
route ::/0 via GW6;
|
||||
}
|
||||
|
||||
# This node's own pod CIDR, via the interface Cilium serves it on. Originated
|
||||
# rather than read back out of the kernel: reading it back made BIRD's view
|
||||
# depend on the shape of Cilium's route, and the IPv4 one is rejected outright,
|
||||
# its next hop sitting inside the prefix it describes.
|
||||
#
|
||||
# It belongs in the table, not just in the BGP announcement. A reply from a pod
|
||||
# on another node is forwarded traffic with a pod source, so the rule matches it
|
||||
# too; without this route it would find only the default and leave for the UDM.
|
||||
# Measured — cross-node pod traffic, and with it cluster DNS, stopped.
|
||||
protocol static own4 {
|
||||
ipv4 { table master4; };
|
||||
route OWN_POD4 blackhole;
|
||||
route OWN_POD4 via "cilium_host";
|
||||
}
|
||||
protocol static own6 {
|
||||
ipv6 { table master6; };
|
||||
route OWN_POD6 blackhole;
|
||||
route OWN_POD6 via "cilium_host";
|
||||
}
|
||||
|
||||
# Only BGP-learned routes reach table 48 — not the static above, and not
|
||||
# anything else that may end up in the master tables.
|
||||
filter to_t48 {
|
||||
if source != RTS_BGP then reject;
|
||||
accept;
|
||||
}
|
||||
|
||||
protocol kernel k48v4 {
|
||||
kernel table 48;
|
||||
protocol kernel k4 {
|
||||
kernel table 47;
|
||||
metric 0;
|
||||
ipv4 { table master4; import none; export filter to_t48; };
|
||||
ipv4 { table master4; import none; export all; };
|
||||
}
|
||||
protocol kernel k48v6 {
|
||||
kernel table 48;
|
||||
protocol kernel k6 {
|
||||
kernel table 47;
|
||||
metric 0;
|
||||
ipv6 { table master6; import none; export filter to_t48; };
|
||||
ipv6 { table master6; import none; export all; };
|
||||
}
|
||||
|
||||
# iBGP over pod0. 64514 keeps clear of Cilium's 64513, which peers with the UDM
|
||||
@@ -49,19 +74,30 @@ protocol kernel k48v6 {
|
||||
# direct is explicit because iBGP defaults to multihop, which makes the gateway
|
||||
# recursive — BIRD would try to resolve the peer in the pod RIB. strict bind
|
||||
# keeps the listener off mgmt0 and ingress0.
|
||||
#
|
||||
# Only the node's own CIDR is offered: the export filter drops everything the
|
||||
# static protocols and the direct protocol produce.
|
||||
template bgp pod4 {
|
||||
local as 64514;
|
||||
source address MY4;
|
||||
direct;
|
||||
strict bind;
|
||||
ipv4 { next hop self; import all; export all; };
|
||||
ipv4 {
|
||||
next hop self;
|
||||
import all;
|
||||
export filter { if net = OWN_POD4 then accept; reject; };
|
||||
};
|
||||
}
|
||||
template bgp pod6 {
|
||||
local as 64514;
|
||||
source address MY6;
|
||||
direct;
|
||||
strict bind;
|
||||
ipv6 { next hop self; import all; export all; };
|
||||
ipv6 {
|
||||
next hop self;
|
||||
import all;
|
||||
export filter { if net = OWN_POD6 then accept; reject; };
|
||||
};
|
||||
}
|
||||
|
||||
include "/etc/bird/peers.conf";
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
define MY6 = {{ index .metadata.annotations "net.renner.uno/pod0-v6" }};
|
||||
define MY4 = {{ index .metadata.annotations "net.renner.uno/pod0-v4" }};
|
||||
define GW6 = {{ index .metadata.annotations "net.renner.uno/egress-gw-v6" }};
|
||||
define GW4 = {{ index .metadata.annotations "net.renner.uno/egress-gw-v4" }};
|
||||
{{/* podCIDRs follows the cluster's podSubnets order, v4 first. A flip puts an
|
||||
IPv6 prefix into an ipv4 protocol, which fails the parse in the init
|
||||
container rather than passing silently. */}}
|
||||
|
||||
@@ -126,6 +126,16 @@ spec:
|
||||
addressRanges:
|
||||
cluster:
|
||||
- 10.99.0.0/29
|
||||
# Reads go to the OSD in the client's own CRUSH location. crushLocationLabels
|
||||
# is unset, so Rook's default list applies; of those the nodes carry
|
||||
# topology.kubernetes.io/zone, one zone per node, matching the zone buckets
|
||||
# the OSDs are created under.
|
||||
#
|
||||
# Passed as a krbd/mount option at map time, so a volume keeps its current
|
||||
# behaviour until it is remounted.
|
||||
csi:
|
||||
readAffinity:
|
||||
enabled: true
|
||||
# Nodes listed explicitly, not useAllNodes: under useAllNodes the operator
|
||||
# re-provisions an OSD on any node whose device still carries a BlueStore
|
||||
# signature, so purging an OSD before pulling its drive is impossible — it
|
||||
|
||||
@@ -188,14 +188,13 @@ addresses:
|
||||
# network's clients selectively, so the client goes rather than the record.
|
||||
# DHCP is off on VLAN 47 for the same reason.
|
||||
#
|
||||
# Both defaults live in table 47, not in main, so only the pod-source rules in
|
||||
# site-homelab.yaml reach them. mgmt0 keeps main's default and with it every
|
||||
# reply the node sends from its own addresses.
|
||||
# No route here. Table 47 belongs to bird, which fills it with this node's
|
||||
# attached networks, the peers' pod CIDRs and the egress default, and empties it
|
||||
# if it stops — leaving the rule to fall through to main. A default declared
|
||||
# here would outlive the specific routes and send pod traffic to the UDM.
|
||||
#
|
||||
# The v6 gateway is the UDM's configured ULA on this segment, the same address
|
||||
# BGP peers with. The RA-learned link-local would be the alternative and is not
|
||||
# a value to write down. accept_ra_defrtr stays 0 for that reason: the RA
|
||||
# default would land in main, where it does not belong.
|
||||
# accept_ra_defrtr stays 0 on this link so the RA default does not land in main,
|
||||
# where mgmt0 owns it.
|
||||
apiVersion: v1alpha1
|
||||
kind: VLANConfig
|
||||
name: ingress0
|
||||
@@ -204,11 +203,6 @@ parent: uplink
|
||||
up: true
|
||||
addresses:
|
||||
- address: 10.2.47.11/24
|
||||
routes:
|
||||
- gateway: fd02:47::1
|
||||
table: "47"
|
||||
- gateway: 10.2.47.1
|
||||
table: "47"
|
||||
---
|
||||
# Dedicated Ceph cluster network on X710 port 1, VLAN 49, untagged on the
|
||||
# wire. OSD-to-OSD replication and nothing else. The udev rule in the machine
|
||||
|
||||
@@ -132,11 +132,6 @@ parent: uplink
|
||||
up: true
|
||||
addresses:
|
||||
- address: 10.2.47.12/24
|
||||
routes:
|
||||
- gateway: fd02:47::1
|
||||
table: "47"
|
||||
- gateway: 10.2.47.1
|
||||
table: "47"
|
||||
---
|
||||
# Dedicated Ceph cluster network on X710 port 1, VLAN 49, untagged on the
|
||||
# wire. OSD-to-OSD replication only, not routed off its own segment.
|
||||
|
||||
@@ -119,11 +119,6 @@ parent: uplink
|
||||
up: true
|
||||
addresses:
|
||||
- address: 10.2.47.13/24
|
||||
routes:
|
||||
- gateway: fd02:47::1
|
||||
table: "47"
|
||||
- gateway: 10.2.47.1
|
||||
table: "47"
|
||||
---
|
||||
# Dedicated Ceph cluster network — see k8s-1.yaml. The node holds NO address
|
||||
# here on purpose; the link is handed whole to the OSD pod (host-device).
|
||||
|
||||
@@ -20,62 +20,30 @@ machine:
|
||||
validSubnets:
|
||||
- 10.2.40.0/24
|
||||
- fd40::/64
|
||||
# The UDM on the ingress segment. Published here rather than as a route
|
||||
# because bird owns table 47 and originates the default from it; this is the
|
||||
# one place the address is stated.
|
||||
nodeAnnotations:
|
||||
net.renner.uno/egress-gw-v6: fd02:47::1
|
||||
net.renner.uno/egress-gw-v4: 10.2.47.1
|
||||
---
|
||||
# Pod egress leaves over ingress0. Matching on the pod source prefix is what
|
||||
# keeps the node's own traffic out of it: the node IP, the API VIP and apid all
|
||||
# answer on mgmt0, and a reply that left over ingress0 would carry an mgmt0
|
||||
# source onto VLAN 47.
|
||||
# All pod traffic goes to table 47, and the table decides — longest prefix
|
||||
# match, no exceptions to carve out. bird fills it with the node's attached
|
||||
# networks, the peers' pod CIDRs and the egress default, so a packet to a node
|
||||
# on the mgmt segment takes the connected route, a packet to a pod on another
|
||||
# node takes pod0, and anything else takes the default over ingress0.
|
||||
#
|
||||
# The routing decision runs before Cilium's masquerade hook, so the rule sees
|
||||
# the pod address and the masquerade then rewrites it to ingress0's.
|
||||
# Matching the source is what keeps the node's own traffic out: it answers on
|
||||
# mgmt0, and a reply leaving over ingress0 would carry an mgmt0 source onto
|
||||
# VLAN 47. The routing decision runs before Cilium's masquerade hook, so the
|
||||
# rule sees the pod address and the masquerade then rewrites it.
|
||||
#
|
||||
# Table 47 holds nothing but the two default routes, so the destination rules
|
||||
# below have to run first: without them a pod's packet to a service or to a pod
|
||||
# on another node also matches the source rule, finds only the default, and
|
||||
# leaves for the UDM. Measured — in-cluster DNS stopped resolving.
|
||||
#
|
||||
# They send everything inside the cluster and the local networks back to main.
|
||||
# The prefixes are Cilium's ipv4/ipv6NativeRoutingCIDR, which already mark what
|
||||
# is not foreign traffic.
|
||||
# If bird stops, table 47 empties, the lookup misses and the rule falls through
|
||||
# to main — pod traffic returns to VLAN 40, unseparated rather than misrouted.
|
||||
#
|
||||
# Priorities are below main (32766) and clear of the rule Cilium installs at 9.
|
||||
# The two families are separate rule tables, so the priorities need not match;
|
||||
# the document name is the priority and must be unique.
|
||||
# Pod to pod between nodes goes over pod0, via table 48, which the bird mesh
|
||||
# fills with one route per peer pod CIDR and the peer's pod0 address as the
|
||||
# next hop. Ahead of 990/991 so it wins for that traffic, and matching source
|
||||
# AND destination so nothing else lands there: the node's own traffic to a pod
|
||||
# would otherwise leave with a pod0 source, which CiliumNode does not list, so
|
||||
# Cilium would classify it as world rather than host.
|
||||
#
|
||||
# Traffic to a pod on this node falls through — table 48 holds only the peers'
|
||||
# CIDRs, so the lookup misses and 990/991 send it to main and cilium_host.
|
||||
apiVersion: v1alpha1
|
||||
kind: RoutingRuleConfig
|
||||
name: "980"
|
||||
src: fd10:244::/48
|
||||
dst: fd10:244::/48
|
||||
table: "48"
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: RoutingRuleConfig
|
||||
name: "981"
|
||||
src: 10.244.0.0/16
|
||||
dst: 10.244.0.0/16
|
||||
table: "48"
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: RoutingRuleConfig
|
||||
name: "990"
|
||||
dst: fd00::/8
|
||||
table: main
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: RoutingRuleConfig
|
||||
name: "991"
|
||||
dst: 10.0.0.0/8
|
||||
table: main
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: RoutingRuleConfig
|
||||
name: "1000"
|
||||
|
||||
Reference in New Issue
Block a user