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.

  1. Infrastructure / node-pool information — spec.infrastructure[]
  2. 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.

FieldNotes
roleFree-form role label (e.g. hot, standby, blue, green). Copied to VRS labels.
typeCluster type label.
activeWhen 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.
nodePoolsMap 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.logLevel
  • agent.ngt — dimension (required, ≥2), creationEdgeSize, searchEdgeSize, distanceType, objectType
  • agent.persistentVolume — { enabled, storageClass, accessMode } (storageClass/accessMode fall back to env defaults when omitted)
  • indexer — indexSchedule, saveSchedule, concurrency, manager, suspend flags, durations
  • gateway — indexReplica, serviceType, ingress{ enabled, host }
  • discoverer.kind — DaemonSet or Deployment
  • overlay — 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)BuilderCheckerPurpose
WaitingClusterCreate–yesValidate infra config; wait until every cluster has id and name.
WaitingCreateVrsyesyesBuild 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):

ResultCondition statusMeaning
Progressing("")Unknown / ProgressingThe controller is actively working.
Pending("msg")Unknown / PendingWaiting on something external (e.g. external system assigning a cluster ID).
Succeeded()True / SucceededDone.
Failed(err)False / FailedMisconfiguration 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.

  1. Validate the CR (infrastructure non-empty, each cluster has a name).
  2. Iterate infrastructure[], skipping entries where active == false (and, when node-pool matching is enabled, entries with no matching general pool).
  3. Assemble the VRS spec from the CR (all in pkg/operator/vald/service/builder.go):
    • buildDefaults — log level
    • buildGateway — indexReplica, service type, ingress
    • buildAgent — NGT settings
    • buildManager — Manager mode when indexer.manager == true, otherwise Creator/Saver mode
    • buildDiscoverer — kind + namespaced RBAC names
  4. Resolve resources from node pools — SetScaledResources derives replicas and per-component CPU/memory from the resolved agent node pool. The general-pool fallback rule lives in resolveAgentNodePool (pkg/operator/vald/service/rules.go).
  5. Optional settings — persistent volume and node affinities (both in builder.go).
  6. Per cluster — for each infra.clusters[], set name = <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):

  • Build the desired objects, then CreateOrUpdate each with an owner reference and a managed-generation label set to the owner’s .metadata.generation.
  • Prune: list owned resources of the same kind and delete any that the current Build no 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 a NodePoolCapability; a VRS is generated only when a general pool is present (pkg/operator/vald/service/capability.go).
  • Node match is by labels namespace=<vor namespace> and type=general (required); type=agent is optional — when present, agent pods are scheduled onto type=agent, otherwise they fall back to type=general.
  • Label keys default to namespace / type; set NODEPOOL_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 varDefaultPurpose
DEFAULT_VRS_PATH/opt/valdoperatorrelease/config/vrs.yamlDefault VRS template merged with the overlay.
REQUIRE_NODEPOOL_MATCHfalseOnly generate VRS where matching node pools exist.
NODEPOOL_LABEL_PREFIX""Prefix for the namespace/type/role node labels.
AGENT_PODS_PER_NODE2Agent pods packed per node when computing replicas.
DEFAULT_STORAGE_CLASSstandardPV storage class fallback.
DEFAULT_ACCESS_MODEReadWriteOncePV access mode fallback.
PV_BUFFER_RATIO1.5PV size = max(memoryRequest * ratio, min).
PV_MIN_SIZE_BYTES1073741824Minimum PV size in bytes.
ENABLE_INGRESStrueEnable gateway ingress generation.
GATEWAY_INGRESS_ANNOTATIONS""YAML map of annotations applied to the gateway ingress.
GATEWAY_SERVICE_TYPENodePortGateway service type (NodePort / ClusterIP / LoadBalancer).
VRS_LOG_LEVELwarnLog level passed through to the generated VRS.
DISCOVERER_DS_MAX_SURGE30%Discoverer DaemonSet rolling-update maxSurge.
DISCOVERER_DS_MAX_UNAVAILABLE0%Discoverer DaemonSet rolling-update maxUnavailable.

Source map

ConcernLocation
CRD typesinternal/k8s/vald/operator/api/v1/
Reconcilerpkg/operator/vald/service/reconciler.go
Resource apply / pruneinternal/k8s/resource/syncer.go
Phase / conditionspkg/operator/vald/service/phase.go
Node-pool rulespkg/operator/vald/service/rules.go
VRS builderpkg/operator/vald/service/builder.go
Node-pool capabilitypkg/operator/vald/service/capability.go
VRS API modelinternal/k8s/vald/operator/api/valdrelease/
Config / envpkg/operator/vald/config/config.go
Entry pointcmd/operator/vald/main.go
Manifests / CRDsk8s/operator/vald/