Architecture
Big picture
KEDA runs as three binaries built by make build: the operator (manager), the metrics adapter, and the admission webhooks ([2], Makefile:211). The operator owns reconciliation, scaler polling, and the 0-to-1 / idle scaling the HPA cannot do. The adapter implements the Kubernetes External Metrics API but computes nothing itself; it forwards every metric request to the operator over gRPC. The HPA, created and managed by KEDA, still decides 1-to-N scaling.
Components
Operator (controller manager)
Entry point cmd/operator/main.go (func main at :69). It reconciles ScaledObject, ScaledJob, and TriggerAuthentication resources, polls scalers, and performs activation/deactivation. In the same process it starts the gRPC Metrics Service server: metricsservice.NewGrpcServer(&scaledHandler, ...) ([2], cmd/operator/main.go:355).
Metrics Adapter
Entry point cmd/adapter/main.go (func main at :226). It implements the External Metrics API on top of custom-metrics-apiserver and answers HPA queries. It holds no scaling state: it opens a gRPC client to the operator with metricsservice.NewGrpcClient(...) ([2], cmd/adapter/main.go:115) and wraps it in kedaprovider.NewProvider(...) ([2], cmd/adapter/main.go:126).
Admission Webhooks
Entry point cmd/webhooks/main.go. It validates ScaledObject and ScaledJob resources before they are admitted.
How a request flows
Tracing one metric query from HPA to value:
- The HPA calls the External Metrics API, hitting
KedaProvider.GetExternalMetric([2],pkg/provider/provider.go:75). The provider extracts the owning ScaledObject name from the label selector viaselector.Get(kedav1alpha1.ScaledObjectOwnerAnnotation)([2],pkg/provider/provider.go:99). - The adapter does not compute the value. It forwards over gRPC:
p.grpcClient.GetMetrics(ctx, scaledObjectName, namespace, info.Metric)([2],pkg/provider/provider.go:107). If the connection is not yet ready it blocks onWaitForConnectionReady([2],pkg/provider/provider.go:87). - On the operator side the gRPC handler calls
scaleHandler.GetScaledObjectMetrics([2],pkg/scaling/scale_handler.go:585). It looks up the scalers cache withgetScalersCacheForScaledObject([2],pkg/scaling/scale_handler.go:590) and fans out across triggers, adding a goroutine per scaler (wg.Add(1)inside the loop at:635/:666). - Each scaler implements the
Scalerinterface ([2],pkg/scalers/scaler.go:44). Concrete scalers come from thebuildScalerswitch keyed on the trigger type string ([2],pkg/scaling/scalers_builder.go:123). - The result returns as an
external_metrics.ExternalMetricValueListback through the adapter to the HPA, which decides the 1-to-N scale.
Separately, the operator drives the 0-to-1 and idle path itself, because the HPA floor is one replica. The reconcile-driven scale loop calls scaleExecutor.RequestScale ([2], pkg/scaling/executor/scale_scaledobjects.go:40), which branches on activity to scale up from zero/idle or down to zero/idle ([2], pkg/scaling/executor/scale_scaledobjects.go:73-117).
The ScaledObject reconcile body is reconcileScaledObject ([2], controllers/keda/scaledobject_controller.go:231), reached from Reconcile ([2], controllers/keda/scaledobject_controller.go:155). It checks pause state (:240), confirms the target is scalable via checkTargetResourceIsScalable (:280), validates triggers (:290), ensures the HPA via ensureHPAForScaledObjectExists (:301), and on a generation change kicks the scale loop with requestScaleLoop (:318).
Key design decisions
- Operator and adapter split, with gRPC delegation. The adapter advertises the External Metrics API but owns no values; it delegates every read to the operator with
GetMetrics([2],pkg/provider/provider.go:107). Scaling state stays centralised in the operator and the adapter stays stateless. - 0-to-1 is the operator's job, not the HPA's. The HPA cannot scale below one replica, so KEDA performs activation and deactivation in the
RequestScalebranches ([2],pkg/scaling/executor/scale_scaledobjects.go:73-117) and leaves 1-to-N to the HPA. This division of labour is the core of KEDA.
Extension points
- CRDs:
ScaledObject,ScaledJob,TriggerAuthentication, andClusterTriggerAuthentication, defined inapis/keda/v1alpha1/([2]). - Scaler interface: third-party and built-in scalers implement
Scaler([2],pkg/scalers/scaler.go:44); a push variantPushScaleraddsRun(ctx, active chan<- bool)([2],pkg/scalers/scaler.go:57). - Authentication providers:
TriggerAuthenticationSpecresolves credentials from Pod Identity, Kubernetes secrets, HashiCorp Vault, Azure Key Vault, and others ([2],apis/keda/v1alpha1/triggerauthentication_types.go:75).