-
Notifications
You must be signed in to change notification settings - Fork 9.8k
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 initial Tracing with OpenTelemetry #12919
Conversation
@ptabor @dashpole @logicalhan @hexfusion please have a look, thank you! |
@lilic just to be sure zero cost if experimental-enable-tracing is false (which is default) right? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @lilic overall I think this is a nice feature to try and move forward into 3.5. apiserver[1] is working on this as well so the pairing will be an important part of the observability story for k8s. While the performance hit is unfortunate documenting it as such and gating under an experimental flag seems appropriate. My understanding is that there are continuing efforts to improve performance. +1
server/embed/config.go
Outdated
ExperimentalTracingAddress string `json:"experimental-tracing-address"` | ||
// ExperimentalTracingServiceName is the name of the service. | ||
// Can only be used if ExperimentalEnabledTracing is true. | ||
ExperimentalTracingServiceName string `json:"experimental-tracing-service-name"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it make sense to use etcd name and reduce flag footprint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the ability to specify the name allows you to collect the traces from all the running instances of etcd separately, so you can detect issues per running instance of etcd, this way you can in your UI select just the instance you had an issue with and troubleshoot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the ability to specify the name allows you to collect the traces from all the running instances of etcd separately, so you can detect issues per running instance of etcd, this way you can in your UI select just the instance you had an issue with and troubleshoot.
Can we have this as flag description or some format of doc :)
e.g.,
// ExperimentalTracingServiceName is the tracing service name for
// OpenTelemetry (if enabled) -- "etcd" is the default service name.
// When shared, all telemetry data are aggregated under the same namespace.
// Use different names in order to collect data per each node.
(if I understand your comment correctly)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a good idea, will add it there!
The plan was to also add some docs if PR is merged, maybe similar to where metrics docs are right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the ability to specify the name allows you to collect the traces from all the running instances of etcd separately, so you can detect issues per running instance of etcd, this way you can in your UI select just the instance you had an issue with and troubleshoot.
If I understand what your saying this name should be unique per etcd? This is why I was wondering if using etcd member name[1] which is unique for the cluster would make sense maybe as a default.
[1]https://github.com/etcd-io/etcd/blob/master/server/config/config.go#L37
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I like that idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes should be unique per etcd process/member.
Missed this comment sorry, nice will use that as default! But I think we should still let users override it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, if the name is not passed it defaults to... default
. IMHO this makes the discoverability out of the box in the tracing UI a bit harder, when the service is called default
. Should we stick to Name
as default or constant"etcd"
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about if the name is default
use etcd
otherwise use name and allow override with flag?
Exactly, we only ever enable OTel tracing if that flag is passed yes. You can see this more clearly here https://share.polarsignals.com/442c0d2/ I ran some load when the |
5f89e13
to
7853fb6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great overall. Requesting some minor changes. Thanks!
server/embed/config.go
Outdated
ExperimentalTracingAddress string `json:"experimental-tracing-address"` | ||
// ExperimentalTracingServiceName is the name of the service. | ||
// Can only be used if ExperimentalEnabledTracing is true. | ||
ExperimentalTracingServiceName string `json:"experimental-tracing-service-name"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the ability to specify the name allows you to collect the traces from all the running instances of etcd separately, so you can detect issues per running instance of etcd, this way you can in your UI select just the instance you had an issue with and troubleshoot.
Can we have this as flag description or some format of doc :)
e.g.,
// ExperimentalTracingServiceName is the tracing service name for
// OpenTelemetry (if enabled) -- "etcd" is the default service name.
// When shared, all telemetry data are aggregated under the same namespace.
// Use different names in order to collect data per each node.
(if I understand your comment correctly)
Codecov ReportAttention: Patch coverage is
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## master #12919 +/- ##
==========================================
- Coverage 73.12% 72.77% -0.36%
==========================================
Files 430 430
Lines 34185 34238 +53
==========================================
- Hits 24998 24915 -83
- Misses 7252 7376 +124
- Partials 1935 1947 +12
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
@lilic On second thought. tracing is not descriptive enough, given that we do already have tracing within etcd process. I think we should name it something like |
I think that is a good idea yes, for clarity! So we would have everything prefixed with |
I noticed the help text file (server/etcdmain/help.go) , is there a way to generate that file or is it manually edited? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm :)
We manually edit :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, thanks!
server/embed/etcd.go
Outdated
tracesdk.WithSyncer(exporter), | ||
tracesdk.WithResource(res), | ||
) | ||
otel.SetTracerProvider(tp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to call this out to reviewers less familiar with opentelemetry: This is setting a global variable, which is used by tracing libraries. In the kubernetes APIServer, we will likely avoid doing this by explicitly passing the TracerProvider to the libraries we want to do tracing. It is more code, but avoids global variables. Not saying you should do it one way or another, but wanted to point it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Agreed on globals! 💯 Right now we are not creating any indivual spans only using the gRPC so I don't see it being different, but I could be wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If, for example, a library you rely on added OpenTelemetry tracing, you would start getting traces from that library without changing anything on your end, since it will look up the global TracerProvider to see where to send traces. That may or may not be desirable. Also, if you have multiple different "services" in the same go binary, you can't customize the TracerProviders individually for each service while using the global TracerProvider. I think it is perfectly fine to start with using the global one, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Embed server is being used 'as a library' by our customers. Also in tests we create multiple instances of Embed server to mimic multiple etcd nodes. Recently we get rid of 'global loggers' (including grpc loggers) being overridden by each of the embed servers (leading to misleading zap-fields being reported).
I don't know how differently otl
can be configured for different libs, but having lib-specific tracer seems to be on the safer side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am all for removing globals, I felt the pain many times with Prometheus global registry (which I would love to remove from etcd one day as well, if folks agree).
From what I can tell we can pass it via https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc#WithTracerProvider, so will do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side note here: @dashpole I would love to see the default examples have all the knowledge you shared here, as all the various tutorials and examples out there for OTel use global registry and essentially do what I did initially. I fear this will cause some of the same issues as Prometheus has with the global registry (think etcd + apiserver). What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Embed server is being used 'as a library' by our customers. Also in tests we create multiple instances of Embed server to mimic multiple etcd nodes.
If the embedded server is used as a library it may be better to avoid use of the OTel SDK at all and only use the API. In that case the server should receive its TracerProvider and propagators using the API interfaces from the main
package that has instantiated them with the SDK. That would allow the consumer of the embedded server to decide whether to enable tracing, which exporters and propagators to use, even which SDK to use if alternate implementations of the SDK become available.
server/embed/etcd.go
Outdated
tracesdk.WithBatcher(exporter), | ||
tracesdk.WithResource(res), | ||
) | ||
otel.SetTracerProvider(tp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in addition to setting the global TracerProvider, you should also set the global propagators:
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
The TraceContext propagator will allow linking traces from the APIServer to etcd, and Baggage allows passing additional tags via context.
One thing you don't do here, but probably should is add a sampling policy. The default is to ParentBased(AlwaysSample) (always sample unless there is an incoming parent context that is explicitly not sampled), which is quite verbose. Something like sampling 1% of requests is probably more appropriate. But that can easily be done in a follow-up |
Yes agreed, this was a minimal PR and was also one of my questions in the PR description, if we can do follow-ups even in the patch releases that would include new configurations. |
The tests fail due to:
Should get fixed by:
|
Okay, this should be ready for another look:
Please have another look, thank you! TODO from my side:
|
) | ||
// As Tracing service Instance ID must be unique, it should | ||
// never use the empty default string value, so we only set it | ||
// if it's a non empty string. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so if Instance ID is empty string is that an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this field is optional in OTel I wouldn't return any errors just skip. We just don't add it, as that is our default value, but also as an empty string can never be unique and the ID must be unique. Happy to do another approach here well, suggestions welcome!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I think driving reasonable defaults from the current member would be a nice option. If the flag is not required folks will leave it blank and then get errors later on. as noted above etcd name or peerURL could be considered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could be a followup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
etcd name is not unique per etcd instance, at least not by default (default
:) ). But happy to do peerURL
.
There tend to be two approaches from what I have seen, either nothing by default or in some places, hostname is what some projects use the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
|
037421f
to
662cb11
Compare
6f939e0
to
3cdd242
Compare
Thank you all, some new changes: I ran the above script, added the changelog entries and the help text for the flags. Please have another look. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm. Let's follow up in separate PRs for other changes mentioned here.
This PR adds initial tracing using OpenTelemetry, as per discussion on the #12460. Let me know if we want this merged in 3.5, I believe it still adds value and observability with some tradeoffs to resource usage, that I detailed below.
This introduces:
etcd --experimental-enable-tracing --experimental-tracing-address="0.0.0.0:55680" --experimental-tracing-service-name="etcd-new"
experimental-enable-tracing
is enabled does tracing of the gRPC start collecting.And the individual gRPC request span:
Performance/resource usage
I ran an initial small load with tracing on and found that overall on average it adds a 1.5% - 4% CPU overhead:
This is the merged profile during the total load test: https://share.polarsignals.com/d92bfce/, we can see under the icicle graph there that the
otelgrpc.UnaryServerInterceptor
function was 1.84% of the total CPU. Plus extra 0.84% CPU time in theotelgrpc.messageType.Event
.In some individual profiles, we can see around 4.71% CPU overhead. https://share.polarsignals.com/e5d524c/
Happy to share more profiles, if needed, or run more load tests!
Open Questions
Follow up improvements and features: