Skip to main content

Helm chart

Chart path: deploy/helm/rivora. The rivora CLI installs the same chart (embedded in each release), so rivora install --set ... and helm install ... --set ... take identical values.

It installs:

  • rivorad, a privileged host-network DaemonSet (the dataplane and per-node reconcilers);
  • rivora-controller, a two-replica Deployment (IPAM and status, Lease-elected);
  • the CRDs (AddressPool, ServicePolicy, BGPPeer), RBAC and service accounts, a PodDisruptionBudget for the controller, and, if asked, seeded AddressPools and a GatewayClass.

Install

helm install rivora deploy/helm/rivora \
--namespace rivora-system --create-namespace \
--set rivorad.interface=eth0 \
--set addressPools[0].name=default \
--set addressPools[0].addresses='{10.0.0.0/24}'

rivorad.interface is required: the interface every node attaches XDP and TCX to. A cluster whose nodes name their interface differently is not supported by one release; pin the DaemonSet to matching nodes with rivorad.nodeSelector. Run rivora-doctor --interface <if> on a node first.

Dual-stack pools

helm upgrade rivora deploy/helm/rivora --namespace rivora-system --reuse-values \
--set addressPools[1].name=v6 --set addressPools[1].addresses='{2001:db8:1::/64}'

Large /64s allocate sparsely: see IPv6.

Values

The main ones (all documented in values.yaml):

PathDefaultMeaning
image.rivorad.*, image.controller.*ghcr.io/zyvorai/rivora-*, tag = chart appVersionImages (cosign-signed, with an SBOM).
rivorad.interface""Required. Interface XDP/TCX attach to.
rivorad.xdpModegenericgeneric, native or auto. See XDP attach mode.
rivorad.bpfDir/usr/local/share/rivora/bpfWhere the BPF objects are in the image.
rivorad.loadBalancerClass""Must equal controller.loadBalancerClass.
rivorad.workers2Reconcile workers.
rivorad.speakertrueThe L2 ARP + NDP speaker. Set false to use BGP instead and to honour externalTrafficPolicy: Local.
rivorad.apiKey, rivorad.apiReadOnlyKey""Admin and read-only API keys (in a Secret). See API.
rivorad.tls.selfSigned, .cert, .keyoffHTTPS for the pod-local API.
rivorad.securityContext.privilegedtrueNeeded to create pins under /sys/fs/bpf; the same posture as other eBPF DaemonSets.
rivorad.resources, .nodeSelector, .tolerations, .affinitysmall requests, tolerate allScheduling.
servicePolicy.enabledtrueHonour ServicePolicy. Adds the RBAC and -service-policy.
gatewayApi.enabledfalseAlso run the Gateway API reconcilers. Needs the Gateway API CRDs installed first.
gatewayClass.create, .namefalse, rivoraCreate a GatewayClass for Rivora.
bgp.enabledfalseThe BGP speaker.
bgp.asn, bgp.routerId, bgp.ipv6NextHopSpeaker identity and next hops.
bgp.peers[][]address, asn, bfd. For more, use bgp.configSecret or BGPPeer resources.
bgp.configSecret""A Secret whose bgp.yaml holds a bgp: section; replaces asn, routerId, ipv6NextHop and peers.
bgp.peerResourcestrueAlso read BGPPeer resources; grants RBAC to read them, the node, and Secrets in the release namespace.
addressPools[][]Seeded AddressPools (name, addresses, autoAssign, avoidBuggyIPs).
controller.replicaCount2One works, one is a hot standby.
controller.podDisruptionBudget.enabled, .minAvailabletrue, 1Keeps a replica up during drains. minAvailable must be below replicaCount.
controller.networkPolicy.enabledfalseRestrict ingress to the controller's metrics port.
rbac.create, serviceAccount.*, imagePullSecretsStandard.

CRD lifecycle

The CRDs live in the chart's crds/ directory. Helm installs them on the first helm install but never upgrades or deletes them: not on helm upgrade, not on rivora upgrade, not on uninstall. When a release adds or changes a CRD, apply it yourself before upgrading:

kubectl apply -f deploy/helm/rivora/crds/

This matters most when upgrading from a release that predates a CRD:

  • ServicePolicy or BGPPeer missing: rivorad probes for the CRD at start-up, logs that the feature is ignored, and carries on serving Services (an informer on a CRD the cluster does not serve would never sync and would stall everything). Apply the CRD and restart the DaemonSet.
  • AddressPool schema changed: a chart that assumes a newer schema than the installed one surfaces as rivora-controller reconcile errors reading AddressPool, not as a Helm failure. Diff crds/addresspool-crd.yaml against kubectl get crd addresspools.rivora.zyvor.dev -o yaml first.

All three CRDs ship one version (v1alpha1) and no conversion webhook, so additive schema changes apply in place. See the chart README's Evolving the schema checklist for a breaking one.

Upgrading

  1. Read the release notes and apply any changed CRDs (above).
  2. helm upgrade (or rivora upgrade). The DaemonSet rolls one node at a time; watch kubectl rollout status daemonset/rivora-rivorad -n rivora-system. Surviving nodes keep serving every VIP.
  3. A restarted rivorad adopts the datapath the maps still hold and reclaims its VIPs as the reconcilers catch up, so the disruption is the brief re-attach of the XDP program unless -persist-datapath is on. A release that changes the shape of a BPF map will not load against maps from an older version; the release notes say so.

Permissions

rivorad gets a ClusterRole to read Services, EndpointSlices, Leases (read and write, for the speaker), Events, and, only when the feature is on, ServicePolicy; BGPPeer and Nodes (get); Gateway API objects, ReferenceGrants and Namespaces; plus a namespaced Role to read Secrets in the release namespace only (for BGPPeer passwords). rivora-controller additionally writes Service and Gateway status and patches finalizers. The chart's RBAC is the authoritative list.

Observability

Both workloads serve /healthz, /readyz and /metrics on port 9871 (plain HTTP, no authentication), wired into the liveness and readiness probes and annotated prometheus.io/scrape: "true". This is separate from rivorad's API port (9870, loopback) because a host-network pod's 127.0.0.1 is not reachable from an in-cluster Prometheus. The metrics, alerts and how to read them: Metrics and the runbook.