| description | Step-by-step guide to deploying and operating Spice.ai workloads with the Kubernetes Operator. |
|---|---|
| icon | book-open |
This guide walks through deploying and operating Spice.ai on Kubernetes with the operator — from installing the controller to configuring storage, networking, rollouts, scaling, and observability. For exhaustive field references, see SpicepodSet and SpicepodCluster.
{% hint style="info" %}
All manifests below use the current spice.ai/v2 API version. Existing spice.ai/v1 / spice.ai/v1alpha1 manifests continue to apply and are converted automatically — see Migrating from spice.ai/v1.
{% endhint %}
- Kubernetes 1.33+
- Helm 3.x
kubectlconfigured for your cluster- Access to a Spice runtime image (the enterprise image is pulled from the AWS Marketplace ECR registry; a pull secret is required)
The operator is distributed as an OCI Helm chart. Subscribe to the AWS Marketplace listing, authenticate to the Marketplace ECR registry, then install into its own namespace:
helm install spiceai-operator \
oci://709825985650.dkr.ecr.us-east-1.amazonaws.com/spice-ai/charts/spiceai-operator \
--namespace spiceai-operator-system --create-namespaceThe chart installs the SpicepodSet and SpicepodCluster CRDs by default (crds.enabled: true). Verify the controller is running:
kubectl -n spiceai-operator-system get pods
kubectl get crds | grep spice.aiSee the Overview for the full list of Helm values.
{% hint style="info" %}
To connect this cluster to Spice.ai Cloud so that Spice.ai Cloud can deploy and observe Spicepods in your account (Bring-Your-Own-Cluster), install with spice.managedMode.enabled=true and an enrollment token — see Bring-Your-Own-Cluster (BYOC).
{% endhint %}
A SpicepodSet is the simplest way to run a Spicepod. Create spicepodset.yaml:
apiVersion: spice.ai/v2
kind: SpicepodSet
metadata:
name: my-spicepod
namespace: default
spec:
replicas: 1
spicepod:
name: my-spicepod
kind: Spicepod
version: v1
# datasets, catalogs, models, views, ... go hereApply it and watch it come up:
kubectl apply -f spicepodset.yaml
kubectl get spicepodset my-spicepod -w
# NAME READY ROLE AGE LAST UPDATED
# my-spicepod 1/1 <none> 30s 5sEach SpicepodSet is deployed as one or more StatefulSets — one per replica, each with an ordinal suffix — so every pod has a stable identity and predictable DNS name, even with a single replica. The spicepod object is stored in a ConfigMap and mounted into each pod; editing it triggers a rollout.
Port-forward and query the runtime:
kubectl port-forward svc/my-spicepod 8080:8080
curl http://localhost:8080/healthThe sections below cover the most common configuration tasks. Each maps to a field on spec; see the SpicepodSet reference for every option.
spec:
image:
repository: 709825985650.dkr.ecr.us-east-1.amazonaws.com/spice-ai/spiceai-enterprise-plan
tag: 2.0.0-enterprise-models
pullPolicy: IfNotPresentrepository is the full registry + name path. To use a public build instead, set repository: spiceai/spiceai (Docker Hub) or repository: ghcr.io/spiceai/spiceai.
Provide credentials with a pull secret:
kubectl create secret docker-registry spice-pull-secret \
--docker-server=709825985650.dkr.ecr.us-east-1.amazonaws.com \
--docker-username=AWS --docker-password="$(aws ecr get-login-password)"spec:
image:
repository: 709825985650.dkr.ecr.us-east-1.amazonaws.com/spice-ai/spiceai-enterprise-plan
tag: 2.0.0-enterprise-models
pullSecret: spice-pull-secretspec:
http:
port: 8090
flight:
port: 50051
metrics:
port: 9090These set what the Spiced container listens on. The managed Service always exposes fixed ports 8080 (HTTP), 50051 (Flight), and 9090 (metrics), mapping targetPort to the configured Spiced ports above.
spec:
resources:
requests:
cpu: 200m
memory: 1Gi
limits:
cpu: "2"
memory: 4GiThe runtime derives its thread pools, query partitioning, and accelerator concurrency from a single CPU entitlement, which it detects from the processors available to it: the pod's cgroup CPU quota where resources.limits.cpu sets one, and otherwise the cores on the node. A requests.cpu value is a scheduling floor rather than a ceiling, so it is never inferred as an entitlement.
You may want to leave limits.cpu unset to allow bursting above the requested CPU, which performs better when other workloads on the machine are idle. In such cases, if the machine has significantly more cores than requested (or than are typically used), you may find the runtime is oversized — more threads and query partitions than the CPU it actually gets. Set the entitlement directly with runtime.cpu.cores:
spec:
spicepod:
name: my-spicepod
kind: Spicepod
version: v1
runtime:
cpu:
cores: 4 # `auto` (the default) detects itcores accepts a Kubernetes CPU quantity — 4, 3.5, or 3500m — or auto. The equivalent environment variable is SPICE_CPU_CORES, which is useful when the entitlement should track the pod spec rather than the Spicepod:
spec:
env:
- name: SPICE_CPU_CORES
value: "2"Precedence is the --cpu-cores command-line flag, then SPICE_CPU_CORES, then runtime.cpu.cores, then detection. The effective value, its source, and the derived sizing are logged at startup and exported as the spiced_cpu_budget_cores gauge — see Observability.
{% hint style="warning" %}
The CPU entitlement is applied at startup only. The thread pools it sizes cannot be resized by editing the Spicepod, so changing cores requires a rollout.
{% endhint %}
spec:
env:
- name: SPICE_LOG_LEVEL
value: debug
- name: MY_SECRET
valueFrom:
secretKeyRef:
name: my-secret
key: api-key
envFromSource:
- secretRef:
name: my-secretThe Spicepod YAML supports ${secrets:KEY} references to Kubernetes Secret values. Inject the backing Secret with envFromSource so the value resolves inside the pod at runtime.
Attach per-replica volumes with the volumeClaimTemplates list; the operator creates a PersistentVolumeClaim per entry, per StatefulSet. The entry named data (the default when metadata.name is omitted) is auto-mounted at /data:
spec:
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: standard
resources:
requests:
storage: 10GiIncreasing the storage request triggers automatic PVC resizing when the StorageClass has allowVolumeExpansion: true. Shrinking is not supported.
spec:
serviceAccount:
enabled: true
create: true
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/spice-ai-roleSet create: false and name: to reuse an existing ServiceAccount.
A NetworkPolicy is opt-in — the operator only creates one when you supply network.ingress and/or network.egress, and writes those rules verbatim. No implicit DNS, operator-namespace, or cluster-peer rules are added, so include a DNS egress rule yourself:
spec:
network:
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53The admission webhook warns when egress omits a DNS rule. See Network and DNS for the full schema.
spec:
replicas: 5
updateStrategy:
type: RollingParallel # RollingOrdered (default) | RollingParallel | BlueGreen
maxUnavailable: 2RollingOrdered(default) — one pod at a time, waiting for Ready.RollingParallel— parallel updates bounded bymaxUnavailable.BlueGreen— brings up a full parallel generation, then atomically switches the Service.
The legacy Parallel strategy has been removed; resources specifying it are converted to RollingOrdered.
Retain the previous generation after a rollout so traffic can be cut back instantly:
spec:
updateStrategy:
type: BlueGreen
standbyVersion:
enabled: true
retentionPeriodSeconds: 900To roll back, re-apply the previous spec (helm rollback, kubectl apply of an earlier manifest, or a Git revert). Because the trigger is a content-based SHA of the spec, rollback is GitOps-compatible with ArgoCD and Flux — no operator-owned annotation to round-trip. See Standby Versions & Instant Rollback.
Change replicas to scale. Set replicas: 0 to pause the workload while retaining the Service, ConfigMap, ServiceAccount, and any user-supplied NetworkPolicy:
spec:
replicas: 0annotations and labels propagate to every managed resource. Changing either triggers a full rollout — a convenient restart mechanism:
spec:
annotations:
rollout-trigger: "2024-06-01T12:00:00Z"
labels:
environment: productionOperator-reserved spice.ai/* keys are rejected by the admission controller on spec.labels / spec.annotations.
spec:
probes:
liveness:
initialDelaySeconds: 10
periodSeconds: 30
readiness:
initialDelaySeconds: 5
periodSeconds: 10
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values: [amd64]
tolerations:
- key: dedicated
operator: Equal
value: spice
effect: NoSchedule
terminationGracePeriodSeconds: 30For query workloads that benefit from dedicated scheduler and executor nodes, use a SpicepodCluster. The operator provisions mTLS certificates and manages child SpicepodSets automatically:
apiVersion: spice.ai/v2
kind: SpicepodCluster
metadata:
name: my-cluster
namespace: default
spec:
schedulerSpec:
replicas: 1
spicepod:
name: my-cluster-scheduler
kind: Spicepod
version: v1
executorSpec:
replicas: 3Executors pull their Spicepod configuration from the scheduler, so executorSpec needs no spicepod field. See the SpicepodCluster reference for mTLS, port separation, and per-node overrides.
To add a Spice sidecar to any existing Pod, annotate its template and point at a ConfigMap holding spicepod.yaml:
template:
metadata:
annotations:
spice.ai/inject: "true"
spice.ai/inject-config: demo-spice-configThe ConfigMap must exist before the Pod is created. See Sidecar Injection for all supported annotations.
kubectl works natively thanks to standard status conditions:
kubectl get spicepodset
kubectl wait spicepodset/my-spicepod --for=condition=Ready
# Operator HTTP status API (port 8090)
kubectl -n spiceai-operator-system port-forward deploy/spiceai-operator 8090:8090
curl http://localhost:8090/default/my-spicepodThe status API returns per-pod details (phase, IP, Spiced health/readiness, error reasons). Paused SpicepodSets report paused: true with a pauseReason. For operator self-telemetry over Prometheus and OTLP, see Operator Metrics.
The operator monitors pods for repeated failures and, when the configured threshold is exceeded, pauses the workload (replicas → 0), sets status.pauseReason = CrashLooping, and emits a Warning event. Configure the threshold with the Helm value pauseCrashloopingPodsThreshold (the chart ships 0, disabled, by default). To recover, fix the configuration and set replicas back to the desired count.
helm upgrade spiceai-operator \
oci://709825985650.dkr.ecr.us-east-1.amazonaws.com/spice-ai/charts/spiceai-operator \
--namespace spiceai-operator-system \
--values my-values.yamlv2 is served with automatic conversion of legacy resources, so existing SpicepodSet / SpicepodCluster manifests continue to apply after the upgrade.
- SpicepodSet reference — every
SpicepodSetfield. - SpicepodCluster reference — distributed clusters and mTLS.
- Bring-Your-Own-Cluster (BYOC) — connect this cluster to Spice.ai Cloud.
- Operator Metrics — Prometheus scraping and OTLP push.
- Overview — installation, Helm values, CLI, and roadmap.