KEDA external scalers for Asya transport backends.

Problem#

KEDA's built-in gcp-pubsub scaler queries Cloud Monitoring (Stackdriver) for subscription/num_undelivered_messages. Cloud Monitoring has a 2-4 minute metric ingestion lag (60s sampling + up to 120s visibility delay), making 0-to-1 scaling unacceptably slow for latency-sensitive workloads. The scaler also does not work with the Pub/Sub emulator (no Cloud Monitoring available).

scaler-pubsub#

A Go gRPC service implementing KEDA's ExternalScaler interface. Queries Pub/Sub subscriptions directly via the Pull API, bypassing Cloud Monitoring entirely.

How it works#

gRPC Method Implementation Latency
IsActive Pull(maxMessages=1) + nack ~100ms
GetMetricSpec Return metric name + target from metadata instant
GetMetrics Pull(maxMessages=N) + nack ~100ms
StreamIsActive Poll IsActive every 5s 5s

Messages are immediately nacked (ack deadline set to 0) so they remain available for actual consumers. No messages are consumed or lost by the scaler.

ScaledObject configuration#

When the external scaler is enabled, the Crossplane composition generates:

triggers:
- type: external
  name: queue
  metadata:
    scalerAddress: "asya-scaler-pubsub.asya-system.svc.cluster.local:6000"
    subscriptionName: "projects/PROJECT/subscriptions/asya-NAMESPACE-ACTOR"
    targetValue: "5"

Instead of the default type: gcp-pubsub trigger.

Metadata parameters#

Parameter Description Default
subscriptionName Full Pub/Sub subscription resource name required
targetValue Messages per replica (HPA target) 5
maxMessages Max messages to pull for GetMetrics 100
streamInterval Polling interval for StreamIsActive 5s

Authentication#

The scaler authenticates to GCP Pub/Sub using: - GKE Workload Identity (recommended): annotate the scaler's ServiceAccount - Service account key: set GOOGLE_APPLICATION_CREDENTIALS env var - Pub/Sub emulator: set PUBSUB_EMULATOR_HOST env var (no auth needed)

Required IAM permissions: pubsub.subscriber (Pull + ModifyAckDeadline).

Deployment#

The scaler is deployed via the asya-crossplane Helm chart:

# values.yaml
pubsub:
  keda:
    scaler:
      enabled: true
      address: "asya-scaler-pubsub.asya-system.svc.cluster.local:6000"
      image:
        repository: ghcr.io/deliveryhero/asya-crew  # temporary
        tag: "0.6.0"
      serviceAccountName: "asya-scaler-pubsub"  # for Workload Identity
      env:
      - name: PUBSUB_EMULATOR_HOST              # for emulator
        value: "emulator:8085"

When scaler.enabled=true: - A Deployment + Service (asya-scaler-pubsub) is created - ScaledObjects use type: external instead of type: gcp-pubsub - TriggerAuthentication is skipped (scaler handles auth)

Docker image#

The scaler binary is currently built into the asya-crew Docker image (/scaler-pubsub). A standalone asya-scalers image will be published once the registry name is approved.

Environment variables#

Variable Description
SCALER_PORT gRPC listen port (default: 6000)
LOG_LEVEL DEBUG, INFO, WARN, ERROR (default: INFO)
PUBSUB_EMULATOR_HOST Pub/Sub emulator address (bypasses GCP auth)
GOOGLE_APPLICATION_CREDENTIALS Path to service account JSON key

Observability#

The scaler exposes: - gRPC health checks (used by K8s liveness/readiness probes) - gRPC reflection (for debugging with grpcurl) - Structured logging via slog