Files
spinifex/cmd/lb-agent/main.go
T
Julian Sommer c9e330d8ae refactor: consume bluebottle otelsetup, drop the local bootstrap
otelsetup/otelsetup.go and otelsetup/slog.go were a near-copy of the shared
bootstrap. Entrypoints now call bluebottle directly, passing
WithEnvironmentFallback for SPINIFEX_CI_ENV; the package keeps only the HTTP
middleware and request metrics. Init reattaches the OTLP bridge to a default
logger installed before it ran, so the local addFanoutHandler is no longer
needed.

The bridge is now level-gated, so services no longer ship Debug records to
the sink while running at Info.
2026-08-12 11:26:25 +10:00

100 lines
2.9 KiB
Go

// lb-agent runs inside load balancer VMs and manages HAProxy configuration.
//
// It polls the AWS gateway for config updates and reports health via heartbeats.
// All communication uses SigV4-signed HTTP requests.
//
// Usage:
//
// lb-agent --lb-id=lb-xxxxx --gateway=https://192.168.1.33:9999 --access-key=AKIA... --secret-key=...
//
// Flags default to environment variables set by cloud-init:
// LB_LB_ID, LB_GATEWAY_URL, LB_ACCESS_KEY, LB_SECRET_KEY, LB_REGION.
package main
import (
"flag"
"log/slog"
"os"
"os/signal"
"syscall"
"github.com/mulgadc/bluebottle/pkg/otelsetup"
"github.com/mulgadc/spinifex/spinifex/lbagent"
_ "github.com/mulgadc/spinifex/internal/fipsboot"
)
func main() {
// Without this the agent falls back to slog's default text handler, which folds
// attributes into the message so the console scraper recovers a level and nothing
// else. No OTLP export: the agent's only egress is the gateway it is watching.
otelsetup.SetDefaultJSONLogger("lb-agent", slog.LevelInfo)
var (
lbID string
gatewayURL string
accessKey string
secretKey string
region string
)
hostname, _ := os.Hostname()
flag.StringVar(&lbID, "lb-id", envOrDefault("LB_LB_ID", hostname), "Load balancer ID")
flag.StringVar(&gatewayURL, "gateway", os.Getenv("LB_GATEWAY_URL"), "Gateway URL (e.g. https://192.168.1.33:9999)")
flag.StringVar(&accessKey, "access-key", os.Getenv("LB_ACCESS_KEY"), "AWS access key ID")
flag.StringVar(&secretKey, "secret-key", os.Getenv("LB_SECRET_KEY"), "AWS secret access key")
flag.StringVar(&region, "region", envOrDefault("LB_REGION", "us-east-1"), "AWS region for SigV4 signing")
flag.Parse()
if lbID == "" {
slog.Error("--lb-id is required (or set LB_LB_ID)")
os.Exit(1)
}
if gatewayURL == "" {
slog.Error("--gateway is required (or set LB_GATEWAY_URL)")
os.Exit(1)
}
// Empty keys select IMDS instance-role credentials (the SDK default chain
// falls back to the in-VM IMDS endpoint); static keys sign as-is.
credSource := "imds"
if accessKey != "" && secretKey != "" {
credSource = "static"
}
slog.Info("Starting LB agent", "lbId", lbID, "gateway", gatewayURL, "credSource", credSource)
agent, err := lbagent.New(lbID, gatewayURL, accessKey, secretKey, region)
if err != nil {
slog.Error("Failed to create agent", "err", err)
os.Exit(1)
}
// Start poll loop in a goroutine — it blocks until Stop is called.
errCh := make(chan error, 1)
go func() {
if err := agent.Start(); err != nil {
errCh <- err
}
}()
// Wait for shutdown signal or fatal error.
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
select {
case sig := <-sigCh:
slog.Info("Received signal, shutting down", "signal", sig)
case err := <-errCh:
slog.Error("Agent error", "err", err)
}
agent.Stop()
}
func envOrDefault(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}