Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🌱 Add distributed tracing framework #1211

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 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
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module sigs.k8s.io/controller-runtime
go 1.15

require (
github.com/HdrHistogram/hdrhistogram-go v0.9.0 // indirect
github.com/codahale/hdrhistogram v0.9.0 // indirect
github.com/evanphx/json-patch v4.9.0+incompatible
github.com/fsnotify/fsnotify v1.4.9
github.com/go-logr/logr v0.2.1
Expand All @@ -13,8 +15,12 @@ require (
github.com/imdario/mergo v0.3.10 // indirect
github.com/onsi/ginkgo v1.14.1
github.com/onsi/gomega v1.10.2
github.com/opentracing/opentracing-go v1.2.0
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/client_model v0.2.0
github.com/stretchr/testify v1.5.1
github.com/uber/jaeger-client-go v2.25.0+incompatible
github.com/uber/jaeger-lib v2.4.0+incompatible // indirect
go.uber.org/goleak v1.1.10
go.uber.org/zap v1.15.0
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/HdrHistogram/hdrhistogram-go v0.9.0 h1:dpujRju0R4M/QZzcnR1LH1qm+TVG3UzkWdp5tH1WMcg=
github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
Expand Down Expand Up @@ -60,6 +62,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.9.0 h1:9GjrtRI+mLEFPtTfR/AZhcxp+Ii8NZYWq5104FbZQY0=
github.com/codahale/hdrhistogram v0.9.0/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
Expand Down Expand Up @@ -291,6 +295,8 @@ github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
Expand Down Expand Up @@ -347,6 +353,7 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
Expand All @@ -357,6 +364,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ=
github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
Expand Down
58 changes: 58 additions & 0 deletions pkg/tracing/annotation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package tracing

import (
"context"

ot "github.com/opentracing/opentracing-go"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)

// AddTraceAnnotationToUnstructured adds an annotation encoding span's context to all objects
// Objects are modified in-place.
func AddTraceAnnotationToUnstructured(span ot.Span, objs []unstructured.Unstructured) error {
spanContext, err := GenerateEmbeddableSpanContext(span)
if err != nil {
return err
}

for _, o := range objs {
a := o.GetAnnotations()
if a == nil {
a = make(map[string]string)
}
a[TraceAnnotationKey] = spanContext
o.SetAnnotations(a)
}

return nil
}

// AddTraceAnnotationToObject - if there is a span for the current context, and
// the object doesn't already have one set, adds it as an annotation
func AddTraceAnnotationToObject(ctx context.Context, obj runtime.Object) error {
sp := ot.SpanFromContext(ctx)
if sp == nil {
return nil
}
spanContext, err := GenerateEmbeddableSpanContext(sp)
if err != nil {
return err
}
m, err := meta.Accessor(obj)
if err != nil {
return err
}
annotations := m.GetAnnotations()
if annotations == nil {
annotations = map[string]string{TraceAnnotationKey: spanContext}
} else {
// Don't overwrite if the caller already set one.
if _, exists := annotations[TraceAnnotationKey]; !exists {
annotations[TraceAnnotationKey] = spanContext
}
}
m.SetAnnotations(annotations)
return nil
}
39 changes: 39 additions & 0 deletions pkg/tracing/jaeger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package tracing

import (
"fmt"
"io"

ot "github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
)

// SetupJaeger sets up Jaeger with some defaults and config taken from the environment
func SetupJaeger(serviceName string) (io.Closer, error) {
jcfg := &jaegercfg.Configuration{
ServiceName: serviceName,
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
// This name chosen so you can create a Service in your cluster
// pointing to wherever Jager is running; override by env JAEGER_AGENT_HOST
LocalAgentHostPort: "jaeger-agent.default:6831",
},
}
jcfg, err := jcfg.FromEnv()
if err != nil {
return nil, fmt.Errorf("failed read config from environment: %w", err)
}

// Initialize tracer with a logger and a metrics factory
tracer, closer, err := jcfg.NewTracer()
if err != nil {
return nil, err
}
// Set the singleton opentracing.Tracer with the Jaeger tracer.
ot.SetGlobalTracer(tracer)
return closer, nil
}
47 changes: 47 additions & 0 deletions pkg/tracing/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package tracing

import (
"github.com/go-logr/logr"
ot "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)

type tracingLogger struct {
logr.Logger
ot.Span
}

func (t tracingLogger) Enabled() bool {
return t.Logger.Enabled()
}

func (t tracingLogger) Info(msg string, keysAndValues ...interface{}) {
t.Logger.Info(msg, keysAndValues...)
t.Span.LogKV(append([]interface{}{"info", msg}, keysAndValues...)...)
}

func (t tracingLogger) Error(err error, msg string, keysAndValues ...interface{}) {
t.Logger.Error(err, msg, keysAndValues...)
t.Span.LogKV(append([]interface{}{"error", err, "message", msg}, keysAndValues...)...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way to avoid needing an extra allocation here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, is there a semantic difference between WithTags and the key-value pairs specified here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That LogKV() call is through an interface so the memory will escape; it's unlikely to be possible to avoid an allocation. This particular line is only hit under error conditions so probably not performance-sensitive, but there are similar ones.

Tags apply to the whole span so it wouldn't be right to put details from one error as a tag, however the next line sets an error tag on the span so you can pick it out as one that had at least one error.

ext.Error.Set(t.Span, true)
}

func (t tracingLogger) V(level int) logr.Logger {
return tracingLogger{Logger: t.Logger.V(level), Span: t.Span}
}

func (t tracingLogger) WithValues(keysAndValues ...interface{}) logr.Logger {
for i := 0; i+1 < len(keysAndValues); i += 2 {
key, ok := keysAndValues[i].(string)
if !ok {
continue
}
t.Span.SetTag(key, keysAndValues[i+1])
}
return tracingLogger{Logger: t.Logger.WithValues(keysAndValues...), Span: t.Span}
}

func (t tracingLogger) WithName(name string) logr.Logger {
t.Span.SetTag("name", name)
return tracingLogger{Logger: t.Logger.WithName(name), Span: t.Span}
}
128 changes: 128 additions & 0 deletions pkg/tracing/runtime_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package tracing

import (
"context"

ot "github.com/opentracing/opentracing-go"
otlog "github.com/opentracing/opentracing-go/log"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
)

// WrapRuntimeClient wraps a NewRuntimeClient function with one that does tracing
func WrapRuntimeClient(upstreamNew manager.NewClientFunc) manager.NewClientFunc {
return func(cache cache.Cache, config *rest.Config, options client.Options) (client.Client, error) {
delegatingClient, err := upstreamNew(cache, config, options)
if err != nil {
return nil, err
}
return &tracingClient{Client: delegatingClient, scheme: options.Scheme}, nil
}
}

func objectFields(obj runtime.Object) (fields []otlog.Field) {
if gvk := obj.GetObjectKind().GroupVersionKind(); !gvk.Empty() {
fields = append(fields, otlog.String("objectKind", gvk.String()))
}
if m, err := meta.Accessor(obj); err == nil {
fields = append(fields, otlog.String("objectKey", m.GetNamespace()+"/"+m.GetName()))
}
return
}

func logStart(ctx context.Context, op string, fields ...otlog.Field) ot.Span {
sp := ot.SpanFromContext(ctx)
if sp != nil {
sp.LogFields(append([]otlog.Field{otlog.Event(op)}, fields...)...)
}
return sp
}

func logError(sp ot.Span, err error) error {
if sp != nil && err != nil {
sp.LogFields(otlog.Error(err))
}
return err
}

// wrapper for Client which emits spans on each call
type tracingClient struct {
client.Client
scheme *runtime.Scheme
}

func (c *tracingClient) blankObjectFields(obj runtime.Object) (fields []otlog.Field) {
if c.scheme != nil {
gvks, _, _ := c.scheme.ObjectKinds(obj)
for _, gvk := range gvks {
fields = append(fields, otlog.String("objectKind", gvk.String()))
}
}
return
}

func (c *tracingClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error {
sp := logStart(ctx, "k8s.Get", append([]otlog.Field{otlog.String("objectKey", key.String())}, c.blankObjectFields(obj)...)...)
return logError(sp, c.Client.Get(ctx, key, obj))
}

func (c *tracingClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
sp := logStart(ctx, "k8s.List", c.blankObjectFields(list)...)
return logError(sp, c.Client.List(ctx, list, opts...))
}

func (c *tracingClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
AddTraceAnnotationToObject(ctx, obj)
sp := logStart(ctx, "k8s.Create", c.blankObjectFields(obj)...)
return logError(sp, c.Client.Create(ctx, obj, opts...))
}

func (c *tracingClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
sp := logStart(ctx, "k8s.Delete", objectFields(obj)...)
return logError(sp, c.Client.Delete(ctx, obj, opts...))
}

func (c *tracingClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
sp := logStart(ctx, "k8s.Update", objectFields(obj)...)
return logError(sp, c.Client.Update(ctx, obj, opts...))
}

func (c *tracingClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
fields := objectFields(obj)
if data, err := patch.Data(obj); err == nil {
fields = append(fields, otlog.String("patch", string(data)))
}
sp := logStart(ctx, "k8s.Patch", fields...)
return logError(sp, c.Client.Patch(ctx, obj, patch, opts...))
}

func (c *tracingClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
sp := logStart(ctx, "k8s.DeleteAllOf", c.blankObjectFields(obj)...)
return logError(sp, c.Client.DeleteAllOf(ctx, obj, opts...))
}

func (c *tracingClient) Status() client.StatusWriter {
return &tracingStatusWriter{StatusWriter: c.Client.Status()}
}

type tracingStatusWriter struct {
client.StatusWriter
}

func (s *tracingStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
sp := logStart(ctx, "k8s.Status.Update", objectFields(obj)...)
return logError(sp, s.StatusWriter.Update(ctx, obj, opts...))
}

func (s *tracingStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
fields := objectFields(obj)
if data, err := patch.Data(obj); err == nil {
fields = append(fields, otlog.String("patch", string(data)))
}
sp := logStart(ctx, "k8s.Status.Patch", fields...)
return logError(sp, s.StatusWriter.Patch(ctx, obj, patch, opts...))
}
Loading