Skip to main content

Kubernetes Backend Discovery and Automated Binding

Abstract

This RFC defines automated backend discovery and binding for Prism pattern runners deployed in Kubernetes environments. It enables pattern runners to automatically discover and connect to existing backend services (NATS, Redis, PostgreSQL, Kafka) that are already deployed via Helm charts or other means, without manual configuration of connection details.

The approach leverages Kubernetes-native service discovery, integrates with the prism-operator, and builds upon RFC-039's backend configuration registry to provide a seamless developer experience for deploying pattern runners on top of existing infrastructure.

Motivation

The Developer Experience Problem

Organizations often have standard backend deployments managed by platform teams:

# Platform team has already deployed these via Helm
helm install redis bitnami/redis -n infrastructure
helm install postgresql bitnami/postgresql -n infrastructure
helm install nats nats/nats -n infrastructure
helm install kafka bitnami/kafka -n infrastructure

When developers want to deploy Prism pattern runners, they currently need to:

  1. Manually discover connection details from each Helm release
  2. Copy-paste connection strings into pattern runner configs
  3. Manage secrets for authentication separately
  4. Update configs when backends change (e.g., password rotation)
  5. Repeat for every pattern runner in every namespace

This is tedious, error-prone, and doesn't scale.

What We Want Instead

Developers should be able to deploy pattern runners with minimal configuration:

apiVersion: prism.io/v1alpha1
kind: PrismPattern
metadata:
name: order-consumer
spec:
pattern: consumer
image: ghcr.io/prism/consumer-runner:latest

# Automatic discovery: Just reference the service name!
backendBindings:
message_source:
discover:
service: kafka.infrastructure.svc.cluster.local
port: 9092
state_store:
discover:
service: redis-master.infrastructure.svc.cluster.local
port: 6379

Or even better, with pre-registered backends:

apiVersion: prism.io/v1alpha1
kind: PrismPattern
metadata:
name: order-consumer
spec:
pattern: consumer
image: ghcr.io/prism/consumer-runner:latest

# Reference pre-registered backends
backendBindings:
message_source:
backendRef: kafka-production
state_store:
backendRef: redis-production

Current State vs. Desired State

AspectCurrent (Manual)Desired (Automated)
Backend DiscoveryDeveloper manually finds service DNSAutomatic via K8s service discovery
Connection ConfigCopy-paste from Helm release notesAuto-generated from service metadata
Secret ManagementManual secret creation and mountingAutomatic secret discovery and binding
Backend UpdatesUpdate every pattern runner configUpdate once in backend registry
New Pattern Deployment10-15 minutes of config hunting30 seconds
Operational ComplexityHigh (N pattern configs to update)Low (1 backend config to update)

Goals

  1. Automatic Service Discovery: Pattern runners discover backend services using Kubernetes DNS and service metadata
  2. Helm Chart Integration: Detect and bind to backends deployed via standard Helm charts (Bitnami, official charts)
  3. Backend Registry Integration: Integrate with RFC-039's backend configuration registry for centralized management
  4. Secret Auto-Discovery: Automatically discover and mount secrets created by Helm charts
  5. Operator-Driven: Leverage prism-operator to orchestrate discovery and binding
  6. Zero-Config Deployment: Pattern runners work out-of-box with default Helm chart deployments
  7. Local-First Testing: Support local development with kind/Docker Desktop (ADR-004)

Non-Goals

  1. Cross-Cluster Discovery: Initial implementation focuses on same-cluster backends
  2. Dynamic Backend Switching: Pattern runners bind at startup; runtime changes require restart
  3. Backend Health Monitoring: Focus on discovery/binding, not runtime health checks
  4. Custom Resource Provisioning: This RFC assumes backends already exist; provisioning is separate
  5. Cloud-Managed Services: Initial focus on self-hosted K8s services; cloud services (RDS, ElastiCache) are future work

Design

Approach 1: Kubernetes-Native Service Discovery with PrismBackend CRD

Philosophy: Leverage Kubernetes-native primitives (Services, Secrets, ConfigMaps) and extend with a lightweight CRD for Prism-specific metadata.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Infrastructure Namespace │ │
│ │ │ │
│ │ Helm Release: redis │ │
│ │ ├─ Service: redis-master (6379) │ │
│ │ ├─ Service: redis-replicas (6379) │ │
│ │ └─ Secret: redis (password) │ │
│ │ │ │
│ │ Helm Release: postgresql │ │
│ │ ├─ Service: postgresql (5432) │ │
│ │ └─ Secret: postgresql (username, password) │ │
│ │ │ │
│ │ Helm Release: kafka │ │
│ │ ├─ Service: kafka (9092) │ │
│ │ └─ ConfigMap: kafka (bootstrap servers) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ │ K8s Service Discovery │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Prism System Namespace │ │
│ │ │ │
│ │ PrismBackend CRDs (created by admin/operator) │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ apiVersion: prism.io/v1alpha1 │ │ │
│ │ │ kind: PrismBackend │ │ │
│ │ │ metadata: │ │ │
│ │ │ name: redis-production │ │ │
│ │ │ spec: │ │ │
│ │ │ type: redis │ │ │
│ │ │ discovery: │ │ │
│ │ │ service: │ │ │
│ │ │ name: redis-master │ │ │
│ │ │ namespace: infrastructure │ │ │
│ │ │ port: 6379 │ │ │
│ │ │ secret: │ │ │
│ │ │ name: redis │ │ │
│ │ │ namespace: infrastructure │ │ │
│ │ │ passwordKey: redis-password │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Prism Operator (reconciles PrismBackend) │ │
│ │ ├─ Validates service exists │ │
│ │ ├─ Resolves DNS to ClusterIP │ │
│ │ ├─ Validates secret exists │ │
│ │ └─ Syncs to admin backend registry │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Admin Sync │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Prism Admin Control Plane │ │
│ │ │ │
│ │ Backend Registry (from RFC-039) │ │
│ │ { │ │
│ │ "redis-production": { │ │
│ │ type: REDIS, │ │
│ │ config: { │ │
│ │ host: "redis-master.infrastructure.svc", │ │
│ │ port: 6379, │ │
│ │ password: <from-secret> │ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ │ GetBackend RPC │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Application Namespace │ │
│ │ │ │
│ │ PrismPattern: order-consumer │ │
│ │ ├─ Deployment: consumer-runner pods │ │
│ │ │ └─ Fetches backend config at startup │ │
│ │ │ admin.GetBackend("redis-production") │ │
│ │ └─ Connects to redis-master.infrastructure.svc:6379 │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

PrismBackend CRD Definition

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: prismbackends.prism.io
spec:
group: prism.io
names:
kind: PrismBackend
plural: prismbackends
singular: prismbackend
shortNames: [pbackend]
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: [type, discovery]
properties:
# Backend type
type:
type: string
enum: [kafka, nats, redis, postgresql, mysql, mongodb]

# Service discovery configuration
discovery:
type: object
required: [service]
properties:
# Kubernetes service to discover
service:
type: object
required: [name]
properties:
name:
type: string
description: "Service name (e.g., redis-master)"
namespace:
type: string
description: "Service namespace (defaults to same as PrismBackend)"
port:
type: integer
description: "Service port (defaults to backend type default)"

# Secret discovery
secret:
type: object
properties:
name:
type: string
description: "Secret name (e.g., redis)"
namespace:
type: string
description: "Secret namespace"
usernameKey:
type: string
description: "Key in secret for username"
passwordKey:
type: string
description: "Key in secret for password"

# ConfigMap discovery (for complex configs)
configMap:
type: object
properties:
name:
type: string
namespace:
type: string
key:
type: string
description: "Key in ConfigMap for config data"

# Backend-specific overrides
config:
type: object
x-kubernetes-preserve-unknown-fields: true

# Capabilities (from RFC-039)
capabilities:
type: array
items:
type: string

# Metadata
metadata:
type: object
additionalProperties:
type: string

status:
type: object
properties:
phase:
type: string
enum: [Pending, Discovered, Error]
resolvedEndpoint:
type: string
description: "Resolved service endpoint (e.g., redis-master.infrastructure.svc:6379)"
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
message:
type: string
lastTransitionTime:
type: string
format: date-time

Example: Discovering Bitnami Redis

Step 1: Platform team deploys Redis via Helm

helm install redis bitnami/redis \
--namespace infrastructure \
--create-namespace \
--set auth.enabled=true \
--set auth.password=supersecret

This creates:

  • Service: redis-master (6379)
  • Service: redis-replicas (6379)
  • Secret: redis with key redis-password

Step 2: Platform team creates PrismBackend CRD

apiVersion: prism.io/v1alpha1
kind: PrismBackend
metadata:
name: redis-production
spec:
type: redis

discovery:
# Discover master service
service:
name: redis-master
namespace: infrastructure
port: 6379

# Discover password secret
secret:
name: redis
namespace: infrastructure
passwordKey: redis-password

# Backend capabilities
capabilities:
- keyvalue_basic
- keyvalue_scan
- keyvalue_ttl
- keyvalue_transactional
- pubsub_basic

metadata:
helm_release: redis
helm_chart: bitnami/redis
helm_version: "19.0.0"
region: us-west-2
env: production

Step 3: Operator reconciles PrismBackend

func (r *PrismBackendReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var backend prismv1alpha1.PrismBackend
if err := r.Get(ctx, req.NamespacedName, &backend); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// 1. Validate service exists
svcName := backend.Spec.Discovery.Service.Name
svcNamespace := backend.Spec.Discovery.Service.Namespace
if svcNamespace == "" {
svcNamespace = backend.Namespace
}

var service corev1.Service
if err := r.Get(ctx, client.ObjectKey{Name: svcName, Namespace: svcNamespace}, &service); err != nil {
return r.updateStatus(ctx, &backend, "Error", fmt.Sprintf("Service not found: %v", err))
}

// 2. Resolve service endpoint
endpoint := fmt.Sprintf("%s.%s.svc.cluster.local:%d",
svcName, svcNamespace, backend.Spec.Discovery.Service.Port)

// 3. Validate secret exists (if specified)
var password string
if backend.Spec.Discovery.Secret != nil {
secretName := backend.Spec.Discovery.Secret.Name
secretNamespace := backend.Spec.Discovery.Secret.Namespace
if secretNamespace == "" {
secretNamespace = svcNamespace
}

var secret corev1.Secret
if err := r.Get(ctx, client.ObjectKey{Name: secretName, Namespace: secretNamespace}, &secret); err != nil {
return r.updateStatus(ctx, &backend, "Error", fmt.Sprintf("Secret not found: %v", err))
}

passwordKey := backend.Spec.Discovery.Secret.PasswordKey
if passwordKey == "" {
passwordKey = "password"
}
password = string(secret.Data[passwordKey])
}

// 4. Build Backend configuration (from RFC-039)
backendConfig := &adminpb.Backend{
Name: backend.Name,
Type: r.mapBackendType(backend.Spec.Type),
Config: &adminpb.BackendConfig{
Config: &adminpb.BackendConfig_Redis{
Redis: &adminpb.RedisConfig{
Host: fmt.Sprintf("%s.%s.svc.cluster.local", svcName, svcNamespace),
Port: backend.Spec.Discovery.Service.Port,
Password: password,
TlsEnabled: false,
},
},
},
Capabilities: backend.Spec.Capabilities,
Metadata: backend.Spec.Metadata,
}

// 5. Register backend with admin control plane
if err := r.syncToAdmin(ctx, backendConfig); err != nil {
return r.updateStatus(ctx, &backend, "Error", fmt.Sprintf("Admin sync failed: %v", err))
}

// 6. Update status
return r.updateStatus(ctx, &backend, "Discovered", endpoint)
}

Step 4: Developer deploys pattern runner

apiVersion: prism.io/v1alpha1
kind: PrismPattern
metadata:
name: order-consumer
namespace: applications
spec:
pattern: consumer
image: ghcr.io/prism/consumer-runner:latest
replicas: 2

# Backend bindings using discovered backends
backendBindings:
message_source:
backendRef: kafka-production
state_store:
backendRef: redis-production
dead_letter_queue:
backendRef: kafka-production

autoscaling:
enabled: true
scaler: keda
minReplicas: 2
maxReplicas: 50
triggers:
- type: kafka
metadata:
# KEDA automatically uses kafka-production connection details
lagThreshold: "1000"

Step 5: Pattern runner fetches backend config at startup

// In consumer-runner startup
func (r *ConsumerRunner) Start(ctx context.Context, config *PatternConfig) error {
// Fetch backend configs from admin
backendName := config.BackendBindings["state_store"] // "redis-production"

backend, err := r.adminClient.GetBackend(ctx, backendName)
if err != nil {
return fmt.Errorf("failed to fetch backend %s: %w", backendName, err)
}

// Backend config is fully resolved with connection details
redisConfig := backend.Config.GetRedis()
log.Printf("Connecting to Redis at %s:%d", redisConfig.Host, redisConfig.Port)

// Connect using resolved config
redisClient := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", redisConfig.Host, redisConfig.Port),
Password: redisConfig.Password,
DB: int(redisConfig.Database),
})

r.stateStore = redisClient
return nil
}

Approach 2: Helm-Aware Auto-Discovery

Philosophy: Automatically discover backends from Helm releases, eliminating the need for manual PrismBackend creation.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ Helm Releases (any namespace) │
│ ├─ redis (bitnami/redis) │
│ ├─ postgresql (bitnami/postgresql) │
│ ├─ kafka (bitnami/kafka) │
│ └─ nats (nats/nats) │
│ │ │
│ │ Helm List │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Prism Backend Discovery Controller │ │
│ │ (runs as part of prism-operator) │ │
│ │ │ │
│ │ Periodically: │ │
│ │ 1. helm list --all-namespaces │ │
│ │ 2. Detect known charts (bitnami/redis, etc.) │ │
│ │ 3. Extract service names and secrets │ │
│ │ 4. Auto-create PrismBackend CRDs │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Creates │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Auto-Discovered PrismBackend CRDs │ │
│ │ (labeled: prism.io/auto-discovered: "true") │ │
│ │ │ │
│ │ redis-infrastructure │ │
│ │ postgresql-infrastructure │ │
│ │ kafka-infrastructure │ │
│ │ nats-infrastructure │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

Helm Chart Detection Patterns

// Known Helm chart patterns for auto-discovery
type HelmChartPattern struct {
ChartName string
BackendType string
ServicePattern string // Template for service name
PortDefaults map[string]int32 // Default ports
SecretPattern string // Template for secret name
SecretPasswordKey string // Key in secret for password
Capabilities []string // Backend capabilities
}

var knownCharts = []HelmChartPattern{
{
ChartName: "bitnami/redis",
BackendType: "redis",
ServicePattern: "{{.Release}}-master",
PortDefaults: map[string]int32{"redis": 6379},
SecretPattern: "{{.Release}}",
SecretPasswordKey: "redis-password",
Capabilities: []string{"keyvalue_basic", "keyvalue_scan", "keyvalue_ttl", "pubsub_basic"},
},
{
ChartName: "bitnami/postgresql",
BackendType: "postgresql",
ServicePattern: "{{.Release}}-postgresql",
PortDefaults: map[string]int32{"postgresql": 5432},
SecretPattern: "{{.Release}}-postgresql",
SecretPasswordKey: "postgres-password",
Capabilities: []string{"keyvalue_basic", "keyvalue_scan", "keyvalue_transactional", "sql_basic"},
},
{
ChartName: "bitnami/kafka",
BackendType: "kafka",
ServicePattern: "{{.Release}}",
PortDefaults: map[string]int32{"kafka": 9092},
SecretPattern: "", // Bitnami Kafka may not use secrets by default
SecretPasswordKey: "",
Capabilities: []string{"pubsub_basic", "pubsub_persistent", "stream_basic"},
},
{
ChartName: "nats/nats",
BackendType: "nats",
ServicePattern: "{{.Release}}",
PortDefaults: map[string]int32{"client": 4222, "monitoring": 8222},
SecretPattern: "{{.Release}}-auth",
SecretPasswordKey: "password",
Capabilities: []string{"pubsub_basic", "pubsub_persistent", "stream_basic"},
},
}

// Auto-discovery controller
func (c *BackendDiscoveryController) discoverFromHelm(ctx context.Context) error {
// 1. List all Helm releases
releases, err := c.listHelmReleases(ctx)
if err != nil {
return fmt.Errorf("failed to list Helm releases: %w", err)
}

// 2. Match releases against known patterns
for _, release := range releases {
pattern := c.matchHelmChart(release.Chart)
if pattern == nil {
continue // Unknown chart, skip
}

// 3. Extract service and secret names
serviceName := c.renderTemplate(pattern.ServicePattern, release)
secretName := c.renderTemplate(pattern.SecretPattern, release)

// 4. Check if PrismBackend already exists
backendName := fmt.Sprintf("%s-%s", release.Name, release.Namespace)
if c.backendExists(ctx, backendName) {
continue // Already discovered
}

// 5. Create PrismBackend CRD
backend := &prismv1alpha1.PrismBackend{
ObjectMeta: metav1.ObjectMeta{
Name: backendName,
Labels: map[string]string{
"prism.io/auto-discovered": "true",
"prism.io/helm-release": release.Name,
"prism.io/helm-chart": release.Chart,
},
},
Spec: prismv1alpha1.PrismBackendSpec{
Type: pattern.BackendType,
Discovery: prismv1alpha1.DiscoverySpec{
Service: prismv1alpha1.ServiceDiscovery{
Name: serviceName,
Namespace: release.Namespace,
Port: pattern.PortDefaults[pattern.BackendType],
},
Secret: &prismv1alpha1.SecretDiscovery{
Name: secretName,
Namespace: release.Namespace,
PasswordKey: pattern.SecretPasswordKey,
},
},
Capabilities: pattern.Capabilities,
Metadata: map[string]string{
"helm_release": release.Name,
"helm_chart": release.Chart,
"helm_version": release.Version,
"namespace": release.Namespace,
},
},
}

if err := c.Create(ctx, backend); err != nil {
log.Printf("Failed to create auto-discovered backend %s: %v", backendName, err)
} else {
log.Printf("✅ Auto-discovered backend: %s (from Helm release %s/%s)",
backendName, release.Namespace, release.Name)
}
}

return nil
}

Developer Experience

Platform team deploys backends:

# Deploy standard backends
helm install redis bitnami/redis -n infrastructure
helm install postgresql bitnami/postgresql -n infrastructure
helm install kafka bitnami/kafka -n infrastructure

Auto-discovery runs automatically (no manual steps needed):

# Prism operator auto-discovers and creates:
# - redis-infrastructure
# - postgresql-infrastructure
# - kafka-infrastructure

kubectl get prismbackend
# NAME TYPE STATUS AGE
# redis-infrastructure redis Discovered 1m
# postgresql-infrastructure postgresql Discovered 1m
# kafka-infrastructure kafka Discovered 1m

Developer deploys pattern runner:

apiVersion: prism.io/v1alpha1
kind: PrismPattern
metadata:
name: order-consumer
spec:
pattern: consumer
image: ghcr.io/prism/consumer-runner:latest

# Use auto-discovered backends
backendBindings:
message_source:
backendRef: kafka-infrastructure
state_store:
backendRef: redis-infrastructure

Approach 3: Sidecar-Based Service Discovery

Philosophy: Use a sidecar container to perform service discovery and generate config, keeping pattern runners decoupled from Kubernetes APIs.

Architecture

┌────────────────────────────────────────────────────────────┐
│ Pattern Runner Pod │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Init Container: prism-backend-resolver │ │
│ │ │ │
│ │ 1. Reads backend bindings from pod annotations │ │
│ │ 2. Queries K8s API for services and secrets │ │
│ │ 3. Resolves DNS names and ports │ │
│ │ 4. Fetches secrets for auth │ │
│ │ 5. Writes resolved config to shared volume │ │
│ │ /config/backends.json │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ (shared volume) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Main Container: consumer-runner │ │
│ │ │ │
│ │ 1. Reads /config/backends.json │ │
│ │ 2. Parses backend configs │ │
│ │ 3. Connects to backends │ │
│ │ 4. Starts processing │ │
│ └─────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘

Generated Pattern Runner Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
name: order-consumer
spec:
template:
metadata:
annotations:
# Backend bindings encoded as annotations
prism.io/backend-bindings: |
message_source:
service: kafka.infrastructure.svc.cluster.local:9092
secret: kafka-credentials
state_store:
service: redis-master.infrastructure.svc.cluster.local:6379
secret: redis
spec:
# Init container resolves backends
initContainers:
- name: backend-resolver
image: ghcr.io/prism/backend-resolver:latest
volumeMounts:
- name: backend-config
mountPath: /config
env:
- name: BACKEND_BINDINGS
valueFrom:
fieldRef:
fieldPath: metadata.annotations['prism.io/backend-bindings']

# Main pattern runner container
containers:
- name: consumer-runner
image: ghcr.io/prism/consumer-runner:latest
volumeMounts:
- name: backend-config
mountPath: /config
env:
- name: BACKEND_CONFIG_PATH
value: /config/backends.json

volumes:
- name: backend-config
emptyDir: {}

Resolved Backend Config

The init container writes /config/backends.json:

{
"message_source": {
"type": "kafka",
"config": {
"brokers": ["kafka.infrastructure.svc.cluster.local:9092"],
"security_protocol": "PLAINTEXT"
},
"capabilities": ["pubsub_basic", "pubsub_persistent"]
},
"state_store": {
"type": "redis",
"config": {
"host": "redis-master.infrastructure.svc.cluster.local",
"port": 6379,
"password": "supersecret",
"database": 0
},
"capabilities": ["keyvalue_basic", "keyvalue_scan", "keyvalue_ttl"]
}
}

Pattern runner reads this config at startup:

func (r *ConsumerRunner) Start(ctx context.Context) error {
// Read resolved backend config from sidecar
configPath := os.Getenv("BACKEND_CONFIG_PATH")
if configPath == "" {
configPath = "/config/backends.json"
}

configData, err := os.ReadFile(configPath)
if err != nil {
return fmt.Errorf("failed to read backend config: %w", err)
}

var backends map[string]BackendConfig
if err := json.Unmarshal(configData, &backends); err != nil {
return fmt.Errorf("failed to parse backend config: %w", err)
}

// Connect to backends
for slotName, backendConfig := range backends {
if err := r.bindSlot(slotName, backendConfig); err != nil {
return fmt.Errorf("failed to bind slot %s: %w", slotName, err)
}
}

return nil
}

Approach 4: Service Catalog / Crossplane Integration

Philosophy: Leverage existing Kubernetes service binding standards (Service Catalog, Crossplane) for maximum ecosystem compatibility.

Architecture Using Crossplane

# Crossplane Composition for Prism Backend
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: prism-redis-backend
spec:
compositeTypeRef:
apiVersion: prism.crossplane.io/v1alpha1
kind: XPrismBackend

resources:
# 1. Provision Redis (if not exists)
- name: redis-instance
base:
apiVersion: kubernetes.crossplane.io/v1alpha1
kind: Object
spec:
forProvider:
manifest:
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
spec:
chart:
spec:
chart: redis
version: "19.0.0"
sourceRef:
kind: HelmRepository
name: bitnami

# 2. Create service binding
- name: backend-binding
base:
apiVersion: servicebinding.io/v1alpha3
kind: ServiceBinding
spec:
service:
apiVersion: v1
kind: Service
name: redis-master
workload:
apiVersion: prism.io/v1alpha1
kind: PrismPattern

# Developer creates backend claim
apiVersion: prism.crossplane.io/v1alpha1
kind: PrismBackend
metadata:
name: redis-production
spec:
parameters:
type: redis
highAvailability: true
memorySize: 4Gi
compositionSelector:
matchLabels:
provider: bitnami

Benefits:

  • Standard K8s service binding (servicebinding.io)
  • Can provision AND bind backends
  • Works with cloud-managed services (RDS, ElastiCache)
  • Ecosystem compatibility

Trade-offs:

  • Requires Crossplane installation
  • More complex setup
  • May be overkill for simple use cases

Comparison of Approaches

AspectApproach 1: CRDApproach 2: Helm-AwareApproach 3: SidecarApproach 4: Crossplane
ComplexityMediumMediumLowHigh
K8s Native✅ Yes✅ Yes⚠️ Init container✅ Yes
Auto-Discovery❌ Manual CRD✅ Automatic⚠️ Annotations✅ Automatic
Flexibility✅ High⚠️ Helm-only✅ High✅ Very High
Admin Control✅ Centralized✅ Centralized❌ Decentralized✅ Centralized
Secret Management✅ K8s secrets✅ K8s secrets✅ K8s secrets✅ K8s secrets
Ecosystem Integration⚠️ Custom⚠️ Custom⚠️ Custom✅ Standard
MaintenanceMediumMediumLowHigh
Local Testing✅ kind/Docker Desktop✅ kind/Docker Desktop✅ kind/Docker Desktop⚠️ Requires Crossplane
Cloud Services⚠️ Future work⚠️ Future work⚠️ Future work✅ Native

Combine PrismBackend CRD with Helm auto-discovery for best of both worlds:

  1. PrismBackend CRD provides the core abstraction (Approach 1)
  2. Helm auto-discovery creates PrismBackend CRDs automatically (Approach 2)
  3. Manual CRD creation supported for custom backends
  4. Admin sync to RFC-039 backend registry

Implementation Architecture

┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ Helm Releases │
│ ├─ redis (bitnami) │
│ ├─ postgresql (bitnami) │
│ └─ kafka (bitnami) │
│ │ │
│ ▼ (auto-discovery) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Prism Operator │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Helm Discovery Controller │ │ │
│ │ │ - Detects Helm releases │ │ │
│ │ │ - Creates PrismBackend CRDs │ │ │
│ │ │ - Labels: auto-discovered: true │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ PrismBackend Controller │ │ │
│ │ │ - Validates service/secret exists │ │ │
│ │ │ - Resolves DNS endpoints │ │ │
│ │ │ - Syncs to admin backend registry │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ PrismPattern Controller │ │ │
│ │ │ - Creates Deployment │ │ │
│ │ │ - Injects backend bindings as env vars │ │ │
│ │ │ - Sets up KEDA triggers with backend info │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ PrismBackend CRDs │
│ ├─ redis-infrastructure (auto) │
│ ├─ postgresql-infrastructure (auto) │
│ ├─ kafka-infrastructure (auto) │
│ └─ custom-memcached (manual) │
│ │ │
│ ▼ (synced to) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Prism Admin Control Plane │ │
│ │ - Backend Registry (RFC-039) │ │
│ │ - GetBackend RPC for pattern runners │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Pattern Runners │
│ ├─ Fetch backend config from admin │
│ ├─ Connect using resolved connection details │
│ └─ Start processing │
└─────────────────────────────────────────────────────────────┘

Why This Hybrid Approach?

  1. Zero-config for common cases: Helm charts auto-discovered
  2. Flexibility for custom backends: Manual PrismBackend CRD creation
  3. Centralized control: Admin team has visibility and control
  4. Local-first testing: Works with kind/Docker Desktop (ADR-004)
  5. Client-originated configuration: Developers reference backend names, not connection details (ADR-002)
  6. Kubernetes-native: Uses CRDs and standard K8s patterns
  7. Gradual adoption: Can start with manual CRDs, add auto-discovery later

Implementation Plan

Phase 1: Core PrismBackend CRD (Week 1)

  • Define PrismBackend CRD schema
  • Implement PrismBackend controller
  • Add service/secret validation
  • Add DNS resolution
  • Add admin sync (to RFC-039 backend registry)
  • Add status tracking
  • Write unit tests

Phase 2: Helm Auto-Discovery (Week 2)

  • Implement Helm list integration
  • Define known chart patterns (Bitnami, official charts)
  • Implement template rendering for service/secret names
  • Add auto-discovery controller
  • Add reconciliation loop (detect new Helm releases)
  • Label auto-discovered backends
  • Write integration tests

Phase 3: PrismPattern Integration (Week 2-3)

  • Update PrismPattern CRD with backendBindings field
  • Update PrismPattern controller to:
    • Validate backendRef exists
    • Fetch backend config from admin
    • Inject backend connection env vars
    • Configure KEDA triggers with backend details
  • Add backward compatibility for old config style
  • Write end-to-end tests

Phase 4: Admin Integration (Week 3)

  • Implement admin sync in PrismBackend controller
  • Add GetBackend RPC caching
  • Add backend health monitoring (optional)
  • Add admin CLI commands for backend management
  • Update admin UI to show backends

Phase 5: Pattern Runner SDK (Week 4)

  • Create SlotBinder utility (from RFC-039)
  • Add backend config fetching from admin
  • Add backward compatibility for file-based config
  • Update all pattern runners to use SlotBinder
  • Write pattern runner integration tests

Phase 6: Documentation and Examples (Week 4-5)

  • Write operator guide for backend management
  • Create example YAMLs for common backends
  • Document Helm chart auto-discovery patterns
  • Update quickstart guide
  • Create video walkthrough
  • Write migration guide from manual config

Phase 7: Testing and Validation (Week 5)

  • Test with kind/Docker Desktop
  • Test with all supported backends (Redis, PostgreSQL, Kafka, NATS)
  • Test auto-discovery with Bitnami charts
  • Test manual PrismBackend creation
  • Test backend updates and pattern runner restarts
  • Performance testing (discovery latency, admin sync)
  • Write test report

Success Criteria

  1. Zero-config deployment: Developer can deploy pattern runner with just backend names, no connection details
  2. Auto-discovery: Bitnami Helm charts automatically discovered within 30 seconds
  3. Centralized management: Platform team can list all backends via kubectl get prismbackend
  4. Pattern runner portability: Same PrismPattern YAML works across dev/staging/prod with different backends
  5. Admin visibility: Admin UI shows which patterns use which backends
  6. Local testing: Works with kind/Docker Desktop without modifications (ADR-004)
  7. Performance: Backend discovery < 10 seconds, admin sync < 5 seconds, pattern runner startup < 30 seconds

Alternatives Considered

Alternative 1: Direct K8s Service Reference (No CRD)

Approach: Pattern runners directly reference K8s services via DNS.

apiVersion: prism.io/v1alpha1
kind: PrismPattern
spec:
backendBindings:
state_store:
service: redis-master.infrastructure.svc.cluster.local:6379
secret: redis

Pros:

  • Simple, no new CRD
  • Kubernetes-native DNS

Cons:

  • No centralized backend registry (violates RFC-039)
  • No capability metadata
  • Secret management per-pattern
  • Admin has no visibility
  • Hard to migrate backends

Rejected: Loses centralized control and backend abstraction.

Alternative 2: Embedded Backend Config in PrismPattern

Approach: Embed full backend config in each PrismPattern.

apiVersion: prism.io/v1alpha1
kind: PrismPattern
spec:
backends:
state_store:
type: redis
host: redis-master.infrastructure.svc.cluster.local
port: 6379
password: supersecret

Pros:

  • Self-contained
  • No external dependencies

Cons:

  • Massive duplication (every pattern has same Redis config)
  • Secrets in CRDs (security risk)
  • No shared backend updates
  • Violates DRY principle

Rejected: Does not scale operationally.

Alternative 3: External Config Management (Consul, Vault)

Approach: Use external tools for service discovery and config management.

Pros:

  • Mature ecosystem
  • Service mesh integration

Cons:

  • Adds external dependencies
  • Not Kubernetes-native
  • Requires additional infrastructure
  • Learning curve

Rejected: Adds complexity, not aligned with K8s-native approach.

Open Questions

  1. Multi-cluster backends: How do we support backends in different clusters?

    • Proposed: Phase 2 feature—add remote cluster references in PrismBackend CRD
  2. Backend versioning: What if backend schema changes (e.g., Redis 6 to 7)?

    • Proposed: Add version field to PrismBackend, pattern runners check compatibility
  3. Dynamic credential rotation: How to handle password rotation without restarts?

    • Proposed: Phase 2 feature—watch secret changes, reconnect on update
  4. Namespace isolation: Should backends be namespaced or cluster-scoped?

    • Proposed: Cluster-scoped CRD, but discovery limited to specified namespaces
  5. Cloud-managed services: How to bind to RDS, ElastiCache, etc.?

    • Proposed: Manual PrismBackend creation with external endpoints, or Crossplane integration

References

Appendix A: Supported Helm Charts

Bitnami Charts

ChartBackend TypeService PatternSecret PatternPassword Key
bitnami/redisredis{{.Release}}-master{{.Release}}redis-password
bitnami/postgresqlpostgresql{{.Release}}-postgresql{{.Release}}-postgresqlpostgres-password
bitnami/mysqlmysql{{.Release}}{{.Release}}mysql-root-password
bitnami/mongodbmongodb{{.Release}}{{.Release}}mongodb-root-password
bitnami/kafkakafka{{.Release}}{{.Release}}-user-passwordsclient-passwords
bitnami/rabbitmqrabbitmq{{.Release}}{{.Release}}rabbitmq-password

Official Charts

ChartBackend TypeService PatternSecret PatternPassword Key
nats/natsnats{{.Release}}{{.Release}}-authpassword
elastic/elasticsearchelasticsearch{{.Release}}-master{{.Release}}-credentialspassword

Custom Patterns

Organizations can extend auto-discovery by adding custom patterns:

apiVersion: v1
kind: ConfigMap
metadata:
name: prism-helm-patterns
namespace: prism-system
data:
patterns.yaml: |
- chartName: my-org/custom-redis
backendType: redis
servicePattern: "{{.Release}}-redis-primary"
secretPattern: "{{.Release}}-redis-secret"
secretPasswordKey: "password"

Appendix B: Example Deployment Flow

Scenario: Deploy Consumer Pattern on Existing Infrastructure

1. Platform team has deployed backends:

helm install redis bitnami/redis -n infrastructure
helm install kafka bitnami/kafka -n infrastructure

2. Install Prism operator (one time):

helm install prism-operator prism/prism-operator -n prism-system --create-namespace

3. Auto-discovery runs (automatically):

# Operator detects Helm releases and creates PrismBackend CRDs
kubectl get prismbackend
# NAME TYPE STATUS AGE
# redis-infrastructure redis Discovered 30s
# kafka-infrastructure kafka Discovered 30s

4. Developer deploys pattern:

kubectl apply -f - <<EOF
apiVersion: prism.io/v1alpha1
kind: PrismPattern
metadata:
name: order-consumer
namespace: applications
spec:
pattern: consumer
image: ghcr.io/prism/consumer-runner:latest
replicas: 2

backendBindings:
message_source:
backendRef: kafka-infrastructure
state_store:
backendRef: redis-infrastructure

autoscaling:
enabled: true
scaler: keda
minReplicas: 2
maxReplicas: 50
triggers:
- type: kafka
metadata:
lagThreshold: "1000"
EOF

5. Pattern runner starts (automatically):

kubectl logs -n applications deployment/order-consumer -c consumer-runner
# [INFO] Starting consumer pattern runner v0.1.0
# [INFO] Fetching backend config for message_source: kafka-infrastructure
# [INFO] Connecting to Kafka at kafka.infrastructure.svc.cluster.local:9092
# [INFO] Fetching backend config for state_store: redis-infrastructure
# [INFO] Connecting to Redis at redis-master.infrastructure.svc.cluster.local:6379
# [INFO] ✅ All backends connected
# [INFO] Starting message consumption...

Total developer time: 30 seconds (down from 10-15 minutes of config hunting).