This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a Kubernetes operator for Restate, written in Rust. The operator manages three main Custom Resource Definitions (CRDs):
RestateCluster- Manages Restate server clusters with StatefulSetsRestateDeployment- Manages Restate SDK service deployments (similar to Kubernetes Deployments but with Restate-specific versioning)RestateCloudEnvironment- Integrates with Restate Cloud environments
The operator enforces network isolation by default, handles service versioning/draining, and supports both ReplicaSet and Knative Serving deployment modes.
Required Rust version: 1.80+
# Build the operator
just build
# Build with specific features, architecture, or libc
just build features="telemetry"
just build arch="amd64"
just build libc="musl"
# Build Docker image
just docker
# Run the operator locally (requires OPERATOR_NAMESPACE env var)
OPERATOR_NAMESPACE=restate-operator RUST_LOG=info cargo runThe operator uses code generation for CRDs and Pkl schemas:
# Generate CRD YAML files from Rust code
just generate
# Generate Pkl schema templates
just generate-pkl
# Generate example YAML from Pkl templates
just generate-examplesImportant: When modifying CRD structs in src/resources/*.rs, run just generate to regenerate the YAML files in crd/.
# Run all tests
cargo test
# Run a specific test
cargo test <test_name># Install CRDs into the current Kubernetes cluster
just install-crds
# Note: Uses kubectl create (not apply), so remove existing CRDs first if needed# Format code
just fmt
# Run clippy linter
just lint
# Run both formatting and linting
just checkThe operator runs three concurrent controllers (see src/main.rs:94-105):
- RestateCluster Controller (
src/controllers/restatecluster/) - Manages StatefulSets, Services, NetworkPolicies for Restate server clusters - RestateCloudEnvironment Controller (
src/controllers/restatecloudenvironment/) - Manages tunnel deployments for Restate Cloud integration - RestateDeployment Controller (
src/controllers/restatedeployment/) - Manages service deployments with versioning and draining
Each controller implements a reconciliation loop using the Kube-rs runtime.
CRD structs are defined in src/resources/:
restateclusters.rs- RestateCluster CRDrestatedeployments.rs- RestateDeployment CRDrestatecloudenvironments.rs- RestateCloudEnvironment CRDknative.rs- Knative Serving resource definitions
Additional AWS-specific resources:
podidentityassociations.rs- AWS EKS Pod Identitysecuritygrouppolicies.rs- AWS Security Groups for Podssecretproviderclasses.rs- CSI Secret Provider
RestateDeployment supports two modes (see src/resources/restatedeployments.rs:20-28):
- ReplicaSet (default): Traditional Kubernetes Deployment pattern
- Knative: Knative Serving with Configuration-per-tag architecture
Key Concept - Tag-Based Identity:
- A Restate deployment is immutable once registered
- The
tagfield determines deployment identity - Same tag = in-place update (new Knative Revision within same Restate deployment)
- Changed tag = versioned update (new Restate deployment ID)
- No tag = template hash as tag (every template change creates new deployment)
By default, RestateCluster enforces network isolation via NetworkPolicies:
- Allows peer-to-peer traffic between Restate pods
- Allows operator access to admin API
- Allows egress to public internet and coredns
- Allows traffic to namespaces labeled with
allow.restate.dev/<cluster-name> - Denies all other traffic
Disable with spec.security.disableNetworkPolicies: true
Controllers share state via src/controllers/mod.rs:State:
- Diagnostics (last reconciliation events)
- Prometheus metrics registry
- Operator configuration (namespace, labels, AWS settings)
Always use kubectl apply --server-side for CRDs to avoid client-side validation issues.
When developing with Knative deployment mode using locally built images:
Problem: Knative Serving's revision-controller resolves image tags to digests before creating pods. This fails for local-only images that don't exist in a remote registry.
Solution: Use the dev.local image prefix
# Build your service image
docker build -t ghcr.io/restatedev/restate-operator/greeter:latest \
examples/services/greeter/
# Tag with dev.local prefix for Knative compatibility
docker tag ghcr.io/restatedev/restate-operator/greeter:latest \
dev.local/restatedev/restate-operator/greeter:latestWhy it works:
dev.localis in Knative's defaultregistries-skipping-tag-resolvinglist- Knative skips digest resolution for images with this prefix
- No cluster configuration changes needed
- Works out-of-the-box with standard Knative Serving installations
Key differences between deployment modes:
- ReplicaSet mode: Can use any image prefix (e.g.,
ghcr.io/*) withimagePullPolicy: Never - Knative mode: Must use
dev.local/*prefix for local images
Alternative prefixes (also in default skip list):
ko.local- Used by thekobuild toolkind.local- Convention for Kind clusters
All example Knative manifests in examples/services/greeter/k8s/knative-*.yaml already use the dev.local prefix.
Use the helper function from src/controllers/mod.rs:79-94:
use crate::controllers::service_url;
let url = service_url("service-name", "namespace", 9070, Some("/path"), "cluster.local")?;Error types are defined in src/lib.rs. Use the Error enum for controller errors:
NotReady- Cluster not ready (triggers requeue)DeploymentNotReady- Deployment not ready (triggers requeue)InvalidRestateConfig- Configuration validation errorsSecretNotFound/SecretKeyNotFound- Missing credentials
Both RestateCluster and RestateDeployment use finalizers for cleanup:
clusters.restate.dev- Ensures namespace deletiondeployments.restate.dev- Ensures service deregistration and ReplicaSet cleanup
Important: The operator aligns with Restate's invocation retention model when determining deployment activity:
- Invocation retention: Completed invocations remain in
sys_invocation_statusfor 24 hours (default) before automatic purging - Deployment status: A deployment is considered "active" if it has ANY invocation in
sys_invocation_status, including completed ones - Cleanup timing: Configurations tied to "active" deployments are retained until Restate purges the invocations
- Manual override: Use
restate invocations purge <id>to immediately purge completed invocations for testing
Deployment states (restate deployments list):
Active: Has latest service revisionDraining: Superseded but has pinned invocations (including completed)Drained: Superseded and all invocations purged (active_inv == 0)
SQL tables:
sys_invocation_status: ALL invocations including completed (used by operator'slist_deploymentsquery)sys_invocation: Same content as sys_invocation_status- Both tables include a
statuscolumn to filter by invocation state
The operator's cleanup logic intentionally waits for Restate's invocation purge before considering a deployment truly inactive, ensuring Configuration lifecycle aligns with Restate's internal state management.
The Helm chart is located in charts/restate-operator-helm/.
To deploy a locally built operator image:
# 1. Generate CRDs
just generate
# 2. Build Docker image (tagged as ghcr.io/restatedev/restate-operator:local)
just docker
# 3. Deploy or upgrade with Helm
helm upgrade --install restate-operator charts/restate-operator-helm \
--namespace restate-operator \
--create-namespace \
--set version=local
# 4. Force rollout (Helm upgrade doesn't always trigger pod restart with imagePullPolicy: IfNotPresent)
kubectl delete pod -n restate-operator -l app=restate-operatorImportant:
- The chart uses the
versionparameter (notimage.tag) to set the image tag. Theversionfield defaults to.Chart.Versionif not specified. - After
helm upgrade, you may need to manually delete the operator pod to force a rollout and pull the new local image, especially when usingimagePullPolicy: IfNotPresent.
version- Image tag override (defaults to chart version)image.repository- Image repository (default:ghcr.io/restatedev/restate-operator)image.pullPolicy- Pull policy (default:IfNotPresent)awsPodIdentityAssociationCluster- Enables EKS Pod Identity supportgcpWorkloadIdentity- Enables GCP Workload Identity via Config ConnectorcanaryImage- Container image for canary jobs and the trusted CA certs init container (default:alpine:3.21); must providecat,grep,wgetand a CA bundle at/etc/ssl/certs/ca-certificates.crtoperatorNamespace- Namespace where operator runsoperatorLabelName/Value- Labels for network policy selectors
- Update version in
charts/restate-operator-helm/Chart.yamlandCargo.{toml,lock} - Push tag
v<version>(e.g.,v0.0.2) - Accept the draft release after CI completes
# Set required environment variables
export OPERATOR_NAMESPACE=restate-operator
export RUST_LOG=info
# Run with cargo
cargo runThe operator exposes HTTP endpoints on port 8080:
/health- Health check/metrics- Prometheus metrics/- Diagnostics (last event timestamp)
# View RestateCluster status
kubectl get restatecluster -A
# View RestateDeployment status with details
kubectl get restatedeployment -o wide
# Check operator logs
kubectl logs -n restate-operator -l app=restate-operator