Kubernetes
Install the Helm chart, choose server roles, assign maintenance, and add KEDA autoscaling.
Install the Helm chart, choose server roles, assign maintenance, and add KEDA autoscaling.
The repository includes a Helm chart for running flexiq-server on Kubernetes.
Start with the chart instead of assembling the scheduler, dashboard, Services,
probes, and optional sidecar injector by hand.
The chart source is available in the repository today. OCI publishing begins with the first server release after this workflow change; until that release is publicly visible in GHCR, install from a matching server tag:
git clone https://github.com/ByteVeda/flexiq.git
cd flexiq
git checkout server-v2.0.0
helm install flexiq ./deploy/helm/flexiq-server \
--set storage.dsn='postgres://flexiq:secret@postgres:5432/myapp' \
--set attach.token="$(openssl rand -base64 32)"After a chart release is published and made public, install its matching OCI
version with helm install flexiq oci://ghcr.io/byteveda/charts/flexiq-server --version <released-version> .... The chart and server image versions move
together; the image tag defaults to the chart's appVersion.
For production, keep credentials out of command history and values files. Put
the DSN and attach token in Kubernetes Secrets, then use
storage.existingSecret and attach.existingSecret. See
values.yaml
for every value and its source-level comments.
Upgrade from the same source you installed from — check out the new
server-vX.Y.Z tag first, so the chart moves with the version you are moving to:
helm upgrade flexiq ./deploy/helm/flexiq-server --reset-then-reuse-valuesOnce the chart is published, the OCI form is helm upgrade flexiq oci://ghcr.io/byteveda/charts/flexiq-server --version <released-version> --reset-then-reuse-values. Either way, prefer --reset-then-reuse-values over
--reuse-values when the chart version changes: the latter reuses the previous
release's computed values, so a default introduced by the new chart is never
applied.
One release can enable any combination of four listener roles:
| Value | Default | What it runs |
|---|---|---|
attach.enabled | true | The attach listener and scheduler for connected executors |
dashboard.enabled | true | The dashboard SPA, JSON API, health routes, and metrics |
webhook.enabled | false | The admission webhook that injects executor sidecars |
grpc.enabled | false | The gRPC producer door on a separate Service |
At least one role must be enabled. storage.dsn is required unless the release
runs only the webhook, which rewrites pod specs and does not read jobs. The
chart supports Postgres and Redis; it rejects SQLite because replicas cannot
share a pod-local database file safely.
The attach listener requires a strong token when it binds beyond loopback. The
gRPC role instead authenticates scoped API tokens stored in the database and
requires namespace to name the tenant it serves. Enable only the listeners
that the release needs:
attach:
enabled: true
dashboard:
enabled: true
webhook:
enabled: false
grpc:
enabled: falseThe chart validates unsafe combinations during helm template and helm install, before they can become crash-looping pods.
Both listeners can terminate TLS themselves, from a kubernetes.io/tls
Secret — the kind cert-manager issues — rather than needing a sidecar or a
mesh in front of the pod:
namespace: prod
attach:
enabled: true
tls:
secretName: flexiq-attach-tls
grpc:
enabled: true
tls:
secretName: flexiq-grpc-tlsA certificate is checked against the name its client dialled, so the attach
one must name the attach Service executors reach,
<fullname>-attach.<namespace>.svc, and the gRPC one the gRPC Service's.
The chart mounts each Secret's tls.crt/tls.key at
/etc/flexiq/{attach,grpc}-tls and points the matching
FLEXIQ_{LISTEN,GRPC}_TLS_CERT/_TLS_KEY at the mounted files — see
The gRPC door for what the server does
with them, reload included. Because the server polls the mounted files for
changes rather than reading them once, renewing the Secret needs no
checksum/ annotation and no restart: cert-manager's rotation takes effect
on its own, within a minute of the kubelet projecting the new files.
For mTLS, add the CA bundle that signs client certificates:
attach:
tls:
secretName: flexiq-attach-tls
clientCaSecretName: flexiq-attach-client-ca
clientCaKey: ca.crt # defaultclientCaSecretName mounts at /etc/flexiq/{attach,grpc}-client-ca and sets
_TLS_CLIENT_CA from the key named by clientCaKey, ca.crt unless
overridden. grpc.tls takes the same two fields. Setting
clientCaSecretName without secretName is refused — mTLS with no
certificate to pair it with — at helm template time, before it reaches a
cluster.
Kubelet's grpc: probe has no TLS mode. With grpc.tls.secretName set, the
chart falls back to a tcpSocket readiness probe on its own — a grpc: probe
here would fail every check and take the pod out of rotation permanently.
Every server can schedule and claim jobs through shared storage, but only one
replica needs to run the retention sweeps — every replica running them would be
every replica issuing the same deletes. The chart maps its maintenance value
to FLEXIQ_MAINTENANCE, which renders as the string on or off rather than a
boolean:
replicaCount: 1
maintenance: trueA value belongs to the whole Deployment, so replicaCount: 3 cannot turn
maintenance on for only one of its three identical pods. Use one single-replica
release as the maintenance owner and set maintenance: false on any additional
replicated releases:
# The one maintenance owner
helm install flexiq-primary ./deploy/helm/flexiq-server \
--values primary-values.yaml \
--set replicaCount=1 \
--set maintenance=true
# Extra replicas sharing the same Postgres or Redis store
helm install flexiq-workers ./deploy/helm/flexiq-server \
--values workers-values.yaml \
--set replicaCount=3 \
--set maintenance=falseOnly the maintenance owner's pod gets FLEXIQ_MAINTENANCE=on. Every pod in the
replicated release gets FLEXIQ_MAINTENANCE=off and runs no retention sweeps,
so the deletes are never duplicated across replicas. What off does not turn
off is dead-worker reaping and in-flight recovery — those stay on everywhere, on
purpose. See Running flexiq-server.
Use distinct release names and expose only the Services clients should reach. All releases must use the same storage and namespace when they cooperate on the same queues.
With webhook.enabled=true, the chart installs an admission webhook that adds
an executor to an opted-in workload. The injected container reuses the
workload's own image, so it can import the same task handlers:
helm upgrade flexiq ./deploy/helm/flexiq-server --reset-then-reuse-values \
--set webhook.enabled=truemetadata:
annotations:
flexiq.dev/inject: "true"
flexiq.dev/attach: "flexiq-flexiq-server-attach.default.svc:7777"
flexiq.dev/command: "flexiq executor --app myapp:queue"
flexiq.dev/slots: "4"
flexiq.dev/token-secret: "flexiq-flexiq-server-config"
flexiq.dev/token-key: "attach-token"flexiq.dev/command is SDK-specific. Use the executor command for your
application image; the full manual deployment shapes are in
Deployment.
With attach.tls.secretName set, the sidecar dials tls://. It inherits the
app's environment but not its volume mounts, so name the CA and, for mTLS, the
client certificate as Secrets; the webhook mounts them and sets
FLEXIQ_ATTACH_TLS_CA/_CERT/_KEY:
flexiq.dev/attach: "tls://flexiq-flexiq-server-attach.default.svc:7777"
flexiq.dev/tls-ca-secret: "flexiq-attach-tls" # key ca.crt; tls-ca-key overrides
flexiq.dev/tls-client-secret: "myapp-executor-cert" # kubernetes.io/tls, for mTLSA rotated certificate takes effect when the sidecar next attaches; see Deployment for the rotation details.
The webhook defaults to failurePolicy: Ignore, so an unavailable injector
does not block pod creation cluster-wide. Change it to Fail after workloads
depend on injection and the webhook is monitored.
The Helm chart installs flexiq-server; it does not install KEDA or create a
KEDA scaling object. The repository provides three starting manifests under
deploy/keda/:
| Manifest | Use it when |
|---|---|
scaled-object.yaml | KEDA should scale a long-running worker Deployment from FlexiQ's HTTP scaler endpoint |
scaled-object-prometheus.yaml | Prometheus already scrapes FlexiQ queue depth and worker utilization |
scaled-job.yaml | Each batch of pending work should create a short-lived Kubernetes Job |
Copy the appropriate manifest, then replace its example Deployment name, namespace, scaler or Prometheus address, queue, thresholds, and image before applying it:
cp deploy/keda/scaled-object.yaml my-flexiq-scaled-object.yaml
$EDITOR my-flexiq-scaled-object.yaml
kubectl apply -f my-flexiq-scaled-object.yamlThe HTTP examples require a reachable flexiq scaler Service; the Prometheus
example requires the matching metrics collector and scrape configuration.
KEDA autoscaling documents the
scaler commands, endpoints, trigger fields, and security boundary for each SDK.
With the dashboard enabled, liveness uses /health and readiness uses
/readiness. The chart enables dashboard.publicReadiness by default so the
kubelet can reach the storage-aware readiness route without copying a secret
into a literal probe header. /metrics remains protected.
The generated Secret is hashed into the pod template, so changing a chart-owned
attach token or admin password triggers a rollout. Helm cannot hash an external
Secret during template rendering; after rotating an existingSecret, restart
the Deployment explicitly. The chart names it <release>-flexiq-server, so the
flexiq release installed above renders as flexiq-flexiq-server:
kubectl rollout restart deploy/flexiq-flexiq-serverFor the process model, raw Kubernetes manifests, gRPC topology, storage, backups, and production sizing, continue with Deployment.