adapter-sidecar-pattern · diff
git:20260903.f199530 to git:20260906.e6ad4bd
59 added, 121 removed. Audit A to A.
---
name: adapter-sidecar-pattern
description: >
- A Kubernetes sidecar that normalises what an application emits — metrics, logs, health —
- so a heterogeneous fleet presents one interface to the platform; not the Gang-of-Four
- Adapter design pattern for in-process object interfaces (that is java-api-design). Use
- when a legacy or vendor process exposes metrics in a format the platform cannot scrape,
- when every service logs in a different shape, when a legacy process has no usable health
- endpoint, when choosing between a per-pod log sidecar and a DaemonSet node agent, when an
- adapter is being asked to synthesise a trace or correlation ID the application never
- emitted, or when an application upgrade silently changed a log format that an adapter
- parses. Does not cover pod-level container mechanics (sidecar-pattern), how to design the
- telemetry itself (metrics-and-cardinality, structured-logging,
- distributed-tracing-design), probe semantics and configuration
- (kubernetes-service-lifecycle), or collector cost and sampling
- (opentelemetry-performance).
+ Choose and review Kubernetes telemetry adapters when a legacy or vendor process emits
+ incompatible metrics, logs or health signals, when deciding between per-pod translation
+ and a node agent, or when an application upgrade silently changes parsed telemetry.
+ Covers translation contracts, evidence-backed enrichment and failure behavior. Excludes
+ in-process interface adaptation (gof-adapter), container mechanics (sidecar-pattern),
+ probe configuration (kubernetes-service-lifecycle) and telemetry instrumentation design.
---
- # Adapter Pattern
-
- ## Purpose
-
- An adapter sidecar is a translation layer. The application keeps emitting whatever it emits,
- and a peer container converts that into the shape the platform consumes — one scrape format,
- one log schema, one health contract — across a fleet nobody is going to rewrite. The pod
- mechanics that make a peer container possible are `sidecar-pattern`; this skill is only about
- the translation.
+ # Adapter Sidecar Pattern
- The failure this prevents is the adapter treated as a permanent answer to a contract problem.
- An adapter that parses an application's log lines or metric output is coupled to an
- **unversioned interface**: the app's next release renames a field, the adapter keeps running
- and reporting, and the platform receives plausible, wrong data with no error anywhere in the
- chain. Where the application can be changed instead, compare its one-time migration cost with
- the adapter's recurring compatibility, resource and release burden. The adapter is strongest
- when the producer genuinely cannot be changed.
+ An adapter translates what a process emits into a platform contract. Uniform syntax does
+ not imply uniform meaning: two duration metrics may include different work. The specialist
+ decision is whether translation is justified, where it belongs, and how to detect plausible
+ but incorrect output when either contract changes.
## Workflow
- 1. **Write down both formats with a real sample in hand** — what the app emits today, byte for
- byte, and what the platform requires. An adapter designed against remembered formats is
- rewritten against the real ones.
- 2. **Exhaust the cheaper options first.** In-process instrumentation, stdout in the platform's
- own format, or an upstream image that already speaks it. See
- `references/adapter-or-node-agent.md`.
- 3. **Choose the topology** — per pod (sidecar) or per node (DaemonSet). For anything the app
- already writes to stdout this is the real decision, and the node agent usually wins.
- 4. **Pin the input contract.** Capture a genuine sample of the app's output as a fixture, run
- the adapter against it in CI, and treat a format change as a breaking change in a
- dependency you do not control.
- 5. **Bound indexed dimensions.** Metric names and label values must come from controlled sets.
- Log field values can be high-cardinality, but indexing, retention, privacy and redaction
- policies must account for them; identifiers must not become metric labels.
- 6. **Decide the behaviour when the adapter falls behind or dies** — does the application
- block, drop, or fill a volume until the pod is evicted? Pick one deliberately.
- 7. **Test the output contract**, not the adapter's internals: fixture in, expected platform
- format out, asserted by the platform's own parser where one exists.
-
- ## Decision block
-
- ```text
- Use an adapter sidecar when:
- - the workload is a vendor or legacy binary that cannot be instrumented or recompiled, and
- the platform requires a specific exposition or log schema;
- - parsing rules differ per workload, so one node-agent configuration cannot cover the fleet;
- - the translation needs pod-local context at emission time — pod name, workload identity,
- a per-pod certificate.
- Avoid an adapter when:
- - the application is yours and a library already emits the platform's format: a JVM service
- with Micrometer and the Prometheus registry already serves an exposition endpoint, and an
- adapter in front of it is a proxy that adds a failure mode and no information;
- - the source is a log format you also control — change it once instead of parsing it forever;
- - the adapter would have to invent information the app never emitted, such as a trace or
- correlation ID;
- - the app's output format is unstable release to release and nobody owns keeping the two in
- step.
- Prefer a node agent (DaemonSet) instead when:
- - the app writes to stdout and the normalisation is uniform across workloads: one process per
- node replaces one per pod, and the container runtime is already collecting the stream.
- Prefer changing the application instead when:
- - you own the code, and the format will change again anyway — every such change costs an
- adapter release that is coupled to, but not released with, the application.
- ```
+ 1. **Establish the contract before the parser.** Obtain representative source samples with
+ producer image/version and capture conditions, the consumer schema/protocol and version,
+ field meanings and units, and the current collection path. Inspect source/configuration
+ where available; do not infer semantics from field names alone. For placement, obtain
+ producer changeability, collector capabilities/access, replica counts and resource constraints.
+ For failures, obtain timestamps, parser errors, backlog and upstream collection status.
+ 2. **Handle missing evidence explicitly.** Request the smallest missing sample or contract
+ needed for a decision. Continue with a conditional design, but do not invent a production
+ parser, health predicate, resource budget or confirmed diagnosis. Label supplied facts,
+ inferred explanations and untested hypotheses separately; name a check that could refute
+ each consequential hypothesis.
+ 3. **Choose the least costly adequate placement.** Before adding a container or revisiting
+ log collection, read [adapter-or-node-agent.md](references/adapter-or-node-agent.md).
+ Workload-specific parsing and pod metadata alone do not require a sidecar. Record the
+ constraint that the existing collector or producer cannot satisfy. Use `sidecar-pattern`
+ for container mechanics only once per-pod placement is justified.
+ 4. **Specify translation and failure behavior.** When implementing, reviewing or diagnosing
+ an adapter, read [coupling-and-failure.md](references/coupling-and-failure.md). Map source
+ fields to output meaning, including absent/invalid values, enrichment provenance, and
+ compatibility ownership. Define what consumers see on malformed input, stale collection
+ and overload; never silently convert missing data to a successful zero or healthy state.
+ 5. **Verify semantics and the failure path.** Use producer-version fixtures and consumer
+ tooling to check both valid output and its meaning. Include the relevant failure injection
+ (format drift, source outage, restart or sink outage). Record actual results separately
+ from proposed checks. Parser acceptance alone cannot prove correctness.
## Rules
- - **An adapter can only translate what was emitted.** It cannot add a trace ID, a correlation
- ID or a tenant that the application never wrote. A synthesised one is worse than none: it
- looks real and correlates nothing. Designing those identifiers is `structured-logging` and
- `distributed-tracing-design`.
- - Ingestion time is not event time. If the app emits no timestamp, the adapter stamps arrival,
- which is wrong by however long the line sat in a buffer — visible afterwards as bursts of
- events sharing one timestamp, and as ordering that inverts under load.
- - Parsing the app's output is a contract with **no version and no owner**. Pin a fixture in
- CI, and re-capture it from the real image on every application upgrade. "It still parses" is
- not the assertion; "the parsed fields still mean the same thing" is.
- - Every derived label must come from a bounded set. A path label containing identifiers, a
- customer ID, or an exception message creates one time series per distinct value; the series
- count is the label sets multiplied, not added. The sizing is `metrics-and-cardinality`.
- - **stdout ultimately has bounded buffering.** Linux pipe capacity and container-runtime
- buffering vary by kernel and configuration. If the runtime or a custom adapter path stops
- draining, writes can block or logs can be dropped; state which component owns buffering,
- rotation and backpressure and verify it under backend outage.
- - A file handed over through an `emptyDir` is node ephemeral storage. An adapter that falls
- behind fills it, and the pod is evicted for local storage usage. Set a `sizeLimit`, rotate,
- and decide explicitly whether the application drops lines or blocks when the file cannot
- grow.
- - A metrics adapter that scrapes the app and re-exposes must publish **its own** success and
- the age of what it is serving. Serving a cached exposition without its age turns a dead
- application into a healthy-looking flat line.
- - A health adapter should exercise the cheapest operation that proves the condition Kubernetes
- must act on. A TCP connect proves only listening/accept-path availability and can be adequate
- for that narrow contract; it does not prove application semantics. What each probe should
- answer, and how it is configured, is
- `kubernetes-service-lifecycle`.
- - Version the adapter's **output** schema and emit the version in the output. Consumers can
- then fail loudly on a change instead of silently mis-reading a renamed field.
- - Never claim the adapter makes the fleet uniform. It makes the emitted _format_ uniform;
- semantics still differ — one service's `request_duration` includes queueing and another's
- does not. Uniform shape is not uniform meaning, and a dashboard that assumes otherwise
- compares two different quantities.
- - Treat translation as a data boundary: redact secrets and personal data before export, use
- least-privilege access to shared files/endpoints, authenticate remote sinks, and test malformed
- or adversarial input so one record cannot crash the parser or amplify output without bound.
+ - Compare changing a controlled producer or using existing instrumentation with recurring
+ adapter maintenance; ownership makes change possible, not automatically cheaper.
+ - Parsing may target a documented versioned API or an informal log layout. Record which,
+ the supported producer/adapter combinations, and who tests upgrades. Do not assume either
+ stability or absence of ownership.
+ - Enrichment needs an authoritative source and an unambiguous association to the record.
+ Never fabricate request context or infer it from temporal proximity. Missing information
+ stays missing unless the output contract explicitly defines a fallback with provenance.
+ - Treat translation as a data boundary: redact secrets before export or quarantine, restrict
+ file/endpoint access, authenticate remote sinks, and bound record size, parser work and
+ output amplification. Test hostile input without copying real secrets into fixtures.
+ - Version the mapping and test consumer compatibility. Emit schema metadata only through a
+ mechanism the target protocol supports; adding a field does not force consumers to reject
+ incompatible data.
- ## References
+ ## Deliverable
- - [Adapter, node agent, in-process or change the app](references/adapter-or-node-agent.md) —
- the four options with the observable condition that selects each and what each costs, plus
- the log-shipping topology comparison in detail. Read before adding an adapter container, and
- when a log pipeline is being designed or revisited.
- - [Coupling and failure surface](references/coupling-and-failure.md) — the unversioned format
- contract and how to pin it, what an adapter can and cannot synthesise, cardinality
- arithmetic, backpressure when the adapter falls behind or dies, and a contract test for the
- output. Read when writing an adapter, and when telemetry has gone silently wrong after an
- application upgrade.
+ For a small review, a concise finding is enough: evidence (or gap), consequence, proposed
+ adjustment and confirming/refuting check. For a design or implementation, also provide the
+ placement rationale, field/semantic mapping, failure policy and compatibility tests. State
+ what ran and what remains unverified; do not claim a deployment fix from static analysis.
+
+ When evaluating this skill's decisions, use
+ [validation-cases.md](references/validation-cases.md). These are behavioral evaluation
+ prompts, distinct from tests of an adapter implementation.