Skip to content

Design documentation

Overview

HAPTIC is a Kubernetes operator that watches Kubernetes resources and renders them through templates into validated HAProxy configurations.

Components communicate through a central EventBus using pub/sub and request-response patterns, enabling observability, testability, and loose coupling.

Why this architecture:

  • Event-driven design allows components to evolve independently
  • Template-based approach provides maximum flexibility without annotation constraints
  • Multi-phase validation prevents invalid configurations from reaching production
  • Runtime API optimization minimizes service disruption during updates

This section is for people changing HAPTIC's Go code and charts; if you run HAPTIC rather than develop it, the Operations guides (starting with High Availability) are for you.

The design documentation is organized the way the sidebar groups it:

Architecture

  • Architecture Overview - High-level system architecture with component diagrams showing the controller's internal event-driven structure, validation flow, and the operating assumptions and constraints the design rests on

  • Package Structure - Go package organization including directory structure, package dependencies, and key interfaces

  • Sequence Diagrams - Dynamic behavior including startup initialization, resource change handling, configuration validation, and zero-reload deployment

Components

  • Configuration Model - User interface design showing how you configure the controller through HAProxyTemplateConfig CRD

  • Deployment - Kubernetes deployment architecture showing controller pods, HAProxy pods, container configuration, and network topology

  • Leader Election - Lease-based leader election, the leader-only vs all-replica component split, and the bootstrap pattern that prevents missed events on leadership transitions

  • Runtime Introspection - Debug HTTP endpoints for runtime state inspection, event history tracking, and integration with acceptance testing

Validation and decisions

  • CRD Validation - Durable design decisions behind the HAProxyTemplateConfig CRD and the three-layer validation stack (OpenAPI schema, admission webhooks, runtime validation)

  • Design Decisions - Key architectural choices with rationale covering validation strategy, template engine selection, concurrency model, and observability integration

Process pages — Linting, Releasing, and the Gateway API Upgrade Playbook — cover the contributor workflow rather than the design.

Core capabilities

The controller provides these capabilities:

Template-Driven Configuration Generate HAProxy configurations using Scriggo templates with access to any Kubernetes resources you choose to watch. Templates give you complete control over the HAProxy configuration without annotation limitations.

Dynamic Resource Watching Monitor any Kubernetes resource types (Ingress, Service, ConfigMap, custom CRDs) you specify. Resources are indexed using JSONPath expressions for fast template lookups.

Validation-First Deployment Every changed render passes client-native syntax parsing, version-specific OpenAPI schema validation, haproxy -c, and configured rendered-output validators before publication or deployment (ADR-0020). The watched-resource admission webhook runs the same pipeline against an overlay, so a bad Ingress or HTTPRoute never lands in etcd. The HAProxyTemplateConfig itself has no webhook (ADR-0016); the leader validates its complete merged set, while the startup load gate and chart pre-rollout preflight hook also run its embedded validation tests. Identical output returns from a content-checksum cache without repeating validation.

Zero-Reload Optimization Configuration changes that only modify server weight, address, port, or maintenance state are applied through HAProxy's runtime API without process reloads. This maintains existing connections and minimizes service disruption. Changes requiring structural modifications trigger a reload.

Structured Configuration Comparison The controller parses both current and desired configurations into structured representations and performs fine-grained comparison at the attribute level. This minimizes unnecessary deployments and maximizes use of runtime API operations.

Declarative Kubernetes Resource Emission Templates declared under spec.k8sResources produce one or more Kubernetes resources per render (multi-doc YAML, ----separated). The renderer parses the rendered YAML, validates required fields, and feeds the result into the controller's resourceapplier, which reconciles the set to the cluster via Server-Side Apply with field manager haptic, prunes orphans across renders, and injects an OwnerReference to the HAProxyTemplateConfig CR so cascade-delete (for example, helm uninstall) GCs the rendered resources. Same engine context as the main haproxyConfig template (resources, filters, snippets, file registry, shared cache) — chart libraries can compose extension points and shared state across the two passes.

Design principles

Resource-Agnostic Engine The Go code must be agnostic to every Kubernetes resource you choose to watch. If you decide to use some CRD instead of Gateway or Ingress resources, you should only need to touch HAPTIC templates and config — no Go code. Writing templates for an arbitrary CRD must be just as comfortable as for Ingress or Gateway API resources, with no preferential treatment for well-known resources. Resource shape comes from the kube-apiserver (live) or --schema-dir (offline) at runtime; the controller never bakes in a fixed list of supported kinds. This applies to all Go-side machinery — engine filters, runtime-context types, generated wrappers, helpers — and to chart-side scaffolding that crosses the Go/template boundary. The corollary on the chart side: resource-specific behavior lives in resource-specific template libraries (ingress.yaml, gateway/*.yaml, vendor annotation libs), never in base.yaml.

Fail-Safe Operation Invalid configurations are rejected before reaching production. The validation phase catches syntax errors, semantic issues, and configuration conflicts. If validation fails, the current production configuration remains unchanged.

Performance Through Indexing Resource indexing using JSONPath expressions enables O(1) lookups in templates. Debouncing at the per-watcher level coalesces rapid resource changes into a single ResourceIndexUpdatedEvent before the reconciler fires; the reconciler itself triggers immediately with no added latency. Rate limiting (the deployer's minDeploymentInterval) prevents deployment conflicts.

Observable Event Flow All component interactions flow through the EventBus. The Event Commentator subscribes to all events and produces structured logs with contextual insights. Metrics track reconciliation cycles, validation results, and deployment success rates.

Clean Component Separation Pure business logic components (templating, k8s, dataplane) have no event dependencies and can be tested in isolation. Event adapters in the controller package coordinate these pure components through EventBus messages.

See also

User guides

Operations

Package documentation

Found a problem on this page? Report it or edit the page with the pencil icon above the title.