mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
Merge pull request #124189 from toddtreece/kube-aggregator-proxy-tracing
Add tracing to kube-aggregator proxyHandler
This commit is contained in:
commit
1cbe7b6b43
@ -37,6 +37,7 @@ import (
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/transport"
|
||||
"k8s.io/component-base/tracing"
|
||||
"k8s.io/component-base/version"
|
||||
v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||
v1helper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper"
|
||||
@ -169,6 +170,9 @@ type APIAggregator struct {
|
||||
|
||||
// rejectForwardingRedirects is whether to allow to forward redirect response
|
||||
rejectForwardingRedirects bool
|
||||
|
||||
// tracerProvider is used to wrap the proxy transport and handler with tracing
|
||||
tracerProvider tracing.TracerProvider
|
||||
}
|
||||
|
||||
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
|
||||
@ -239,6 +243,7 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
|
||||
openAPIV3Config: c.GenericConfig.OpenAPIV3Config,
|
||||
proxyCurrentCertKeyContent: func() (bytes []byte, bytes2 []byte) { return nil, nil },
|
||||
rejectForwardingRedirects: c.ExtraConfig.RejectForwardingRedirects,
|
||||
tracerProvider: c.GenericConfig.TracerProvider,
|
||||
}
|
||||
|
||||
// used later to filter the served resource by those that have expired.
|
||||
@ -518,6 +523,7 @@ func (s *APIAggregator) AddAPIService(apiService *v1.APIService) error {
|
||||
proxyTransportDial: s.proxyTransportDial,
|
||||
serviceResolver: s.serviceResolver,
|
||||
rejectForwardingRedirects: s.rejectForwardingRedirects,
|
||||
tracerProvider: s.tracerProvider,
|
||||
}
|
||||
proxyHandler.updateAPIService(apiService)
|
||||
if s.openAPIAggregationController != nil {
|
||||
|
@ -27,10 +27,13 @@ import (
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||
endpointmetrics "k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
|
||||
apiserverproxyutil "k8s.io/apiserver/pkg/util/proxy"
|
||||
"k8s.io/apiserver/pkg/util/x509metrics"
|
||||
"k8s.io/client-go/transport"
|
||||
"k8s.io/component-base/tracing"
|
||||
"k8s.io/klog/v2"
|
||||
apiregistrationv1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||
apiregistrationv1apihelper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper"
|
||||
@ -59,6 +62,9 @@ type proxyHandler struct {
|
||||
|
||||
// reject to forward redirect response
|
||||
rejectForwardingRedirects bool
|
||||
|
||||
// tracerProvider is used to wrap the proxy transport and handler with tracing
|
||||
tracerProvider tracing.TracerProvider
|
||||
}
|
||||
|
||||
type proxyHandlingInfo struct {
|
||||
@ -155,6 +161,11 @@ func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
proxyRoundTripper = transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), proxyRoundTripper)
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) && !upgrade {
|
||||
tracingWrapper := tracing.WrapperFor(r.tracerProvider)
|
||||
proxyRoundTripper = tracingWrapper(proxyRoundTripper)
|
||||
}
|
||||
|
||||
// If we are upgrading, then the upgrade path tries to use this request with the TLS config we provide, but it does
|
||||
// NOT use the proxyRoundTripper. It's a direct dial that bypasses the proxyRoundTripper. This means that we have to
|
||||
// attach the "correct" user headers to the request ahead of time.
|
||||
|
@ -40,12 +40,17 @@ import (
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/endpoints/filters"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/server/egressselector"
|
||||
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
|
||||
@ -54,6 +59,7 @@ import (
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
apiregistration "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
type targetHTTPHandler struct {
|
||||
@ -774,6 +780,116 @@ func TestGetContextForNewRequest(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestTracerProvider(t *testing.T) {
|
||||
fakeRecorder := tracetest.NewSpanRecorder()
|
||||
otelTracer := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(fakeRecorder))
|
||||
target := &targetHTTPHandler{}
|
||||
user := &user.DefaultInfo{
|
||||
Name: "username",
|
||||
Groups: []string{"one", "two"},
|
||||
}
|
||||
path := "/request/path"
|
||||
apiService := &apiregistration.APIService{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "v1.foo"},
|
||||
Spec: apiregistration.APIServiceSpec{
|
||||
Service: &apiregistration.ServiceReference{Name: "test-service", Namespace: "test-ns", Port: ptr.To(int32(443))},
|
||||
Group: "foo",
|
||||
Version: "v1",
|
||||
CABundle: testCACrt,
|
||||
},
|
||||
Status: apiregistration.APIServiceStatus{
|
||||
Conditions: []apiregistration.APIServiceCondition{
|
||||
{Type: apiregistration.Available, Status: apiregistration.ConditionTrue},
|
||||
},
|
||||
},
|
||||
}
|
||||
targetServer := httptest.NewUnstartedServer(target)
|
||||
serviceCert := svcCrt
|
||||
if cert, err := tls.X509KeyPair(serviceCert, svcKey); err != nil {
|
||||
t.Fatalf("TestTracerProvider: failed to parse key pair: %v", err)
|
||||
} else {
|
||||
targetServer.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
targetServer.StartTLS()
|
||||
defer targetServer.Close()
|
||||
|
||||
serviceResolver := &mockedRouter{destinationHost: targetServer.Listener.Addr().String()}
|
||||
handler := &proxyHandler{
|
||||
localDelegate: http.NewServeMux(),
|
||||
serviceResolver: serviceResolver,
|
||||
proxyCurrentCertKeyContent: func() ([]byte, []byte) { return emptyCert(), emptyCert() },
|
||||
tracerProvider: otelTracer,
|
||||
}
|
||||
|
||||
server := httptest.NewServer(contextHandler(filters.WithTracing(handler, otelTracer), user))
|
||||
defer server.Close()
|
||||
|
||||
handler.updateAPIService(apiService)
|
||||
curr := handler.handlingInfo.Load().(proxyHandlingInfo)
|
||||
handler.handlingInfo.Store(curr)
|
||||
var propagator propagation.TraceContext
|
||||
req, err := http.NewRequest(http.MethodGet, server.URL+path, nil)
|
||||
if err != nil {
|
||||
t.Errorf("expected new request: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("Sending request: %v", req)
|
||||
_, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("http request failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("Ensure the target server received the traceparent header")
|
||||
id, ok := target.headers["Traceparent"]
|
||||
if !ok {
|
||||
t.Error("expected traceparent header")
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("Get the span context from the traceparent header")
|
||||
h := http.Header{
|
||||
"Traceparent": id,
|
||||
}
|
||||
ctx := propagator.Extract(context.Background(), propagation.HeaderCarrier(h))
|
||||
span := trace.SpanFromContext(ctx)
|
||||
|
||||
t.Log("Ensure that the span context is valid and remote")
|
||||
if !span.SpanContext().IsValid() {
|
||||
t.Error("expected valid span context")
|
||||
return
|
||||
}
|
||||
|
||||
if !span.SpanContext().IsRemote() {
|
||||
t.Error("expected remote span context")
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("Ensure that the span ID and trace ID match the expected values")
|
||||
expectedSpanCtx := fakeRecorder.Ended()[0].SpanContext()
|
||||
if expectedSpanCtx.TraceID() != span.SpanContext().TraceID() {
|
||||
t.Errorf("expected trace id to match. expected: %v, but got %v", expectedSpanCtx.TraceID(), span.SpanContext().TraceID())
|
||||
return
|
||||
}
|
||||
|
||||
if expectedSpanCtx.SpanID() != span.SpanContext().SpanID() {
|
||||
t.Errorf("expected span id to match. expected: %v, but got: %v", expectedSpanCtx.SpanID(), span.SpanContext().SpanID())
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("Ensure that the expected spans were recorded when sending a request through the proxy")
|
||||
expectedSpanNames := []string{"HTTP GET", "GET"}
|
||||
spanNames := []string{}
|
||||
for _, span := range fakeRecorder.Ended() {
|
||||
spanNames = append(spanNames, span.Name())
|
||||
}
|
||||
if e, a := expectedSpanNames, spanNames; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("expected span names %v, got %v", e, a)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRequestForProxyWithAuditID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
Loading…
Reference in New Issue
Block a user