Distributed Tracing Basics in Kubernetes
Learn how to follow a single request as it travels through many services in your cluster, how traces, spans, and context fit together, and how to collect, store, and read them safely, from first principles to production habits.
Imagine sending a parcel across the country. It leaves your house, reaches a local depot, travels on a truck to a sorting centre, moves onto a plane, lands in another city, and finally a courier knocks on the door. If the parcel arrives three days late, you want to know where it waited. The tracking page helps, because every stop scans the parcel and adds a line with a time. From those lines you can see that the plane was quick but the parcel sat for two days in the sorting centre.
Distributed tracing is a tracking page for requests inside your software. When a customer clicks a button, that one click may pass through a dozen small programs before an answer returns. Tracing records each stop, how long it took, and how the stops connect. This tutorial explains the idea from the ground up, shows how it works inside Kubernetes, and gives you the patterns, dangers, and habits that real teams rely on.
1Why We Need Tracing
When one request touches many programs, finding the slow or broken part becomes a detective story. Tracing gives the detective a map.
From one big program to many small ones
Years ago, most applications were one large program. A request came in, the program did all the work, and an answer went out. If something was slow, you looked inside that one program. Today, many teams split their software into microservices. A microservice is a small program that does one job, such as handling logins, calculating prices, or sending emails. Each one runs in its own container, and Kubernetes keeps them all running.
This split has real benefits. Teams can change one small part without touching the others, and parts can grow independently. But it also creates a new problem. A single click by a customer may cause the website to call the login service, which calls the user database, then the price service, then the stock service, then the payment service. Any one of these could be slow or broken.
Think of ordering a meal at a busy restaurant with many kitchen stations. One station chops, one grills, one plates, and one adds sauce. If your meal takes forty minutes, the waiter cannot tell you why by looking at the finished plate. You need a slip that every station stamps with the time it received and finished the order. The stamped slip is a trace.
What the other tools cannot tell you
You may already have metrics, which are numbers over time such as requests per second. You may also have logs, which are lines of text that programs write about what they did. Both are valuable. But metrics tell you that the checkout is slow on average, without saying which step is slow. Logs from twelve different programs are written in twelve different places, and it is hard to tell which lines belong to the same customer click.
Tracing fills this gap. It ties together everything that happened for one request, across all the programs, in order, with times. With a trace open, you can say, “this request spent eight hundred milliseconds waiting for the stock service, and the stock service spent most of that time waiting for its database.”
Questions a trace answers
Where is the time going?
A trace shows which step in a long chain is slow, so you fix the right thing.
Where did it fail?
When a request returns an error, the trace shows which program first went wrong and which ones only passed the error along.
Who calls whom?
Traces reveal the real paths between services, which are often different from what the diagrams on the wall say.
Did the release help?
Comparing traces before and after a change shows whether a step became faster or slower.
The three work as a team. Metrics tell you that something is wrong, traces show where, and logs explain why. Later in this tutorial you will see how to connect all three.
2The Core Idea: Traces, Spans, and Context
Three simple words carry the whole idea. A trace is the story, a span is one step of the story, and context is the thread that ties the steps together.
The trace
A trace is the complete record of one request as it moves through your system. It starts when the request first enters, for example when a customer clicks a button, and ends when the last program has finished its part. Every trace has a unique identifier, called the trace ID, which is a long random number. All the steps that belong to the same request carry the same trace ID.
The span
A span is one unit of work inside a trace. It has a name, a start time, and an end time, so it knows how long it took. A span might be “handle the checkout request,” “ask the stock service,” or “run a database query.” Each span has its own span ID. It also remembers the ID of the span that caused it, which is called its parent. The first span in a trace has no parent and is called the root span.
Because each span knows its parent, spans form a tree. The root is the whole request, its children are the steps it took, and their children are the smaller steps inside those.
flowchart TD
ROOT["Root span: checkout request, 900 ms"] --> A["Span: check login, 60 ms"]
ROOT --> B["Span: get prices, 120 ms"]
ROOT --> C["Span: reserve stock, 650 ms"]
ROOT --> D["Span: take payment, 40 ms"]
C --> C1["Span: database query, 600 ms"]
B --> B1["Span: cache lookup, 5 ms"]
Look at the figure. The whole checkout took 900 milliseconds. Login, prices, and payment were quick. Reserving stock took 650 milliseconds, and nearly all of that was a single database query. Without the tree, you would only know that checkout was slow. With it, you know the database query inside the stock service is the place to look.
A trace is like a family tree drawn for one event. The root is the grandparent, who starts things off. Each child is someone who was asked to do a part, and each grandchild is someone that child asked for help. Everyone carries the same family name, which is the trace ID, and everyone knows who their parent is.
Context
Here is the tricky part. The programs are separate and do not share memory. How does the stock service know that its work belongs to a checkout that began in a different program? The answer is context. Context is a small package of identifiers, mainly the trace ID and the ID of the calling span, that travels along with the request. When one program calls another, it attaches this package to the call. The receiving program reads it, and creates its own span as a child of the sender’s span.
Passing this package along is called context propagation. It is the heart of distributed tracing. If any program in the chain forgets to pass it on, the trace breaks into two separate pieces.
| Word | Plain meaning | Everyday comparison |
|---|---|---|
| Trace | The full journey of one request | The whole tracking page of a parcel |
| Span | One step in the journey, with start and end times | One scan at a depot |
| Trace ID | The number shared by every step of one request | The parcel number |
| Span ID | The number of one particular step | The scan number |
| Parent | The step that caused this step | The depot that sent the parcel on |
| Context | The identifiers that travel with the request | The label stuck on the parcel |
If one service does not pass the context on, everything after it starts a new trace. You then see many short traces instead of one long one, and the connection between them is lost. Most tracing problems in real systems are missing context.
3Kubernetes Building Blocks You Need First
Before tracing inside a cluster, here is a short guide to the Kubernetes words that will appear again and again.
Containers, pods, and nodes
A container is a small box that holds a program and everything it needs to run. A pod is the smallest thing Kubernetes runs, and it holds one or more containers that work closely together. A node is a computer, real or virtual, that runs pods. A cluster is a group of nodes managed together.
Deployments and services
A Deployment tells Kubernetes to keep a chosen number of identical pods running. If one crashes, a new one is started. A Service gives a group of pods a single stable name and address, so other programs can find them even when the pods behind the name change. When the checkout program wants the stock program, it calls the stock Service by name, and Kubernetes sends the call to one of the healthy stock pods.
A Service is like the phone number of a shop. The shop may have several people behind the counter, and staff may change during the day, but customers always dial the same number, and someone answers.
Namespaces, ConfigMaps, and environment variables
A namespace is a named area inside the cluster that keeps related things together. A ConfigMap holds settings as text, and pods can read them when they start. An environment variable is a named setting handed to a program when it starts. These matter for tracing, because a program usually learns where to send its trace data, and what to call itself, through environment variables and settings supplied by Kubernetes.
DaemonSets and sidecars
A DaemonSet makes sure that one copy of a pod runs on every node. It is often used for helper programs that must be near everything else, such as collectors of data. A sidecar is a helper container that runs in the same pod as the main container, sharing its network. Sidecars are used to add features, such as traffic handling or data collection, without changing the main program.
Pod
The smallest running unit. Each service in a trace usually runs in one or more pods.
Service
A stable name and address for a group of pods. Calls between programs go through Services.
DaemonSet
Runs one helper pod on every node, a common home for trace collectors.
Sidecar
A helper container living beside the main one in the same pod.
Namespace
A named area of the cluster, often used to separate teams or tools.
Environment variable
A named setting passed to a program at start, often used to say where to send traces.
Why Kubernetes makes tracing both harder and easier
It makes tracing harder because pods appear and disappear, so traces must be sent out quickly before a pod vanishes, and pod names change constantly. It makes tracing easier because Kubernetes gives every program a standard way to find its neighbours, to receive settings, and to run helper pods on every node. Those standard features are exactly what a tracing system needs.
4What Is Inside a Span
A span is more than a name and two times. It carries extra details that make a trace useful when you are searching for a problem at three in the morning.
The parts of a span
| Part | What it holds | Why it helps |
|---|---|---|
| Name | A short label for the work, such as “reserve stock” | Lets you read the trace like a sentence |
| Start time and duration | When it began and how long it lasted | Shows where time is spent |
| Trace ID and span ID | The identifiers linking it to the rest | Lets tools rebuild the tree |
| Parent span ID | The step that caused this one | Gives the tree its shape |
| Kind | Whether it is a server, client, producer, consumer, or internal step | Tells tools which side of a call it is |
| Attributes | Key and value pairs, such as the method used or the status code returned | Lets you search and filter |
| Events | Time-stamped notes inside the span, such as “retrying” | Records important moments |
| Status | Whether the work succeeded or failed | Lets you find failing requests fast |
| Links | References to other traces or spans | Connects work that is related but not a parent and child |
Span kinds
When one program calls another, two spans are usually created. The caller makes a client span that measures how long it waited. The receiver makes a server span that measures how long it worked. The difference between the two is the time spent travelling over the network and waiting in queues. That gap can be surprisingly large and is often the clue to a hidden problem.
When work is passed through a message queue, the sender makes a producer span and the receiver makes a consumer span. Steps that stay inside one program are called internal spans.
Suppose you phone a plumber. You time how long the whole call felt from your side, and the plumber times how long they actually spent working. If you waited ten minutes but the plumber worked for two, eight minutes went missing on hold. Client and server spans measure both sides so the missing time shows up.
Attributes: the labels you can search by
Attributes are pairs of a name and a value attached to a span. Examples are the web address called, the method used, the response code, the name of the database, or the name of the customer tier. They are what let you ask questions like “show me all slow requests to the payment service that failed for the premium tier.”
The tracing community has agreed on common names for many attributes, known as semantic conventions. Using the standard names means that tools from different makers understand your data, and your teams do not each invent their own words for the same idea.
Resource attributes: who made the span
Some details describe the program that made the span rather than the work itself. These are called resource attributes. They include the service name, the version, the pod name, the namespace, and the node. Kubernetes supplies most of them. They let you say, “this slow span came from version 2.3 of the stock service, running in pod number four, on this node.”
A trace with services called “app”, “server”, and “backend” is hard to read. Give each service a clear, unique name, and keep it the same across environments, so that searches and diagrams make sense.
Attributes are stored and often shown to many people. Never put passwords, tokens, full card numbers, or private personal details into them. We return to this in the security chapter.
5Context Propagation: Passing the Baton
Everything in tracing depends on the trace ID reaching the next program. Here is how that happens, and why a standard matters.
How context travels
Programs usually talk to each other over the web, using a protocol called HTTP, or through a faster protocol called gRPC. Both allow small extra pieces of information, called headers, to be added to each call. Tracing uses a header to carry the context. The calling program adds it. The receiving program reads it, and creates its own span linked to the caller’s span. It then adds the header again on every call it makes to others.
Think of a relay race. Each runner carries a baton and hands it to the next. The baton is the context. If a runner drops it, the next runner cannot continue the race as a team. They can only run alone, and the record of the race is split in two.
A shared standard
In the past, different tracing tools used different header names and formats. A program using one tool could not pass context to a program using another. Today, most systems follow a standard from the World Wide Web Consortium, called W3C Trace Context. It defines a header named traceparent, which carries the trace ID, the parent span ID, and a flag telling whether this trace is being recorded. A second header can carry extra vendor-specific details.
Older formats still exist, such as the B3 headers from the Zipkin project. Modern tools can usually read and write several formats at once, which helps when a system is halfway through a move.
| Format | Where it comes from | Notes |
|---|---|---|
| W3C Trace Context | Web standards body | The modern default, widely supported |
| B3 | Zipkin project | Common in older systems |
| Vendor formats | Individual product makers | Avoid for new work unless needed |
Baggage
Besides the identifiers, context can carry small extra facts called baggage, such as the customer tier or the region. Baggage travels to every program down the chain, so each can use it in its own spans. It is powerful, but it is also sent on every call, and it may leave your system when you call outside services. Use it sparingly and never for anything private.
Where context gets lost
Context is most often lost in a few predictable places. Message queues and background jobs are common, because the work does not run inside the original call. Programs that start their own worker threads may forget to carry context across. Proxies, gateways, and load balancers may drop unknown headers. And any program that has no tracing at all simply does not pass the header on.
Message queues
The context must be stored in the message itself and read back by the consumer.
Background threads
Code that hands work to another thread must also hand over the context.
Gateways and proxies
Some strip headers they do not know. Configure them to pass trace headers through.
Untraced services
A single old service without tracing can cut the chain in two.
Choose the standard format, and make sure every gateway, mesh, and library is set to use it. Mixed formats are a common cause of traces that break in the middle.
6Instrumentation and OpenTelemetry
Programs do not create spans by themselves. Someone, or something, must add the tracing. This is called instrumentation.
What instrumentation means
Instrumenting a program means adding the pieces that create spans, pass context, and send the results out. Think of it as fitting a program with sensors. There are two broad ways to do it.
Automatic instrumentation
A tool attaches to the program when it starts and creates spans for common actions, such as web calls and database queries, without changing the code. It is quick to adopt and gives instant coverage.
Manual instrumentation
Developers add spans in their own code around important business steps, such as “apply discount.” It takes more work but adds meaning that no tool can guess.
Most teams begin with automatic instrumentation to get broad coverage, then add manual spans in the few places where the business logic matters most.
Automatic instrumentation is like fitting a car with a standard tracker that records speed and location. Manual instrumentation is like the driver noting in a diary, “stopped here to pick up the client.” The tracker gives coverage, and the diary gives meaning.
OpenTelemetry
OpenTelemetry, often written OTel, is the open standard and set of tools for collecting traces, metrics, and logs. It is a project of the Cloud Native Computing Foundation, and it is now the common way to instrument programs. It offers three main things. First, a specification that defines what spans, attributes, and context look like. Second, libraries called SDKs and APIs for many programming languages. Third, a program called the Collector that receives, processes, and forwards telemetry data.
The great benefit is freedom. If your programs speak OpenTelemetry, you can send the data to almost any tracing backend, and change the backend later without touching your applications. It replaced older projects called OpenTracing and OpenCensus, which were merged into it.
flowchart LR
APP["Application with OpenTelemetry SDK"] -->|"OTLP"| COL["OpenTelemetry Collector"]
COL --> BE1["Tracing backend, such as Jaeger or Tempo"]
COL --> BE2["Another tool or vendor service"]
BE1 --> UI["Trace viewer for people"]
Read the figure from left to right. The application uses the OpenTelemetry SDK to create spans. It sends them, using a standard protocol called OTLP, to the Collector. The Collector can process the data and forward it to one or several backends. People then use a trace viewer to read the results.
Instrumenting in Kubernetes
Kubernetes adds a helpful option here. An OpenTelemetry Operator can be installed in the cluster. An operator is a helper program that manages other programs by watching special Kubernetes objects. With it, you can add a simple note, called an annotation, to a Deployment, saying that its pods should be automatically instrumented. The operator then injects the instrumentation into new pods when they start, and sets the settings that tell them where to send data. This lets a platform team turn on tracing for many services without asking every developer to change code.
Automatic instrumentation adds a small amount of work to every request, and it may create more spans than you want. Test it on one service first, check the added delay and memory, and make sure it supports the language and libraries you use.
A team runs twenty services written in three languages. Instead of asking each team to add tracing code, the platform team installs the OpenTelemetry Operator and labels each Deployment for automatic instrumentation. Within a day, traces from all twenty services appear in one viewer. Later, the payments team adds a few manual spans around “fraud check” and “apply discount,” because those steps matter most to the business.
7Architecture of a Tracing Stack in Kubernetes
A working tracing setup has a handful of parts. Each has one clear job, and knowing them makes the whole picture simple.
The main parts
Instrumented applications
Your own programs, fitted with an SDK or automatic instrumentation. They create spans and pass context.
Agent collector
A Collector running close to the applications, often one per node as a DaemonSet, or beside each pod as a sidecar. It receives spans quickly and adds Kubernetes details.
Gateway collector
A central group of Collectors, run as a Deployment, that receives data from all the agents, applies rules such as sampling, and forwards to storage.
Storage backend
A database designed for traces. Well-known open source choices include Jaeger and Grafana Tempo, and there are many hosted services.
Query service and viewer
The part that searches stored traces and draws them for people, often with a timeline of spans.
Service mesh or gateway (optional)
Network helpers that can create spans for traffic between services, filling gaps in programs that are not instrumented.
Picture a postal system for tracking data. Each depot scans parcels, which are your applications. A local post office collects scans from the depots nearby, which is the agent. A regional sorting centre gathers from many post offices and decides which to keep, which is the gateway. A central archive stores the records, and a public website lets you look them up.
Why two layers of collectors
You could send spans straight from every application to storage. That works for a tiny system, but it has problems. Every application would need the storage address and any secret needed to reach it. Any slowdown in storage would slow the applications. And there would be nowhere to apply shared rules.
Adding a Collector in the middle solves these. Applications send to a nearby agent and move on quickly. The agent adds details such as the pod name and namespace, batches spans together, and forwards them. A central gateway can then make decisions that need a wider view, such as keeping all traces that contain an error. Only the gateway needs the credentials for storage.
| Layer | Typical way to run it | Main jobs |
|---|---|---|
| Agent | DaemonSet, one per node, or a sidecar | Receive quickly, add Kubernetes details, batch, forward |
| Gateway | Deployment with several replicas behind a Service | Sample, filter, remove private data, route to storage |
| Backend | StatefulSet or managed service | Store, index, and answer searches |
The Collector’s own pieces
Inside every Collector are three kinds of building blocks that are joined into pipelines. Receivers accept data, for example in the OTLP format. Processors change data on the way through, for example by batching, adding labels, or dropping items. Exporters send data out to another system. A pipeline is simply a chosen list of receivers, processors, and exporters for one type of data.
Begin with one agent per node and one small gateway. Add more layers only when volume or rules demand them. A simple pipeline is easier to understand and to fix.
Choosing a backend
| Option | Character | Good when |
|---|---|---|
| Jaeger | Mature open source tracing system with its own viewer | You want a classic, well-known choice with flexible storage |
| Grafana Tempo | Stores traces cheaply in object storage, and works closely with Grafana | You already use Grafana and want low storage cost at high volume |
| Zipkin | The older open source tracing system | You have existing Zipkin tools |
| Hosted service | A vendor runs storage and viewing | You prefer to pay rather than operate it |
A company with about eighty services runs an OpenTelemetry Collector on every node. Those agents forward to a gateway of three Collectors that keep every failed trace and a small share of the successful ones, then send the result to Tempo backed by object storage. Developers open traces from Grafana. When they change to a different backend a year later, only the gateway settings change, and no application is touched.
8Internal Working: One Request, Step by Step
Let us follow one customer click through three services and see exactly where each span and each header comes from.
The journey
A customer clicks “buy.” The click reaches the front door of the cluster, a gateway program. It has no trace yet, so it starts one and creates the root span with a new trace ID. It then calls the checkout service and adds the context header. The checkout service reads the header, creates its own server span as a child, and calls the stock service, again adding the header. The stock service creates its span, asks its database, and replies. Each program finishes its span when its work is done, and hands the finished span to the nearby agent.
sequenceDiagram
participant U as Customer
participant G as Gateway
participant C as Checkout service
participant S as Stock service
participant A as Agent collector
U->>G: Click buy
G->>G: Start trace and root span
G->>C: Call with context header
C->>C: Create child span
C->>S: Call with context header
S->>S: Create child span and query database
S-->>C: Reply
C-->>G: Reply
G-->>U: Answer
S->>A: Send finished span
C->>A: Send finished span
G->>A: Send finished span
Notice two separate flows. The request itself moves along the chain, carrying the context. Meanwhile, each program sends its finished spans, quietly and separately, to the agent. The spans are not returned in the reply. This keeps tracing away from the customer’s response path, so a problem in the tracing system does not have to slow the customer.
Think of a courier chain again. The parcel moves from courier to courier with a label. Meanwhile, each courier phones the head office with their own note, “I received it at two, and passed it on at three.” The head office pieces the notes together later using the number on the label.
What happens inside one program
Inside a program, the SDK keeps track of the current span, usually in a small holder that follows the running work. When the program starts a new piece of work, the SDK creates a new span whose parent is the current one. When that work ends, the SDK closes the span, records the end time, and places it in a queue. A background helper takes spans from the queue, groups them into batches, and sends them to the collector. Batching keeps the cost per span low.
What the collector does
When spans arrive at an agent, a processor adds Kubernetes details. It looks at the source of the connection, asks the Kubernetes API which pod that is, and attaches the pod name, namespace, node, and labels to the spans. Another processor batches them. The agent then forwards them to the gateway. The gateway may hold spans in memory for a short time, because it must wait for all the spans of a trace to arrive before it can decide whether to keep the trace.
How a trace is assembled
Spans reach the backend at different times and from different places. The backend groups them by trace ID and uses the parent IDs to rebuild the tree. A span whose parent has not yet arrived is temporarily an orphan, and it joins the tree when the parent shows up. If a parent never arrives, because a service was not instrumented or a span was lost, the viewer shows a gap or a missing parent. This is a useful clue that something upstream is not reporting.
Each span’s times come from the clock of the machine that made it. If two machines disagree about the time, a child may appear to start before its parent. Keep node clocks synchronised using standard time services, and remember that small differences are normal.
The role of a service mesh
A service mesh is a layer of small network helpers, often sidecars, that handle traffic between services. Because they see every call, they can create spans for the network part of each hop, even for programs with no instrumentation. This gives a rough picture of who calls whom and how long each call took. However, a mesh cannot see inside a program, and it cannot pass context from an incoming call to an outgoing one unless the program itself copies the headers. So a mesh helps, but does not replace instrumentation.
9Sampling: Keeping the Traces That Matter
A busy system produces far more traces than anyone can store or read. Sampling means deciding which ones to keep.
Why not keep everything
Imagine a service that handles ten thousand requests each second. Each request creates a trace with maybe twenty spans. That is two hundred thousand spans every second, around the clock. Storing and searching all of it would be very costly, and most of it would be nearly identical successful requests that nobody will ever look at. Sampling reduces the volume while keeping enough to learn from.
A bakery does not keep a photo of every loaf. It keeps a few photos for quality checks, and it always keeps a photo of any loaf that came out burnt. Sampling is the same: keep a small share of the ordinary ones, and keep every unusual one.
Head sampling
In head sampling, the decision is made at the very start of the trace, when the root span is created. For example, the system might decide to keep one trace in every hundred. The decision is written into the context flags, so every later program follows it. This is cheap and simple, because unwanted traces are never recorded. The weakness is that the decision is made before anyone knows how the trace will turn out. An interesting failure has only a one in a hundred chance of being kept.
Tail sampling
In tail sampling, the decision is made at the end, after the whole trace has been collected. The system can then keep traces that had errors, or that took longer than a chosen time, and keep only a small share of the rest. This keeps the most useful traces. The price is complexity. All the spans of a trace must reach the same collector, so it can see the whole trace, and that collector must hold spans in memory while it waits. This is usually done in the gateway layer.
flowchart TD
START["Request arrives"] --> H{"Head sampling: keep this one?"}
H -->|"No"| DROP1["Not recorded"]
H -->|"Yes"| REC["Spans recorded and sent"]
REC --> T{"Tail sampling at gateway"}
T -->|"Trace has an error"| KEEP1["Keep"]
T -->|"Trace is very slow"| KEEP2["Keep"]
T -->|"Ordinary and fast"| PROB{"Random small share?"}
PROB -->|"Chosen"| KEEP3["Keep"]
PROB -->|"Not chosen"| DROP2["Discard"]
The figure shows both methods working together. Head sampling can cheaply throw away a large share at the start. Tail sampling then inspects what remains and always keeps the errors and the slow ones, while keeping only a small random share of ordinary traces.
| Method | When it decides | Strength | Weakness |
|---|---|---|---|
| Head, fixed share | At the start | Simple, cheap, low overhead | May miss rare failures |
| Tail, rule based | After the trace ends | Keeps errors and slow traces | Needs memory and careful routing |
| Always on | Never drops | Complete data | Costly at high volume |
| Always off | Never records | No cost | No tracing at all |
Practical rules of thumb
Keep all traces in small systems and in test environments, because volume is low and the detail is valuable. In larger systems, use a low fixed share at the head, and add tail rules at the gateway that keep every trace with an error and every trace slower than a threshold. Always record the sampling rate, so that when you count traces in the viewer you know that each one stands for many.
Because of sampling, the number of stored traces is not the number of requests. Use metrics for counts and rates, and use traces for the detail of individual requests.
If spans of the same trace are spread across several gateway pods, none of them can judge the whole trace. Route spans by trace ID so that all spans of a trace reach the same gateway pod, or use a two-layer design built for this.
10Data Flow and Lifecycle of a Trace
Follow one trace from the first click to its final deletion.
Start
A request enters the system. The first program creates a trace ID and a root span, and makes the head sampling decision.
Propagation
The request moves from program to program. Each one reads the context header, creates a child span, and passes the header on.
Finish and queue
Each span closes when its work ends and waits in a small queue inside the program.
Export to the agent
A background helper sends batches of spans to the nearby agent using the standard protocol.
Enrich and forward
The agent adds pod, node, and namespace details, batches again, and forwards to the gateway.
Decide
The gateway waits until the trace looks complete, then applies tail sampling and privacy rules.
Store and index
The backend writes the spans, and indexes chosen fields, such as the service name and the status, so they can be searched.
Search and read
A person finds the trace by its ID, or by searching for slow or failed requests, and reads the timeline of spans.
Retire
After the retention period, often days or a few weeks, the trace is deleted to save space and reduce risk.
A trace’s life is like a letter in a busy office. It is written, carried from desk to desk with notes added, copied by the mailroom, filed in the archive by a clerk who decides what is worth keeping, looked up when a question arises, and shredded after the retention period passes.
What happens when things fail
If the agent is down, the SDK’s queue fills up. Well-built SDKs drop the oldest spans rather than block the application, because tracing must never break the thing it observes. If the gateway is overloaded, agents may buffer briefly and then drop data. If the backend is down, the gateway may keep data in a limited queue. In every case, the goal is the same: protect the customer’s request first, and accept that some trace data may be lost.
Tracing is diagnostic information. It is fine to lose a few spans during trouble. It is never fine for tracing problems to make customer requests slow or fail. Check that your queues have limits and that timeouts are short.
Choosing retention
| Kind of trace | Suggested keeping time | Reason |
|---|---|---|
| Ordinary sampled traces | A few days to two weeks | Most investigations happen soon after an event |
| Error and slow traces | Two to four weeks | Useful for reviews after an incident |
| Traces linked to an incident | Copy out and keep with the report | Preserve evidence for learning |
11Connecting Traces, Metrics, and Logs
Each signal answers a different question. The magic happens when you can jump between them in a single click.
Three signals, three roles
| Signal | What it is | Best at |
|---|---|---|
| Metrics | Numbers over time | Noticing that something is wrong, and how big it is |
| Traces | The path and timing of single requests | Finding where in the chain the problem is |
| Logs | Text written by programs | Explaining exactly what happened and why |
Consider a doctor’s visit. The thermometer reading is a metric: it says you have a fever. The scan showing which organ is inflamed is like a trace: it says where. The doctor’s notes about symptoms and history are the logs: they say why. A good doctor uses all three together.
Logs that carry the trace ID
The simplest and most valuable link is to write the trace ID and span ID into every log line. Most logging libraries can do this automatically when tracing is set up. Then, from a slow trace, you can search your logs for that trace ID and see every log line from every service for that one request. Conversely, from an error log, you can copy the trace ID and open the whole trace.
Exemplars: from a number to a trace
Metrics are averages and totals, so they hide individual requests. An exemplar is a small link stored alongside a metric value that points to one example trace. A chart of slow requests can then show dots, and clicking a dot opens a real trace that contributed to that point. This is one of the fastest ways to move from noticing a spike to seeing its cause.
Metrics made from traces
Because spans record durations and outcomes, they can be turned into metrics, such as the rate, error count, and duration of each service and each call between services. Some backends and collectors do this for you. It gives a complete map of requests, errors, and latency between services, with no extra instrumentation.
flowchart LR
M["Metric chart shows a spike"] -->|"exemplar link"| T["Trace shows the slow step"]
T -->|"trace ID search"| L["Logs show the exact error"]
L -->|"trace ID in log line"| T
T -->|"span durations become"| M2["Service level metrics"]
The figure shows a typical investigation. A person sees a spike on a chart, clicks an example dot to open a trace, sees which span is slow, and follows the trace ID into the logs to read the exact error message. The same trace also feeds summary metrics for services.
When metrics, traces, and logs all appear in one tool, moving between them is easy. Tools such as Grafana can connect to several backends and offer links between them, which saves precious minutes during an incident.
At two in the morning, an alert shows that checkout is slow. The on-call engineer opens the latency chart and clicks an example dot. The trace shows a delay in one call to a pricing service. The pricing service span carries an event saying it retried three times. Searching logs by the trace ID reveals a message about a full connection pool. The engineer restarts the pricing pods, then files a fix to increase the pool. The whole diagnosis takes ten minutes.
12Security and Privacy of Traces
Traces describe exactly how your system works and what your users do. Treat them as sensitive data.
What traces might reveal
Traces can contain web addresses with private values inside, database statements, error messages, customer identifiers, and internal service names. Attackers could use this to learn how your system is built. Careless attributes might also expose private data such as emails, names, or tokens. In many countries, laws about personal data apply to traces just as they apply to databases.
A trace is like a detailed diary of everything a customer did in your shop, with times. Handy for improving service, but you would not leave the diary on the counter, and you would not write down their card number in it.
Controls to put in place
Redact at the source and the gateway
Never record secrets. Use collector processors to delete or hash risky attributes, such as tokens, cookies, and email addresses, before storage.
Limit query text
Record database statements with values replaced by placeholders, so private values never appear.
Encrypt the path
Use encrypted connections between applications, collectors, and storage, and require proof of identity where possible.
Control who can read
Put the viewer behind single sign-on, and give access by team. Some traces should be restricted to fewer people.
Restrict the network
Use NetworkPolicies so that only applications may send to collectors, and only collectors may reach storage.
Limit retention
Keep data only as long as it is useful. Deleting old traces reduces both cost and risk.
Danger from outside headers
The public can send requests with their own trace headers. If your system trusts them blindly, an outsider could push their trace IDs into your data, force your system to record every request, or use baggage to inject values. At the edge of your system, decide whether to accept incoming context or start a new trace, and cap the size of any baggage you accept.
| Risk | What could happen | Protection |
|---|---|---|
| Secrets in attributes | Tokens or passwords stored and readable by many | Redaction rules, review of new attributes |
| Open collector port | Anyone can send fake or huge amounts of data | Network rules, authentication, limits |
| Public viewer | Outsiders read internal details | Single sign-on and private access |
| Trusting outside headers | Forced sampling, cost blowup, injected data | Start fresh traces at the edge |
| Long retention | Old private data lingers | Short, deliberate keeping times |
Collectors that add pod details need permission to read information about pods. Give them a read-only role for exactly the objects they need, and nothing that can change the cluster.
13Advantages, Disadvantages, and Trade-offs
Tracing is powerful, but it has real costs. Knowing both sides helps you invest where it pays off.
Advantages
- Shows exactly where time goes in a chain of services.
- Finds the first service that failed, not just the ones that passed the error on.
- Reveals real dependencies between services.
- Speeds up incident diagnosis, especially in large systems.
- Open standards mean you can change tools without changing code.
- Works together with metrics and logs for a full picture.
Disadvantages
- Adds a small delay and resource use to every instrumented program.
- Data volume and storage cost can grow quickly.
- Missing context breaks traces, and finding the gap takes effort.
- Sampling means some requests are never seen.
- Traces can expose private data if handled carelessly.
- Teams need training to read traces well.
The trade-offs in plain words
| Choice | Benefit of going further | Cost of going further |
|---|---|---|
| Keep more traces | Fewer rare problems missed | More storage and more money |
| Add more spans | Finer detail inside services | Bigger traces, harder to read, slower programs |
| Add more attributes | Better searching and filtering | More storage, more privacy risk |
| Tail sampling | Keeps the most useful traces | More memory, more moving parts |
| Keep data longer | Compare with earlier weeks | Higher cost and more risk |
Think of installing cameras in a shop. More cameras show more, but they cost money, produce mountains of video, and raise privacy questions. A sensible owner covers the entrance and the till first, and adds more only where real problems appear.
When tracing is worth it
Tracing pays off most when requests cross many services, when teams are separate and cannot see each other’s code, and when problems are about slowness or partial failure rather than a single crash. If you have one or two services, good logs and metrics may be enough for now. As the number of services grows, the value of tracing grows faster than its cost.
You do not need to trace everything on day one. Start with the most important customer journey, such as sign-in or checkout, and trace that end to end. Expand from there.
14Design Patterns and Anti-patterns
Patterns are arrangements that many teams have found to work. Anti-patterns are habits that seem sensible but cause trouble.
Helpful patterns
Agent and gateway
Applications send to a nearby agent, agents send to a central gateway, and the gateway decides what to keep and where to send it.
Automatic first, manual where it matters
Use automatic instrumentation for coverage, and add hand-made spans around key business steps.
Keep all errors, sample the rest
Use tail sampling to keep every failed and slow trace, and a small share of ordinary ones.
Trace ID in every log
Write the trace ID into log lines so one search links the trace to all its logs.
Standard names and conventions
Follow the shared attribute names, and agree on service names, so tools and people understand each other.
Fresh trace at the edge
Start a new trace at the public entrance instead of trusting headers from outside.
A well-run airport gives every bag a barcode at check-in, uses the same barcode format at every stage, and keeps a full record for any bag that goes missing. These habits are the same as agreeing on standards, adding identifiers early, and keeping evidence for problems.
Anti-patterns to avoid
Problem
Tracing every request at full detail, forever, with no sampling and no retention limit.
Why It’s Harmful
Storage and network costs grow without limit, searches slow down, and the important traces are buried among millions of ordinary ones.
Correct Approach
Choose a sampling plan, keep errors and slow traces, and set clear retention periods.
Problem
Creating a span for every tiny function call inside a program.
Why It’s Harmful
Traces become huge and unreadable, the program slows down, and the real story is lost in noise.
Correct Approach
Create spans for meaningful units of work: calls to other services, database queries, and key business steps.
Problem
Putting values such as user emails, full web addresses with tokens, or order contents into span attributes.
Why It’s Harmful
Private data is stored and shown widely, which breaks privacy rules and creates a security risk.
Correct Approach
Record safe identifiers or categories instead, and use redaction rules in the collector as a second line of defence.
Problem
Sending spans directly from every application to storage, with no collector in between.
Why It’s Harmful
Every application must hold storage details, a slow backend can affect applications, and there is nowhere to apply shared rules.
Correct Approach
Send to a collector layer, and change the shared rules in one place.
Problem
Treating tracing as a one-time project, with nobody owning the data quality afterwards.
Why It’s Harmful
New services join without tracing, context breaks appear, names drift, and trust in the tool falls until people stop using it.
Correct Approach
Give a team ownership, include tracing in the checklist for new services, and review trace quality regularly.
15Best Practices and Common Mistakes
A short list of habits that keep tracing useful, and the mistakes that most often spoil it.
Best practices
Use OpenTelemetry and the standard header
Standard tools and formats keep you free to change backends and let services from different teams work together.
Start with one important journey
Trace the customer path that matters most, prove its value, then widen coverage.
Name services and spans clearly
Use stable service names and short, meaningful span names that describe the work, not the customer or the ID.
Put a collector layer in front of storage
Let agents and gateways enrich, batch, sample, and redact before anything is stored.
Sample wisely
Keep errors and slow traces, sample the rest, and record the sampling rate.
Link the three signals
Add trace IDs to logs and use exemplars on metrics so you can jump between them.
Protect the data
Redact, encrypt, restrict access, and delete on a schedule.
Make it part of the launch checklist
Every new service should ship with tracing, standard names, and a link to its dashboards.
Keeping tracing healthy is like keeping a library catalogue in order. If every new book is catalogued the same way on the day it arrives, anyone can find anything. If books are added with no labels, the catalogue slowly becomes useless.
Common mistakes
| Mistake | What goes wrong | Better habit |
|---|---|---|
| Forgetting to pass context through queues | Traces end at the queue and restart on the other side | Store context in the message and read it back |
| Different header formats in different services | Traces split in the middle | Choose one format and set every tool to it |
| High-variety values in span names | Thousands of unique names make searching useless | Keep names generic and put details in attributes |
| No timeouts on the exporter | A slow collector slows the application | Set short timeouts and limited queues |
| Sampling only at the head | Rare failures are usually missed | Add tail rules for errors and slow traces |
| Ignoring the gap between client and server spans | Network and queue delays stay hidden | Compare both spans to find the missing time |
| Never checking that traces are complete | Broken traces are trusted as real | Watch for orphans and short traces regularly |
One trace is a single example. A slow trace may be unusual, and a fast one may be lucky. When you find something interesting, look at several similar traces and at metrics before deciding what is normal.
A gentle start plan
In the first week, install the collector agents and the backend, and turn on automatic instrumentation for two or three services on your main customer journey. In the second week, fix any broken context, add trace IDs to logs, and build a first dashboard link. In the third week, add a few manual spans, choose a sampling plan, and add redaction rules. Then review with the team and grow from there.
16Monitoring the Tracing Pipeline and Troubleshooting
A tracing system also needs watching. If it fails quietly, you will only find out when you need it most.
Numbers to watch
Collectors and backends report their own metrics. A few tell you most of what you need. Watch how many spans are received and how many are sent onward. Watch how many are dropped or refused, and why. Watch the size of the internal queues, the memory use of collectors, and how long storage takes to accept data. Watch also how much the query service is used and how slow it is.
A security camera system needs someone to check that the cameras are still recording. A camera that failed last Tuesday is worthless when the incident happens on Friday. The same goes for your tracing pipeline.
Alerts worth having
Warn when the number of spans received drops suddenly, which often means a service lost its instrumentation or a collector is down. Warn when drops or refusals rise, when queues stay nearly full, and when collector memory approaches its limit. Warn also when the storage backend is failing to write. These alerts should go through your normal alert system, not through the tracing system itself.
Troubleshooting the usual suspects
| Symptom | Likely cause | Where to look |
|---|---|---|
| No traces from one service | Instrumentation not attached, wrong collector address, or a blocking network rule | Service settings, network rules, collector received counts |
| Traces split in two | A hop that does not pass context, such as a queue or a gateway | The place where the trace ends and the next one begins |
| Child span starts before its parent | Clocks out of sync between nodes | Time synchronisation on the nodes |
| Traces missing errors | Only head sampling is used, or tail rules do not match | Sampling settings and gateway routing |
| Collector restarts with out-of-memory | Tail sampling holding too many spans, or too much traffic | Memory limits, wait times, and scaling of the gateway |
| Application slower after enabling tracing | Too many spans, or exporter blocking | Span counts, queue settings, and timeouts |
| Traces slow to appear | Long batching delays or overloaded backend | Batch settings and backend write times |
When spans go missing, check each stage in order: is the program creating them, is the agent receiving them, is the gateway forwarding them, and is the backend storing them? The collector counters at each stage will show where the numbers stop matching.
Scaling the pipeline
As traffic grows, add more agent and gateway replicas and more storage capacity. Keep the routing by trace ID so that tail sampling still works. Lower the cost by sampling more, shortening retention, or removing spans and attributes that nobody uses. Review the top contributors to volume every few months, since one busy service can account for most of the data.
17Real-World Examples
Three short stories show the ideas at work. The details are typical rather than taken from any one company.
Example 1: The mysterious slow checkout
Customers complain that checkout is sometimes very slow. Metrics show that average time is fine, but the slowest one percent is terrible. A team opens a few slow traces and sees the same pattern: a call to the tax service takes six seconds, and inside the tax service, a span called “load rules” is the slow part. The tax service was loading a big rules file from a remote store on every request for a certain region. They add a cache, the slow requests vanish, and the trace comparison after the change proves it.
Example 2: A hidden dependency
A team plans to retire an old reporting service. The wall diagram shows nobody uses it. The service map built from traces shows that the mobile app’s login flow quietly calls it once per day to fetch a banner message. Removing it would have broken logins for some users. Because traces show the real calls, the team plans a safe replacement before shutting it down.
Example 3: Retries that hide a failure
Error metrics look calm, yet customers report occasional delays. A tail sampling rule keeps traces with more than one attempt on a call. Those traces show a notification service failing about one call in five, and the caller retrying up to three times before success. Users never see errors, but every retry adds a second. The team fixes the notification service, and the delay complaints stop.
These stories are like a detective using a security tape instead of guessing. Metrics say a crime happened somewhere in town. The trace is the tape of one visit to the shop, showing exactly who went where and how long they waited.
What the stories share
In each story, the answer came from looking at real requests instead of averages and assumptions. Teams started with a real complaint, opened a small number of representative traces, found a single surprising step, and confirmed the fix by looking at traces again afterwards.
18Frequently Asked Questions
Short answers to questions that beginners often ask.
If you have only a few services, maybe not yet. As the number of services grows, tracing becomes the fastest way to see the path and timing of a single request across them. It complements, rather than replaces, logs and metrics.
A trace is the whole journey of one request. A span is one step within that journey. A trace is made of many spans arranged in a tree.
OpenTelemetry is an open standard and set of tools for creating and collecting traces, metrics, and logs. You do not have to use it, but it is the widely supported choice and keeps you free from being tied to one vendor.
A little. Creating spans and sending them takes some processor time and memory. With sensible sampling, batching, and limits, the cost is usually small, but you should measure it on your own workloads.
Almost always because context was not passed at some hop. Common causes are message queues, background threads, gateways that remove headers, services with no tracing, and mixed header formats.
Head sampling decides at the start of the request, cheaply, before it is known how it will turn out. Tail sampling decides after the whole trace is collected, so it can keep errors and slow traces, but it needs more memory and careful routing.
A DaemonSet, one per node, is simpler and uses fewer resources for most teams. A sidecar gives stronger separation per application and finer control, but costs more resources across many pods. Many teams start with a DaemonSet.
Partly. A mesh can create spans for network calls, giving a rough map. But it cannot see inside your programs, and your programs must still copy the trace headers from incoming to outgoing calls for the traces to connect.
Days to a couple of weeks is common, since most investigations happen soon after an event. Keep error and slow traces a little longer, and copy important ones into incident reports.
It can be, with care. Avoid personal data in attributes, redact risky values in the collector, encrypt connections, control access, and set short retention. Check the rules that apply in your country and industry.
An exemplar is a link attached to a metric value that points to an example trace. It lets you click from a spike on a chart straight to a real request that contributed to it.
Pick one important customer journey, turn on automatic instrumentation for the services on it, send data through a collector to a simple backend, and open a few traces with your team. Fix broken context, add trace IDs to logs, and grow from there.
19Summary and Key Takeaways
A last look at the whole picture, in plain words.
Modern applications are built from many small services, and a single customer click may pass through a dozen of them. When something is slow or broken, metrics can tell you that there is a problem, and logs can tell you what a single program said, but only a trace shows the whole path of one request, step by step, with times.
A trace is a tree of spans. Each span is one piece of work, with a start, a duration, and helpful details. The trace ID and the parent link tie them together, and context propagation carries these identifiers from one program to the next. Lose the context, and the story breaks. Use a shared standard, and everything connects.
In Kubernetes, programs are instrumented with OpenTelemetry, automatically or by hand, and send spans to nearby agent collectors. A central gateway enriches, samples, and cleans the data, and forwards it to a backend where people can search and read it. Sampling keeps costs under control while preserving errors and slow requests. Linking traces to logs and metrics turns a spike on a chart into a cause in a few clicks. Finally, treat the data with care, and watch the pipeline itself, so it is ready when you need it.
Key Takeaways
- A trace is one request’s whole journey — it is a tree of spans, each with a name, times, and details, joined by a shared trace ID and parent links.
- Context propagation is the heart of tracing — the trace ID must travel with every call, through queues, threads, and gateways, using one standard header format.
- OpenTelemetry gives you freedom — standard instrumentation and a Collector let you change backends without changing applications, and the operator can automate setup in Kubernetes.
- Use agents and a gateway — agents receive and enrich near the applications, and the gateway samples, redacts, and forwards to storage.
- Sample to keep what matters — keep every error and slow trace, keep a small share of the rest, and never use trace counts as request counts.
- Connect the three signals — put trace IDs in logs and exemplars on metrics, so you can move from a spike to a trace to an exact error quickly.
- Protect and monitor the pipeline — redact private data, encrypt and restrict access, keep retention short, and alert when spans stop flowing.