The two ways to scale
Replicas of one Instance
spec.replicas: N. One Service, one credential, one configuration; the operator balances across the pods.Several Instances
One
Instance per purpose (AIOps, chat for a team, another provider). Each has its own Deployment, Service, credentials and encryption key.Replicas
- With
replicas> 1 the Service is headless, and every new connection resolves to one of the ready pods. - Each pod is an independent server: its own memory, watcher and
emptyDirstate. Nothing is shared between replicas except the sessions volume, when there is one. - The operator’s alert stream attaches to one pod at a time, so the Instance that drives AIOps gains nothing from extra replicas.
Several Instances
- Create as many as you need, in one namespace or several.
- AIOps attaches to exactly one Instance, the one pinned with
aiops.instancein the operator chart (CHATCLI_OPERATOR_AIOPS_INSTANCE=<namespace>/<name>). The other Instances serve chat and API traffic and are never picked up implicitly. - Each Instance gets its own encryption-at-rest key Secret,
<instance>-atrest-key. Back up each one (see Encryption at Rest).
Persistence decides the rollout
spec.persistence.enabled gives the sessions a ReadWriteOnce PVC. That one setting changes how the Instance can scale and how it rolls:
The shutdown drain
When a pod is replaced, it leaves the Service endpoints and the cluster DNS a few seconds after it receives SIGTERM (DNS caches Service records for 5 seconds). A server that stopped listening at once would refuse the connections that still resolve it. WithCHATCLI_SERVER_SHUTDOWN_DELAY, the server answers health checks with NOT_SERVING at once, so load balancers and health-checking clients move away, but keeps serving requests for the delay, and only then stops.
- The operator sets
5son every Instance without persistence. ACHATCLI_SERVER_SHUTDOWN_DELAYentry inspec.extraEnvreplaces it (0sturns it off). - The Helm chart of the server sets it through
server.shutdownDelay(see Docker & Kubernetes). - Keep it well below the pod’s
terminationGracePeriodSeconds(30 seconds by default).
Recommended layout
One Instance for AIOps that keeps its state, and a pool of stateless replicas for chat traffic.1
Keep the AIOps Instance at one replica, with persistence
Recreate). During it the WatcherBridge logs Alert stream ended; reopening and reconnects by itself.2
Add a stateless pool for chat traffic
preferredanti-affinity spreads the pods across nodes when it can, and still schedules them when there are fewer nodes than replicas. Userequiredonly when you have at least as many nodes as replicas.- The TLS certificate must be valid for
chat-pool.<namespace>.svc.cluster.local. You can reuse one Secret for several Instances if its SANs cover all of their names. - Sessions live in
emptyDir: a client whose pod is replaced starts a new session.
3
Check both
READY true, the pool’s pods are on different nodes, its strategy is RollingUpdate, and the operator’s Connected to Instance line names only the AIOps Instance.What a rollout costs, measured
A probe pod ranchatcli healthcheck against each Instance’s Service every 0.3 seconds while a change to spec.extraEnv rolled it. The cluster was kind with one control plane and two workers, node-local storage, ChatCLI 1.221.1, plus a build of the server with the shutdown drain.
The two persistent replicas landed on the same node, the one the local-path volume was bound to. Requiring them on different nodes left the second pod
Pending, as described in the warning above.
To reproduce it, build a probe pod from busybox with a chatcli binary for the node’s architecture and the CA of the Instance mounted, then loop:
Limits to know
- The operator creates no HorizontalPodAutoscaler or PodDisruptionBudget for Instances. Add your own if you autoscale or drain nodes often; an HPA on an Instance competes with
spec.replicas, so prefer adjustingspec.replicas. - Replicas do not share memory or sessions. Put state that must survive a pod in persistence (one replica) or outside the server.
- The operator itself scales separately:
replicaCount: 2or more withleaderElect: truein its chart. The controllers run on the leader; the dashboard and REST API answer on every replica.