Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pkg/imageverifier/bindir/bindir.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

"github.com/containerd/containerd/v2/internal/tomlext"
"github.com/containerd/containerd/v2/pkg/imageverifier"
"github.com/containerd/containerd/v2/pkg/tracing"
"github.com/containerd/log"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
Expand Down Expand Up @@ -123,6 +124,14 @@ func (v *ImageVerifier) runVerifier(ctx context.Context, bin string, imageName s

cmd := exec.CommandContext(ctx, binPath, args...)

// Attach OTEL propagators trace context env var to the child process
if traceContext, err := tracing.GetPropagatorsTraceContext(ctx); err != nil {
log.G(ctx).Warn("could not marshall propagators trace context", err)
} else {
traceContextEnv := fmt.Sprintf("OTEL_PROPAGATORS_TRACE_CONTEXT=%s", traceContext)
cmd.Env = append(os.Environ(), traceContextEnv)
}

// We construct our own pipes instead of using the default StdinPipe,
// StoutPipe, and StderrPipe in order to set timeouts on reads and writes.
stdinRead, stdinWrite, err := os.Pipe()
Expand Down
35 changes: 33 additions & 2 deletions pkg/tracing/plugin/otlp.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"io"
"os"
"strconv"
"strings"
"time"

"github.com/containerd/containerd/v2/pkg/deprecation"
Expand Down Expand Up @@ -52,8 +53,10 @@ const (
otlpProtocolEnv = "OTEL_EXPORTER_OTLP_PROTOCOL"
otlpTracesProtocolEnv = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"

otelTracesExporterEnv = "OTEL_TRACES_EXPORTER"
otelServiceNameEnv = "OTEL_SERVICE_NAME"
otelTracesExporterEnv = "OTEL_TRACES_EXPORTER"
otelServiceNameEnv = "OTEL_SERVICE_NAME"
otelTracesSamplerEnv = "OTEL_TRACES_SAMPLER"
otelTracesSamplerArgEnv = "OTEL_TRACES_SAMPLER_ARG"
)

func init() {
Expand Down Expand Up @@ -193,6 +196,15 @@ func newTracer(ctx context.Context, procs []trace.SpanProcessor) (io.Closer, err
for _, proc := range procs {
opts = append(opts, trace.WithSpanProcessor(proc))
}

// Configure custom NameBased sampler if specified in the env
if nameSampler := nameSamplerFromEnv(); nameSampler != nil {
opts = append(opts, trace.WithSampler(nameSampler))
// Unset the env vars so that otel sdk does not attempt to configure the sampler automatically
os.Unsetenv(otelTracesSamplerEnv)
os.Unsetenv(otelTracesSamplerArgEnv)
}

provider := trace.NewTracerProvider(opts...)
otel.SetTracerProvider(provider)

Expand All @@ -202,6 +214,25 @@ func newTracer(ctx context.Context, procs []trace.SpanProcessor) (io.Closer, err

}

func nameSamplerFromEnv() trace.Sampler {
sampler, ok := os.LookupEnv(otelTracesSamplerEnv)
if !ok {
return nil
}

sampler = strings.ToLower(strings.TrimSpace(sampler))
allowedNames := strings.Split(strings.TrimSpace(os.Getenv(otelTracesSamplerArgEnv)), ",")

switch sampler {
case samplerNameBased:
return NameBased(allowedNames)
case samplerParentBasedName:
return trace.ParentBased(NameBased(allowedNames))
default:
return nil
}
}

func warnTraceConfig(ic *plugin.InitContext) error {
if ic.Config == nil {
return nil
Expand Down
51 changes: 51 additions & 0 deletions pkg/tracing/plugin/sampler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package plugin

import (
"fmt"

sdkTrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)

const (
samplerNameBased = "namebased"
samplerParentBasedName = "parentbased_name"
)

type NameSampler struct {
// allow is a set of names that should be sampled.
// Uses a map of empty structs for O(1) lookups and no memory overhead.
allow map[string]struct{}
}

// NameBased returns a Sampler that samples every span having a certain name.
// It should be used in conjunction with the ParentBased sampler so that the child spans are also sampled.
func NameBased(allowedNames []string) NameSampler {
allowedNamesMap := make(map[string]struct{}, len(allowedNames))
for _, name := range allowedNames {
allowedNamesMap[name] = struct{}{}
}
return NameSampler{
allow: allowedNamesMap,
}
}

func (ns NameSampler) ShouldSample(parameters sdkTrace.SamplingParameters) sdkTrace.SamplingResult {
psc := trace.SpanContextFromContext(parameters.ParentContext)

if _, ok := ns.allow[parameters.Name]; ok {
return sdkTrace.SamplingResult{
Decision: sdkTrace.RecordAndSample,
Tracestate: psc.TraceState(),
}
}

return sdkTrace.SamplingResult{
Decision: sdkTrace.Drop,
Tracestate: psc.TraceState(),
}
}

func (ns NameSampler) Description() string {
return fmt.Sprintf("NameBased:{%v}", ns.allow)
}
10 changes: 10 additions & 0 deletions pkg/tracing/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ package tracing

import (
"context"
"encoding/json"
"net/http"
"strings"

"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/trace"
)
Expand Down Expand Up @@ -130,3 +132,11 @@ func Attribute(k string, v any) attribute.KeyValue {
func HTTPStatusCodeAttributes(code int) []attribute.KeyValue {
return []attribute.KeyValue{semconv.HTTPStatusCodeKey.Int(code)}
}

// GetPropagatorsTraceContext returns the current propagators trace context as a JSON string
func GetPropagatorsTraceContext(ctx context.Context) ([]byte, error) {
propagator := propagation.TraceContext{}
carrier := propagation.MapCarrier{}
propagator.Inject(ctx, carrier)
return json.Marshal(carrier)
}