Deployment diagrams¶
Kubernetes Deployment Architecture¶
graph TB
subgraph "Kubernetes Cluster"
API[Kubernetes API Server]
subgraph "haptic Namespace (release namespace)"
subgraph "Controller Deployment (2 replicas, leader-elected)"
CTRL1[Controller Pod 1<br/>leader]
CTRL2[Controller Pod 2<br/>hot standby]
end
HTPLCFG[HAProxyTemplateConfig CRD<br/>Templates, watched resources, settings]
CREDS[Secret<br/>agent credentials]
CTRL_SVC[Controller Service<br/>ClusterIP<br/>:8080 healthz + /debug<br/>:9090 metrics<br/>:9443 webhook]
subgraph "HAProxy Deployment (2+ replicas)"
subgraph "haproxy pod A"
HAP1[HAProxy<br/>:80, :443, :8404]
AG1[HAPTIC agent<br/>:5555]
end
subgraph "haproxy pod B"
HAP2[HAProxy<br/>:80, :443, :8404]
AG2[HAPTIC agent<br/>:5555]
end
end
HAP_SVC[HAProxy Service<br/>NodePort by default<br/>:80 → :80<br/>:443 → :443]
end
subgraph "Application Namespaces"
ING[Ingress / HTTPRoute / GRPCRoute]
APPSVC[Services + EndpointSlices]
PODS[Application Pods]
end
end
USERS[External Users] --> HAP_SVC
HAP_SVC --> HAP1
HAP_SVC --> HAP2
CTRL_SVC --> CTRL1 & CTRL2
API --> CTRL1 & CTRL2
HTPLCFG --> CTRL1 & CTRL2
CREDS --> CTRL1 & CTRL2
ING -.Watch.-> CTRL1 & CTRL2
APPSVC -.Watch.-> CTRL1 & CTRL2
CTRL1 --> AG1 & AG2
AG1 --> HAP1
AG2 --> HAP2
HAP1 --> PODS
HAP2 --> PODS
Deployment Components:
-
Controller Deployment — defaults to 2 replicas with leader election
- All replicas watch Kubernetes resources, run admission webhooks, and discover HAProxy pods (hot standby — keeps caches warm so failover is instant)
- Only the elected leader runs the render Pipeline and applies configuration through each pod's HAPTIC agent
- See High Availability for tuning failover and Leader Election for the full all-replica vs leader-only component split
-
Controller Service (ClusterIP) — operational endpoints only
:8080→ healthz probes and/debug/*introspection:9090→ Prometheus metrics:9443→ validating webhook
-
HAProxy Deployment (not StatefulSet) — scales horizontally
- Each pod runs HAProxy plus the HAPTIC agent, sharing the config volume
- Pods are auto-discovered via
controller.config.podSelector; a pod is admitted once it has an IP, itsagentcontainer is running, and itsGET /v1/stateanswers
-
HAProxy Service — NodePort by default; set
haproxy.service.type: LoadBalancerfor cloud providers- Service port 80 maps to HAProxy container port 80, service port 443 maps to 443 (the chart binds HAProxy on the literal 80/443; set
haproxy.ports.http/httpsto override)
- Service port 80 maps to HAProxy container port 80, service port 443 maps to 443 (the chart binds HAProxy on the literal 80/443; set
-
HAProxyTemplateConfig CRD — holds every piece of configuration the controller needs
- Template bodies (
haproxyConfig,templateSnippets,maps,files,sslCertificates) watchedResources(what to subscribe to and how to index it)- Apply tuning (
minDeploymentInterval,driftPreventionInterval, storage paths) - Validation tests shipped alongside the templates
- Template bodies (
-
Credentials Secret referenced by
spec.credentialsSecretRef— holds the agent's username and password. The controller watches it live; the HAProxy pods read it through their environment, so a rotation needs a pod roll on their side.
Container Architecture¶
graph TB
subgraph "Controller Pod"
CTRL_MAIN[Controller Process<br/>:8080 healthz + /debug<br/>:9090 metrics<br/>:9443 webhook]
CTRL_TMP["/tmp emptyDir<br/>haproxy -c validation"]
end
subgraph "HAProxy Pod (Deployment member)"
HAP_PROC[HAProxy Process<br/>:80 HTTP<br/>:443 HTTPS<br/>:8404 Stats]
AGENT_PROC[HAPTIC agent<br/>:5555 API<br/>Unix master + worker sockets]
HAP_VOL[Shared config emptyDir<br/>/etc/haproxy<br/>maps/, ssl/, general/]
end
HTPLCFG[HAProxyTemplateConfig CRD] -. watch .-> CTRL_MAIN
CREDS_SECRET[Credentials Secret] -. watch .-> CTRL_MAIN
CTRL_TMP --> CTRL_MAIN
AGENT_PROC <-. master + worker sockets .-> HAP_PROC
HAP_VOL --> HAP_PROC
HAP_VOL --> AGENT_PROC
Resource Requirements: chart defaults, the sizing table, and the GOMAXPROCS/GOMEMLIMIT container-awareness mechanics live in Performance — Controller Resource Sizing. Diagram-relevant specifics: the controller writes transient haproxy -c validation files to a /tmp emptyDir (root filesystem is read-only), and both HAProxy-pod containers share the config emptyDir mounted at /etc/haproxy.
Network topology¶
graph LR
subgraph "External Network"
INET[Internet]
end
subgraph "Kubernetes Cluster Network"
HAP_LB[HAProxy Service<br/>LoadBalancer<br/>External IP]
CTRL_SVC_NET[Controller Service<br/>ClusterIP<br/>10.96.0.10]
subgraph "Pod Network"
subgraph "Controller Pod<br/>10.0.0.10"
CTRL[Controller Process<br/>:8080, :9090]
end
subgraph "HAProxy Instances"
subgraph "haproxy pod A<br/>10.0.1.10"
HAP1[HAProxy Process<br/>:80, :443, :8404]
AG1[HAPTIC agent<br/>:5555]
end
subgraph "haproxy pod B<br/>10.0.1.11"
HAP2[HAProxy Process<br/>:80, :443, :8404]
AG2[HAPTIC agent<br/>:5555]
end
end
subgraph "Application Pods"
APP1[app-pod-1<br/>10.0.2.10]
APP2[app-pod-2<br/>10.0.2.11]
end
end
KUBE_API[Kubernetes API<br/>443]
PROM_NET[Prometheus]
end
INET --> HAP_LB
HAP_LB --> HAP1
HAP_LB --> HAP2
CTRL_SVC_NET --> CTRL
PROM_NET --> CTRL_SVC_NET
CTRL --> KUBE_API
CTRL --> AG1
CTRL --> AG2
HAP1 --> APP1
HAP1 --> APP2
HAP2 --> APP1
HAP2 --> APP2
AG1 -.sockets.-> HAP1
AG2 -.sockets.-> HAP2
Network Flow:
- Ingress Traffic: Internet → HAProxy Service → HAProxy Pods → Application Pods (the diagram shows the
haproxy.service.type: LoadBalancervariant; the chart default is NodePort) - Control Plane: Controller → Kubernetes API (resource watching)
- Configuration apply: Controller → each pod's agent (HTTP)
- Service Discovery: Controller watches HAProxy pods via Kubernetes API
- Monitoring: Prometheus → Controller Service (ClusterIP) → Controller Pod (metrics endpoint)
- Health Checks: Kubernetes → Controller Service → Controller Pod (healthz endpoint)
Scaling Considerations: HAProxy scales horizontally via haproxy.replicaCount (pods are auto-discovered through controller.config.podSelector); the controller scales for availability, not throughput — see Performance — Scaling Strategies and High Availability. NetworkPolicy must allow the controller to reach the agent on port 5555 on each HAProxy pod (Networking).
Build optimizations (contributors)¶
Controller images are built with Go's Profile-Guided Optimization (PGO), which typically provides 2-7% CPU improvement by optimizing frequently called functions. A baseline CPU profile (cmd/haptic/default.pgo) is committed to the repository; Go automatically uses it during builds to optimize hot paths.
Updating the profile from the development environment:
-
Start the dev environment:
-
Port-forward to the controller's debug port:
-
Generate workload (trigger reconciliation by modifying resources).
-
Collect a 30-second CPU profile:
-
Rebuild with the new profile:
For optimal results, collect profiles from production during representative workloads and merge multiple profiles for broader coverage: