donkeyx/cluster-utils-api

By donkeyx

โ€ขUpdated 2 days ago

Simple docker image to allow testing within clusters or locally and provides endpoints for debugging

Image
Networking
Developer tools
1

50K+

donkeyx/cluster-utils-api repository overview

โ ๐Ÿด DonkeyX's Cluster Utils API

โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
|   ๐Ÿด DonkeyX's Cluster Utils API      โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

        //\\
       (/oo\)   .----.
       (____)  | API |
        /||\   '----'
       //||\\   ๐Ÿ”Œ Probe Mode
      ^^ ^^ ^^
   "Kick the tyres on the mesh!"

โ description

HTTP side of the cluster-utils toolkit. Where cluster-utilsโ  is the shell box you exec into, this is the service you drop into an environment and hit over HTTP โ€” same donkey energy, different job.

Throw it into a namespace / ECS task / compose stack and use it to test:

  • probes โ€” real kube-style /startupz, /livez, /readyz with fail + delay + flap + runtime control
  • routing & ingress โ€” hit it through a service, ingress, ALB, mesh; see what actually arrives
  • east-west hops โ€” north-south into this pod, then /proxy out to another svc (headers ride along)
  • headers & identity โ€” what the proxy rewrote, client IP, host, path (/headers, /debug, /echo)
  • config / params in the env โ€” dump process env behind auth (/a/env) so you can check secrets, configmaps, task defs actually landed
  • bad / slow upstreams โ€” force status codes and long delays (/status/503, /delay/90)
  • any entrypoint noise โ€” binary is also linked as node / npm so broken charts that call weird commands still come up and serve the api

Default route dumps you into swagger so you can poke things from the browser without memorising paths.

| dockerhub: https://hub.docker.com/r/donkeyx/cluster-utils-apiโ 

| ghcr: ghcr.io/donkeyx/cluster-utils-api

| github: https://github.com/donkeyx/cluster-utils-apiโ 

| pair with: https://github.com/donkeyx/cluster-utilsโ  (shell / toolkit image)

โ Auth (how to get the token)

Most routes are open on purpose (probes, ingress debug). Anything under /a/ needs a bearer token:

Authorization: Bearer <token>
pathwhy it's locked
GET /a/envdumps all env โ€” secrets, keys, tokens
GET/PUT /a/control/probescan fail live (restarts) / ready (drop traffic)
GET/POST /a/proxySSRF if open โ€” scan the cluster, hit metadata, pull internal APIs

See Security below for the full split.

โ Option 1 โ€” fixed token (easiest for demos)
docker run -d -p 8080:8080 -e AUTH_TOKEN=dev --name test-api donkeyx/cluster-utils-api:latest
export TOKEN=dev

Same idea in k8s โ€” set AUTH_TOKEN on the container env.

โ Option 2 โ€” random token from logs (default)

If you donโ€™t set AUTH_TOKEN, a random token is generated every process start and printed in the logs (JSON).

Look for fields like token / header, or grep:

# local docker
docker logs test-api 2>&1 | grep -E 'token|Bearer|example curl' | head

# pull just the token value out of the json line (if jq + logs are one json object per line)
docker logs test-api 2>&1 | grep '"token"' | tail -1 | jq -r '.token'

# kubernetes
kubectl -n default logs deploy/cluster-utils-api --tail=50 | grep -E 'token|Bearer|example curl'

On startup the app also logs ready-made curls (env dump + probe control) with the token already filled in โ€” copy/paste those.

โ Using it
export TOKEN=dev   # or whatever you pulled from logs

curl -sS -H "Authorization: Bearer $TOKEN" localhost:8080/a/env | jq
curl -sS -H "Authorization: Bearer $TOKEN" localhost:8080/a/control/probes | jq
โ Swagger UI

Yes โ€” for /a/* you Authorize before Execute (same value as curl).

Local podman (AUTH_TOKEN=dev on :18080):

  1. Open http://127.0.0.1:18080/โ  (same host/port as the API)

  2. Click the green Authorize lock (top right) โ€” not a random field on the op

  3. Paste exactly:

    Bearer dev
    
    ValueResult
    Bearer dev200
    dev only401
    bearer dev (lowercase)401 (we require exact Bearer )
  4. Authorize โ†’ Close

  5. Operation โ†’ Try it out โ†’ Execute

If still 401: hard-refresh swagger (stale token), confirm
curl -sS -o /dev/null -w '%{http_code}\n' -H 'Authorization: Bearer dev' http://127.0.0.1:18080/a/env
is 200, or check podman logs cu-api | grep '"token"'.

โ Change the Try-it-out API host (query params โ€” most common)

By default Swagger calls whatever host you opened the docs on.
If that is wrong (port-map, ingress, in-cluster Service, tunnel), pass the host on the docs URL โ€” you will do this a lot:

# point Try-it-out at a different host:port
/api-docs/index.html?host=127.0.0.1:18080&scheme=http

# in-cluster Service from a port-forwarded docs UI
/api-docs/index.html?host=cluster-utils-api.default.svc.cluster.local:8080&scheme=http

# public ingress
/api-docs/index.html?host=api.example.com&scheme=https
QueryExampleWhat it does
hostmy-svc.ns.svc:8080Host:port Execute / Try-it-out uses
schemehttp or httpsScheme for those calls
themelightStock bright Swagger (dark is default)

Bookmark the full URL once; every open keeps that target.

โ Or set it once at process start (env)
EnvExamplePurpose
SWAGGER_HOSTapi.example.com or cluster-utils-api.ns.svc:8080Default host:port for Try-it-out
SWAGGER_SCHEMEhttps or httpDefault scheme

Priority: query ?host=&scheme= โ†’ env SWAGGER_* โ†’ request Host / X-Forwarded-Proto.
(PORT is only the process listen port; it does not set the public URL.)

Theme: dark by default (custom CSS skin โ€” stock Swagger has no real dark mode).
Light: ?theme=light.


โ Quick start

docker run -d -p 8080:8080 -e AUTH_TOKEN=dev --name test-api donkeyx/cluster-utils-api:latest
export TOKEN=dev

curl -sS localhost:8080/help | jq
curl -sS localhost:8080/version | jq
curl -sS localhost:8080/livez
curl -sS -H "Authorization: Bearer $TOKEN" localhost:8080/a/control/probes | jq

Port defaults to 8080 (PORT to override).


โ Examples (debugging recipes)

Assume TOKEN is set (see Auth above) and the api is on localhost:8080.

โ 1. What did the ingress / mesh actually send me?
# headers as the pod saw them (X-Forwarded-*, cookies, auth, host, โ€ฆ)
curl -sS -H 'X-Request-Id: demo-1' -H 'X-Forwarded-For: 1.2.3.4' \
  localhost:8080/headers | jq

# fuller dump: hostname, client ip, uri, all headers
curl -sS localhost:8080/debug | jq

# bounce method + body + query back (good for POST through a gateway)
curl -sS -X POST 'localhost:8080/echo?from=ingress' \
  -H 'Content-Type: application/json' \
  -d '{"hello":"cluster"}' | jq
โ 2. Did my ConfigMap / Secret / task def actually land?
curl -sS -H "Authorization: Bearer $TOKEN" localhost:8080/a/env | jq
# or one key:
curl -sS -H "Authorization: Bearer $TOKEN" localhost:8080/a/env | jq '."MY_FEATURE_FLAG"'
โ 3. Readiness: pull the pod out of the Service (no restart)
# fail ready โ†’ kube should remove endpoints; process stays up
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"ready":{"mode":"fail"}}' localhost:8080/a/control/probes | jq

curl -sS -o /dev/null -w 'readyz=%{http_code}\n' localhost:8080/readyz
# in cluster: kubectl get endpoints cluster-utils-api-svc -w

# put it back
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"ready":{"mode":"ok"}}' localhost:8080/a/control/probes | jq
โ 4. Liveness: make kube restart the container
# careful โ€” this will restart once failureThreshold is hit
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"live":{"mode":"fail"}}' localhost:8080/a/control/probes | jq

# watch
# kubectl get pod -l type=api -w
โ 5. Probe timeouts (slow answers)

Sample manifest uses timeoutSeconds: 1. Anything slower counts as a failed probe.

# ready answers after 3s โ†’ timeouts with timeoutSeconds: 1
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"ready":{"mode":"delay","delaySeconds":3}}' localhost:8080/a/control/probes | jq

time curl -sS -o /dev/null -w '%{http_code}\n' localhost:8080/readyz
โ 6. Flapping readiness (in/out of endpoints)
# time based: 5s ok, 5s fail, repeat
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"ready":{"mode":"flap","flapSeconds":5}}' localhost:8080/a/control/probes | jq

# or every 2nd request fails (handy from a loop)
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"ready":{"mode":"flap","flapEvery":2}}' localhost:8080/a/control/probes | jq

for i in 1 2 3 4; do curl -sS -o /dev/null -w "$i %{http_code}\n" localhost:8080/readyz; done

# see which half of the flap you're in
curl -sS -H "Authorization: Bearer $TOKEN" localhost:8080/a/control/probes \
  | jq '{ready, readyFlapPhase, uptimeSeconds}'
โ 7. Slow startup / cold start
# pretend the app needs 15s to init, then latch "started"
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"startup":{"mode":"ok","bootDelaySeconds":15},"resetStartupLatch":true}' \
  localhost:8080/a/control/probes | jq

curl -sS -o /dev/null -w 'startupz=%{http_code}\n' localhost:8080/startupz
# wait, then:
curl -sS -o /dev/null -w 'startupz=%{http_code}\n' localhost:8080/startupz

Deploy-time without control API:

docker run -d -p 8080:8080 \
  -e AUTH_TOKEN=dev \
  -e STARTUP_BOOT_DELAY=20 \
  donkeyx/cluster-utils-api:latest
โ 8. Upstream returns 502 / 503 / 418
curl -sS -o /dev/null -w '%{http_code}\n' localhost:8080/status/502
curl -sS -o /dev/null -w '%{http_code}\n' localhost:8080/status/503
curl -sS -o /dev/null -w '%{http_code}\n' localhost:8080/status/418
โ 9. Really slow request
# sleep then 200 โ€” default cap 120s (override with MAX_DELAY_SECONDS, hard max 600)
curl -sS localhost:8080/delay/90
# delayed=90.000s requested=90.000s max=120s

# or make a *probe* slow (so kube timeoutSeconds trips)
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"ready":{"mode":"delay","delaySeconds":30}}' localhost:8080/a/control/probes | jq
โ 10. East-west hop via north-south (/a/proxy) โ€” auth required

Pattern: ingress โ†’ this api โ†’ another service (mesh / NetworkPolicy / DNS / header propagation).

Locked behind bearer on purpose: an open proxy is SSRF (anyone could make your pod call internal URLs).

โ What you get back (default)

Unless "raw": true, the HTTP response from this api is always 200 + JSON wrap (even if upstream was 502). Inside that wrap you get the full upstream response:

fieldwhat it is
response.statusstatus code from the other API
response.headersall response headers from the other API
response.bodybody as a string (capped ~2MB in the wrap)
request.url / method / headers / bodywhat we actually sent east-west
meta.durationMshop timing
meta.forwardIncomingHeaderswhether inbound headers were copied
meta.forwardSensitiveHeaderswhether Authorization/Cookie were copied

Example shape:

{
  "request": {
    "url": "http://other-api:8080/debug",
    "method": "GET",
    "headers": { "X-Request-Id": ["demo"], "X-Cu-Proxy-Hop": ["pod-a"] },
    "body": ""
  },
  "response": {
    "status": 200,
    "headers": {
      "Content-Type": ["application/json; charset=utf-8"]
    },
    "body": "{\"Hostname\":\"other-pod\", ...}"
  },
  "meta": {
    "durationMs": 12,
    "timeoutSeconds": 10,
    "forwardIncomingHeaders": true,
    "forwardSensitiveHeaders": false,
    "proxyHostname": "edge-pod"
  }
}

Handy jq:

# just upstream status + headers + body
curl -sS -H "Authorization: Bearer $TOKEN" -H 'X-Request-Id: demo' \
  "$BASE/a/proxy?url=http://other-api:8080/debug" \
  | jq '{status: .response.status, headers: .response.headers, body: .response.body}'

"raw": true โ†’ no wrap; you get the upstream status/headers/body as the real HTTP response (harder to inspect the hop).

โ Header forwarding
inbound headersdefault
tracing / custom (X-Request-Id, etc.)forwarded
hop-by-hop (Host, Connection, Content-Length, โ€ฆ)stripped
Authorization / Cookienot forwarded (so your /a/* bearer is not sent to the other svc by accident)

To forward credentials east-west on purpose: "forwardSensitiveHeaders": true, or set headers.Authorization in the JSON body.

export BASE=http://localhost:8080
export TOKEN=dev

# simple GET hop
curl -sS -H "Authorization: Bearer $TOKEN" \
  -H 'X-Request-Id: demo-ew-1' -H 'X-Trace: abc' \
  "$BASE/a/proxy?url=http://other-api:8080/debug" | jq

# POST form โ€” full control
curl -sS -X POST "$BASE/a/proxy" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'X-Request-Id: demo-ew-2' \
  -d '{
    "url": "http://other-api:8080/echo",
    "method": "POST",
    "body": "{\"ping\":true}",
    "headers": {"Content-Type": "application/json"},
    "timeoutSeconds": 15,
    "forwardIncomingHeaders": true
  }' | jq

# upstream headers only
curl -sS -H "Authorization: Bearer $TOKEN" \
  "$BASE/a/proxy?url=http://other-api:8080/headers" | jq '.response.headers'

# slow peer
curl -sS -X POST "$BASE/a/proxy" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"url":"http://other-api:8080/delay/5","timeoutSeconds":30}' | jq '.meta'

In-cluster (service DNS) from a port-forwarded edge api:

curl -sS -H "Authorization: Bearer $TOKEN" -H 'X-Request-Id: from-laptop' \
  "$BASE/a/proxy?url=http://cluster-utils-api-svc.other-ns.svc.cluster.local:8080/debug" | jq

Chain multi-hop if you want: A /a/proxy โ†’ B /a/proxy โ†’ C /debug (each hop needs a token for that api).

โ 11. Which build is this pod?
curl -sS localhost:8080/version | jq
# {"version":"...","gitHash":"...","hostname":"..."}
โ 12. Traces + Istio-style request ids
# simulate gateway/mesh headers
curl -sS -D- \
  -H 'X-Request-Id: istio-style-id-001' \
  -H 'traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01' \
  localhost:8080/debug -o /dev/null | grep -iE 'x-trace-id|x-request-id'

# with OTEL enabled, X-Trace-Id is the Tempo id; X-Request-Id echoes the mesh id
# logs: {"msg":"Request","trace_id":"...","request_id":"istio-style-id-001",...}

See Observability for push vs scrape and full header table.

โ 13. From inside the cluster (with cluster-utils shell)
# port-forward
kubectl -n default port-forward svc/cluster-utils-api-svc 8080:8080

# or exec into the toolkit image and curl the service DNS
kubectl exec -it deploy/cluster-utils -- \
  curl -sS http://cluster-utils-api-svc:8080/debug | jq

# token from api pod logs
TOKEN=$(kubectl -n default logs deploy/cluster-utils-api --tail=100 \
  | grep '"token"' | tail -1 | jq -r '.token')
kubectl exec -it deploy/cluster-utils -- \
  curl -sS -H "Authorization: Bearer $TOKEN" http://cluster-utils-api-svc:8080/a/env | jq

โ Probes (reference)

These follow the usual kube split. Status codes matter more than bodies โ€” kube only cares 2xx vs not (and timeouts).

pathkube rolefail meansaliases
GET /startupzstartupProbestill starting / forced fail/startup
GET /livezlivenessProberestart the container/healthz, /health
GET /readyzreadinessProbeleave Service endpoints (process stays up)/ready
โ Startup behaves like a real startup endpoint
  1. While bootDelaySeconds has not elapsed (from process start, or after a latch reset) โ†’ 503 starting
  2. First success โ†’ latches to started
  3. After latch โ†’ always 200 started (fast), until process restart or resetStartupLatch
  4. mode=fail โ†’ 503 startup failed and never latches
โ Modes
modeafter optional delay
ok200
fail503
delay200 (use delaySeconds > probe timeoutSeconds for timeouts)
flapalternates ok/fail โ€” flapSeconds (time half-period, default 5) or flapEvery (every Nth request)

On startup, flap only applies before the latch.

โ Seed from env (steady state at deploy)
envdefaultnotes
LIVE_MODE / HEALTHY_MODE / HEALTHYokfail / flap / delay
LIVE_DELAY / HEALTHY_DELAY0
LIVE_FLAP_SECONDS / LIVE_FLAP_EVERYflap knobs
READY_MODE / READYok
READY_DELAY0
READY_FLAP_SECONDS / READY_FLAP_EVERY
STARTUP_MODE / STARTUPok
STARTUP_DELAY0per-request sleep while not latched
STARTUP_BOOT_DELAY0wall clock before first success
STARTUP_FLAP_SECONDS / STARTUP_FLAP_EVERYflap before latch

Query ?ok=0 still works on live/ready for quick curl hacks โ€” kube will never send that, so use env or /a/control/probes for real demos.

โ Control API (no redeploy)
curl -sS -H "Authorization: Bearer $TOKEN" localhost:8080/a/control/probes | jq

curl -sS -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{
    "ready":   {"mode":"flap","flapSeconds":5},
    "live":    {"mode":"ok"},
    "startup": {"mode":"ok","bootDelaySeconds":5},
    "resetStartupLatch": true
  }' localhost:8080/a/control/probes | jq

โ Endpoints

pathnotes
GET /redirect to swagger
GET /api-docs/*swagger ui
GET /helpjson list of routes
GET /versionversion + git hash + hostname
GET /startupz /startupstartup probe (latch)
GET /livez /healthz /healthliveness
GET /readyz /readyreadiness
GET /pingPONG (not a kube probe)
GET /headersrequest headers
GET /debughostname / ip / headers / uri
GET /metricsprometheus scrape (OpenMetrics): request count/latency/in-flight + Go/process
OTEL tracespush OTLP to Alloy/collector (not scrape) โ€” see Observability
GET /status/:coderespond with that http status (100-599)
GET /delay/:secondssleep then 200 (cap MAX_DELAY_SECONDS, default 120)
ANY /echobounce method / query / headers / body
GET /a/envenv vars โ€” auth
GET/PUT /a/control/probesprobe state โ€” auth
GET/POST /a/proxyeast-west hop; full upstream status/headers/body in wrap โ€” auth
โ other config
envdefaultwhat it does
PORT8080listen port
AUTH_TOKENrandom each startfixed bearer for /a/* if set
MAX_DELAY_SECONDS120 (hard max 600)cap for /delay, probe delays, proxy timeouts

โ Observability (metrics vs traces)

Two different pipelines โ€” don't mix them up:

SignalHow it leaves the appEndpoint / protocolTypical sink
MetricsScrape (pull)GET /metrics Prometheus/OpenMetricsAlloy prometheus.scrape โ†’ Mimir/Prometheus
TracesPushOTLP http/protobuf (default) or grpcAlloy OTLP receiver โ†’ Tempo
Logsstdout JSON (zap)not OTLP yetAlloy/loki.source.kubernetes โ†’ Loki

Traces are not scraped. The app exports spans to a collector. Grafana Alloy is the usual middle hop: receive OTLP โ†’ forward to Tempo.

โ How end-to-end tracing works (Istio / meshes)

Normal path in 2024โ€“26 stacks:

  1. Edge / sidecar (Envoy, Istio, Linkerd) accepts the request and either
    • continues an existing W3C traceparent, or
    • creates/propagates B3 (x-b3-traceid, โ€ฆ) if the mesh is still on Zipkin-style config
  2. App SDKs extract that context, create child spans, inject the same headers on outbound calls
  3. App pushes spans via OTLP โ†’ Alloy โ†’ Tempo
  4. x-request-id (Envoy/Istio) is a separate correlation id used in access logs โ€” it is not the OTEL trace id. Join them by putting x-request-id on the span (we do) and echoing both on the response.
HeaderWhat it isWe do
traceparent / tracestateW3C trace context (modern default)extract + inject
x-b3-* / b3Zipkin B3 (common with Istio)extract + inject
uber-trace-idJaegerextract + inject
x-request-idEnvoy request id (logs)span attr http.request_id + response echo
x-correlation-idapp/gateway variantspan attr + echo if present

So: we match mesh traffic by speaking W3C + B3 + Jaeger, and we use Istioโ€™s request id as an attribute / response header so you can jump from Envoy logs to Tempo (X-Trace-Id).

โ Defaults (when env is unset)
VariableDefault here
export / SDKdisabled (no-op) until OTEL_EXPORTER_OTLP_ENDPOINT (or traces endpoint) is set
OTEL_SERVICE_NAMEcluster-utils-api
OTEL_EXPORTER_OTLP_PROTOCOLhttp/protobuf (port 4318 on Alloy)
OTEL_TRACE_SAMPLE_RATIO1.0 (all traces when enabled)
OTEL_TRACE_PROBESoff (no spans for /livez /readyz /startupz /metrics /ping)
propagatorsalways tracecontext + baggage + b3 + jaeger
OTEL_EXPORTER_OTLP_INSECUREunset (exporter default); set "true" in-cluster without TLS

On every startup we log a single line otel config (effective) with enabled flag, endpoints, protocol, sample ratio, probe tracing, and propagators โ€” grep pod logs for otel config.

โ Enable traces (OTLP push)
env:
  - name: OTEL_SERVICE_NAME
    value: cluster-utils-api
  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: "alloy.observability.svc.cluster.local:4318"   # http/protobuf default
  - name: OTEL_EXPORTER_OTLP_PROTOCOL
    value: http/protobuf   # or grpc (often :4317)
  - name: OTEL_EXPORTER_OTLP_INSECURE
    value: "true"          # TLS off inside the mesh
  # optional:
  # - name: OTEL_TRACE_SAMPLE_RATIO
  #   value: "1.0"
  # - name: OTEL_SDK_DISABLED
  #   value: "true"
  # - name: OTEL_TRACE_PROBES
  #   value: "true"   # also span kube probes (noisy)

What gets instrumented:

  • Inbound HTTP โ€” Gin (otelgin) + mesh header attributes
  • Outbound /a/proxy โ€” otelhttp client span + header inject east-west
  • Response X-Trace-Id (OTEL) and X-Request-Id (if the mesh/client sent one)
curl -sS -D- -H 'X-Request-Id: demo-from-gateway' localhost:8080/debug -o /dev/null | grep -iE 'x-trace-id|x-request-id'

Important: X-Request-Id (Istio/Envoy) โ‰  X-Trace-Id (OpenTelemetry/Tempo).
They are both useful; we keep both. In Tempo, search by trace id, or by span attribute http.request_id when the mesh sent a request id. Pod logs include trace_id + request_id on each request line when present.

โ Join Envoy / Istio access logs โ†” Tempo
# 1) call through the mesh (or simulate Envoy's header)
curl -sS -D /tmp/hdrs -H 'X-Request-Id: 0a1b2c3d-demo' localhost:8080/debug -o /dev/null
grep -iE 'x-trace-id|x-request-id' /tmp/hdrs

# 2) app logs (same ids)
# kubectl logs deploy/cluster-utils-api | grep 0a1b2c3d-demo

# 3) Tempo: search TraceID = value of X-Trace-Id
#    or attribute http.request_id = 0a1b2c3d-demo

On boot, always check:

kubectl logs deploy/cluster-utils-api | grep 'otel config'
# โ†’ enabled, endpoint, protocol, sample_ratio, propagators, mesh_headers, โ€ฆ
โ Alloy sketch
// metrics: scrape this app
prometheus.scrape "cu_api" {
  targets      = [{ __address__ = "cluster-utils-api-svc:8080" }]
  metrics_path = "/metrics"
  forward_to   = [prometheus.remote_write.mimir.receiver]
}

// traces: receive OTLP *push* from the app
otelcol.receiver.otlp "default" {
  http { endpoint = "0.0.0.0:4318" }
  grpc { endpoint = "0.0.0.0:4317" }
  output { traces = [otelcol.exporter.otlp.tempo.input] }
}

otelcol.exporter.otlp "tempo" {
  client { endpoint = "tempo:4317" tls { insecure = true } }
}

Istio tip: prefer mesh config that emits W3C (or dual W3C+B3). If you only have B3 today, our B3 propagator still joins the chain.


โ Sec

Tag summary

Content type

Image

Digest

sha256:51cdb404dโ€ฆ

Size

42.6 MB

Last updated

2 days ago

docker pull donkeyx/cluster-utils-api