fleet-manager: checkpoint 4 — InfluxDB metrics gateway (write + Flux read)
The manager is the ONLY thing that talks to InfluxDB, both directions; hosts hold
no token and never reach it.
- influx.go: influxSink implements MetricsSink — each agent HealthSnapshot is
written as a fleet_health point, identity (serial/hostnqn/starship) as TAGS
from the AUTHENTICATED stream (a host can only write its own series), telemetry
as fields. QueryField answers the SPA's read: a Flux query built with a strict
FIELD WHITELIST + hex-serial guard + parsed-duration range, so no caller input
is interpolated raw (injection-safe). Server-side influxdb-client-go/v2.
- admin.go: GET /api/hosts/{serial}/metrics?field=&range= -> time-series JSON;
503 when InfluxDB isn't wired, 400 on a bad range, 404 for unknown host.
- main.go: sink created when FLEET_MGR_INFLUX_URL+_TOKEN set, fed to the agent
hub; nil -> no-op sink so the manager runs without metrics.
Tests: snapshotPoint tags/fields/time, fluxQuery contents + %q quoting, and
QueryField rejects a non-hex serial + non-whitelisted field. build/test/vet/gofmt
clean. Live InfluxDB path validated in the compose bench (ckpt 6).
This commit is contained in:
@@ -42,6 +42,9 @@ type config struct {
|
||||
brokerClientKey string
|
||||
brokerCA string
|
||||
|
||||
// InfluxDB (the manager is the only reader/writer; token is server-side only).
|
||||
influx influxConfig
|
||||
|
||||
inventoryPath string
|
||||
seed bool
|
||||
hostnqnBase string
|
||||
@@ -65,6 +68,13 @@ func loadConfig() config {
|
||||
brokerClientKey: env("FLEET_MGR_BROKER_CLIENT_KEY", ""),
|
||||
brokerCA: env("FLEET_MGR_BROKER_CA", ""),
|
||||
|
||||
influx: influxConfig{
|
||||
url: env("FLEET_MGR_INFLUX_URL", ""),
|
||||
token: env("FLEET_MGR_INFLUX_TOKEN", ""),
|
||||
org: env("FLEET_MGR_INFLUX_ORG", "fleet"),
|
||||
bucket: env("FLEET_MGR_INFLUX_BUCKET", "fleet_health"),
|
||||
},
|
||||
|
||||
inventoryPath: env("FLEET_MGR_INVENTORY", "/var/lib/fleet-manager/inventory.json"),
|
||||
seed: env("FLEET_MGR_SEED", "") == "true",
|
||||
hostnqnBase: env("HOSTNQN_BASE", "nqn.2026-06.org.example.fleet:host-"),
|
||||
@@ -77,6 +87,7 @@ type server struct {
|
||||
store Store
|
||||
hub *agentHub // live agent streams; nil if the agent plane isn't configured
|
||||
broker *brokerClient // enrol-broker admin client; nil if authorise isn't configured
|
||||
influx *influxSink // metrics gateway; nil if InfluxDB isn't configured
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -106,11 +117,24 @@ func main() {
|
||||
log.Printf("authorise pillar DISABLED (set FLEET_MGR_BROKER_URL + client cert/key + CA to enable)")
|
||||
}
|
||||
|
||||
// Metrics gateway: the manager is the only reader/writer of InfluxDB. nil (a
|
||||
// no-op MetricsSink) when unconfigured, so the manager runs without metrics.
|
||||
var sink MetricsSink
|
||||
if cfg.influx.url != "" && cfg.influx.token != "" {
|
||||
s.influx = newInfluxSink(cfg.influx)
|
||||
defer s.influx.Close()
|
||||
sink = s.influx
|
||||
log.Printf("InfluxDB metrics gateway -> %s (org=%s bucket=%s)", cfg.influx.url, cfg.influx.org, cfg.influx.bucket)
|
||||
} else {
|
||||
log.Printf("InfluxDB DISABLED (set FLEET_MGR_INFLUX_URL + _TOKEN to enable metrics)")
|
||||
}
|
||||
|
||||
// Agent plane (gRPC/mTLS, fleet CA). Started only when configured; the admin
|
||||
// plane can run standalone (e.g. before any host is enrolled). The hub is the
|
||||
// live-stream registry the lifecycle API pushes desired-state through.
|
||||
// live-stream registry the lifecycle API pushes desired-state through, and it
|
||||
// writes each snapshot to the metrics sink.
|
||||
if cfg.agentCert != "" && cfg.agentKey != "" && cfg.agentClientCA != "" {
|
||||
s.hub = newAgentHub(store, nil) // InfluxDB sink wired in checkpoint 4
|
||||
s.hub = newAgentHub(store, sink)
|
||||
go func() {
|
||||
if err := serveAgent(cfg, s.hub); err != nil {
|
||||
log.Fatalf("agent plane: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user