diff --git a/deploy/lineage-attach/DESIGN.md b/deploy/lineage-attach/DESIGN.md index 68a8fc2bf..705dd3805 100644 --- a/deploy/lineage-attach/DESIGN.md +++ b/deploy/lineage-attach/DESIGN.md @@ -184,13 +184,18 @@ the shim's interlock lets the image through. ### Baking is not purely additive -The shim installs one pinned OpenTelemetry contrib release -(`OTEL_CONTRIB_VERSION` in `Dockerfile.otel-shim`), and the resolver brings -the SDK that release requires — on an image that already carries an older SDK, -the bake *upgrades* it (observed: `opentelemetry-sdk 1.42.1 → 1.44.0`, semconv -`0.63b1 → 0.65b0`). Harmless for an app that does not pin, but an app pinning -an older SDK can be affected by being wrapped. Check the build output if your -app is sensitive to those versions. +The install resolves into the app's own environment, so it can move a package +the OpenTelemetry set depends on (`wrapt`, `typing-extensions`); `uv pip check` +fails the build when the app pins one. The OpenTelemetry core itself does not +move when the app already carries it: each contrib release requires one exact +core (`0.63b1` requires `opentelemetry-api==1.42.1`), so `Dockerfile.otel-shim` +pins the install to the app's `opentelemetry-api` and the resolver lands on the +paired release. An app with no OpenTelemetry, or with `opentelemetry-api` below +1.34.0, gets `OTEL_CONTRIB_VERSION`; for the older app that moves its core, and +`uv pip check` decides. (The releases paired with older cores instrument only +`starlette<0.15`, and before `0.52b0` they lack the `initialize()` the hook +calls. The releases paired with 1.34.0 to 1.40.x require `wrapt<2`, so a +`wrapt` 2.x the app holds loosely is moved down.) The static `uv` used to install the shim is copied into the image and left there — a `rm` in a later layer would not reclaim the copy layer's space, so diff --git a/deploy/lineage-attach/Dockerfile.otel-shim b/deploy/lineage-attach/Dockerfile.otel-shim index cd5dcc001..d8a084097 100644 --- a/deploy/lineage-attach/Dockerfile.otel-shim +++ b/deploy/lineage-attach/Dockerfile.otel-shim @@ -42,11 +42,20 @@ USER root # Our own static uv, pinned by digest: a builder-stage image may have neither # uv nor pip in its runtime layer. COPY --from=ghcr.io/astral-sh/uv:0.9.24@sha256:816fdce3387ed2142e37d2e56e1b1b97ccc1ea87731ba199dc8a25c04e4997c5 /uv /usr/local/bin/uv -# One contrib release for everything, so a bake never silently upgrades the -# OTel set under an app. -distro is the SDK; the eight others — server -# (starlette/asgi/fastapi), client (httpx/requests/aiohttp/urllib3) and -# -threading — are the instrumentors build-otel-shim.sh's interlock probes -# for; keep the two lists in sync. +# One contrib release for everything, and the app chooses it. Each release +# requires one exact OpenTelemetry core (0.63b1 requires +# opentelemetry-api==1.42.1, through opentelemetry-semantic-conventions), so +# the install is pinned to the opentelemetry-api the app already carries and +# the resolver lands on the one release paired with it: the bake does not move +# the api/sdk under the app. An app that carries none, or one older than +# 1.34.0, is pinned to OTEL_CONTRIB_VERSION instead; there the bake may move +# the core, and the check below decides. (1.34.0 pairs with contrib 0.55b0: +# earlier releases instrument only starlette<0.15, and before 0.52b0 they lack +# the initialize() the hook calls.) +# -distro is the SDK; the eight others — server (starlette/asgi/fastapi), +# client (httpx/requests/aiohttp/urllib3) and -threading — are the +# instrumentors build-otel-shim.sh's interlock probes for; keep the two lists +# in sync. # `-urllib3` covers clients that bypass `requests`: boto3/botocore (S3 and # every AWS-style store) drive urllib3 directly. Without it an object read # lands in a trace of its own (seen live: a MinIO GetObject escaping into a @@ -58,16 +67,21 @@ COPY --from=ghcr.io/astral-sh/uv:0.9.24@sha256:816fdce3387ed2142e37d2e56e1b1b97c # `--system --break-system-packages` lets the install land in a non-venv # python too; both flags are inert when --python is a venv. ARG OTEL_CONTRIB_VERSION=0.65b0 -RUN uv pip install --no-cache --python "${VENV_PYTHON}" --system --break-system-packages \ - "opentelemetry-distro==${OTEL_CONTRIB_VERSION}" \ - "opentelemetry-instrumentation-starlette==${OTEL_CONTRIB_VERSION}" \ - "opentelemetry-instrumentation-asgi==${OTEL_CONTRIB_VERSION}" \ - "opentelemetry-instrumentation-fastapi==${OTEL_CONTRIB_VERSION}" \ - "opentelemetry-instrumentation-httpx==${OTEL_CONTRIB_VERSION}" \ - "opentelemetry-instrumentation-requests==${OTEL_CONTRIB_VERSION}" \ - "opentelemetry-instrumentation-aiohttp-client==${OTEL_CONTRIB_VERSION}" \ - "opentelemetry-instrumentation-urllib3==${OTEL_CONTRIB_VERSION}" \ - "opentelemetry-instrumentation-threading==${OTEL_CONTRIB_VERSION}" +RUN pin="$("${VENV_PYTHON}" -c 'from importlib.metadata import version; v = version("opentelemetry-api"); \ + assert tuple(map(int, v.split(".")[:2])) >= (1, 34); print("opentelemetry-api==" + v)' 2>/dev/null)" \ + || pin="opentelemetry-distro==${OTEL_CONTRIB_VERSION}"; \ + echo "the bake is pinned to ${pin}"; \ + uv pip install --no-cache --python "${VENV_PYTHON}" --system --break-system-packages "${pin}" \ + opentelemetry-distro \ + opentelemetry-instrumentation-starlette \ + opentelemetry-instrumentation-asgi \ + opentelemetry-instrumentation-fastapi \ + opentelemetry-instrumentation-httpx \ + opentelemetry-instrumentation-requests \ + opentelemetry-instrumentation-aiohttp-client \ + opentelemetry-instrumentation-urllib3 \ + opentelemetry-instrumentation-threading \ + || { echo "REFUSING: the shim could not be installed alongside ${pin} (uv's error is above; if it found no solution, no contrib release is paired with that version)" >&2; exit 1; } # The install resolves into the app's OWN environment, so it may move a package # an app dependency pins (wrapt, deprecated, typing-extensions are the usual # suspects) — and the attestations import only OpenTelemetry, never the app. diff --git a/deploy/lineage-attach/README.md b/deploy/lineage-attach/README.md index db4309783..680fb0c97 100644 --- a/deploy/lineage-attach/README.md +++ b/deploy/lineage-attach/README.md @@ -318,7 +318,7 @@ BAKE — once per app image ATTACH — once per Deployment | `RECIPE.md` · `DESIGN.md` | the step-by-step; the reasoning, envelope and limits | | `attach-lineage.sh` | **the one generator** — every YAML byte of the attachment, `EMIT=patch` / `EMIT=cm`, env-driven, stdout only, every input validated or refused | | `sidecar-patch.sh` | the live applier: preconditions, then ConfigMap + patch + rollout wait; owns no YAML | -| `Dockerfile.otel-shim` | the propagate-only layer, one recipe for every in-envelope app, instrumentors pinned to one contrib release | +| `Dockerfile.otel-shim` | the propagate-only layer, one recipe for every in-envelope app, instrumentors on the contrib release paired with the app's own `opentelemetry-api` (a pinned default when it carries none, or one older than 1.34.0) | | `build-otel-shim.sh` | bakes, attests (gate off: nothing OTel-shaped loads; gate on: a `traceparent` is injected), kind-loads; refuses images it cannot safely wrap | | `lineage-propagate-hook.py` | the env-gated site hook the Dockerfile installs (`.pth` + module); read its docstring for the contract | | `container-runtime.sh` | sourced helper: docker vs podman, kind load either way | diff --git a/deploy/lineage-attach/RECIPE.md b/deploy/lineage-attach/RECIPE.md index 7be00043e..aba8b0dce 100644 --- a/deploy/lineage-attach/RECIPE.md +++ b/deploy/lineage-attach/RECIPE.md @@ -55,6 +55,7 @@ attestation runs before the load and prints nothing when it passes). Fail `REFUSING to bake … already instruments …` (exit 3): the app instruments itself — go to step 3 **without** `APP_CONTAINER`/`APP_IMAGE` (capture only). Fail `REFUSING to bake … no runnable Python found` (exit 3): outside the shim's envelope (DESIGN "The envelope") — same, capture only; or pass the interpreter as arg 3 if you know it. Fail `REFUSING to bake … is not present locally` (exit 3): wrong `IMAGE` — see Inputs; nothing was built. +Fail `REFUSING: the shim could not be installed alongside …` (the build fails): no contrib release is paired with the app's `opentelemetry-api` (a core newer than its contrib release, or a package index that lags) — uv's own error is above the line. Fail `ATTESTATION FAILED` (exit 4): the bake itself is broken (the image was not loaded) — read the assertion it prints; not an app property. ## 3. Attach (once per Deployment)