Skip to content

Discovery & Monitoring (Agent)

flowchart LR
    API[Berth API\nCQRS + PostgreSQL 18 + Redis 8.10] <-->|gRPC mTLS\ncommands / state / metrics / logs| A[Berth Agent Go]
    A <-->|Docker Engine API\n/var/run/docker.sock| D[Docker daemon]
    D --> C[Containers locaux]
    A -->|heartbeat + snapshots| API
    API -->|audit asynchrone| RS[(Redis Streams)]
  1. Démarrage : l’Agent charge sa configuration, valide ses certificats, ouvre Docker, établit le canal mTLS et s’enregistre.
  2. Discovery : ContainerList fournit un snapshot initial ; Events suit ensuite create/start/stop/die/destroy/rename.
  3. Observation : une collecte périodique envoie des métriques et l’état utile. Les événements ne remplacent pas le snapshot périodique, qui corrige les éventuelles pertes de connexion au daemon.
  4. Commande : l’API authentifie l’utilisateur, enregistre l’intention, envoie une commande idempotente à l’Agent ; l’Agent vérifie le contexte, exécute via Docker et renvoie le résultat.
  5. Logs : l’API ouvre un stream pour un container ; l’Agent lit ContainerLogs avec Follow=true et transmet des chunks bornés.
  6. Persistance : l’Agent ne persiste pas l’état métier. L’API écrit PostgreSQL 18 et publie son audit selon le pattern déjà défini.

La discovery combine :

  • un snapshot ContainerList(ctx, types.ContainerListOptions{All: true}) au démarrage puis à intervalle lent configurable ;
  • Events avec filtres type=container pour recevoir rapidement les changements ;
  • une réconciliation après reconnexion, car un stream d’événements interrompu ne doit pas laisser un état obsolète.
internal/docker/discovery.go
package docker
import (
"context"
"io"
"log/slog"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
)
type Discovery struct {
cli *client.Client
log *slog.Logger
}
func (d *Discovery) Snapshot(ctx context.Context) ([]types.Container, error) {
return d.cli.ContainerList(ctx, types.ContainerListOptions{All: true})
}
func (d *Discovery) Watch(ctx context.Context, onEvent func(types events.Message)) error {
f := filters.NewArgs(filters.Arg("type", "container"))
msgs, errs := d.cli.Events(ctx, types.EventsOptions{Filters: f})
for {
select {
case <-ctx.Done():
return ctx.Err()
case msg, ok := <-msgs:
if !ok { return io.EOF }
onEvent(msg)
case err := <-errs:
if err != nil { return err }
}
}
}

Exemple conceptuel : selon la version du SDK, importer github.com/docker/docker/api/types/events pour le type events.Message et adapter la signature. Le wrapper interne masque ces détails au reste de l’Agent.

Les événements déclenchent une mise à jour légère (état, nom, labels). Un snapshot périodique vérifie également que chaque container géré est toujours présent, récupère les nouveaux containers éligibles et supprime les entrées disparues.