adapter-sidecar-pattern · git:20260903.f199530 · 2026-09-03 · sha256 42cdf84cbcab9146
adapter-sidecar-pattern git:20260903.f199530A
Immutable. This exact content never changes and is served at /api/v1/blob/42cdf84cbcab9146.
--- 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). --- # 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. 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. ## 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. ``` ## 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. ## References - [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.