Skip to main content
Version: 1.0

Core Services

KubeOpera's backend is a polyrepo of Go microservices following hexagonal architecture. Most services share the same structural conventions closely enough that understanding one goes a long way toward understanding the rest — the layout below is the norm, not a strict rule every service obeys to the letter. A handful of older or earlier-stage services (nodes-manager is the clearest example) use a mix of an older internal/adapter/ singular layout alongside the newer internal/adapters/ plural one, or configure themselves from a YAML file with environment overrides rather than pure environment variables. Where a service's own page notes a difference from this pattern, trust the service's page.

Hexagonal Architecture Pattern​

Every service uses the same layout:

cmd/server/main.go                   Entry point: wire dependencies, start HTTP server
internal/
core/
domain/models.go Domain types, bun ORM models
ports/ports.go Interfaces: repository, publisher, consumer, etc.
services/ Business logic (depends only on ports)
adapters/
http/handler/
http_handler.go chi router, HTTP handlers
auth_middleware.go X-API-Key validation
dto.go Request/response types
messaging/rabbitmq/
consumer.go AMQP consumer (exchange + queue declaration)
publisher.go AMQP publisher
repository/postgres/
bun_repository.go bun ORM repository implementation
migrations/ goose SQL migrations (per-service schema)
deployments/
base/ Kubernetes Deployment + Service manifests
overlays/{development,staging,production}/
Dockerfile

Services only import from their own internal/ packages. Cross-service communication is exclusively via RabbitMQ exchanges (async) or HTTP (sync, from the frontend proxy layer).

Common Dependencies​

All services use the same versions of:

PackageVersionPurpose
github.com/go-chi/chi/v55.0.10HTTP router
github.com/go-chi/cors1.2.1CORS middleware
github.com/uptrace/bun1.1.17ORM + query builder
github.com/uptrace/bun/driver/pgdriver1.1.17PostgreSQL driver
github.com/pressly/goose/v33.26.0SQL migrations
github.com/rabbitmq/amqp091-go1.10.0RabbitMQ client
github.com/google/uuid1.6.0UUID generation

Service Discovery​

In Kubernetes, services resolve each other by short name within the same namespace (e.g. http://analysis-agent-srv:8093). Locally, each service defaults to http://localhost:{port} and can be overridden via environment variables.

Health Checks​

Every service exposes GET /healthz returning 200 OK. Used by Kubernetes liveness and readiness probes, the AI agent health dashboard, and the MCP server.

Database Conventions​

  • Each service declares CREATE SCHEMA IF NOT EXISTS {schema} in its first goose migration
  • The DATABASE_URL DSN is extended with ?search_path={schema} so all queries are automatically scoped
  • Tables use the full {schema}.{table} syntax in bun.BaseModel tags
  • Foreign keys only exist within a single schema — no cross-service FK constraints

RabbitMQ Conventions​

All exchanges are topic type and durable. Queue names follow the pattern {consumer-service}.{exchange-short-name}. All queues are durable with autoDelete: false. Publishers declare the exchange on startup; consumers declare exchange, queue, and binding on startup.

The table below states what each exchange is for — whether a given publish actually reaches its intended consumer today is a separate question, and two rows are flagged where it doesn't. A topic exchange only delivers a message to a queue whose binding pattern matches the message's routing key segment-by-segment from the left; a publisher and a consumer can agree on the exchange name and still never exchange a single message if their routing keys and binding patterns don't actually line up. Both broken rows below were confirmed by reading the publisher's and consumer's code side by side, not assumed from the exchange name alone.

ExchangePublisherConsumersStatus
observability.telemetryobservability-agent-srvanalysis-agent-srvWorking
analysis.decisionsanalysis-agent-srvaction-agent-srvWorking
analysis.insightsanalysis-agent-srvrecommendation-agent-srv, agent-runtimeWorking
action.outcomesaction-agent-srvfeedback-agent-srvWorking
feedback.signalsfeedback-agent-srvanalysis-agent-srvBroken — feedback-agent-srv publishes real reinforcement/correction signals here, but analysis-agent-srv never starts a consumer for this exchange at all. The adaptive-threshold feedback loop this exchange exists for has never run.
security.posturesecurity-collectorincident-managerWorking
k8s.anomaliesk8s-monitor (opt-in)anomaly-detector, incident-managerWorking
k8s.anomalies (also)kubeopera-ai (when health_score < 70)anomaly-detector, incident-managerBroken — published with routing key kubeopera-ai.{severity}, but anomaly-detector's queue binds only to anomaly.#. The first routing-key segment never matches, so the broker silently drops every one of these messages. See Optimizer for detail.
k8s.selfhealanomaly-detectorincident-managerWorking
cicd.eventscicd-gateway(future consumers)Publishes correctly; nothing subscribes yet
incidents.eventsincident-manager(future consumers)Publishes correctly; nothing subscribes yet
nodes.eventsnodes-manager(future consumers)Publishes correctly; nothing subscribes yet