Skip to content

feat(compose): NATS-backed micro.mu example services in Docker - #4883

Draft
alex-dna-tech wants to merge 38 commits into
micro:masterfrom
alex-dna-tech:feat/examples-docker-compose
Draft

feat(compose): NATS-backed micro.mu example services in Docker#4883
alex-dna-tech wants to merge 38 commits into
micro:masterfrom
alex-dna-tech:feat/examples-docker-compose

Conversation

@alex-dna-tech

@alex-dna-tech alex-dna-tech commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Runs micro.mu example services in a dedicated micro-run container against a shared NATS registry/broker; gw container owns the dashboard/API/MCP gateway. Includes a fix syncing the configured registry back to registry.DefaultRegistry so cross-process discovery works.

What

Runs the micro.mu example services in a dedicated micro-run container against a shared NATS registry/broker, with the gw container owning the dashboard/API/MCP gateway. Verified end-to-end: services register on NATS, micro registry list/get works, MCP gateway discovers 19 tools across 5 services, and RPC calls route through the gateway.

Commits

  • feat: micro.mu examples container, compose builds image from source Dockerfile
  • fix(gateway): create admin account from MICRO_ADMIN_PASSWORD env
  • fix(compose): admin/micro credentials for examples container too
  • feat(compose): NATS registry/broker for container stack
  • fix(registry): sync configured registry to package global
  • feat(compose): dedicated micro-run service container

Key fix

cmd.Init isolated the configured registry in a local copy while the gateway and CLI registry command read registry.DefaultRegistry directly — so cross-process discovery silently returned nothing. Now the configured registry is synced back to the package global after configure (cmd/cmd.go).

Verification

  • micro registry list in micro-run: 5 services (users, web.service, orders, contacts, greeter)
  • MCP gateway: "Discovered 19 tools from 5 services"
  • RPC via gateway: greeter.Greeter.Hello{"message":"Hello from-gateway"}
  • One-shots pass: first-agent ✓, flow-loop quality 90, graceful-stop, agent-x402-buyer paid

@alex-dna-tech
alex-dna-tech marked this pull request as draft August 10, 2026 18:11
…ockerfile

- relocate docker-compose.yml to internal/docker/, fix volume mount paths
- add root micro.mu defining example services for micro run
- add examples container running micro run --address :8081 against repo
- build go-micro image from internal/docker/Dockerfile: compiles micro CLI and protoc-gen-micro from source, installs git/make/curl/protoc; repo root supplied as additional build context so paths stay relative to the compose file
- combine gw and mcp into one gw container: gateway --mcp-address :3000, ports 8080+3000
- llama: entrypoint starts server only if MiniCPM5-1B-Q4_K_M.gguf exists, otherwise downloads it from Hugging Face into the models folder; -n 1024 context
- gitignore runtime data; .dockerignore keeps build context lean
Container gateway (docker-compose) exposed :8080 with auth on, but the
browser dashboard login only accepts a stored user/password account and
the generated machine token is not accepted there — dead end, no way in.

Restore the documented admin/micro default for the compose image without
reintroducing a hardcoded credential: the gateway creates the admin
account only when the operator sets MICRO_ADMIN_PASSWORD (user defaults
to admin, overridable via MICRO_ADMIN_USER). Respects an existing
account and the auth/.admin-deleted marker. The compose file opts in
with the documented admin/micro.
The examples container (micro run --address :8081) binds an exposed
address so auth is on, but without MICRO_ADMIN_PASSWORD no admin account
exists and the documented admin/micro login fails with 'Invalid
credentials'. Set the same credentials as the gw container.
- add nats:latest service, point gw and examples at nats://nats:4222
- gateway: create logs/pid dirs so /logs and /status don't 500
cmd.Init isolated the registry in a local copy, but the gateway and CLI
registry command read registry.DefaultRegistry directly, so they never
saw the registry selected via flags/env. Sync it back after configure so
CLI list/get/watch, gateway and MCP discovery all query the same registry.
Run services from micro.mu in their own container (no gateway, no HTTP
listener); gw owns dashboard/API/MCP and proxies via NATS. Drop stale
go.mod/go.sum from hello-world and web-service so they build inside the
root v6 module. Add buildkit cache mounts and a warm Go module cache so
micro run rebuilds services offline and fast.
@alex-dna-tech
alex-dna-tech force-pushed the feat/examples-docker-compose branch from b0a1c7b to 3e2abca Compare August 12, 2026 08:58
Docker builds now resolve the owning go.mod via findModuleRoot and use
the module root as build context with SVC build-arg, so services nested
under a parent module (examples/) build correctly. Standalone modules
keep their own Dockerfile.

Compose: drop obsolete version field, add default gw service that
builds cmd/micro from source (internal/docker/Dockerfile) with admin
credentials and depends_on all services.

Also update llama-entrypoint.sh to the MiniCPM5 fable5 model and
-с 8192 context.
PID signal checks cannot reach processes in other containers; use
registry heartbeats so dashboard/status show live state in the
compose stack.
Teach agents tool discovery (micro_registry_*) via instructions field across httpjsonrpc, stdio, streamable, websocket transports. Compress AI.md.
Wire OTLP HTTP exporter into micro CLI, agent, flow, and service from
OTEL_EXPORTER_OTLP_ENDPOINT. Agent/flow default TraceProvider to the
global provider so spans emit whenever an exporter is configured, and
flush on Run/Stop so short-lived one-shot processes do not lose spans
at exit.
- Flow plain runs (flow exec / broker-triggered single step) now emit
  flow.run and flow.step spans via startRunSpan/runStepSpan; the single
  implicit step is tagged as RunInfo.Step=respond.
- Gateway /api/{service}/{method} proxy calls emit an api.proxy span
  with rpc.service/rpc.method, accept an inbound traceparent header, and
  carry the span context in the RPC metadata so downstream services nest.
- Set service.name on the OTLP resource (OTEL_SERVICE_NAME, else binary
  basename) so traces no longer show unknown_service:<binary>.
- micro gateway gets OTEL_SERVICE_NAME=go-micro, micro-server gets
  debug log level.
- Grafana now auto-provisions Tempo/Loki/Prometheus datasources at
  entrypoint instead of the pinned 13.1.0 image with mounted config.
- Drop agent-x402-buyer from micro.mu.
MCP tool spans already existed but never fired in the micro binary:
buildMCPOptions left TraceProvider unset and startToolSpan returned a
noop span when it was nil. Tracer now falls back to the global provider
(set by internal/otel), and invokeTool extracts the inbound traceparent
header so REST/streamable-HTTP calls nest under the caller's trace, the
same as the api.proxy spans.
Alloy collects all signals: metrics → Prometheus, logs → Loki,
traces → Tempo, profiles → Pyroscope. Prometheus becomes store-only
(remote-write receiver). OTEL routes through Alloy.

- alloy/config.alloy: prometheus.scrape, loki.source.docker,
  otelcol.receiver.otlp, pyroscope.scrape (pprof, not eBPF)
- loki/loki-config.yml: single-binary, TSDB, 7-day retention
- pyroscope/pyroscope-config.yml: single-binary, filesystem storage
- grafana datasources: add Loki + Pyroscope, remove inline entrypoint
- prometheus: simplified config (Alloy owns scraping)
- all services: json-file logging with max-size 10m / max-file 3

eBPF profiling skipped — incompatible with macOS Docker Desktop.
ponytail: pyroscope.scrape targets gateway only; per-service pprof
needs explicit import + port exposure.
- Enable NATS monitoring (--http_port 8222)
- Add natsio/prometheus-nats-exporter for Prometheus scraping
- Scrape NATS exporter via Alloy
- Provision one overview dashboard: NATS broker stats, Loki logs,
  Tempo traces
- Add explicit UIDs to datasource provisioning
- Mount grafana/provisioning into Grafana container
- Fix dashboards.yml path to json/ subdirectory

Refs #9, #13
Add curl-based healthcheck to llama service (10s interval, 30s start_period).
Add go.mod/go.sum for mcp example directories.
Exporter takes monitor URL positionally (no -conn_str flag) and
uses -subz, not -subsz. Point it at the monitoring port of
nats-0. Includes 3-node NATS cluster and shared logging anchor.
- Move alloy/grafana/loki/prometheus/pyroscope/tempo configs into config/
- Move llama entrypoint to config/llama/
- Point micro-run at local src/ tree instead of examples/mcp
- nats-exporter scrapes all three cluster nodes
- Run detached so container survives without TTY
The pyroscope scrape hit the gateway :8080 mux, which has no pprof
routes — the dashboard catch-all answered text/html and every
scrape failed. micro run now serves net/http/pprof on an internal
:6060 listener (unpublished in compose) started before service
builds, which can block for minutes on a cold cache.

Also fixes two compose regressions:
- micro-run build context pointed at ./src (examples), so the image
  could no longer compile the CLI from the repo root; restore repo: ../..
- grafana/pyroscope runs as uid 1001 but Docker Desktop creates
  root-owned named volumes, so all ingestion was rejected; run as root

Alloy scrape target moved to micro-run:6060 with service_name micro-run.
Alloy scrapes micro-run:8080 but the mux had no /metrics route; the
dashboard catch-all answered HTML and the scrape failed (up == 0).
Serve promhttp.Handler() so go_services metrics reach Prometheus.
- tempo: migrate config to 3.x schema (ingester/compactor/
  local-blocks removed, retention via backend_scheduler); image
  floats on :latest again
- grafana: provision pyroscope with real plugin id
  (grafana-pyroscope-datasource), was a nonexistent 'pyroscope' type
- alloy: relabel container names to compose service names
  (/docker-micro-run-1 -> service_name=micro-run) for loki logs
- micro.NewService boots the global OTel provider and wires handler,
  subscriber, client and call trace wrappers when
  OTEL_EXPORTER_OTLP_ENDPOINT is set; unset keeps prior behavior.
- micro run labels each spawned service with its own OTEL_SERVICE_NAME
  (explicit --env override wins) so traces no longer collapse into one
  shared service name.
- internal/docker: mount the repo at /src for micro-run, add
  'replace go-micro.dev/v6 => /src' to all example modules so child
  binaries compile against the patched library (go.mod/go.sum tidied),
  gofmt fixes in example mains.
- Grafana overview dashboard: rename spanmetrics label service_name to
  service to match Tempo metrics_generator output.

Verified end-to-end: gateway api.proxy spans nest child server spans
(e.g. contacts.Contacts.List) under one trace ID with per-service names;
spanmetrics feed Prometheus per service.
- loadtest.js: login via /auth/login, health + greeter + contacts CRUD
  through the micro-run gateway
- stress.js: ramp to 500 VUs with p(95)<200 threshold
- compose services k6 (profile test) and k6-stress (profile stress)
- promote stdouttrace otel exporter to direct dependency
1. GO-2026-5158 — bumped all go.opentelemetry.io/otel submodules v1.43.0 → v1.44.0 in go.mod:42-46 (pulls metric/otlptrace along; genproto/grpc-gateway minor bumps as transitive deps).
2. gofmt — moved misplaced promhttp import into sorted position, cmd/micro/gateway/server.go:27.
resource.Merge failed with "conflicting Schema URL" because the
service-name attrs pinned semconv v1.40.0's SchemaURL while
otel sdk v1.44.0's resource.Default() bundles 1.41.0. Build the
attrs schemaless so Merge adopts Default's bundled schema; this
cannot regress when either side bumps semconv versions.

The docker src services and examples/grpc still required otel
v1.43.0/grpc-gateway v2.28.0 from before the parent bump to
v1.44.0/v2.29.0, so `replace => /src` builds failed with
"updates to go.mod needed". Re-tidied each module.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant