Docs · Glidepath
OTEL Tracing & CDEvents Flow Architecture
Overview
This document illustrates how OpenTelemetry spans and CloudEvents (CDEvents) are created, threaded through Tekton PipelineRuns, and used to correlate distributed traces across a complete CI/CD flow (build → test → deploy → release).
Key Concepts
W3C Traceparent Format
00-trace-id-span-id-01
↓ ↓ ↓
| | +-- trace flags (01 = sampled)
| +---------- span-id (16 hex chars = 64 bits)
+------------------- trace-id (32 hex chars = 128 bits)
Two Distinct but Complementary Correlators
| Field | Purpose | Owner | Lifecycle |
|---|---|---|---|
| traceparent | W3C distributed trace context; stitches OTEL spans across all PipelineRuns into one Tempo trace | OTEL | Flows unchanged through entire build→test→deploy→release sequence |
| chainId | CDEvents’ own causal-sequence identifier; documents event ordering | CDEvents spec | Also flows unchanged through sequence; used by Trigger templates |
Both are generated at flow-start (build) and threaded through all downstream stages.
Stage 1: BUILD Pipeline
1.1 Flow Initialization (start-flow-root-span task)
# otel_flow_root_begin generates:
traceparent="00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01"
flow_start_time="2025-08-06T14:32:15.123456789Z"
chain_id="550e8400-e29b-41d4-a716-446655440000"
# These are emitted as Task results and passed as params to all downstream stages
1.2 Build Stage Span Creation
# start-stage-span task calls otel_stage_span_begin:
stage_span_id="f2e1d3c5a7b9e1f3" # Fresh 16-char hex ID
stage_start_time="2025-08-06T14:32:20.234567890Z"
# Result structure:
span-id: f2e1d3c5a7b9e1f3
start-time: 2025-08-06T14:32:20.234567890Z
1.3 Building & Publishing Image
Task build-image.yaml runs kaniko and emits a span:
# In emit-image-ref-and-span step:
otel_task_span_send "build-image" \
"00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01" \
"f2e1d3c5a7b9e1f3" \
"2025-08-06T14:32:20.234567890Z" \
"2025-08-06T14:32:45.567890123Z" \
"Succeeded"
OTEL Span Emitted to Collector:
{
"traceId": "2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c",
"spanId": "<freshly-minted-by-openssl-rand>",
"parentSpanId": "a7e3f2d8c1b5e9a2",
"name": "build-image",
"startTime": "2025-08-06T14:32:20.234567890Z",
"endTime": "2025-08-06T14:32:45.567890123Z",
"status": {
"code": "OK"
},
"attributes": {
"service.name": "platform-cicd"
}
}
1.4 Build Pipeline Completes - Emit CDEvent
The finally block calls send-cdevent task with:
PLATFORM_CHAIN_ID="550e8400-e29b-41d4-a716-446655440000"
PLATFORM_TRACEPARENT="00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01"
PLATFORM_FLOW_START_TIME="2025-08-06T14:32:15.123456789Z"
TEKTON_PIPELINE_RUN="build-nodejs-demo-app-abc123"
NAMESPACE="app-myapp-cicd"
CDEvent Payload Sent to Broker:
{
"context": {
"version": "0.4.1",
"id": "a1b2c3d4e5f6g7h8",
"source": "/platform-cicd/app-myapp-cicd/build-nodejs-demo-app-abc123",
"type": "dev.cdevents.artifact.published.0.3.0",
"timestamp": "2025-08-06T14:33:12.123456Z",
"chainId": "550e8400-e29b-41d4-a716-446655440000"
},
"subject": {
"id": "build-nodejs-demo-app-abc123",
"source": "/platform-cicd/app-myapp-cicd/build-nodejs-demo-app-abc123",
"type": "artifact",
"content": {
"name": "myapp",
"version": "abc123",
"uri": "ghcr.io/myorg/myapp@sha256:abc123def456"
}
},
"customData": {
"platform": {
"traceparent": "00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01",
"flow_start_time": "2025-08-06T14:32:15.123456789Z"
}
},
"customDataContentType": "application/json"
}
1.5 End Flow-Root Span (Build’s Finally Block)
# end-flow-root-span task calls otel_span_send with empty span_id:
otel_span_send \
"build" \
"00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01" \
"" \ # Empty = use flow root's own span ID as the span ID
"2025-08-06T14:32:15.123456789Z" \
"2025-08-06T14:33:12.456789012Z" \
"Succeeded"
OTEL Flow-Root Span:
{
"traceId": "2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c",
"spanId": "a7e3f2d8c1b5e9a2",
"parentSpanId": null,
"name": "build",
"startTime": "2025-08-06T14:32:15.123456789Z",
"endTime": "2025-08-06T14:33:12.456789012Z",
"status": { "code": "OK" },
"attributes": {
"service.name": "platform-cicd",
"platform.flow": "build"
}
}
Stage 2: TEST Pipeline
2.1 Receiving the Event
Tekton Triggers receives the CDEvent from broker and creates a new PipelineRun:
# TriggerTemplate uses:
chain-id="$(body.context.chainId)" # ← Same as build!
traceparent="$(body.customData.platform.traceparent)"
flow-start-time="$(body.customData.platform.flow_start_time)"
New PipelineRun created:
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: test-a1b2c3d4e5f6g7h8 # Named from CDEvent ID
namespace: app-myapp-cicd
spec:
pipelineRef:
resolver: cluster
params:
- name: name
value: test
params:
- name: flow-traceparent
value: "00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01"
- name: chain-id
value: "550e8400-e29b-41d4-a716-446655440000"
- name: flow-start-time
value: "2025-08-06T14:32:15.123456789Z"
2.2 Test Stage Span
# start-stage-span generates fresh test stage span (same trace ID, different span ID):
trace_id="2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c" # ← SAME as build!
stage_span_id="e9d2f7a1c4b8e3f6" # ← DIFFERENT sibling
stage_start_time="2025-08-06T14:33:45.789012345Z"
# Relationship:
# Flow-root span (a7e3f2d8c1b5e9a2) spans entire flow
# ├── Build stage span (f2e1d3c5a7b9e1f3) [parent: flow-root]
# └── Test stage span (e9d2f7a1c4b8e3f6) [parent: flow-root] ← NEW
2.3 Test Execution Spans
# Multiple nested Task spans under test stage:
# Task: clone-repo
otel_task_span_send "clone-repo" \
"00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01" \
"e9d2f7a1c4b8e3f6" \
"2025-08-06T14:33:45.789012345Z" \
"2025-08-06T14:33:48.901234567Z" \
"Succeeded"
# Task: run-tests
otel_task_span_send "run-tests" \
"00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01" \
"e9d2f7a1c4b8e3f6" \
"2025-08-06T14:33:50.123456789Z" \
"2025-08-06T14:34:02.345678901Z" \
"Succeeded"
2.4 Test Pipeline Finally Block
# end-stage-span sends test stage span:
otel_span_send \
"stage:test" \
"00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01" \
"e9d2f7a1c4b8e3f6" \
"2025-08-06T14:33:45.789012345Z" \
"2025-08-06T14:34:05.567890123Z" \
"Succeeded"
# attrs includes chain_id:
# "platform.chain_id=550e8400-e29b-41d4-a716-446655440000,platform.stage=test"
2.5 Test Success CDEvent
{
"context": {
"version": "0.4.1",
"id": "b2c3d4e5f6g7h8i9",
"source": "/platform-cicd/app-myapp-cicd/test-a1b2c3d4e5f6g7h8",
"type": "dev.cdevents.testcaserun.finished.0.3.0",
"timestamp": "2025-08-06T14:34:10.123456Z",
"chainId": "550e8400-e29b-41d4-a716-446655440000"
},
"subject": {
"id": "test-a1b2c3d4e5f6g7h8",
"source": "/platform-cicd/app-myapp-cicd/test-a1b2c3d4e5f6g7h8",
"type": "testCaseRun",
"content": {
"outcome": "success",
"testSuite": "nodejs-demo-app",
"testName": "unit-tests"
}
},
"customData": {
"platform": {
"traceparent": "00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01",
"flow_start_time": "2025-08-06T14:32:15.123456789Z"
}
}
}
Note: Same traceparent and chainId as build! This is what stitches the distributed trace together.
Stage 3: DEPLOY Pipeline
3.1 Trigger Receives Test CDEvent
# Extract from CDEvent context:
chain-id="550e8400-e29b-41d4-a716-446655440000"
traceparent="00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01"
flow-start-time="2025-08-06T14:32:15.123456789Z"
# PipelineRun created:
metadata:
name: deploy-b2c3d4e5f6g7h8i9 # ← Named from test CDEvent ID
3.2 Deploy Stage Spans
# Deploy stage span (sibling to build and test):
stage_span_id="c3d5e8f2a6b9d1e4" # ← DIFFERENT, but same trace
stage_start_time="2025-08-06T14:34:30.123456789Z"
# Span hierarchy in Tempo:
Trace: 2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c
├─ Flow-root span: a7e3f2d8c1b5e9a2 (14:32:15 → flow-end)
│ ├─ Build stage: f2e1d3c5a7b9e1f3 (14:32:20 → 14:33:12)
│ │ └─ build-image task: <random> (14:32:20 → 14:32:45)
│ ├─ Test stage: e9d2f7a1c4b8e3f6 (14:33:45 → 14:34:05)
│ │ ├─ clone-repo task: <random> (14:33:45 → 14:33:48)
│ │ └─ run-tests task: <random> (14:33:50 → 14:34:02)
│ └─ Deploy stage: c3d5e8f2a6b9d1e4 (14:34:30 → 14:35:10) ← NOW
│ └─ deploy task: <random> (14:34:30 → 14:35:05)
3.3 Deploy Success CDEvent
{
"context": {
"version": "0.4.1",
"id": "c3d4e5f6g7h8i9j0",
"source": "/platform-cicd/app-myapp-cicd/deploy-b2c3d4e5f6g7h8i9",
"type": "dev.cdevents.service.deployed.0.3.0",
"timestamp": "2025-08-06T14:35:15.123456Z",
"chainId": "550e8400-e29b-41d4-a716-446655440000"
},
"subject": {
"id": "deploy-b2c3d4e5f6g7h8i9",
"source": "/platform-cicd/app-myapp-cicd/deploy-b2c3d4e5f6g7h8i9",
"type": "service",
"content": {
"environment": "dev",
"service": "myapp",
"deployment": "myapp-deployment"
}
},
"customData": {
"platform": {
"traceparent": "00-2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c-a7e3f2d8c1b5e9a2-01",
"flow_start_time": "2025-08-06T14:32:15.123456789Z"
}
}
}
Stage 4: RELEASE Pipeline (Optional Phase 2+)
Similar structure, but:
- Promotes from dev → staging → prod
- May have additional governance/approval gates
- Sends final promotion CDEvent
Complete Trace View in Tempo
When you query Tempo for traceparent = 2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c:
Trace Duration: ~2m 55s (14:32:15 → 14:35:10)
Timeline:
14:32:15 ◆─────────────────────────────── build (1m 57s) ◆
│ 14:32:20 ◆──────────◆ build-image (25s)
│
└─────────────────────────────── test (20s) ◆
14:33:45 ◆──●──────●◆ clone+run-tests
└─────────────────────────────── deploy (40s) ◆
14:34:30 ◆──────────◆ deploy task
Platform Events Captured:
✓ flow-root span → platform-cicd build orchestration
✓ stage spans + attributes → stage timing + chain_id for audit
✓ task spans → individual work unit timing
✓ CDEvent chainId → cross-PipelineRun causality tracking
Data Threading Summary
BUILD PipelineRun (triggered by git commit)
│
├─ otel_flow_root_begin()
│ └─ generates: traceparent, flow_start_time
│
├─ otel_stage_span_begin()
│ └─ generates: build stage span_id + start_time
│
├─ Task steps
│ └─ otel_task_span_send() → OTEL Collector
│
└─ finally: send-cdevent (artifact.published)
├─ includes: traceparent, chain_id, flow_start_time
└─ POST to broker → triggers TEST
TEST PipelineRun (triggered by CDEvent from build)
│
├─ Receives from CDEvent:
│ ├─ traceparent (unchanged)
│ ├─ chain_id (unchanged)
│ └─ flow_start_time (unchanged)
│
├─ otel_stage_span_begin() → new span, SAME trace_id
│
├─ Task spans (nested under test stage)
│ └─ otel_task_span_send() → OTEL Collector
│
└─ finally: send-cdevent (testcaserun.finished)
├─ includes: SAME traceparent, SAME chain_id
└─ POST to broker → triggers DEPLOY
DEPLOY PipelineRun (triggered by CDEvent from test)
│
├─ Receives from CDEvent:
│ ├─ traceparent (unchanged)
│ ├─ chain_id (unchanged)
│ └─ flow_start_time (unchanged)
│
├─ otel_stage_span_begin() → new span, SAME trace_id
│
├─ Deploy task
│ └─ otel_task_span_send() → OTEL Collector
│
└─ finally: send-cdevent (service.deployed)
└─ includes: SAME traceparent, SAME chain_id
Key Invariants
-
Trace ID is Constant
- Generated once at flow start (build)
- Embedded in traceparent
- Never changes across all PipelineRuns
-
Stage Spans are Siblings
- All parent to flow-root span
- Never nested under each other
- Each gets unique span ID
-
Chain ID is Constant
- Generated once at flow start
- Carried in CDEvent context
- Used by Trigger templates to name downstream PipelineRuns
-
Flow Start Time is Constant
- Set at build start
- Carried through all CDEvents
- Useful for calculating total flow duration
-
Idempotent Event IDs
- CDEvent ID = hash(PipelineRun:event-type)[0:20]
- Same step retried = same event ID
- Broker deduplicates based on this
Tempo Query Examples
# All spans in this flow
{traceId="2f8d7a4c3b1e9f6d2a8c5e7b3f1d9a4c"}
# All spans with platform.chain_id attribute
{resource.attributes.platform.chain_id="550e8400-e29b-41d4-a716-446655440000"}
# Find all stage spans
{spanName=~"stage:.*"}
# Find all task spans
{spanName=~"build-image|clone-repo|run-tests|deploy"}
# Find errors
{status.code="ERROR"}
Files Involved
| File | Purpose |
|---|---|
| otel.sh | Span minting & sending functions |
| cdevents.sh | Event payload construction & broker delivery |
| build.yaml | Build Pipeline (flow start) |
| start-flow-root-span.yaml | Initializes traceparent & chain_id |
| start-stage-span.yaml | Stage span initialization |
| end-stage-span.yaml | Stage span completion to OTEL |
| end-flow-root-span.yaml | Flow-root span completion |
| send-cdevent.yaml | CDEvent delivery to broker |
| build-image.yaml | Example of task span emission |
| deploy.yaml | Deploy Pipeline (receives CDEvent) |