Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,21 @@ jobs:
platforms: linux/amd64,linux/arm64
secrets: inherit

publish-fabric-router-image:
permissions:
id-token: write
contents: read
packages: write
attestations: write
uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.20.0
with:
image-name: fabric-router
dockerfile-path: containers/fabric-router/Dockerfile
platforms: linux/amd64,linux/arm64
secrets: inherit

publish-kustomize-bundles:
needs: [publish-galactic-cni-image, publish-galactic-router-image]
needs: [publish-galactic-cni-image, publish-galactic-router-image, publish-fabric-router-image]
permissions:
id-token: write
contents: read
Expand All @@ -45,4 +58,5 @@ jobs:
images: |
- { path: config/cni, name: ghcr.io/datum-cloud/galactic-cni, tag: "${{ needs.publish-galactic-cni-image.outputs.tag }}" }
- { path: config/router/base, name: ghcr.io/datum-cloud/galactic-router, tag: "${{ needs.publish-galactic-router-image.outputs.tag }}" }
- { path: config/fabric, name: ghcr.io/datum-cloud/fabric-router, tag: "${{ needs.publish-fabric-router-image.outputs.tag }}" }
secrets: inherit
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Summary:
- **`config/router/tenant-control/`** — the BGP route-reflector role (`galactic-router-control`, `GALACTIC_ROUTER_REFLECTOR=true`); opt-in only, requires nodes labeled `galactic.datumapis.com/node: control` (stays at zero replicas otherwise). `GALACTIC_ROUTER_BGP_LOCAL_ADDRESS` is auto-detected from the host's `lo` interface by default; see the comments in `daemonset-patch.yaml` for when to override it.
- **`config/router/base/`** — the DaemonSet spec shared by both roles; not applied directly.
- Apply the whole router component (both roles) with `kubectl apply -k config/router/`, or a single role with e.g. `kubectl apply -k config/router/tenant/`.
- **`config/fabric/`** — the FRR underlay eBGP DaemonSet (`fabric-router`; `galactic-router` needs a working underlay before it can start). Unlike `config/router/`, this is a single flat DaemonSet with no `tenant`/`tenant-control`-style role split — its affinity matches nodes labeled `galactic.datumapis.com/node` `In` `[edge, control]` directly, since (unlike galactic-router's route-reflector role) there's no env/config difference between running on a regular node vs. the control node. **Not** part of the root `config/kustomization.yaml` and not covered by `kubectl apply -k config/` — unlike every other component here, it has no generic default: the `frr.conf` it needs (interface addresses, remote-AS, etc.) is site-specific, so the deployer must hand-author a `fabric-config` ConfigMap (just `frr.conf` — `daemons`/`vtysh.conf` are baked into the `fabric-router` image) before applying `kubectl apply -k config/fabric/`.
- **`deploy/containerlab/`** — ContainerLab topology (`gvpc.clab.yaml`) for three Kind clusters (dfw, iad, sjc) wired over an IPv6 SRv6 transit mesh. FRR runs as a hostNetwork DaemonSet on each worker for eBGP underlay; `galactic-router` (tenant role) handles EVPN path distribution over iBGP, and the iad route reflector builds on `config/router/tenant-control/`. See `deploy/containerlab/README.md` and `deploy/containerlab/Taskfile.yaml` for bring-up commands.

## New Developer Entry Points
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,27 @@ kubectl apply -k config/

Each component can also be applied on its own, e.g. `kubectl apply -k config/router` for just the router (both roles) or `kubectl apply -k config/router/tenant` for just the per-node role.

`config/fabric/` (the FRR underlay eBGP DaemonSet `galactic-router` depends on) is **not** included in `kubectl apply -k config/` and has its own prerequisite below — apply it separately once you've met that prerequisite:

```bash
kubectl apply -k config/fabric/
```

#### Prerequisites

- **Container images.** `.github/workflows/publish.yaml` builds `ghcr.io/datum-cloud/galactic-cni` and `ghcr.io/datum-cloud/galactic-router` (from `containers/galactic-cni/Dockerfile` and `containers/galactic-router/Dockerfile` respectively) on every push and release — but it never publishes a `:latest` tag, only date-stamped tags per push/release (e.g. `v0.0.0-main-20260713-170924`) and, for tagged releases, semver tags. The `image:` references committed in `config/cni/daemonset.yaml` and `config/router/base/daemonset.yaml` say `:latest` only as a placeholder that CI substitutes with a real published tag when it builds the `ghcr.io/datum-cloud/galactic-kustomize` OCI Kustomize bundle — that substitution never happens in the git checkout itself. Applying `config/` directly from a clone will therefore fail to pull `:latest`. Before applying, resolve the current tag (check the [package pages](https://github.com/orgs/datum-cloud/packages?repo_name=galactic) or the latest successful run of `publish.yaml` on `main`) and pin it, e.g.:
- **Container images.** `.github/workflows/publish.yaml` builds `ghcr.io/datum-cloud/galactic-cni`, `ghcr.io/datum-cloud/galactic-router`, and `ghcr.io/datum-cloud/fabric-router` (from `containers/galactic-cni/Dockerfile`, `containers/galactic-router/Dockerfile`, and `containers/fabric-router/Dockerfile` respectively) on every push and release — but it never publishes a `:latest` tag, only date-stamped tags per push/release (e.g. `v0.0.0-main-20260713-170924`) and, for tagged releases, semver tags. The `image:` references committed in `config/cni/daemonset.yaml`, `config/router/base/daemonset.yaml`, and `config/fabric/daemonset.yaml` say `:latest` only as a placeholder that CI substitutes with a real published tag when it builds the `ghcr.io/datum-cloud/galactic-kustomize` OCI Kustomize bundle — that substitution never happens in the git checkout itself. Applying `config/` directly from a clone will therefore fail to pull `:latest`. Before applying, resolve the current tag (check the [package pages](https://github.com/orgs/datum-cloud/packages?repo_name=galactic) or the latest successful run of `publish.yaml` on `main`) and pin it, e.g.:

```bash
cd config/cni && kustomize edit set image ghcr.io/datum-cloud/galactic-cni=ghcr.io/datum-cloud/galactic-cni:<resolved-tag>
cd config/router/base && kustomize edit set image ghcr.io/datum-cloud/galactic-router=ghcr.io/datum-cloud/galactic-router:<resolved-tag>
cd config/fabric && kustomize edit set image ghcr.io/datum-cloud/fabric-router=ghcr.io/datum-cloud/fabric-router:<resolved-tag>
```

- **`config/fabric/`: site-specific `frr.conf`.** Unlike every other component under `config/`, `config/fabric/daemonset.yaml` has no generic default config — the underlay eBGP session (interface addresses, remote-AS, etc.) is different for every site. Before applying `config/fabric/`, create a `fabric-config` ConfigMap in the `galactic-system` namespace with your site's `frr.conf` under that key (the other two files FRR needs, `daemons` and `vtysh.conf`, are already baked into the `fabric-router` image — see `containers/fabric-router/Dockerfile`). `deploy/containerlab/resources/fabric/{dfw,iad,sjc}/frr.conf` are worked examples from the lab, not something you can apply as-is.

- **Talos: gRPC health port.** `galactic-router` runs `hostNetwork: true` and defaults to gRPC health checks on port `5000`, which collides with Talos's built-in dashboard (`/sbin/dashboard` permanently binds `127.0.0.1:5000` on every Talos node). `config/router/base/daemonset.yaml` already ships with `GALACTIC_ROUTER_GRPC_HEALTH_PORT=5179` (and matching probe/containerPort) to avoid this; if you run `galactic-router` outside these manifests on Talos, set `GALACTIC_ROUTER_GRPC_HEALTH_PORT` to something other than `5000` yourself.

- **`galactic-router` tenant mode: BGP local address.** The node needs a global-unicast IPv6 address assigned to `lo` (typically by an underlay/fabric BGP daemon that starts before `galactic-router`), or you must set `GALACTIC_ROUTER_BGP_LOCAL_ADDRESS` explicitly — this is required even when `GALACTIC_ROUTER_BGP_LISTEN_PORT=-1` (no inbound listener), since `galactic-router` still needs a source address for outbound BGP connections. Without one of these, startup fails with `GALACTIC_ROUTER_BGP_LOCAL_ADDRESS not set and no address could be detected on lo: no global-unicast IPv6 address found on lo`. See [`docs/router/configuration.md`](./docs/router/configuration.md) for details.
- **`galactic-router` tenant mode: BGP local address.** The node needs a global-unicast IPv6 address assigned to `lo` (typically by `config/fabric/`'s underlay eBGP daemon, which must start and converge before `galactic-router`), or you must set `GALACTIC_ROUTER_BGP_LOCAL_ADDRESS` explicitly — this is required even when `GALACTIC_ROUTER_BGP_LISTEN_PORT=-1` (no inbound listener), since `galactic-router` still needs a source address for outbound BGP connections. Without one of these, startup fails with `GALACTIC_ROUTER_BGP_LOCAL_ADDRESS not set and no address could be detected on lo: no global-unicast IPv6 address found on lo`. See [`docs/router/configuration.md`](./docs/router/configuration.md) for details.

See [`docs/router/configuration.md`](./docs/router/configuration.md) for the full `galactic-router` CLI flag / environment variable reference — note that env var names generally follow `GALACTIC_ROUTER_<FLAG_NAME>` but aren't always the naive uppercased guess (e.g. `--mode` is `GALACTIC_ROUTER_ROUTER_MODE`, not `GALACTIC_ROUTER_MODE`); the reference table has the exact name for every flag.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: iad-control-fabric
name: fabric-router
namespace: galactic-system
labels:
app.kubernetes.io/name: fabric-router
spec:
selector:
matchLabels:
app.kubernetes.io/name: iad-control-fabric
app.kubernetes.io/name: fabric-router
template:
metadata:
labels:
app.kubernetes.io/name: iad-control-fabric
app.kubernetes.io/name: fabric-router
spec:
hostNetwork: true
# FRR here establishes the underlay eBGP session to the physical fabric
# and brings up the node's lo address, both of which galactic-router
# depends on before it can start — so this must tolerate NotReady the
# same way a CNI plugin does, or it never gets scheduled early enough.
tolerations:
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoSchedule
- key: galactic.datumapis.com/node
value: control
effect: NoSchedule
# Opt-in only, same as galactic-cni and galactic-router: runs on every
# node labeled either for regular tenant traffic or for the
# galactic-router route-reflector role, since both need underlay
# connectivity; new node types (GPU, monitoring, etc.) don't
# automatically get fabric.
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
Expand All @@ -30,14 +38,23 @@ spec:
- key: galactic.datumapis.com/node
operator: In
values:
- edge
- control
initContainers:
- name: frr-init
image: quay.io/frrouting/frr:10.2.0
# ":latest" is a placeholder only: no such tag is ever pushed to
# GHCR. CI resolves and stamps a real version tag here when it
# publishes the ghcr.io/datum-cloud/galactic-kustomize OCI bundle
# (see .github/workflows/publish.yaml); applying this manifest
# directly from a git checkout will fail to pull the image unless
# you override the tag first — see the README's "Production
# Deployment" section.
image: ghcr.io/datum-cloud/fabric-router:latest
command:
- sh
- -c
- |
cp /etc/frr-defaults/* /etc/frr/
cp /tmp/frr-config/* /etc/frr/
install -d -o frr -g frr -m 775 /run/frr
install -d -o frr -g frr -m 775 /var/log/frr
Expand All @@ -57,7 +74,7 @@ spec:
mountPath: /var/log/frr
containers:
- name: frr
image: quay.io/frrouting/frr:10.2.0
image: ghcr.io/datum-cloud/fabric-router:latest
securityContext:
capabilities:
add:
Expand All @@ -72,9 +89,14 @@ spec:
- name: frr-log
mountPath: /var/log/frr
volumes:
# fabric-config is NOT generated by this kustomization — the deployer
# must create it themselves with a site-specific frr.conf (interface
# addresses, remote-AS, etc. for the local underlay fabric). There is
# no generic default that peers correctly everywhere. See the
# README's "Production Deployment" section.
- name: frr-config-source
configMap:
name: iad-control-fabric-config
name: fabric-config
- name: frr-etc
emptyDir: {}
- name: frr-run
Expand Down
2 changes: 2 additions & 0 deletions config/fabric/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
resources:
- daemonset.yaml
21 changes: 21 additions & 0 deletions containers/fabric-router/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Fabric router image: the upstream FRR image used for the eBGP fabric/transit
# roles in deploy/containerlab, layered with common network debugging tools
# so issues can be triaged from inside the container without a separate
# toolbox image.
ARG FRR_VERSION=10.2.0
FROM quay.io/frrouting/frr:${FRR_VERSION}

RUN apk add --no-cache \
tcpdump \
mtr \
busybox-extras \
bind-tools \
curl

# Static FRR config common to every fabric/transit role. Copied to a
# non-standard path (rather than /etc/frr directly) because /etc/frr is an
# emptyDir in the fabric DaemonSets, populated at pod start by the frr-init
# container so frr.conf can still be swapped in per site; the daemonset's
# init command seeds it from here before layering the site-specific config
# in on top.
COPY containers/fabric-router/daemons containers/fabric-router/vtysh.conf /etc/frr-defaults/
Loading