deployer

package
v0.2.0-alpha.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

README

pkg/controller/deployer

Three components that together get validated configurations onto HAProxy pods:

  • DeploymentScheduler — decides when to deploy. Keeps the last validated config + last discovered endpoints, rate-limits to minDeploymentInterval, and queues at most one pending deployment ("latest wins"). Also times out deployments that take longer than deploymentTimeout so a dropped DeploymentCompletedEvent can't wedge the pipeline forever.
  • Component (the deployer itself) — stateless executor. Consumes DeploymentScheduledEvent and deploys to every discovered HAProxy endpoint in parallel using pkg/dataplane.Client. Its per-sync timeouts (reloadVerificationTimeout, syncTimeout, passed to New) are forwarded to each pkg/dataplane sync.
  • DriftPreventionMonitor — fires a synthetic DriftPreventionTriggeredEvent every driftPreventionInterval when nothing has deployed recently, so an out-of-band change applied directly via the Dataplane API gets overwritten by the controller's last-known-good config.

All three are leader-only — only the replica holding the Lease deploys, observers on other replicas stay idle.

Minimal Usage

import (
    "context"
    "time"

    "gitlab.com/haproxy-haptic/haptic/pkg/controller/deployer"
    "gitlab.com/haproxy-haptic/haptic/pkg/events"
)

scheduler := deployer.NewDeploymentScheduler(
    bus, logger,
    2*time.Second,   // minDeploymentInterval
    30*time.Second,  // deploymentTimeout
)
exec := deployer.New(bus, logger,
    10*time.Second, // reloadVerificationTimeout
    2*time.Minute,  // syncTimeout
)
monitor := deployer.NewDriftPreventionMonitor(bus, logger, 60*time.Second)

go scheduler.Start(ctx)
go exec.Start(ctx)
go monitor.Start(ctx)

All durations / ints come from spec.dataplane and spec.controller on the CRD: minDeploymentInterval, deploymentTimeout, driftPreventionInterval, reloadVerificationTimeout, syncTimeout.

Event Flow

TemplateRenderedEvent ───────┐
ValidationCompletedEvent ────┤
HAProxyPodsDiscoveredEvent ──┤
DriftPreventionTriggeredEvent┤
DeploymentCompletedEvent ────┤       (feedback edge)
                             ▼
                     DeploymentScheduler
                             │
                             ▼
                     DeploymentScheduledEvent
                             │
                             ▼
                         Component
                             │
                             ▼
           DeploymentStartedEvent
           InstanceDeployedEvent         (per endpoint)
           InstanceDeploymentFailedEvent (per endpoint)
           DeploymentCompletedEvent
                             │
                             ▼
                   DriftPreventionMonitor
                             │
                             ▼
                   DriftPreventionTriggeredEvent (if idle for > interval)

Notable details:

  • The scheduler only deploys when it has all three inputs: a rendered config, a successful validation, and at least one discovered HAProxy endpoint. Partial state waits.
  • "Latest wins" is a single slot — concurrent changes don't queue up as a FIFO, they coalesce to the most recent one.
  • DeploymentCompletedEvent both closes the in-progress flag in the scheduler and resets the drift-monitor's idle timer, which is why it's on the feedback edge in the diagram.
  • deploymentTimeout is a safety net, not an operational target — hitting it means a lost completion event or a stuck dataplane call, both of which are bugs to investigate.

Leadership Transitions

On LostLeadershipEvent the scheduler drops any pending deployment and clears its in-progress flag (otherwise a new leader would wait on a deployment the dead leader was handling); the drift monitor stops its timer.

On BecameLeaderEvent the scheduler is bootstrapped from two sides:

  • All-replica components that maintain state replay their last event so the new leader's scheduler doesn't have to wait. Currently that's HAProxyPodsDiscoveredEvent (from pkg/controller/discovery) and ConfigValidatedEvent (from pkg/controller/configchange). Grep for leadership.NewStateReplayer[ to see the canonical list.
  • Neither TemplateRenderedEvent nor ValidationCompletedEvent is replayed — both are published by the leader-only reconciler.Coordinator from inside Pipeline.Execute (ADR-0001), so they only exist on the leader to begin with. Instead, the reconciler triggers a fresh reconciliation on BecameLeaderEvent (see pkg/controller/reconciler/reconciler.go:handleBecameLeader), which produces fresh render+validate events rather than stale replays. The new leader's scheduler then assembles all three inputs naturally.

See pkg/controller/LEADER_ONLY_COMPONENTS.md for the full replay/clear contract every leader-only component implements.

See Also

  • pkg/dataplane — the Client.Sync call that the executor drives
  • pkg/controller/discovery — publishes HAProxyPodsDiscoveredEvent
  • pkg/controller/reconciler — leader-only Coordinator that publishes TemplateRenderedEvent and ValidationCompletedEvent from inside Pipeline.Execute (the synchronous renderer + validator services live in pkg/controller/renderer and pkg/controller/validation, but are called directly, not subscribed to)
  • pkg/controller/leadership — the gating helper these components use
  • pkg/controller/LEADER_ONLY_COMPONENTS.md — leadership-transition patterns
  • docs/controller/docs/operations/high-availability.md — user-facing view of the leader-only deployment split

License

Apache-2.0 — see root LICENSE.

Documentation

Overview

Package deployer implements the Deployer component that deploys validated HAProxy configurations to discovered HAProxy pod endpoints.

The Deployer is a stateless executor that receives DeploymentScheduledEvent and executes deployments to the specified endpoints. All deployment scheduling, rate limiting, and queueing logic is handled by the DeploymentScheduler component.

Package deployer implements deployment scheduling and execution components.

Index

Constants

View Source
const (
	// ComponentName is the unique identifier for this component.
	ComponentName = "deployer"

	// EventBufferSize is the size of the event subscription buffer.
	// Low-volume component (~1-2 deployment events per reconciliation cycle).
	EventBufferSize = busevents.StandardSubscriberBuffer
)
View Source
const (
	// SchedulerComponentName is the unique identifier for the DeploymentScheduler component.
	SchedulerComponentName = "deployment-scheduler"

	// SchedulerEventBufferSize is the size of the event subscription buffer for the scheduler.
	// Moderate-volume component handling template, validation, and discovery events.
	// Named with "Scheduler" prefix to avoid conflict with EventBufferSize in this package.
	SchedulerEventBufferSize = busevents.StandardSubscriberBuffer
)
View Source
const (
	// DriftMonitorComponentName is the unique identifier for the drift prevention monitor component.
	DriftMonitorComponentName = "drift-monitor"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Component

type Component struct {
	*component.Base
	*component.ReadySignal
	// contains filtered or unexported fields
}

Component implements the deployer component.

It subscribes to DeploymentScheduledEvent and deploys configurations to HAProxy instances. This is a stateless executor - all scheduling logic is handled by the DeploymentScheduler component.

Event subscriptions:

  • DeploymentScheduledEvent: Execute deployment to specified endpoints
  • DeploymentCancelRequestEvent: Cancel in-progress deployment

The component publishes deployment result events for observability.

func New

func New(eventBus *busevents.EventBus, logger *slog.Logger, reloadVerificationTimeout, syncTimeout time.Duration) *Component

New creates a new Deployer component.

Parameters:

  • eventBus: The EventBus for subscribing to events and publishing results
  • logger: Structured logger for component logging
  • reloadVerificationTimeout: bounds how long each sync waits for HAProxy to report a graceful reload as completed
  • syncTimeout: overall per-endpoint sync timeout (parse + diff + apply + optional reload-verify)

Returns:

  • A new Component instance ready to be started

func (*Component) CoalescesOn

func (c *Component) CoalescesOn() []string

CoalescesOn implements component.CoalescingHandler: after each dispatch, the embedded component.Base drains the subscription channel and processes only the LATEST pending coalescible DeploymentScheduledEvent ("latest wins"), superseding intermediates. Non-coalescible events (e.g. from drift_prevention, validation_fallback) are always processed and never skipped.

This coalescing is load-bearing: deployment is single-threaded, but the validator + scheduler upstream can fire many DeploymentScheduledEvents during a single deployment. Without it, the deployer would process every queued event in FIFO order — deploying the OLDEST pending config first and falling further and further behind under load.

func (*Component) HandleEvent

func (c *Component) HandleEvent(event busevents.Event)

HandleEvent implements component.EventHandler: it routes events to the appropriate handler.

func (*Component) HealthCheck

func (c *Component) HealthCheck() error

HealthCheck implements the lifecycle.HealthChecker interface. Returns an error if the component appears to be stalled (processing for > timeout). Returns nil when idle (not processing) - idle is always healthy for event-driven components.

func (*Component) Start

func (c *Component) Start(ctx context.Context) error

Start begins the deployer's event loop.

This method blocks until the context is cancelled or an error occurs.

Parameters:

  • ctx: Context for cancellation and lifecycle management

Returns:

  • nil when context is cancelled (graceful shutdown)
  • Error only in exceptional circumstances

type DeploymentScheduler

type DeploymentScheduler struct {
	*component.ReadySignal
	// contains filtered or unexported fields
}

DeploymentScheduler implements deployment scheduling with rate limiting.

It subscribes to events that trigger deployments, maintains the state of rendered and validated configurations, and enforces minimum deployment intervals.

Event subscriptions:

  • TemplateRenderedEvent: Track rendered config and auxiliary files
  • ValidationCompletedEvent: Cache validated config and schedule deployment
  • ValidationFailedEvent: Deploy cached config for drift prevention fallback
  • HAProxyPodsDiscoveredEvent: Update endpoints and schedule deployment

The component publishes DeploymentScheduledEvent when a deployment should execute.

func NewDeploymentScheduler

func NewDeploymentScheduler(eventBus *busevents.EventBus, logger *slog.Logger, minDeploymentInterval, deploymentTimeout time.Duration) *DeploymentScheduler

NewDeploymentScheduler creates a new DeploymentScheduler component.

Parameters:

  • eventBus: The EventBus for subscribing to events and publishing scheduled deployments
  • logger: Structured logger for component logging
  • minDeploymentInterval: Minimum time between consecutive deployments (rate limiting)
  • deploymentTimeout: Maximum time to wait for a deployment to complete before retrying

Returns:

  • A new DeploymentScheduler instance ready to be started

func (*DeploymentScheduler) HealthCheck

func (s *DeploymentScheduler) HealthCheck() error

HealthCheck implements the lifecycle.HealthChecker interface. Returns an error if the component appears to be stalled (processing for > timeout). Returns nil when idle (not processing) - idle is always healthy for event-driven components.

func (*DeploymentScheduler) Name

func (s *DeploymentScheduler) Name() string

Name returns the unique identifier for this component. Implements the lifecycle.Component interface.

func (*DeploymentScheduler) Start

func (s *DeploymentScheduler) Start(ctx context.Context) error

Start begins the deployment scheduler's event loop.

This method blocks until the context is cancelled or an error occurs. It subscribes to events when called (after leadership is acquired).

Parameters:

  • ctx: Context for cancellation and lifecycle management

Returns:

  • nil when context is cancelled (graceful shutdown)
  • Error only in exceptional circumstances

type DriftPreventionMonitor

type DriftPreventionMonitor struct {
	*component.ReadySignal
	// contains filtered or unexported fields
}

DriftPreventionMonitor triggers periodic reconciliation to prevent configuration drift in HAProxy pods.

When no deployment has occurred within the configured interval, it publishes a DriftPreventionTriggeredEvent to trigger reconciliation. This helps detect and correct configuration drift caused by other Dataplane API clients or manual changes.

This is a leader-only component that starts when leadership is acquired. Only the leader needs drift prevention since only the leader deploys. The Reconciler triggers fresh reconciliation on BecameLeaderEvent to provide current state.

Event subscriptions:

  • DeploymentCompletedEvent: Reset drift prevention timer
  • LostLeadershipEvent: Stop drift timer when losing leadership

The component publishes DriftPreventionTriggeredEvent when drift prevention is needed.

func NewDriftPreventionMonitor

func NewDriftPreventionMonitor(eventBus *busevents.EventBus, logger *slog.Logger, driftPreventionInterval time.Duration) *DriftPreventionMonitor

NewDriftPreventionMonitor creates a new DriftPreventionMonitor component.

As a leader-only component, subscription happens in Start() after leadership is acquired, not during construction.

Parameters:

  • eventBus: The EventBus for subscribing to events and publishing triggers
  • logger: Structured logger for component logging
  • driftPreventionInterval: Interval after which to trigger drift prevention deployment

Returns:

  • A new DriftPreventionMonitor instance ready to be started

func (*DriftPreventionMonitor) HealthCheck

func (m *DriftPreventionMonitor) HealthCheck() error

HealthCheck implements the lifecycle.HealthChecker interface. Returns an error if the component appears to be stalled (no timer tick for > stallTimeout). For a timer-based component like DriftPreventionMonitor, a healthy state means the timer is firing at the expected interval.

func (*DriftPreventionMonitor) Name

func (m *DriftPreventionMonitor) Name() string

Name returns the unique identifier for this component. Implements the lifecycle.Component interface.

func (*DriftPreventionMonitor) Start

Start begins the drift prevention monitor's event loop.

This method blocks until the context is cancelled or an error occurs. As a leader-only component, it subscribes to events when started (after leadership is acquired).

Event handling:

  • DeploymentCompletedEvent: Resets the drift prevention timer
  • LostLeadershipEvent: Stops drift timer when losing leadership
  • Drift timer expiration: Publishes DriftPreventionTriggeredEvent

The component runs until the context is cancelled, at which point it performs cleanup and returns.

Parameters:

  • ctx: Context for cancellation and lifecycle management

Returns:

  • nil when context is cancelled (graceful shutdown)
  • Error only in exceptional circumstances

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL