Design Overview
This document explains what the valdoperatorrelease controller does and how it works.
It is grounded in the current code; see the referenced files for the source of truth.
What it is
A Kubernetes controller that manages the lifecycle of Vald
clusters through a single custom resource, ValdOperatorRelease (short name vor,
API group vald.vdaas.org/v1).
From a small, high-level input the controller generates one or more
ValdRelease (VRS) resources — the
manifests that the Vald Helm Operator (VHO) reconciles into a running Vald cluster.
ValdOperatorRelease (vor) ──reconcile──▶ ValdRelease (VRS) × (active infra × clusters)
minimal input consumed by vald-helm-operator
The controller only generates VRS definitions. It does not run Vald itself; a VHO running in the target cluster turns each VRS into Vald pods. This separation lets a management cluster emit VRS definitions and distribute them to other clusters (for example via an external workflow engine).
The ValdOperatorRelease (MVRS) resource
The goal of MVRS is to collapse the large ValdRelease configuration surface into the
minimum a user must supply. There are two input groups.
- Infrastructure / node-pool information —
spec.infrastructure[] - Minimal Vald settings —
spec.vectorEngine.vald
Source of truth: internal/k8s/vald/operator/api/v1/valdoperatorrelease_types.go.
spec.infrastructure[]
A list of infrastructure entries. Each entry produces VRS objects for its clusters.
| Field | Notes |
|---|---|
role | Free-form role label (e.g. hot, standby, blue, green). Copied to VRS labels. |
type | Cluster type label. |
active | When false, the entry is skipped during VRS generation. |
clusters[] | { id, name }. The id field is typically filled in by an external system once the target cluster exists; name must be set. |
nodePools | Map keyed by pool type: general (required) and agent (optional). Each pool carries name, replicas, and machineResource{ cpu, memory, storage }. |
The machineResource values drive per-component resource and replica calculations for
the generated VRS.
spec.vectorEngine.vald
The minimal Vald configuration reflected into each VRS:
defaults.logLevelagent.ngt—dimension(required, ≥2),creationEdgeSize,searchEdgeSize,distanceType,objectTypeagent.persistentVolume—{ enabled, storageClass, accessMode }(storageClass/accessMode fall back to env defaults when omitted)indexer—indexSchedule,saveSchedule,concurrency,manager, suspend flags, durationsgateway—indexReplica,serviceType,ingress{ enabled, host }discoverer.kind—DaemonSetorDeploymentoverlay— an arbitrary JSON patch merged onto the generated VRS (see Overlay below)
Lifecycle
Reconciliation is modeled as an ordered flow of phases. Each phase carries a Condition
plus an optional Builder (creates/updates resources) and Checker (reports readiness).
Phases (pkg/operator/vald/service/phase.go):
Phase (status.phase) | Builder | Checker | Purpose |
|---|---|---|---|
WaitingClusterCreate | – | yes | Validate infra config; wait until every cluster has id and name. |
WaitingCreateVrs | yes | yes | Build the VRS objects and wait until they are ready. |
Completed | – | – | Terminal phase. |
Accumulating, self-healing conditions
status.conditions accumulates as phases progress (conditions are never removed),
and every reconcile re-evaluates all conditions. This is intentional: if a
previously-True condition later breaks (e.g. a generated VRS is deleted, or the spec
changes), the controller detects it and restarts work from that phase. status.phase
tracks the condition currently being processed.
See pkg/operator/vald/service/reconciler.go (reconcileValdOperatorRelease /
reconcilePhase) and pkg/operator/vald/service/phase.go.
Readiness result states
A phase check returns one of four result states
(pkg/operator/vald/service/phase.go):
| Result | Condition status | Meaning |
|---|---|---|
Progressing("") | Unknown / Progressing | The controller is actively working. |
Pending("msg") | Unknown / Pending | Waiting on something external (e.g. external system assigning a cluster ID). |
Succeeded() | True / Succeeded | Done. |
Failed(err) | False / Failed | Misconfiguration or unrecoverable error. |
For the cluster-create check: a missing cluster.id/cluster.name yields Pending
(external system not done yet), while empty clusters or empty infrastructure yields
Failed (misconfiguration). See pkg/operator/vald/service/reconciler.go.
VRS generation flow
Implemented in pkg/operator/vald/service/builder.go. vrsBuilder.Build is a pure
function of (CR, Config, Capability) — it makes no Kubernetes API calls.
- Validate the CR (
infrastructurenon-empty, each cluster has aname). - Iterate
infrastructure[], skipping entries whereactive == false(and, when node-pool matching is enabled, entries with no matching general pool). - Assemble the VRS spec from the CR (all in
pkg/operator/vald/service/builder.go):buildDefaults— log levelbuildGateway—indexReplica, service type, ingressbuildAgent— NGT settingsbuildManager— Manager mode whenindexer.manager == true, otherwise Creator/Saver modebuildDiscoverer— kind + namespaced RBAC names
- Resolve resources from node pools —
SetScaledResourcesderives replicas and per-component CPU/memory from the resolved agent node pool. The general-pool fallback rule lives inresolveAgentNodePool(pkg/operator/vald/service/rules.go). - Optional settings — persistent volume and node affinities (both in
builder.go). - Per cluster — for each
infra.clusters[], setname = <namespace>-<clusterName>(truncated to 63 chars), apply labels (namespace,type,role), then merge the overlay. One VRS is produced per cluster, so a single MVRS can yield many VRS objects.
Applying generated resources (ResourceSyncer)
The controller delegates the write side to resource.Syncer
(internal/k8s/resource/syncer.go):
Buildthe desired objects, thenCreateOrUpdateeach with an owner reference and amanaged-generationlabel set to the owner’s.metadata.generation.- Prune: list owned resources of the same kind and delete any that the current
Buildno longer produces (stale generations).
Overlay
spec.vectorEngine.vald.overlay is merged onto the generated VRS on top of the default
VRS template loaded at startup (DEFAULT_VRS_PATH). The merge uses internal/config.Merge
(reflection-based deep merge, later elements override earlier). See mergeOverlay in
pkg/operator/vald/service/builder.go.
Multi-cluster distribution and node-pool matching
The same MVRS can be distributed to multiple clusters, generating a VRS only where a matching node pool exists.
- With
REQUIRE_NODEPOOL_MATCH=true, the controller lists Nodes and resolves aNodePoolCapability; a VRS is generated only when ageneralpool is present (pkg/operator/vald/service/capability.go). - Node match is by labels
namespace=<vor namespace>andtype=general(required);type=agentis optional — when present, agent pods are scheduled ontotype=agent, otherwise they fall back totype=general. - Label keys default to
namespace/type; setNODEPOOL_LABEL_PREFIX(e.g.vald.vdaas.org) to namespace them. - With
REQUIRE_NODEPOOL_MATCH=false(default) the controller skips node listing and treats every infra entry as schedulable (AlwaysAvailable).
Configuration (environment variables)
Loaded once at startup into config.Config (pkg/operator/vald/config/config.go).
| Env var | Default | Purpose |
|---|---|---|
DEFAULT_VRS_PATH | /opt/valdoperatorrelease/config/vrs.yaml | Default VRS template merged with the overlay. |
REQUIRE_NODEPOOL_MATCH | false | Only generate VRS where matching node pools exist. |
NODEPOOL_LABEL_PREFIX | "" | Prefix for the namespace/type/role node labels. |
AGENT_PODS_PER_NODE | 2 | Agent pods packed per node when computing replicas. |
DEFAULT_STORAGE_CLASS | standard | PV storage class fallback. |
DEFAULT_ACCESS_MODE | ReadWriteOnce | PV access mode fallback. |
PV_BUFFER_RATIO | 1.5 | PV size = max(memoryRequest * ratio, min). |
PV_MIN_SIZE_BYTES | 1073741824 | Minimum PV size in bytes. |
ENABLE_INGRESS | true | Enable gateway ingress generation. |
GATEWAY_INGRESS_ANNOTATIONS | "" | YAML map of annotations applied to the gateway ingress. |
GATEWAY_SERVICE_TYPE | NodePort | Gateway service type (NodePort / ClusterIP / LoadBalancer). |
VRS_LOG_LEVEL | warn | Log level passed through to the generated VRS. |
DISCOVERER_DS_MAX_SURGE | 30% | Discoverer DaemonSet rolling-update maxSurge. |
DISCOVERER_DS_MAX_UNAVAILABLE | 0% | Discoverer DaemonSet rolling-update maxUnavailable. |
Source map
| Concern | Location |
|---|---|
| CRD types | internal/k8s/vald/operator/api/v1/ |
| Reconciler | pkg/operator/vald/service/reconciler.go |
| Resource apply / prune | internal/k8s/resource/syncer.go |
| Phase / conditions | pkg/operator/vald/service/phase.go |
| Node-pool rules | pkg/operator/vald/service/rules.go |
| VRS builder | pkg/operator/vald/service/builder.go |
| Node-pool capability | pkg/operator/vald/service/capability.go |
| VRS API model | internal/k8s/vald/operator/api/valdrelease/ |
| Config / env | pkg/operator/vald/config/config.go |
| Entry point | cmd/operator/vald/main.go |
| Manifests / CRDs | k8s/operator/vald/ |