How OpenTelemetry Integrates with Service Mesh Telemetry
Two systems watch the same requests from different seats. This guide shows how to make them tell one story, so that the traces, metrics and logs from your mesh proxies and from your application code join up instead of competing.
Picture a busy airport. The airline knows exactly which passengers are on which flight and what each one ordered for dinner. The security checkpoint knows who walked through which gate, when, and whether anyone was stopped. Both are recording the same journey, yet neither record is complete. If you want the full story of one traveler, you need to put the two records side by side and match them up.
Modern software has the same situation. Your application code records what the business logic did. A service mesh, a layer of helper programs that sits beside your services and handles their network traffic, records what happened on the wire. OpenTelemetry is the open standard that lets both of them speak one language. When they are joined well, you can follow a single customer request from the front door to the deepest database and see every pause, every retry and every failure along the way.
You do not need to know anything about meshes or monitoring to follow this guide. Every new word is explained the first time it appears, and every big idea has an everyday comparison.
1The Vocabulary: Services, Proxies, Meshes and Telemetry
Eight words carry the entire topic. Learn them here and the rest of the guide reads easily.
Many modern applications are built as a collection of small programs called microservices. One handles logins, another handles payments, another recommends products. They talk to each other over the network all day long. As the number of services grows into the dozens or hundreds, a new problem appears: every service needs the same network chores done, such as encrypting traffic, retrying failed calls and measuring how long calls take. Writing that code again and again in every service is slow and error-prone.
Think of a large office building where hundreds of employees send and receive packages. Instead of asking each employee to learn how to seal, label, track and insure a parcel, the building gives every desk a personal assistant. The employee hands over the parcel, and the assistant does all the shipping chores. A service mesh is that army of personal assistants for network calls.
Proxy
A small program that sits in the path of network traffic and can inspect, forward, retry or block it. Envoy is the most widely used one, created at the ride-sharing company Lyft.
Sidecar
A proxy that runs right next to each service instance, in the same pod. All traffic in and out of the service passes through it, like a sidecar attached to a motorbike.
Data Plane
The group of all those proxies. They move the actual requests and are the part that does the work.
Control Plane
The central brain that tells every proxy the rules: where to send traffic, who may talk to whom, how to record telemetry. Istio’s control program is one example.
Traces, Metrics, Logs
The three kinds of recorded evidence. A trace is the story of one request. A metric is a number over time. A log is a written record of an event.
OpenTelemetry
Usually shortened to OTel. A vendor-neutral standard and toolset for producing and moving telemetry, run under the Cloud Native Computing Foundation.
Popular meshes include Istio, Linkerd, Consul and Cilium’s service mesh. They differ in design, but they share the idea that a layer outside your application code handles network behavior. That layer is in a unique position to watch every request, which is why meshes produce so much telemetry for free.
The mesh sees the wire. OpenTelemetry in your application sees the work. Integration is the art of joining the two views.
2What a Service Mesh Records on Its Own
Without writing a single line of monitoring code, a mesh already produces three kinds of telemetry.
Because every request passes through a proxy, the proxy can note it down. Nobody has to change the application. That is the great attraction of mesh telemetry: it works uniformly across every service, whatever language it was written in, including old programs nobody wants to touch.
| Signal | What the proxy records | Typical example |
|---|---|---|
| Metrics | Counts, sizes and timings of requests, grouped by source, destination and result | Requests per second from Orders to Payments, with the share that failed |
| Traces | A span for each hop a request makes through a proxy | A span showing the call from Orders to Payments took 120 milliseconds |
| Access logs | One line per request with addresses, status, timing and flags | A record that a call was refused because no healthy server was available |
The golden metrics
Istio, for example, produces a standard set of metrics inside the proxy itself. They count requests, measure how long requests took, and measure the size of requests and responses. Each is labeled with who called, who answered, the response code and more. Together they give you the classic health indicators of a service: how much traffic, how many errors, and how slow. Engineers sometimes call these the RED measures, for rate, errors and duration.
Spans without application code
The proxy can also start and finish spans. Each proxy creates a span for the request it handles, recording the time it took and details such as the destination and the status. Envoy, the proxy under several meshes, supports sending these spans out using the OpenTelemetry protocol, and Istio can be told to use an OpenTelemetry provider for tracing.
Logs of every call
An access log is a line written for each request. It is the oldest form of network telemetry, and it is rich: it includes the response code, how long the call took, the bytes moved and a short code called a response flag that explains unusual endings such as a timeout or a refused connection.
If you have no monitoring today, turning on mesh telemetry can give you a map of which services call which, plus error and speed figures, within a day. It is the quickest route from nothing to something.
3What OpenTelemetry Adds, and Why Two Systems Are Needed
If the mesh already records so much, why bring OpenTelemetry in at all?
The mesh sees only the edges of each service. It knows a request entered the payment service and left 90 milliseconds later. It cannot know what happened inside those 90 milliseconds: whether the time went into a database query, a call to a bank or a slow loop. Only code inside the service can say that, and OpenTelemetry is the standard way for that code to speak.
A delivery company’s tracking page says “package arrived at the warehouse at 9:00 and left at 11:30.” That is the mesh view. It does not say what happened during those two and a half hours. Warehouse staff would record “waiting for a pallet,” “scanned,” “repacked” and “loaded.” That is the application view. The full story needs both.
Four things OpenTelemetry provides
- SDKs and instrumentation that live inside your application and create detailed spans, metrics and logs.
- A common data protocol, OTLP, so telemetry from any source travels in one format.
- Semantic conventions, which are agreed names for common attributes, such as the HTTP method or the database name, so that data from different tools lines up.
- The Collector, a standalone program that receives telemetry from many sources, changes it and forwards it to storage.
The comparison
| Question | Mesh telemetry | OpenTelemetry in the application |
|---|---|---|
| Needs code changes? | No | Some, or an agent that instruments automatically |
| Works for every language? | Yes | Only where an SDK or agent exists |
| Sees inside a service? | No | Yes |
| Sees network behavior? | Yes, in great detail | Only from the application’s side |
| Knows business meaning? | No | Yes, such as order number or customer tier |
Each side covers the other’s blind spot. A wise team does not choose one. It joins them, and the rest of this guide explains how.
4The Big Picture: One Collector, Two Sources
The simplest and most reliable design sends both streams to one OpenTelemetry Collector.
Here is the overall shape of a healthy integration. The application, using an OpenTelemetry SDK, sends its telemetry out. The proxy next to it sends its own telemetry out. Both go to a Collector, which tidies, labels and combines them, and then forwards everything to the backend where people look at dashboards.
flowchart LR
A["Application with OTel SDK"] -->|"local traffic"| P["Mesh proxy beside the app"]
A --> C["OpenTelemetry Collector"]
P --> C
CP["Mesh control plane"] -.-> P
C --> B["Backend for traces metrics and logs"]
Five jobs of the integration
Join the traces
Mesh spans and application spans must share one trace identifier, so they appear as one story.
Agree on sampling
Decide once, at the start, whether a request is recorded, so no hop records it differently.
Unify the metrics
Bring mesh metrics and application metrics into one naming and unit scheme, and avoid counting the same request twice.
Correlate the logs
Stamp every log line with the trace identifier, so one click moves from a slow span to the exact log lines.
Name things consistently
Give each service one name and one set of labels in every signal, so charts and traces agree.
When both streams pass through the same Collector, you have one place to fix labels, drop noise, limit volume and add security. Sending proxy data to one vendor and application data to another makes joining them nearly impossible.
5Traces: Stitching Mesh Spans and Application Spans Together
The most valuable integration is also the most delicate one. A trace is only as good as its weakest link.
A trace is a tree of spans that share one trace identifier, a long random number created when the request first arrives. Every service that touches the request adds its own span to the tree. To do that, it must know the trace identifier, and the only way to learn it is from the request itself. The identifier is carried in a small set of HTTP headers. The web standard for this is called W3C Trace Context, and its main header is named traceparent.
sequenceDiagram
participant U as User
participant G as Ingress Gateway Proxy
participant PA as Proxy of Service A
participant A as Service A App
participant PB as Proxy of Service B
participant B as Service B App
U->>G: Request
Note over G: Creates the root span
G->>PA: Request with trace headers
PA->>A: Request with trace headers
Note over A: App reads the headers and keeps the trace id
A->>PA: Outgoing call with the same trace headers
PA->>PB: Request with trace headers
PB->>B: Request with trace headers
The part the mesh cannot do alone
A proxy sees an incoming request and, a moment later, an outgoing request. It cannot tell that the outgoing request was caused by the incoming one. Only the application knows that. So the application must copy the trace headers from the incoming request onto every outgoing request. This is called context propagation. If the application forgets, the outgoing call starts a brand new trace, and the story splits in two.
Imagine a relay race where each runner must hand the baton to the next. The proxies are the track officials who note each handoff. If a runner drops the baton and picks up a fresh one, the officials still record every handoff, but they now think two separate races are happening. The application is the runner. Passing the headers along is passing the baton.
Two easy ways to pass the baton
- Use an OpenTelemetry SDK or automatic agent. It reads the incoming headers and writes them onto outgoing calls without extra effort. This also adds the application’s own spans inside the same trace.
- Copy the headers by hand. For tiny services without an SDK, forward the trace headers your mesh uses. This keeps the trace connected but adds no inner detail.
How mesh spans and application spans nest
In a healthy joined trace, each hop appears as a pair of mesh spans around the application’s own work. The calling proxy records a client span, which measures the call from the caller’s side, including time on the wire. The receiving proxy records a server span, which measures the request from the moment it arrived. Inside the server span, the application adds its own spans for the work it does. Reading from the top, the trace therefore shows: caller proxy, receiving proxy, application code, and then the next call out. The gaps between neighboring spans are time spent in transit or in queues, which is exactly the information you cannot get from application spans alone.
Many teams turn on mesh tracing, open the trace view, and find disconnected fragments: one tiny trace per hop. The cause is almost always headers not being forwarded by the application. Fixing that single behavior joins the whole picture.
6Propagation Formats and Who Starts the Trace
Everybody must speak the same header dialect, and someone must be first to speak.
Trace headers come in more than one dialect. The modern standard is W3C Trace Context. An older and still common format, B3, came from the Zipkin tracing project. Other formats exist for particular vendors. A propagator is the small piece of code that reads and writes one of these formats. If the proxy writes B3 headers and the application only understands W3C headers, the application sees no trace at all and starts a new one.
| Format | Main header | Notes |
|---|---|---|
| W3C Trace Context | traceparent and tracestate | The standard, and the OpenTelemetry default |
| B3 | X-B3-TraceId and friends, or a single b3 header | Common in older meshes and Zipkin setups |
| Vendor formats | Vary | Avoid unless forced, since they split the ecosystem |
Pick one format and use it everywhere
The cleanest choice is W3C Trace Context in both the mesh and the applications. If a migration is slow, configure the application SDK with both propagators at once, so it can read whichever the proxy sent and write both when calling onward. Check your mesh’s documentation for how to set the format, because the setting differs between products and versions.
Who is the root?
The first proxy a request meets, usually the ingress gateway at the edge of your cluster, decides whether a trace exists. If the incoming request has no trace headers, the gateway creates the identifier and the first span, called the root span. Every later hop becomes a child. If the caller outside your system already sent trace headers, such as a partner’s system, you can choose to continue their trace or start your own. Starting your own is safer when the caller is untrusted, because outsiders could otherwise influence your sampling or flood your storage.
Baggage: a small passenger on the same ride
Baggage is a companion standard that carries small name and value pairs along with the request, such as a customer tier or a test-traffic flag. Proxies generally pass baggage along untouched, so a service far down the chain can read what the front door noted. It is handy for labeling, but remember that baggage travels in clear headers, so never put secrets in it.
Meshes often add their own request identifier header, which is a separate short random value used to find a single request in logs. It is useful, but it is not the same as the trace identifier. Put the trace identifier in your logs as well, so both can be searched.
7Sampling: Deciding Which Requests Get Recorded
Recording everything is expensive. The danger is that the mesh and the application each decide differently.
Storing a trace for every request would drown most systems, so engineers record only a fraction. This is called sampling. In a mesh, the proxies can sample, and the applications can sample too. If they do so independently, you get half-finished stories: the proxy kept the trace, but the application dropped it, or the other way around.
flowchart TB
R["Request arrives at the gateway"] --> D{"Is there already a sampling decision in the headers?"}
D -- "Yes" --> F["Every proxy and app follows that decision"]
D -- "No" --> M["Gateway rolls the dice at the configured rate"]
M --> K{"Sampled?"}
K -- "Yes" --> S["Mark the trace as sampled and record spans"]
K -- "No" --> N["Mark as not sampled and skip spans"]
The rule that keeps stories whole
The trace headers include a sampled flag. The first component to see the request decides and writes the flag. Everyone downstream should follow the parent’s decision. In OpenTelemetry SDKs this behavior is called a parent-based sampler. When the application uses it, the application records spans exactly when the gateway did, and the trace is complete or absent, never half there.
A school decides which classes will be filmed for a documentary. If the principal’s office says “film Room 12,” every teacher and cameraman in Room 12 films. If each camera operator flipped a coin on their own, the footage would have missing scenes. A single instruction from the top keeps the film whole.
A note on defaults
Meshes usually ship with a low default sampling rate, since tracing every request on a busy mesh is costly. Istio, for instance, commonly defaults to about one percent. That is wise for production, and it surprises people who try the system with a single test request and see nothing. In test environments, raise the rate to a hundred percent. In production, raise it only as far as your budget and storage allow.
Choosing a rate: a quick calculation
Suppose the gateway receives 1,000 requests per second. At one percent sampling, ten requests per second are kept, which is 864,000 traces per day. At a hundred percent, it would be 86,400,000 traces per day, a hundred times more. Because every trace holds many spans, one for each hop and each internal step, the span count is higher still. A sensible plan keeps a low rate for ordinary traffic and finds a way to keep the interesting requests, such as errors and slow ones, through smarter sampling in the Collector.
If the proxy samples at ten percent and the application then independently samples at ten percent, you keep only one percent and most traces are broken. Always have the application defer to the incoming decision.
8Metrics: Bringing Mesh Numbers into OpenTelemetry
Mesh metrics speak a Prometheus dialect. OpenTelemetry has its own conventions. The Collector translates between them.
A mesh like Istio exposes its metrics in the Prometheus format, a plain-text layout that is read by periodically visiting a web address on each proxy, an act called scraping. The OpenTelemetry Collector has a Prometheus receiver that does exactly this, turning the scraped values into OpenTelemetry metrics and passing them along with everything else.
Same idea, different names and units
| Idea | Typical mesh metric | OpenTelemetry convention |
|---|---|---|
| Request count | istio_requests_total | Count derived from the HTTP duration metric |
| Request duration | istio_request_duration_milliseconds | http.server.request.duration, measured in seconds |
| Caller and callee | source and destination workload labels | Resource attributes and peer attributes |
Notice the unit difference. The mesh reports milliseconds, and the OpenTelemetry convention uses seconds. A call that takes 250 milliseconds is 0.25 seconds. If you plot the two on one chart without converting, one line is a thousand times larger than the other. The Collector can rename metrics and convert units, so decide on one convention and let the Collector apply it.
The double counting trap
If both the proxy and the application measure the same request, you now have two request counts for one request. Adding them together doubles every number. The usual remedy is to choose a source of truth for each question. Use the mesh metrics for traffic between services, since they cover every service uniformly. Use the application metrics for business meaning and internal work. Keep them in separate, clearly named families, and never sum across the two.
Mesh metrics carry many labels, and histograms add one series per bucket. Suppose 100 workloads each call 10 others, which gives 1,000 caller and callee pairs. With 5 response codes and about 20 histogram buckets, that is 1,000 x 5 x 20 = 100,000 series from one metric family. Drop labels you never chart, before storage bills arrive.
Metrics and traces answer different questions
A metric tells you that something is wrong and how much, for all traffic, cheaply. A trace tells you where and why, but only for the requests you recorded. Because the mesh counts every request in its metrics, even when traces are sampled at one percent, mesh metrics are an unbiased measure of traffic. Use them to notice problems, and use traces to explain them. Teams that rely on sampled traces to count traffic end up undercounting by a factor of a hundred, while teams that rely on metrics alone know something is broken but cannot say where.
Linking numbers to stories
OpenTelemetry supports exemplars, small samples that attach a trace identifier to a metric point. With them, a spike on an error chart can link straight to a trace that shows a failing request. Support depends on your tools, but when present, it makes the integration of metrics and traces feel seamless.
9Logs: Access Logs That Point to Traces
An access log line is far more useful when it carries the trace identifier. This chapter shows how to get there.
By default, many meshes write access logs to the proxy’s standard output as plain text, which the platform collects like any other container log. That works, but it leaves the log as a separate island. Two improvements bring it into the OpenTelemetry world. First, the mesh can be told to send access logs through OpenTelemetry’s protocol directly to the Collector, as structured records rather than text. Istio’s configuration includes an OpenTelemetry access log provider for this, although the exact setup differs between versions. Second, the log should include the trace identifier, so a person looking at a slow span can jump to the exact log line for that request, and the other way around.
A hotel keeps a door log of every key-card swipe, and a separate camera recording. The log says room 412 opened at 2:14 a.m. The camera shows the corridor. Without a shared timestamp or number, matching them takes ages. A trace identifier on the log line is that shared number.
What an access log adds that spans do not
- Every request, not a sample. Traces are usually sampled at a low rate, but access logs can cover all requests, so no failed call goes unrecorded.
- Response flags. Short codes that say why a request ended unusually, for example no healthy server was available, the upstream timed out or the connection was reset.
- Network facts. Source and destination addresses, bytes sent and received, and the time spent in each phase of the call.
Volume is the catch
An access log line is typically a few hundred bytes. If one service receives 1,000 requests per second, that is 86.4 million lines per day. At about 500 bytes each, that is 43.2 billion bytes, or roughly 43 gigabytes, per day from just one busy service. A mesh with hundreds of services can produce terabytes. The good news is that the Collector can help. You can log only errors, drop health-check traffic, or keep a small random share of successes while keeping every failure.
Logging every successful request in full is rarely worth the storage. Keep all failures, all slow requests, and a small sample of everything else. Meshes allow filtering at the proxy, and the Collector can filter again before storage.
10One Name for One Service: Resource Identity
If the mesh calls a service one thing and the application calls it another, your charts will show two services where there is only one.
Every piece of OpenTelemetry telemetry carries resource attributes, which describe who produced it. The most important is service.name. When the application’s SDK says “this is the checkout service” and the mesh proxy says “this came from a workload named checkout-v2 in the shop namespace,” a naive backend treats these as two different things. Traces then show a mesh node and an application node side by side instead of one merged service.
| Source | How it may name the service | The problem |
|---|---|---|
| Application SDK | checkout | Set by the developer, may differ from the platform name |
| Mesh proxy spans | Often based on the workload or the host name | Includes a namespace or suffix the app does not use |
| Mesh metrics | Workload, namespace and version labels | Different label names from the OpenTelemetry attributes |
The cure: normalize in the Collector
The Collector sits in the middle and can fix this in one place. Its Kubernetes attributes processor labels every record with the pod, namespace and deployment it came from, using information from the cluster. Its attributes and transform processors can rename a value, remove a suffix or set service.name from a trusted source, such as a label on the workload. Choose a single rule, for example “the service name is the value of the app label on the pod,” and apply it to application and mesh data alike.
Other labels worth aligning
- Version. Mesh metrics often include a workload version label, which is perfect for comparing a new release with the old one during a gradual rollout.
- Environment. Production, staging and test, in one spelling.
- Team or owner. Useful for routing alerts and attributing cost.
- Cluster and region. Important for multi-cluster meshes, where the same service name may exist in several places.
Document the naming rule in one short page and enforce it in the Collector configuration. A new service then joins the picture automatically with no bespoke work.
11Where the Collector Lives: Deployment Patterns
Every proxy needs a nearby place to send its telemetry. Where you put the Collector decides how reliable and how costly the whole system is.
Hundreds of proxies, each sending spans, logs and metrics, can overwhelm a single destination. The usual design uses two layers, shown below. A small node Collector runs on every machine, as a daemon-style program that Kubernetes places once per node. Proxies and applications on that machine send to it, over a fast local hop. The node Collectors then forward to a central gateway tier of larger Collectors that do the heavy work and talk to the backend.
flowchart LR
subgraph Node1["Node 1"]
A1["Pods with proxies"] --> D1["Node Collector"]
end
subgraph Node2["Node 2"]
A2["Pods with proxies"] --> D2["Node Collector"]
end
D1 --> G["Gateway Collector tier"]
D2 --> G
G --> B["Backend"]
| Layer | Good at | Typical jobs |
|---|---|---|
| Node Collector | Being close, fast and simple | Receive from proxies and apps, add node and pod labels, batch, forward |
| Gateway Collector | Seeing everything at once | Normalize names, filter noise, apply tail sampling, route to backends |
A city has neighborhood post offices and one big sorting center. Residents drop letters at the nearest small office in seconds. The small offices bundle letters and send them to the sorting center, which sorts, stamps and dispatches. Nobody drives every letter across the city by hand.
A loop to avoid
If the Collector itself runs inside the mesh with a proxy beside it, the traffic that delivers telemetry is also traced by the mesh. Now the act of delivering spans creates new spans, which must be delivered, which creates more spans. This feedback loop wastes resources and fills your storage with telemetry about telemetry. Prevent it by keeping Collector pods out of the mesh, or by turning off tracing and access logging for their traffic.
Sizing and keeping the Collector reliable
The Collector is now part of your production path, so it needs the same care as any service. Give it memory and processor limits, and enable its memory limiter so it refuses data politely instead of crashing when overloaded. Run several gateway replicas, and put a load balancer that understands request traffic in front of them, so a single slow replica does not hold up many proxies. Watch its own metrics: queue size, dropped items and export failures. If the Collector falls behind, proxies and applications drop telemetry silently, and you lose the very evidence you need during an incident.
Proxies export on a schedule and use limited waiting space. A nearby Collector replies in milliseconds, which keeps those proxies from backing up. This is the same reasoning behind sending application telemetry to a local agent first.
12What the Mesh Sees That the Application Cannot
The proxy is the only witness for a whole family of events. Without its telemetry, they are invisible.
Many important events happen in the network layer, before a request ever reaches your code or after it leaves. The application cannot report them because it never knew. Only the proxy can.
Retries
If a call fails and the proxy tries again, the application sees one request that took a long time. The proxy sees two or three attempts. Envoy records the attempt count, which explains mysterious delays.
Timeouts
The proxy can give up on a slow server. The application sees an error. The proxy knows it was a timeout it enforced.
Circuit Breaking
When a destination keeps failing, the proxy can stop sending traffic to it. The application just sees fast failures, and only the proxy knows why.
Encrypted Identity
When two proxies use mutual TLS, each knows the verified identity of the other. The application only sees a plain local connection.
Routing Decisions
Which version received the request during a canary rollout, or which rule matched. The proxy applied the rule, so it can record it.
Refused Traffic
Requests blocked by a policy never reach the application at all, so only the proxy can count them.
Identity you can trust
When the mesh uses mutual TLS, each proxy proves who it is to the other using a certificate tied to the workload. The proxy can therefore record not just “a request came from this address,” but “a request came from this verified workload identity.” Addresses change as pods come and go, but identities stay meaningful. Recording the verified source and destination in metrics and logs makes security reviews far easier, because you can answer “which services actually call the payment service” from evidence rather than from a diagram that may be out of date.
Response flags are a gift
Envoy marks each request with short response flags. A few examples: one means no healthy upstream server was available, another means the upstream connection failed, another means the upstream request timed out, and another means no route matched. When an engineer sees a burst of errors, the flag turns “something is wrong” into “the destination had no healthy servers.” Carrying these flags into your logs and, where possible, into span attributes is one of the most useful pieces of integration work.
You phone a restaurant and the call never connects. From your side, “the call failed.” The phone company’s records say “the restaurant’s line was busy” or “the number was out of service.” Those are the proxy’s records. They explain what you could not see from your own side.
When a service looks slow in the application’s own spans but fast in its code, look at the mesh span for the same call. The gap is often retries, queuing or a timeout that the application never saw.
13What the Application Sees That the Mesh Cannot
The other half of the story is hidden from the proxy. Only your own code can tell it.
From the proxy’s point of view, a service is a closed box. A request goes in, a response comes out, and some time passes. The proxy has no idea what happened inside the box. OpenTelemetry in the application opens the box.
| Event | Visible to the proxy? | Visible to the OpenTelemetry SDK? |
|---|---|---|
| A request entered and left | Yes | Yes |
| A database query took 400 milliseconds | Only if the database is also behind a proxy, and then only as a network call | Yes, with the query type and table |
| A function loop was slow | No | Yes, with custom spans |
| A payment of a certain size was declined | No | Yes, with business attributes |
| A cache hit saved a call | No | Yes |
| An exception was thrown and handled | No | Yes, with the error type and stack |
Business meaning
A proxy knows the address of a request. Your application knows what the request meant: which customer, which product, which feature flag, which plan. Adding such labels to spans, as attributes, turns the mesh’s anonymous call graph into a story that product and support teams can use, such as “orders from premium customers are slowest on the payment step.”
Where the two meet in a trace
In a joined trace, the mesh span for the call to the payment service has the application’s own span nested inside it, and the database and bank calls hang under that. The outer mesh span shows the time on the wire, the inner spans show what the code did. If the outer span is 300 milliseconds and the inner code span is 100 milliseconds, then 200 milliseconds were spent outside your code, in the network, in queues or in retries. That subtraction is one of the great pleasures of joined telemetry.
14Sidecars, Ambient Meshes and Other Designs
Not every mesh puts a proxy next to every service. The integration idea stays the same, but the details move.
The classic design puts one sidecar proxy in every pod. It is simple to reason about, and it costs memory and processor time for every pod. Newer designs try to reduce that cost.
flowchart LR
subgraph Sidecar["Sidecar model"]
S1["App"] --- S2["Proxy in every pod"]
end
subgraph Ambient["Ambient model"]
A1["App"] --- A2["Node level agent for connections"]
A2 --- A3["Optional shared request proxy"]
end
Ambient mode
Istio’s ambient mode, which has become a supported option in recent releases, removes the sidecar. A small agent on each node, named the zero-trust tunnel, handles secure connections for every pod on that node. It sees connection-level facts such as bytes and connections, and it can emit connection metrics. Where you need request-level detail, such as paths, status codes and spans, you add a shared waypoint proxy for a namespace or service. Request-level traces and metrics then come from the waypoint, so the integration rules from the earlier chapters apply to it.
Other meshes
- Linkerd uses its own lightweight proxy rather than Envoy and exposes Prometheus metrics. It can send spans to a Collector through its tracing add-on, although the details vary by version.
- Cilium’s service mesh uses kernel-level technology and a per-node Envoy, which again moves the telemetry source from the pod to the node.
- Consul typically runs Envoy sidecars, so Envoy-based guidance applies.
Whatever the design, the questions are the same. Who creates the trace? Do the headers get forwarded? Who decides sampling? How do names and units line up? Ask them for each mesh you adopt, and read its documentation for the exact settings in your version.
15Performance and Overhead
Telemetry is not free. Both the mesh and OpenTelemetry cost something, and together they can cost more than expected.
Every proxy uses memory and processor time, and each hop through a proxy adds a small delay. Producing telemetry adds more. Spans must be built, logs formatted, metrics counted and everything sent over the network. At a handful of services this does not matter. At hundreds, it is a real line on the budget.
| Cost | Where it comes from | How to reduce it |
|---|---|---|
| Proxy processing | Building spans, formatting logs | Lower sampling, filter logs at the proxy |
| Network traffic | Sending telemetry to the Collector | Batching and compression |
| Storage | Spans and logs kept at the backend | Tail sampling, shorter retention, log filtering |
| Metric series | Many labels on mesh metrics | Drop unneeded labels in the Collector |
| Operations | Running the Collector tiers | Keep the configuration simple and shared |
Spans multiply
A request that passes through five services creates more spans than people expect. Each service can produce a span in the calling proxy, a span in the receiving proxy and one or more spans in the application code. Ten or more spans for a single request is normal. At 1,000 requests per second with ten spans each, that is 10,000 spans per second before sampling. This is why the sampling decision from Chapter 7 matters so much.
Every assistant in the office keeps a diary of each parcel. With ten parcels a day that is easy. With ten thousand parcels a day, the diaries fill warehouses. A sensible office keeps full diaries for the few parcels that went wrong, and short notes for the rest.
Compare the cost of the mesh and the telemetry against what they replace. Many teams find the uniform visibility more than pays for itself, but only if they control sampling, labels and log volume from the start.
16Security and Privacy of Mesh Telemetry
Telemetry describes who talked to whom and what they said. Treat it as sensitive data.
A mesh often exists to improve security, with encrypted connections and identity checks between services. It would be ironic if its telemetry then leaked the very information it protects.
Do Not Log Secrets
Access logs can include request headers. Authorization tokens, cookies and keys must never be written. Allow-list the headers you record.
Mind the URLs
Web addresses sometimes contain personal data such as an email or an order number. Redact or hash such values before storage.
Keep It Clean
Baggage travels through every proxy and may leave your system if a service calls a third party. Carry only harmless identifiers.
Encrypt the Path to the Collector
Use TLS between proxies, Collectors and the backend, and authenticate who may send telemetry in.
Limit Who Sees It
Traces expose internal architecture. Give dashboards to those who need them and keep production detail away from broad audiences.
Do Not Trust Outside Headers
Strip or replace trace headers arriving from the public internet, so outsiders cannot force high sampling or inject fake identities.
The Collector as a privacy filter
Because all telemetry passes through the Collector, it is the natural place to enforce privacy rules. Its processors can delete sensitive attributes, mask parts of a value and drop whole records before anything reaches storage. Writing these rules once, centrally, is far safer than trusting every team to remember them in every service.
Search your stored logs and spans for patterns like email addresses and long tokens, every month. Finding one early is a quick cleanup. Finding one after an audit is a long afternoon.
17Troubleshooting: Reading the Symptoms
Most integration problems show themselves in a handful of ways. Learn the symptoms and the fix is usually short.
When the integration is wrong, the trace view looks wrong in recognizable ways. The flow below turns the most common symptoms into a quick path to the cause.
flowchart TB
S["A trace looks broken"] --> Q1{"Do app spans and mesh spans share a trace id?"}
Q1 -- "No" --> A1["Check header forwarding in the app and the propagation format"]
Q1 -- "Yes" --> Q2{"Is a hop missing?"}
Q2 -- "Yes" --> A2["Check sampling flags and proxy tracing settings for that workload"]
Q2 -- "No" --> A3["Check the export path to the Collector and the backend"]
| Symptom | Likely cause | First thing to check |
|---|---|---|
| Each hop appears as a separate tiny trace | The application does not forward trace headers | Does the service use an SDK, or copy the headers by hand? |
| Application spans exist, but mesh spans are missing | Proxy tracing is off, or the sampling rate is very low | The mesh tracing configuration and rate for that namespace |
| Mesh spans exist, but application spans start new traces | Different header formats between proxy and SDK | The propagators configured on both sides |
| One service appears twice under different names | Inconsistent service naming | The Collector renaming rule |
| Request counts are doubled | Mesh and application metrics summed together | Which metric family each dashboard uses |
| Latency charts differ by a factor of one thousand | Milliseconds mixed with seconds | Unit conversion in the Collector |
| Gaps in data for one namespace | The Collector is not reachable or is dropping data | Collector health and queue metrics |
| A flood of spans from the Collector’s own pods | The telemetry feedback loop | Whether the Collector pods are inside the mesh |
A test that finds most problems
Send one request with a known trace identifier through the system, with the sampling flag set so it is recorded. Then search for that identifier in the backend. You should find the gateway span, each proxy span and every application span, connected as one tree. Wherever the tree breaks, the break is your bug. Keep this as an automatic check after every mesh or Collector upgrade.
Proxies and Collectors publish their own health metrics, including how many spans they exported and how many they dropped. A rising drop counter is the earliest warning that the telemetry path itself is struggling.
18Design Patterns and Trade-offs
Eight habits of teams that run mesh and OpenTelemetry together successfully, and the choices they weigh.
One Collector Path
Send proxy and application telemetry through the same Collector tiers, so every fix and filter applies to both.
W3C Everywhere
Standardize on W3C Trace Context in the mesh and in every SDK, and accept legacy formats only during migrations.
Decide at the Edge
Let the ingress gateway make the sampling decision, and make every application follow it with a parent-based sampler.
Mesh for Edges, App for Insides
Use mesh metrics for traffic between services and application telemetry for business meaning and internal work.
Normalize in the Collector
Apply naming, unit and label rules centrally, so teams do not each invent their own.
Keep Interesting Traces
Combine low random sampling with rules in the Collector that keep errors and slow requests.
Trace Identifier in Every Log
Make every log line, from proxies and applications alike, carry the trace identifier.
Test the Integration
Run a synthetic end-to-end trace check after each upgrade of the mesh, the SDKs or the Collector.
Trade-off: mesh-only tracing, or full application instrumentation?
Mesh-only tracing
- No code changes and uniform across all languages.
- Fast to roll out and easy to explain.
- Gives a clear map of calls between services.
Limits
- Cannot see inside services.
- Traces fall apart where headers are not forwarded.
- No business meaning on the spans.
Trade-off: sampling at the proxy, or in the Collector?
Sampling at the proxy
- Cuts cost early, before data leaves the node.
- Simple to configure.
- Keeps the Collector load low.
Limits
- Decides before the outcome is known, so errors may be dropped.
- Cannot keep only the slow ones.
- Rate changes require mesh configuration changes.
The usual compromise is a modest sample at the edge, so volume stays manageable, with extra rules in the Collector layer that preserve the high-value traces after the outcome is known. That second step needs all spans of a trace to reach the same Collector, which is a design constraint worth planning from the start.
19Anti-Patterns and Common Mistakes
Four habits that look sensible and quietly break the picture.
What Teams Do
They turn on mesh tracing, declare distributed tracing done, and never ask application teams to forward headers.
Why It Hurts
Traces are broken into one-hop fragments. The dashboards look busy but nobody can follow a request end to end.
Do Instead
Treat header forwarding as a requirement for every service, ideally by adopting an OpenTelemetry SDK or agent that does it automatically.
What Teams Do
They let the proxy sample at one rate and the applications sample at another, each with its own random choice.
Why It Hurts
Most traces end up half recorded, and the effective rate is a multiple of the two, which nobody predicted.
Do Instead
Decide once at the edge and make every other component follow the parent’s decision.
What Teams Do
They add up request counts from the mesh and from the application to get a total.
Why It Hurts
Every request is counted twice, error rates are skewed, and capacity plans are wrong.
Do Instead
Pick one source of truth per question and label the metric families clearly so nobody sums across them.
What Teams Do
They send proxy telemetry straight to the vendor and application telemetry through a separate path.
Why It Hurts
Names, labels and sampling cannot be reconciled, and there is no central place to filter or protect data.
Do Instead
Route both through the Collector so normalization, privacy rules and routing live in one place.
Other frequent mistakes
- Running the Collector inside the mesh without excluding its own traffic.
- Leaving the demo sampling rate in production, or the production rate in testing.
- Forgetting that queues and background jobs need trace headers copied into messages.
- Logging authorization headers in access logs.
- Upgrading the mesh without re-running the end-to-end trace check.
20A Step-by-Step Rollout Plan
A practical order that delivers value early and avoids the traps above.
Stand up the Collector tiers
Deploy a node Collector and a small gateway tier outside the mesh. Confirm they receive test data and reach the backend.
Choose the propagation format
Pick W3C Trace Context and set it in the mesh. Configure SDKs to match, with legacy formats only where required.
Send mesh traces and logs to the Collector
Point the proxies at the node Collector using OpenTelemetry’s protocol. Start with a high sampling rate in one test namespace.
Instrument one service chain
Add an OpenTelemetry SDK or agent to two or three connected services. Use the end-to-end test from Chapter 17 to confirm a whole tree appears.
Normalize names, units and labels
Add the Collector rules for service names, seconds versus milliseconds and dropping unneeded labels.
Set production sampling and filtering
Lower the edge sampling rate, make applications parent-based and add Collector rules that keep errors and slow requests.
Roll out and guard
Expand service by service, add privacy rules and put the end-to-end test into the upgrade checklist.
Rolling out one namespace at a time lets you spot issues such as double counting or exploding label counts while they are still small and cheap to fix.
21Real-World Scenarios
Five illustrative stories showing how the integration pays off.
These scenarios are illustrative composites. The numbers are chosen for clarity and are not measurements from any named company. For background, Envoy began at Lyft, Istio was started by Google, IBM and Lyft, and Linkerd, from the company Buoyant, is credited with popularizing the term “service mesh.”
Scenario 1: The invisible retries
A shopping site’s checkout looks fast in application traces, at about 100 milliseconds, yet customers complain it feels slow. The joined trace shows the mesh span for the payment call lasting 600 milliseconds, with three attempts. The first two attempts hit a server that was restarting. The mesh quietly retried, and the application never knew. The team fixes the restart behavior and the delay disappears.
Scenario 2: The half-sampled traces
A media company enables tracing at ten percent on its proxies. Application teams also sample at ten percent. Engineers wonder why only about one request in a hundred has a complete trace. Switching the applications to follow the gateway’s decision restores nearly every sampled trace to a full tree, with no change in total cost.
Scenario 3: The doubled dashboard
A fintech team builds a dashboard that sums mesh and application request counts. Its error rate looks half what the alerts say. The cause is that the mesh and application both count the same requests, which doubles the denominator for one and not the other. The team picks mesh metrics for service-to-service traffic, labels the application metrics as business events, and the chart finally agrees with the alerts.
Scenario 4: The telemetry that watched itself
A platform team installs the Collector inside the mesh with a sidecar. Within an hour the trace storage fills with spans whose only content is the delivery of other spans. Excluding the Collector pods from the mesh stops the loop, and storage use drops by about a third.
Scenario 5: The canary that looked fine
A team rolls out a new version of a recommendation service to five percent of traffic. Application metrics for the new version look healthy. Mesh metrics, which carry the workload version, show that calls to the new version time out about three times as often, because the new version starts slowly and the proxy gives up on it. Only the proxy saw those timeouts. The team pauses the rollout, adjusts the startup behavior and tries again.
22Frequently Asked Questions
Short answers to the questions people ask most often.
23Summary and Key Takeaways
Key Takeaways
- A service mesh sees the wire, and OpenTelemetry in the application sees the work. Joined together, they show a request’s full story.
- Send proxy and application telemetry through one Collector path, so naming, filtering and privacy rules live in one place.
- Traces join only if applications forward trace headers and everyone speaks one propagation format, ideally W3C Trace Context.
- Make one sampling decision at the edge, and have every other component follow it with a parent-based sampler.
- Normalize service names and units in the Collector, and avoid summing mesh and application metrics, which double counts requests.
- Use mesh data for retries, timeouts, circuit breaking, identity and routing, and application data for internal steps and business meaning.
- Keep the Collector out of the mesh’s tracing loop, protect secrets in logs and headers, control volume, and test the end-to-end trace after every upgrade.