High availability
High availability
A default install is a single-node stack: one Postgres, one Redis, one Kafka, one of everything else. That is the right shape for a pilot and the wrong shape for production. Here is what to change.
Data services
Postgres and Kafka run under an operator by default, CloudNativePG for Postgres and Strimzi for Kafka, and there is no mode switch to flip for HA: the operator is already reconciling a real clustered topology, it just defaults to one replica. Raise the counts:
helm upgrade my-mvno oci://ghcr.io/solvegio/charts/platform \
--version <version> --namespace my-mvno -f my-values.yaml \
--set postgres.instances=3 \
--set kafka.replicas=3 \
--set kafka.replicationFactor=3 \
--set kafka.minInsync=2
postgres.instances and kafka.replicas both default to 1, which renders and reconciles perfectly well but gives you no redundancy at all. Three Postgres instances survive losing a node. kafka.replicationFactor and kafka.minInsync default to 1 too; raise them alongside kafka.replicas or a 3-broker cluster still writes every event to just one broker.
Redis is the exception: it stays a single bundled instance regardless, because there is no Redis operator behind it. If you need the cache itself to survive a node failure, point redis.mode: external at a managed Redis instead; that is the only HA path for the cache. Full details for all three, including the credential handling, are in Data services.
API replicas and disruption budgets
highAvailability:
enabled: true
priorityClassName: ""
api:
replicas: 2
auth:
replicas: 2
highAvailability.enabled renders a PodDisruptionBudget for the API and auth Deployments, a soft zone spread across topology.kubernetes.io/zone, and per-node anti-affinity so two replicas do not land on the same machine.
Handle priorityClassName before you enable it
Note the empty priorityClassName above. It is doing real work.
highAvailability.enabled also stamps priorityClassName: solvegio-platform onto the API and auth pods, and no PriorityClass by that name exists in your cluster until you make one. Kubernetes rejects a pod that names a PriorityClass it cannot resolve, so if you turn HA on and leave that value at its default you get zero API pods and zero auth pods. A working install goes to nothing. Decide which of the two you want before the upgrade:
Set it empty, as in the snippet. The field is then not rendered at all and your pods schedule at normal priority. This is the right choice unless you have a reason to want otherwise.
Or create the class first. Worth doing on a shared cluster where you want platform pods to evict lower-priority workloads under node pressure:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: solvegio-platform
value: 1000000
globalDefault: false
description: Solvegio platform pods
Apply that, then leave highAvailability.priorityClassName at its default and the pods pick it up.
Replicas and HA move together
Raise the replica counts and enable HA in the same change. The two are coupled: a PodDisruptionBudget with minAvailable: 1 against a single replica blocks every node drain, because there is no way to evict the only pod without violating the budget. Enabling HA while leaving replicas: 1 gives you a cluster you cannot upgrade.
If you want the API to scale under load rather than sit at a fixed floor, api.autoscaling.enabled adds an HPA on top. The replica count then acts as the floor a drain can always fall back to.
The license agent runs one replica
The license agent is deliberately a single replica with a Recreate rollout strategy. Do not raise replicas on it. It is not supported, and it is not an oversight in the chart.
Two agents running at once means two writers against the same license, and the parent sees that as exactly what it looks like: something has gone wrong with this deployment's licensing. You get a false alarm on an otherwise healthy platform, and somebody has to come and unpick it. There is nothing to gain: a second replica does not renew your lease any faster.
The agent is not on the request path, so a single replica is not a single point of failure for your customers. The API reads the lease from a mounted file and keeps serving whether the agent is up or not. Everything the agent does (renewing the lease, rotating the key) happens well ahead of expiry, so the pod can be down for a rollout, a node drain or a short outage with no visible effect. What the agent's absence eventually costs you is the lease itself: if it stays down long enough for the held lease to expire, the platform fails closed. That is the bound to plan against, and Observability is how you see it coming.
No PodDisruptionBudget is rendered for the agent, for the same reason. A budget against a single replica would block your node drains.
What is left after all that
Even fully configured, the failure that takes a self-hosted platform down is almost never a pod. It is the lease running out because nobody was watching the alert. Wire SolvegioLicenseAtRisk before you wire anything else.