From bc97340d5ddee5ea122203f7048b0b12b602e024 Mon Sep 17 00:00:00 2001 From: yy Date: Wed, 28 Dec 2022 10:18:51 +0800 Subject: [PATCH] add tracing support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yy add some unit test Signed-off-by: yy git rebase Signed-off-by: yy expose configuration for envoy's RateLimitedAsResourceExhausted (#4971) The Rate Limit filter in Envoy translates a 429 HTTP response code to UNAVAILABLE as specified in the gRPC mapping document, but Google recommends translating it to RESOURCE_EXHAUSTED (see https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md) This commit introduces a new setting to allow contour to forward the same parameter introduced in https://github.com/envoyproxy/envoy/pull/4879 The default value is disabled to retain the original behaviour of returning UNAVAILABLE, as changing it would be a breaking change. Closes #4901. Signed-off-by: Víctor Roldán Betancort Signed-off-by: yy rebase Signed-off-by: yy update tracing config validate Signed-off-by: yy make generate Signed-off-by: yy add chengelog Signed-off-by: yy update make general Signed-off-by: yy goimport Signed-off-by: yy update tracing Signed-off-by: yy fix golint Signed-off-by: yy update test Signed-off-by: yy delete unused code Signed-off-by: yy delete error file Signed-off-by: yy update changelog Signed-off-by: yy fix some mistake Signed-off-by: yy feat: Add HTTP support for External Auth (#4994) Support globally configuring an external auth server which is enabled by default for all vhosts, both HTTP and HTTPS. Closes #4954. Signed-off-by: claytonig Signed-off-by: yy refactor DAG and DAG consumers to support >2 Listeners (#5128) Updates #4960. Signed-off-by: Steve Kriss Signed-off-by: yy resolve conflict Signed-off-by: yy fix Signed-off-by: yy --- apis/projectcontour/v1alpha1/contourconfig.go | 50 +++++ .../v1alpha1/zz_generated.deepcopy.go | 67 ++++++ changelogs/unreleased/5043-yangyy93-minor.md | 14 ++ cmd/contour/serve.go | 75 +++++++ cmd/contour/servecontext.go | 27 +++ cmd/contour/servecontext_test.go | 50 ++++- examples/contour/01-crds.yaml | 111 ++++++++++ examples/render/contour-deployment.yaml | 111 ++++++++++ .../render/contour-gateway-provisioner.yaml | 111 ++++++++++ examples/render/contour-gateway.yaml | 111 ++++++++++ examples/render/contour.yaml | 111 ++++++++++ internal/dag/dag.go | 38 ++++ internal/envoy/v3/listener.go | 10 + internal/envoy/v3/tracing.go | 93 +++++++++ internal/envoy/v3/tracing_test.go | 193 ++++++++++++++++++ internal/featuretests/v3/tracing_test.go | 126 ++++++++++++ internal/xdscache/v3/listener.go | 8 + pkg/config/parameters.go | 91 +++++++++ pkg/config/parameters_test.go | 78 +++++++ .../docs/main/config/api-reference.html | 191 ++++++++++++++++- 20 files changed, 1662 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/5043-yangyy93-minor.md create mode 100644 internal/envoy/v3/tracing.go create mode 100644 internal/envoy/v3/tracing_test.go create mode 100644 internal/featuretests/v3/tracing_test.go diff --git a/apis/projectcontour/v1alpha1/contourconfig.go b/apis/projectcontour/v1alpha1/contourconfig.go index f810a0cfbd0..2f2d2866d9f 100644 --- a/apis/projectcontour/v1alpha1/contourconfig.go +++ b/apis/projectcontour/v1alpha1/contourconfig.go @@ -81,6 +81,9 @@ type ContourConfigurationSpec struct { // Contour's default is { address: "0.0.0.0", port: 8000 }. // +optional Metrics *MetricsConfig `json:"metrics,omitempty"` + + // Tracing defines properties for exporting trace data to OpenTelemetry. + Tracing *TracingConfig `json:"tracing,omitempty"` } // XDSServerType is the type of xDS server implementation. @@ -659,6 +662,53 @@ type RateLimitServiceConfig struct { EnableResourceExhaustedCode *bool `json:"enableResourceExhaustedCode,omitempty"` } +// TracingConfig defines properties for exporting trace data to OpenTelemetry. +type TracingConfig struct { + // IncludePodDetail defines a flag. + // If it is true, contour will add the pod name and namespace to the span of the trace. + // the default is true. + // +optional + IncludePodDetail *bool `json:"includePodDetail,omitempty"` + + // ServiceName defines the name for the service. + // contour's default is contour + ServiceName *string `json:"serviceName,omitempty"` + + // OverallSampling defines the sampling rate of trace data. + // contour's default is 100 + // +optional + OverallSampling *string `json:"overallSampling,omitempty"` + + // OverallSampling defines maximum length of the request path + // to extract and include in the HttpUrl tag. + // contour's default is 256. + // +optional + MaxPathTagLength *uint32 `json:"maxPathTagLength,omitempty"` + + // CustomTags defines a list of custom tags with unique tag name. + // +optional + CustomTags []*CustomTag `json:"customTags,omitempty"` + + // ExtensionService identifies the extension service defining the otle-collector. + ExtensionService NamespacedName `json:"extensionService,omitempty"` +} + +// CustomTag defines custom tags with unique tag name +// to create tags for the active span. +type CustomTag struct { + // TagName is the unique name of the custom tag. + TagName string `json:"tagName,omitempty"` + + // Literal is a static custom tag value. + // +optional + Literal string `json:"literal,omitempty"` + + // RequestHeaderName indicates which request header + // the label value is obtained from. + // +optional + RequestHeaderName string `json:"requestHeaderName,omitempty"` +} + // PolicyConfig holds default policy used if not explicitly set by the user type PolicyConfig struct { // RequestHeadersPolicy defines the request headers set/removed on all routes diff --git a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go index 5cc64d74f5f..566a13744ef 100644 --- a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go +++ b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go @@ -185,6 +185,11 @@ func (in *ContourConfigurationSpec) DeepCopyInto(out *ContourConfigurationSpec) *out = new(MetricsConfig) (*in).DeepCopyInto(*out) } + if in.Tracing != nil { + in, out := &in.Tracing, &out.Tracing + *out = new(TracingConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContourConfigurationSpec. @@ -363,6 +368,21 @@ func (in *ContourSettings) DeepCopy() *ContourSettings { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomTag) DeepCopyInto(out *CustomTag) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomTag. +func (in *CustomTag) DeepCopy() *CustomTag { + if in == nil { + return nil + } + out := new(CustomTag) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DaemonSetSettings) DeepCopyInto(out *DaemonSetSettings) { *out = *in @@ -1145,6 +1165,53 @@ func (in *TimeoutParameters) DeepCopy() *TimeoutParameters { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TracingConfig) DeepCopyInto(out *TracingConfig) { + *out = *in + if in.IncludePodDetail != nil { + in, out := &in.IncludePodDetail, &out.IncludePodDetail + *out = new(bool) + **out = **in + } + if in.ServiceName != nil { + in, out := &in.ServiceName, &out.ServiceName + *out = new(string) + **out = **in + } + if in.OverallSampling != nil { + in, out := &in.OverallSampling, &out.OverallSampling + *out = new(string) + **out = **in + } + if in.MaxPathTagLength != nil { + in, out := &in.MaxPathTagLength, &out.MaxPathTagLength + *out = new(uint32) + **out = **in + } + if in.CustomTags != nil { + in, out := &in.CustomTags, &out.CustomTags + *out = make([]*CustomTag, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(CustomTag) + **out = **in + } + } + } + out.ExtensionService = in.ExtensionService +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TracingConfig. +func (in *TracingConfig) DeepCopy() *TracingConfig { + if in == nil { + return nil + } + out := new(TracingConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *XDSServerConfig) DeepCopyInto(out *XDSServerConfig) { *out = *in diff --git a/changelogs/unreleased/5043-yangyy93-minor.md b/changelogs/unreleased/5043-yangyy93-minor.md new file mode 100644 index 00000000000..96ea4258de6 --- /dev/null +++ b/changelogs/unreleased/5043-yangyy93-minor.md @@ -0,0 +1,14 @@ +## Add Tracing Support + +Contour now supports exporting tracing data to [OpenTelemetry][1] + +The Contour configuration file and ContourConfiguration CRD will be extended with a new optional `tracing` section. This configuration block, if present, will enable tracing and will define the trace properties needed to generate and export trace data. + +### Contour supports the following configurations +- Custom service name, the default is `contour`. +- Custom sampling rate, the default is `100`. +- Custom the maximum length of the request path, the default is `256`. +- Customize span tags from literal and request headers. +- Customize whether to include the pod's hostname and namespace. + +[1]: https://opentelemetry.io/ diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index e2aaaeef8ee..fc2b769dc7c 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -24,6 +24,8 @@ import ( "time" "github.com/alecthomas/kingpin/v2" + "k8s.io/utils/pointer" + envoy_server_v3 "github.com/envoyproxy/go-control-plane/pkg/server/v3" contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" @@ -354,6 +356,10 @@ func (s *Server) doServe() error { ConnectionBalancer: contourConfiguration.Envoy.Listener.ConnectionBalancer, } + if listenerConfig.TracingConfig, err = s.setupTracingService(contourConfiguration.Tracing); err != nil { + return err + } + if listenerConfig.RateLimitConfig, err = s.setupRateLimitService(contourConfiguration); err != nil { return err } @@ -594,6 +600,75 @@ func (s *Server) doServe() error { return s.mgr.Start(signals.SetupSignalHandler()) } +func (s *Server) setupTracingService(tracingConfig *contour_api_v1alpha1.TracingConfig) (*dag.TracingConfig, error) { + if tracingConfig == nil { + return nil, nil + } + + // ensure the specified ExtensionService exists + extensionSvc := &contour_api_v1alpha1.ExtensionService{} + key := client.ObjectKey{ + Namespace: tracingConfig.ExtensionService.Namespace, + Name: tracingConfig.ExtensionService.Name, + } + // Using GetAPIReader() here because the manager's caches won't be started yet, + // so reads from the manager's client (which uses the caches for reads) will fail. + if err := s.mgr.GetAPIReader().Get(context.Background(), key, extensionSvc); err != nil { + return nil, fmt.Errorf("error getting tracing extension service %s: %v", key, err) + } + // get the response timeout from the ExtensionService + var responseTimeout timeout.Setting + var err error + + if tp := extensionSvc.Spec.TimeoutPolicy; tp != nil { + responseTimeout, err = timeout.Parse(tp.Response) + if err != nil { + return nil, fmt.Errorf("error parsing tracing extension service %s response timeout: %v", key, err) + } + } + + var sni string + if extensionSvc.Spec.UpstreamValidation != nil { + sni = extensionSvc.Spec.UpstreamValidation.SubjectName + } + + var customTags []*dag.CustomTag + + if pointer.BoolDeref(tracingConfig.IncludePodDetail, true) { + customTags = append(customTags, &dag.CustomTag{ + TagName: "podName", + EnvironmentName: "HOSTNAME", + }, &dag.CustomTag{ + TagName: "podNamespaceName", + EnvironmentName: "CONTOUR_NAMESPACE", + }) + } + + for _, customTag := range tracingConfig.CustomTags { + customTags = append(customTags, &dag.CustomTag{ + TagName: customTag.TagName, + Literal: customTag.Literal, + RequestHeaderName: customTag.RequestHeaderName, + }) + } + + overallSampling, err := strconv.ParseFloat(*tracingConfig.OverallSampling, 64) + if err != nil || overallSampling == 0 { + overallSampling = 100.0 + } + + return &dag.TracingConfig{ + ServiceName: pointer.StringDeref(tracingConfig.ServiceName, "contour"), + ExtensionService: key, + SNI: sni, + Timeout: responseTimeout, + OverallSampling: overallSampling, + MaxPathTagLength: pointer.Uint32Deref(tracingConfig.MaxPathTagLength, 256), + CustomTags: customTags, + }, nil + +} + func (s *Server) setupRateLimitService(contourConfiguration contour_api_v1alpha1.ContourConfigurationSpec) (*xdscache_v3.RateLimitConfig, error) { if contourConfiguration.RateLimitService == nil { return nil, nil diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index 00b07e7900c..ff725351c83 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "os" + "strconv" "strings" "time" @@ -30,6 +31,7 @@ import ( "github.com/projectcontour/contour/internal/ref" xdscache_v3 "github.com/projectcontour/contour/internal/xdscache/v3" "github.com/projectcontour/contour/pkg/config" + "k8s.io/utils/pointer" "github.com/sirupsen/logrus" "google.golang.org/grpc" @@ -366,6 +368,30 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_api_v1alpha dnsLookupFamily = contour_api_v1alpha1.AllClusterDNSFamily } + var tracingConfig *contour_api_v1alpha1.TracingConfig + if ctx.Config.Tracing.ExtensionService != "" { + namespacedName := k8s.NamespacedNameFrom(ctx.Config.Tracing.ExtensionService) + var customTags []*contour_api_v1alpha1.CustomTag + for _, customTag := range ctx.Config.Tracing.CustomTags { + customTags = append(customTags, &contour_api_v1alpha1.CustomTag{ + TagName: customTag.TagName, + Literal: customTag.Literal, + RequestHeaderName: customTag.RequestHeaderName, + }) + } + tracingConfig = &contour_api_v1alpha1.TracingConfig{ + IncludePodDetail: ctx.Config.Tracing.IncludePodDetail, + ServiceName: pointer.String(ctx.Config.Tracing.ServiceName), + OverallSampling: pointer.String(strconv.FormatFloat(ctx.Config.Tracing.OverallSampling, 'f', 1, 64)), + MaxPathTagLength: pointer.Uint32(ctx.Config.Tracing.MaxPathTagLength), + CustomTags: customTags, + ExtensionService: contour_api_v1alpha1.NamespacedName{ + Name: namespacedName.Name, + Namespace: namespacedName.Namespace, + }, + } + } + var rateLimitService *contour_api_v1alpha1.RateLimitServiceConfig if ctx.Config.RateLimitService.ExtensionService != "" { @@ -538,6 +564,7 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_api_v1alpha RateLimitService: rateLimitService, Policy: policy, Metrics: &contourMetrics, + Tracing: tracingConfig, } xdsServerType := contour_api_v1alpha1.ContourServerType diff --git a/cmd/contour/servecontext_test.go b/cmd/contour/servecontext_test.go index cc537a78715..efcc553ed03 100644 --- a/cmd/contour/servecontext_test.go +++ b/cmd/contour/servecontext_test.go @@ -24,17 +24,17 @@ import ( "testing" "time" - "github.com/projectcontour/contour/pkg/config" - "github.com/tsaarni/certyaml" - contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" "github.com/projectcontour/contour/internal/contourconfig" envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3" "github.com/projectcontour/contour/internal/fixture" "github.com/projectcontour/contour/internal/ref" + "github.com/projectcontour/contour/pkg/config" "github.com/stretchr/testify/assert" + "github.com/tsaarni/certyaml" "google.golang.org/grpc" + "k8s.io/utils/pointer" ) func TestServeContextProxyRootNamespaces(t *testing.T) { @@ -741,6 +741,50 @@ func TestConvertServeContext(t *testing.T) { return cfg }, }, + "tracing config": { + getServeContext: func(ctx *serveContext) *serveContext { + ctx.Config.Tracing = config.Tracing{ + IncludePodDetail: nil, + ServiceName: "contour", + OverallSampling: 100, + MaxPathTagLength: 256, + CustomTags: []config.CustomTag{ + { + TagName: "literal", + Literal: "this is literal", + }, + { + TagName: "header", + RequestHeaderName: ":method", + }, + }, + ExtensionService: "otel/otel-collector", + } + return ctx + }, + getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec { + cfg.Tracing = &contour_api_v1alpha1.TracingConfig{ + ServiceName: pointer.String("contour"), + OverallSampling: pointer.String("100.0"), + MaxPathTagLength: pointer.Uint32(256), + CustomTags: []*contour_api_v1alpha1.CustomTag{ + { + TagName: "literal", + Literal: "this is literal", + }, + { + TagName: "header", + RequestHeaderName: ":method", + }, + }, + ExtensionService: contour_api_v1alpha1.NamespacedName{ + Name: "otel-collector", + Namespace: "otel", + }, + } + return cfg + }, + }, } for name, tc := range cases { diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml index 6395cd2d569..4087ef8ef83 100644 --- a/examples/contour/01-crds.yaml +++ b/examples/contour/01-crds.yaml @@ -646,6 +646,61 @@ spec: required: - extensionService type: object + tracing: + description: Tracing defines properties for exporting trace data to + OpenTelemetry. + properties: + customTags: + description: CustomTags defines a list of custom tags with unique + tag name. + items: + description: CustomTag defines custom tags with unique tag name + to create tags for the active span. + properties: + literal: + description: Literal is a static custom tag value. + type: string + requestHeaderName: + description: RequestHeaderName indicates which request header + the label value is obtained from. + type: string + tagName: + description: TagName is the unique name of the custom tag. + type: string + type: object + type: array + extensionService: + description: ExtensionService identifies the extension service + defining the otle-collector. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + includePodDetail: + description: IncludePodDetail defines a flag. If it is true, contour + will add the pod name and namespace to the span of the trace. + the default is true. + type: boolean + maxPathTagLength: + description: OverallSampling defines maximum length of the request + path to extract and include in the HttpUrl tag. contour's default + is 256. + format: int32 + type: integer + overallSampling: + description: OverallSampling defines the sampling rate of trace + data. contour's default is 100 + type: string + serviceName: + description: ServiceName defines the name for the service. contour's + default is contour + type: string + type: object xdsServer: description: XDSServer contains parameters for the xDS server. properties: @@ -3755,6 +3810,62 @@ spec: required: - extensionService type: object + tracing: + description: Tracing defines properties for exporting trace data + to OpenTelemetry. + properties: + customTags: + description: CustomTags defines a list of custom tags with + unique tag name. + items: + description: CustomTag defines custom tags with unique tag + name to create tags for the active span. + properties: + literal: + description: Literal is a static custom tag value. + type: string + requestHeaderName: + description: RequestHeaderName indicates which request + header the label value is obtained from. + type: string + tagName: + description: TagName is the unique name of the custom + tag. + type: string + type: object + type: array + extensionService: + description: ExtensionService identifies the extension service + defining the otle-collector. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + includePodDetail: + description: IncludePodDetail defines a flag. If it is true, + contour will add the pod name and namespace to the span + of the trace. the default is true. + type: boolean + maxPathTagLength: + description: OverallSampling defines maximum length of the + request path to extract and include in the HttpUrl tag. + contour's default is 256. + format: int32 + type: integer + overallSampling: + description: OverallSampling defines the sampling rate of + trace data. contour's default is 100 + type: string + serviceName: + description: ServiceName defines the name for the service. + contour's default is contour + type: string + type: object xdsServer: description: XDSServer contains parameters for the xDS server. properties: diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index 063e42afef1..b771b18b65f 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -859,6 +859,61 @@ spec: required: - extensionService type: object + tracing: + description: Tracing defines properties for exporting trace data to + OpenTelemetry. + properties: + customTags: + description: CustomTags defines a list of custom tags with unique + tag name. + items: + description: CustomTag defines custom tags with unique tag name + to create tags for the active span. + properties: + literal: + description: Literal is a static custom tag value. + type: string + requestHeaderName: + description: RequestHeaderName indicates which request header + the label value is obtained from. + type: string + tagName: + description: TagName is the unique name of the custom tag. + type: string + type: object + type: array + extensionService: + description: ExtensionService identifies the extension service + defining the otle-collector. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + includePodDetail: + description: IncludePodDetail defines a flag. If it is true, contour + will add the pod name and namespace to the span of the trace. + the default is true. + type: boolean + maxPathTagLength: + description: OverallSampling defines maximum length of the request + path to extract and include in the HttpUrl tag. contour's default + is 256. + format: int32 + type: integer + overallSampling: + description: OverallSampling defines the sampling rate of trace + data. contour's default is 100 + type: string + serviceName: + description: ServiceName defines the name for the service. contour's + default is contour + type: string + type: object xdsServer: description: XDSServer contains parameters for the xDS server. properties: @@ -3968,6 +4023,62 @@ spec: required: - extensionService type: object + tracing: + description: Tracing defines properties for exporting trace data + to OpenTelemetry. + properties: + customTags: + description: CustomTags defines a list of custom tags with + unique tag name. + items: + description: CustomTag defines custom tags with unique tag + name to create tags for the active span. + properties: + literal: + description: Literal is a static custom tag value. + type: string + requestHeaderName: + description: RequestHeaderName indicates which request + header the label value is obtained from. + type: string + tagName: + description: TagName is the unique name of the custom + tag. + type: string + type: object + type: array + extensionService: + description: ExtensionService identifies the extension service + defining the otle-collector. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + includePodDetail: + description: IncludePodDetail defines a flag. If it is true, + contour will add the pod name and namespace to the span + of the trace. the default is true. + type: boolean + maxPathTagLength: + description: OverallSampling defines maximum length of the + request path to extract and include in the HttpUrl tag. + contour's default is 256. + format: int32 + type: integer + overallSampling: + description: OverallSampling defines the sampling rate of + trace data. contour's default is 100 + type: string + serviceName: + description: ServiceName defines the name for the service. + contour's default is contour + type: string + type: object xdsServer: description: XDSServer contains parameters for the xDS server. properties: diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml index 6307c404a5e..2d978267712 100644 --- a/examples/render/contour-gateway-provisioner.yaml +++ b/examples/render/contour-gateway-provisioner.yaml @@ -660,6 +660,61 @@ spec: required: - extensionService type: object + tracing: + description: Tracing defines properties for exporting trace data to + OpenTelemetry. + properties: + customTags: + description: CustomTags defines a list of custom tags with unique + tag name. + items: + description: CustomTag defines custom tags with unique tag name + to create tags for the active span. + properties: + literal: + description: Literal is a static custom tag value. + type: string + requestHeaderName: + description: RequestHeaderName indicates which request header + the label value is obtained from. + type: string + tagName: + description: TagName is the unique name of the custom tag. + type: string + type: object + type: array + extensionService: + description: ExtensionService identifies the extension service + defining the otle-collector. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + includePodDetail: + description: IncludePodDetail defines a flag. If it is true, contour + will add the pod name and namespace to the span of the trace. + the default is true. + type: boolean + maxPathTagLength: + description: OverallSampling defines maximum length of the request + path to extract and include in the HttpUrl tag. contour's default + is 256. + format: int32 + type: integer + overallSampling: + description: OverallSampling defines the sampling rate of trace + data. contour's default is 100 + type: string + serviceName: + description: ServiceName defines the name for the service. contour's + default is contour + type: string + type: object xdsServer: description: XDSServer contains parameters for the xDS server. properties: @@ -3769,6 +3824,62 @@ spec: required: - extensionService type: object + tracing: + description: Tracing defines properties for exporting trace data + to OpenTelemetry. + properties: + customTags: + description: CustomTags defines a list of custom tags with + unique tag name. + items: + description: CustomTag defines custom tags with unique tag + name to create tags for the active span. + properties: + literal: + description: Literal is a static custom tag value. + type: string + requestHeaderName: + description: RequestHeaderName indicates which request + header the label value is obtained from. + type: string + tagName: + description: TagName is the unique name of the custom + tag. + type: string + type: object + type: array + extensionService: + description: ExtensionService identifies the extension service + defining the otle-collector. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + includePodDetail: + description: IncludePodDetail defines a flag. If it is true, + contour will add the pod name and namespace to the span + of the trace. the default is true. + type: boolean + maxPathTagLength: + description: OverallSampling defines maximum length of the + request path to extract and include in the HttpUrl tag. + contour's default is 256. + format: int32 + type: integer + overallSampling: + description: OverallSampling defines the sampling rate of + trace data. contour's default is 100 + type: string + serviceName: + description: ServiceName defines the name for the service. + contour's default is contour + type: string + type: object xdsServer: description: XDSServer contains parameters for the xDS server. properties: diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index 37211a40e61..c14f3af6d58 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -865,6 +865,61 @@ spec: required: - extensionService type: object + tracing: + description: Tracing defines properties for exporting trace data to + OpenTelemetry. + properties: + customTags: + description: CustomTags defines a list of custom tags with unique + tag name. + items: + description: CustomTag defines custom tags with unique tag name + to create tags for the active span. + properties: + literal: + description: Literal is a static custom tag value. + type: string + requestHeaderName: + description: RequestHeaderName indicates which request header + the label value is obtained from. + type: string + tagName: + description: TagName is the unique name of the custom tag. + type: string + type: object + type: array + extensionService: + description: ExtensionService identifies the extension service + defining the otle-collector. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + includePodDetail: + description: IncludePodDetail defines a flag. If it is true, contour + will add the pod name and namespace to the span of the trace. + the default is true. + type: boolean + maxPathTagLength: + description: OverallSampling defines maximum length of the request + path to extract and include in the HttpUrl tag. contour's default + is 256. + format: int32 + type: integer + overallSampling: + description: OverallSampling defines the sampling rate of trace + data. contour's default is 100 + type: string + serviceName: + description: ServiceName defines the name for the service. contour's + default is contour + type: string + type: object xdsServer: description: XDSServer contains parameters for the xDS server. properties: @@ -3974,6 +4029,62 @@ spec: required: - extensionService type: object + tracing: + description: Tracing defines properties for exporting trace data + to OpenTelemetry. + properties: + customTags: + description: CustomTags defines a list of custom tags with + unique tag name. + items: + description: CustomTag defines custom tags with unique tag + name to create tags for the active span. + properties: + literal: + description: Literal is a static custom tag value. + type: string + requestHeaderName: + description: RequestHeaderName indicates which request + header the label value is obtained from. + type: string + tagName: + description: TagName is the unique name of the custom + tag. + type: string + type: object + type: array + extensionService: + description: ExtensionService identifies the extension service + defining the otle-collector. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + includePodDetail: + description: IncludePodDetail defines a flag. If it is true, + contour will add the pod name and namespace to the span + of the trace. the default is true. + type: boolean + maxPathTagLength: + description: OverallSampling defines maximum length of the + request path to extract and include in the HttpUrl tag. + contour's default is 256. + format: int32 + type: integer + overallSampling: + description: OverallSampling defines the sampling rate of + trace data. contour's default is 100 + type: string + serviceName: + description: ServiceName defines the name for the service. + contour's default is contour + type: string + type: object xdsServer: description: XDSServer contains parameters for the xDS server. properties: diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index 3089ef047a7..5ed5a810edd 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -859,6 +859,61 @@ spec: required: - extensionService type: object + tracing: + description: Tracing defines properties for exporting trace data to + OpenTelemetry. + properties: + customTags: + description: CustomTags defines a list of custom tags with unique + tag name. + items: + description: CustomTag defines custom tags with unique tag name + to create tags for the active span. + properties: + literal: + description: Literal is a static custom tag value. + type: string + requestHeaderName: + description: RequestHeaderName indicates which request header + the label value is obtained from. + type: string + tagName: + description: TagName is the unique name of the custom tag. + type: string + type: object + type: array + extensionService: + description: ExtensionService identifies the extension service + defining the otle-collector. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + includePodDetail: + description: IncludePodDetail defines a flag. If it is true, contour + will add the pod name and namespace to the span of the trace. + the default is true. + type: boolean + maxPathTagLength: + description: OverallSampling defines maximum length of the request + path to extract and include in the HttpUrl tag. contour's default + is 256. + format: int32 + type: integer + overallSampling: + description: OverallSampling defines the sampling rate of trace + data. contour's default is 100 + type: string + serviceName: + description: ServiceName defines the name for the service. contour's + default is contour + type: string + type: object xdsServer: description: XDSServer contains parameters for the xDS server. properties: @@ -3968,6 +4023,62 @@ spec: required: - extensionService type: object + tracing: + description: Tracing defines properties for exporting trace data + to OpenTelemetry. + properties: + customTags: + description: CustomTags defines a list of custom tags with + unique tag name. + items: + description: CustomTag defines custom tags with unique tag + name to create tags for the active span. + properties: + literal: + description: Literal is a static custom tag value. + type: string + requestHeaderName: + description: RequestHeaderName indicates which request + header the label value is obtained from. + type: string + tagName: + description: TagName is the unique name of the custom + tag. + type: string + type: object + type: array + extensionService: + description: ExtensionService identifies the extension service + defining the otle-collector. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + includePodDetail: + description: IncludePodDetail defines a flag. If it is true, + contour will add the pod name and namespace to the span + of the trace. the default is true. + type: boolean + maxPathTagLength: + description: OverallSampling defines maximum length of the + request path to extract and include in the HttpUrl tag. + contour's default is 256. + format: int32 + type: integer + overallSampling: + description: OverallSampling defines the sampling rate of + trace data. contour's default is 100 + type: string + serviceName: + description: ServiceName defines the name for the service. + contour's default is contour + type: string + type: object xdsServer: description: XDSServer contains parameters for the xDS server. properties: diff --git a/internal/dag/dag.go b/internal/dag/dag.go index e06d3018988..cbe641fb6eb 100644 --- a/internal/dag/dag.go +++ b/internal/dag/dag.go @@ -729,6 +729,44 @@ type SecureVirtualHost struct { // JWTProviders specify how to verify JWTs. JWTProviders []JWTProvider + + // Tracing defines custom tags and the service to send trace data + Tracing TracingConfig +} + +type TracingConfig struct { + ExtensionService types.NamespacedName + + ServiceName string + + SNI string + + Timeout timeout.Setting + + OverallSampling float64 + + MaxPathTagLength uint32 + + CustomTags []*CustomTag +} + +type CustomTag struct { + // TagName is the unique name of the custom tag. + TagName string `json:"tagName"` + + // Literal is a static custom tag value. + // +optional + Literal string `json:"literal"` + + // EnvironmentName indicates that the label value is obtained + // from the environment variable. + // +optional + EnvironmentName string `json:"environment"` + + // RequestHeaderName indicates which request header + // the label value is obtained from. + // +optional + RequestHeaderName string `json:"requestHeaderName"` } type JWTProvider struct { diff --git a/internal/envoy/v3/listener.go b/internal/envoy/v3/listener.go index f4a2caef4ae..5629f17ec86 100644 --- a/internal/envoy/v3/listener.go +++ b/internal/envoy/v3/listener.go @@ -165,6 +165,7 @@ type httpConnectionManagerBuilder struct { serverHeaderTransformation http.HttpConnectionManager_ServerHeaderTransformation forwardClientCertificate *dag.ClientCertificateDetails numTrustedHops uint32 + tracingConfig *http.HttpConnectionManager_Tracing } // RouteConfigName sets the name of the RDS element that contains @@ -401,6 +402,14 @@ func (b *httpConnectionManagerBuilder) AddFilter(f *http.HttpFilter) *httpConnec return b } +func (b *httpConnectionManagerBuilder) Tracing(tracing *http.HttpConnectionManager_Tracing) *httpConnectionManagerBuilder { + if tracing == nil { + return b + } + b.tracingConfig = tracing + return b +} + // Validate runs builtin validation rules against the current builder state. func (b *httpConnectionManagerBuilder) Validate() error { @@ -442,6 +451,7 @@ func (b *httpConnectionManagerBuilder) Get() *envoy_listener_v3.Filter { ConfigSource: ConfigSource("contour"), }, }, + Tracing: b.tracingConfig, HttpFilters: b.filters, CommonHttpProtocolOptions: &envoy_core_v3.HttpProtocolOptions{ IdleTimeout: envoy.Timeout(b.connectionIdleTimeout), diff --git a/internal/envoy/v3/tracing.go b/internal/envoy/v3/tracing.go new file mode 100644 index 00000000000..382590f2f2b --- /dev/null +++ b/internal/envoy/v3/tracing.go @@ -0,0 +1,93 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v3 + +import ( + envoy_config_trace_v3 "github.com/envoyproxy/go-control-plane/envoy/config/trace/v3" + http "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + envoy_trace_v3 "github.com/envoyproxy/go-control-plane/envoy/type/tracing/v3" + envoy_type "github.com/envoyproxy/go-control-plane/envoy/type/v3" + "github.com/projectcontour/contour/internal/dag" + "github.com/projectcontour/contour/internal/protobuf" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +// TracingConfig returns a tracing config, +// or nil if config is nil. +func TracingConfig(tracing *dag.TracingConfig) *http.HttpConnectionManager_Tracing { + if tracing == nil { + return nil + } + + var customTags []*envoy_trace_v3.CustomTag + for _, tag := range tracing.CustomTags { + if traceCustomTag := customTag(tag); traceCustomTag != nil { + customTags = append(customTags, traceCustomTag) + } + } + + return &http.HttpConnectionManager_Tracing{ + OverallSampling: &envoy_type.Percent{ + Value: tracing.OverallSampling, + }, + MaxPathTagLength: wrapperspb.UInt32(tracing.MaxPathTagLength), + CustomTags: customTags, + Provider: &envoy_config_trace_v3.Tracing_Http{ + Name: "envoy.tracers.opentelemetry", + ConfigType: &envoy_config_trace_v3.Tracing_Http_TypedConfig{ + TypedConfig: protobuf.MustMarshalAny(&envoy_config_trace_v3.OpenTelemetryConfig{ + GrpcService: GrpcService(dag.ExtensionClusterName(tracing.ExtensionService), tracing.SNI, tracing.Timeout), + ServiceName: tracing.ServiceName, + }), + }, + }, + } +} + +func customTag(tag *dag.CustomTag) *envoy_trace_v3.CustomTag { + if tag == nil { + return nil + } + if tag.Literal != "" { + return &envoy_trace_v3.CustomTag{ + Tag: tag.TagName, + Type: &envoy_trace_v3.CustomTag_Literal_{ + Literal: &envoy_trace_v3.CustomTag_Literal{ + Value: tag.Literal, + }, + }, + } + } + if tag.EnvironmentName != "" { + return &envoy_trace_v3.CustomTag{ + Tag: tag.TagName, + Type: &envoy_trace_v3.CustomTag_Environment_{ + Environment: &envoy_trace_v3.CustomTag_Environment{ + Name: tag.EnvironmentName, + }, + }, + } + } + if tag.RequestHeaderName != "" { + return &envoy_trace_v3.CustomTag{ + Tag: tag.TagName, + Type: &envoy_trace_v3.CustomTag_RequestHeader{ + RequestHeader: &envoy_trace_v3.CustomTag_Header{ + Name: tag.RequestHeaderName, + }, + }, + } + } + return nil +} diff --git a/internal/envoy/v3/tracing_test.go b/internal/envoy/v3/tracing_test.go new file mode 100644 index 00000000000..8bd09c58521 --- /dev/null +++ b/internal/envoy/v3/tracing_test.go @@ -0,0 +1,193 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v3 + +import ( + "testing" + "time" + + envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_config_trace_v3 "github.com/envoyproxy/go-control-plane/envoy/config/trace/v3" + http "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + envoy_trace_v3 "github.com/envoyproxy/go-control-plane/envoy/type/tracing/v3" + envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" + "github.com/projectcontour/contour/internal/dag" + "github.com/projectcontour/contour/internal/k8s" + "github.com/projectcontour/contour/internal/protobuf" + "github.com/projectcontour/contour/internal/timeout" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func TestTracingConfig(t *testing.T) { + tests := map[string]struct { + tracing *dag.TracingConfig + want *http.HttpConnectionManager_Tracing + }{ + "nil config": { + tracing: nil, + want: nil, + }, + "normal config": { + tracing: &dag.TracingConfig{ + ExtensionService: k8s.NamespacedNameFrom("projectcontour/otel-collector"), + ServiceName: "contour", + SNI: "some-server.com", + Timeout: timeout.DurationSetting(5 * time.Second), + OverallSampling: 100, + MaxPathTagLength: 256, + CustomTags: []*dag.CustomTag{ + { + TagName: "literal", + Literal: "this is literal", + }, + { + TagName: "podName", + EnvironmentName: "HOSTNAME", + }, + { + TagName: "requestHeaderName", + RequestHeaderName: ":path", + }, + }, + }, + want: &http.HttpConnectionManager_Tracing{ + OverallSampling: &envoy_type_v3.Percent{ + Value: 100.0, + }, + MaxPathTagLength: wrapperspb.UInt32(256), + CustomTags: []*envoy_trace_v3.CustomTag{ + { + Tag: "literal", + Type: &envoy_trace_v3.CustomTag_Literal_{ + Literal: &envoy_trace_v3.CustomTag_Literal{ + Value: "this is literal", + }, + }, + }, + { + Tag: "podName", + Type: &envoy_trace_v3.CustomTag_Environment_{ + Environment: &envoy_trace_v3.CustomTag_Environment{ + Name: "HOSTNAME", + }, + }, + }, + { + Tag: "requestHeaderName", + Type: &envoy_trace_v3.CustomTag_RequestHeader{ + RequestHeader: &envoy_trace_v3.CustomTag_Header{ + Name: ":path", + }, + }, + }, + }, + Provider: &envoy_config_trace_v3.Tracing_Http{ + Name: "envoy.tracers.opentelemetry", + ConfigType: &envoy_config_trace_v3.Tracing_Http_TypedConfig{ + TypedConfig: protobuf.MustMarshalAny(&envoy_config_trace_v3.OpenTelemetryConfig{ + GrpcService: &envoy_core_v3.GrpcService{ + TargetSpecifier: &envoy_core_v3.GrpcService_EnvoyGrpc_{ + EnvoyGrpc: &envoy_core_v3.GrpcService_EnvoyGrpc{ + ClusterName: "extension/projectcontour/otel-collector", + Authority: "some-server.com", + }, + }, + Timeout: durationpb.New(5 * time.Second), + }, + ServiceName: "contour", + }), + }, + }, + }, + }, + "no custom tag": { + tracing: &dag.TracingConfig{ + ExtensionService: k8s.NamespacedNameFrom("projectcontour/otel-collector"), + ServiceName: "contour", + SNI: "some-server.com", + Timeout: timeout.DurationSetting(5 * time.Second), + OverallSampling: 100, + MaxPathTagLength: 256, + CustomTags: nil, + }, + want: &http.HttpConnectionManager_Tracing{ + OverallSampling: &envoy_type_v3.Percent{ + Value: 100.0, + }, + MaxPathTagLength: wrapperspb.UInt32(256), + CustomTags: nil, + Provider: &envoy_config_trace_v3.Tracing_Http{ + Name: "envoy.tracers.opentelemetry", + ConfigType: &envoy_config_trace_v3.Tracing_Http_TypedConfig{ + TypedConfig: protobuf.MustMarshalAny(&envoy_config_trace_v3.OpenTelemetryConfig{ + GrpcService: &envoy_core_v3.GrpcService{ + TargetSpecifier: &envoy_core_v3.GrpcService_EnvoyGrpc_{ + EnvoyGrpc: &envoy_core_v3.GrpcService_EnvoyGrpc{ + ClusterName: "extension/projectcontour/otel-collector", + Authority: "some-server.com", + }, + }, + Timeout: durationpb.New(5 * time.Second), + }, + ServiceName: "contour", + }), + }, + }, + }, + }, + "no SNI set": { + tracing: &dag.TracingConfig{ + ExtensionService: k8s.NamespacedNameFrom("projectcontour/otel-collector"), + ServiceName: "contour", + SNI: "", + Timeout: timeout.DurationSetting(5 * time.Second), + OverallSampling: 100, + MaxPathTagLength: 256, + CustomTags: nil, + }, + want: &http.HttpConnectionManager_Tracing{ + OverallSampling: &envoy_type_v3.Percent{ + Value: 100.0, + }, + MaxPathTagLength: wrapperspb.UInt32(256), + CustomTags: nil, + Provider: &envoy_config_trace_v3.Tracing_Http{ + Name: "envoy.tracers.opentelemetry", + ConfigType: &envoy_config_trace_v3.Tracing_Http_TypedConfig{ + TypedConfig: protobuf.MustMarshalAny(&envoy_config_trace_v3.OpenTelemetryConfig{ + GrpcService: &envoy_core_v3.GrpcService{ + TargetSpecifier: &envoy_core_v3.GrpcService_EnvoyGrpc_{ + EnvoyGrpc: &envoy_core_v3.GrpcService_EnvoyGrpc{ + ClusterName: "extension/projectcontour/otel-collector", + Authority: "extension.projectcontour.otel-collector", + }, + }, + Timeout: durationpb.New(5 * time.Second), + }, + ServiceName: "contour", + }), + }, + }, + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := TracingConfig(tc.tracing) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/internal/featuretests/v3/tracing_test.go b/internal/featuretests/v3/tracing_test.go new file mode 100644 index 00000000000..f18c7a75baf --- /dev/null +++ b/internal/featuretests/v3/tracing_test.go @@ -0,0 +1,126 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v3 + +import ( + "testing" + + envoy_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" + contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" + "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" + "github.com/projectcontour/contour/internal/dag" + envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3" + "github.com/projectcontour/contour/internal/featuretests" + "github.com/projectcontour/contour/internal/fixture" + "github.com/projectcontour/contour/internal/k8s" + "github.com/projectcontour/contour/internal/timeout" + xdscache_v3 "github.com/projectcontour/contour/internal/xdscache/v3" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" +) + +func TestTracing(t *testing.T) { + tracingConfig := &dag.TracingConfig{ + ExtensionService: k8s.NamespacedNameFrom("projectcontour/otel-collector"), + ServiceName: "contour", + Timeout: timeout.DefaultSetting(), + OverallSampling: 100, + MaxPathTagLength: 256, + } + withTrace := func(conf *xdscache_v3.ListenerConfig) { + conf.TracingConfig = tracingConfig + } + rh, c, done := setup(t, withTrace) + defer done() + + rh.OnAdd(fixture.NewService("projectcontour/otel-collector"). + WithPorts(corev1.ServicePort{Port: 4317})) + + rh.OnAdd(featuretests.Endpoints("projectcontour", "otel-collector", corev1.EndpointSubset{ + Addresses: featuretests.Addresses("10.244.41.241"), + Ports: featuretests.Ports(featuretests.Port("", 4317)), + })) + + rh.OnAdd(&v1alpha1.ExtensionService{ + ObjectMeta: fixture.ObjectMeta("projectcontour/otel-collector"), + Spec: v1alpha1.ExtensionServiceSpec{ + Services: []v1alpha1.ExtensionServiceTarget{ + {Name: "otel-collector", Port: 4317}, + }, + Protocol: pointer.String("h2c"), + TimeoutPolicy: &contour_api_v1.TimeoutPolicy{ + Response: defaultResponseTimeout.String(), + }, + }, + }) + + rh.OnAdd(fixture.NewService("projectcontour/app-server"). + WithPorts(corev1.ServicePort{Port: 80})) + + rh.OnAdd(featuretests.Endpoints("projectcontour", "app-server", corev1.EndpointSubset{ + Addresses: featuretests.Addresses("10.244.184.102"), + Ports: featuretests.Ports(featuretests.Port("", 80)), + })) + + p := &contour_api_v1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "projectcontour", + Name: "app-server", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + Fqdn: "foo.com", + }, + Routes: []contour_api_v1.Route{ + { + Services: []contour_api_v1.Service{ + { + Name: "app-server", + Port: 80, + }, + }, + }, + }, + }, + } + rh.OnAdd(p) + + httpListener := defaultHTTPListener() + httpListener.FilterChains = envoy_v3.FilterChains(envoy_v3.HTTPConnectionManagerBuilder(). + RouteConfigName(xdscache_v3.ENVOY_HTTP_LISTENER). + MetricsPrefix(xdscache_v3.ENVOY_HTTP_LISTENER). + AccessLoggers(envoy_v3.FileAccessLogEnvoy(xdscache_v3.DEFAULT_HTTP_ACCESS_LOG, "", nil, v1alpha1.LogLevelInfo)). + DefaultFilters(). + Tracing(envoy_v3.TracingConfig(tracingConfig)). + Get(), + ) + + c.Request(listenerType, xdscache_v3.ENVOY_HTTP_LISTENER).Equals(&envoy_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, httpListener), + }) + + c.Request(clusterType).Equals(&envoy_discovery_v3.DiscoveryResponse{ + TypeUrl: clusterType, + Resources: resources(t, + DefaultCluster( + h2cCluster(cluster("extension/projectcontour/otel-collector", "extension/projectcontour/otel-collector", "extension_projectcontour_otel-collector")), + ), + DefaultCluster( + cluster("projectcontour/app-server/80/da39a3ee5e", "projectcontour/app-server", "projectcontour_app-server_80"), + ), + ), + }) +} diff --git a/internal/xdscache/v3/listener.go b/internal/xdscache/v3/listener.go index 6a24fcddc45..a942977925e 100644 --- a/internal/xdscache/v3/listener.go +++ b/internal/xdscache/v3/listener.go @@ -128,6 +128,10 @@ type ListenerConfig struct { // GlobalExternalAuthConfig optionally configures the global external authorization Service to be // used. GlobalExternalAuthConfig *GlobalExternalAuthConfig + + // TracingConfig optionally configures the tracing collector Service to be + // used. + TracingConfig *dag.TracingConfig } type RateLimitConfig struct { @@ -331,8 +335,10 @@ func (c *ListenerCache) OnChange(root *dag.DAG) { MergeSlashes(cfg.MergeSlashes). ServerHeaderTransformation(cfg.ServerHeaderTransformation). NumTrustedHops(cfg.XffNumTrustedHops). + Tracing(envoy_v3.TracingConfig(cfg.TracingConfig)). AddFilter(envoy_v3.GlobalRateLimitFilter(envoyGlobalRateLimitConfig(cfg.RateLimitConfig))). AddFilter(httpGlobalExternalAuthConfig(cfg.GlobalExternalAuthConfig)). + Tracing(envoy_v3.TracingConfig(cfg.TracingConfig)). Get() listeners[listener.Name] = envoy_v3.Listener( @@ -398,6 +404,7 @@ func (c *ListenerCache) OnChange(root *dag.DAG) { MergeSlashes(cfg.MergeSlashes). ServerHeaderTransformation(cfg.ServerHeaderTransformation). NumTrustedHops(cfg.XffNumTrustedHops). + Tracing(envoy_v3.TracingConfig(cfg.TracingConfig)). AddFilter(envoy_v3.GlobalRateLimitFilter(envoyGlobalRateLimitConfig(cfg.RateLimitConfig))). ForwardClientCertificate(forwardClientCertificate). Get() @@ -461,6 +468,7 @@ func (c *ListenerCache) OnChange(root *dag.DAG) { MergeSlashes(cfg.MergeSlashes). ServerHeaderTransformation(cfg.ServerHeaderTransformation). NumTrustedHops(cfg.XffNumTrustedHops). + Tracing(envoy_v3.TracingConfig(cfg.TracingConfig)). AddFilter(envoy_v3.GlobalRateLimitFilter(envoyGlobalRateLimitConfig(cfg.RateLimitConfig))). ForwardClientCertificate(forwardClientCertificate). Get() diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index d3235b002e9..1d856693b6e 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -560,6 +560,51 @@ type Parameters struct { // MetricsParameters holds configurable parameters for Contour and Envoy metrics. Metrics MetricsParameters `yaml:"metrics,omitempty"` + + // Tracing holds the relevant configuration for exporting trace data to OpenTelemetry. + Tracing Tracing `yaml:"tracing,omitempty"` +} + +// Tracing defines properties for exporting trace data to OpenTelemetry. +type Tracing struct { + // IncludePodDetail defines a flag. + // If it is true, contour will add the pod name and namespace to the span of the trace. + // the default is true. + IncludePodDetail *bool `yaml:"includePodDetail,omitempty"` + + // ServiceName defines the name for the service + // contour's default is contour + ServiceName string `yaml:"serviceName,omitempty"` + + // OverallSampling defines the sampling rate of trace data. + // the default value is 100 + OverallSampling float64 `yaml:"overallSampling,omitempty"` + + // OverallSampling defines maximum length of the request path + // to extract and include in the HttpUrl tag. + // the default value is 256 + MaxPathTagLength uint32 `yaml:"maxPathTagLength,omitempty"` + + // CustomTags defines a list of custom tags with unique tag name. + CustomTags []CustomTag `yaml:"customTags,omitempty"` + + // ExtensionService identifies the extension service defining the otle-collector, + // formatted as /. + ExtensionService string `yaml:"extensionService,omitempty"` +} + +// CustomTag defines custom tags with unique tag name +// to create tags for the active span. +type CustomTag struct { + // TagName is the unique name of the custom tag. + TagName string `yaml:"tagName,omitempty"` + + // Literal is a static custom tag value. + Literal string `yaml:"literal,omitempty"` + + // RequestHeaderName indicates which request header + // the label value is obtained from. + RequestHeaderName string `yaml:"requestHeaderName,omitempty"` } // GlobalExternalAuthorizationConfig defines properties of global external authorization. @@ -689,6 +734,47 @@ func (p *MetricsParameters) Validate() error { return nil } +func (t *Tracing) Validate() error { + if t == nil { + return nil + } + if t.OverallSampling == 0 && t.MaxPathTagLength == 0 && t.ExtensionService == "" && t.CustomTags == nil { + return nil + } + + if t.ExtensionService == "" { + return errors.New("tracing.extensionService must be defined") + } + + var customTagNames []string + + for _, customTag := range t.CustomTags { + var fieldCount int + if customTag.TagName == "" { + return errors.New("tracing.customTag.tagName must be defined") + } + + for _, customTagName := range customTagNames { + if customTagName == customTag.TagName { + return fmt.Errorf("tagName %s is duplicate", customTagName) + } + } + + if customTag.Literal != "" { + fieldCount++ + } + + if customTag.RequestHeaderName != "" { + fieldCount++ + } + if fieldCount != 1 { + return errors.New("must set exactly one of Literal or RequestHeaderName") + } + customTagNames = append(customTagNames, customTag.TagName) + } + return nil +} + func (p *MetricsServerParameters) Validate() error { // Check that both certificate and key are provided if either one is provided. if (p.ServerCert != "") != (p.ServerKey != "") { @@ -770,6 +856,10 @@ func (p *Parameters) Validate() error { return err } + if err := p.Tracing.Validate(); err != nil { + return err + } + return p.Listener.Validate() } @@ -817,6 +907,7 @@ func Defaults() Parameters { Listener: ListenerParameters{ ConnectionBalancer: "", }, + Tracing: Tracing{}, } } diff --git a/pkg/config/parameters_test.go b/pkg/config/parameters_test.go index d136eea3467..3ad68b7f3c0 100644 --- a/pkg/config/parameters_test.go +++ b/pkg/config/parameters_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" + "k8s.io/utils/pointer" ) func TestGetenvOr(t *testing.T) { @@ -492,3 +493,80 @@ func TestListenerValidation(t *testing.T) { } require.Error(t, l.Validate()) } + +func TestTracingConfigValidation(t *testing.T) { + var trace *Tracing + require.NoError(t, trace.Validate()) + + trace = &Tracing{ + IncludePodDetail: pointer.Bool(false), + ServiceName: "contour", + OverallSampling: 100, + MaxPathTagLength: 256, + CustomTags: nil, + ExtensionService: "projectcontour/otel-collector", + } + require.NoError(t, trace.Validate()) + + trace = &Tracing{ + IncludePodDetail: pointer.Bool(false), + ServiceName: "contour", + OverallSampling: 100, + MaxPathTagLength: 256, + CustomTags: nil, + } + require.Error(t, trace.Validate()) + + trace = &Tracing{ + IncludePodDetail: pointer.Bool(false), + OverallSampling: 100, + MaxPathTagLength: 256, + CustomTags: nil, + ExtensionService: "projectcontour/otel-collector", + } + require.NoError(t, trace.Validate()) + + trace = &Tracing{ + OverallSampling: 100, + MaxPathTagLength: 256, + CustomTags: []CustomTag{ + { + TagName: "first", + Literal: "literal", + RequestHeaderName: ":path", + }, + }, + ExtensionService: "projectcontour/otel-collector", + } + require.Error(t, trace.Validate()) + + trace = &Tracing{ + OverallSampling: 100, + MaxPathTagLength: 256, + CustomTags: []CustomTag{ + { + Literal: "literal", + }, + }, + ExtensionService: "projectcontour/otel-collector", + } + require.Error(t, trace.Validate()) + + trace = &Tracing{ + IncludePodDetail: pointer.Bool(true), + OverallSampling: 100, + MaxPathTagLength: 256, + CustomTags: []CustomTag{ + { + TagName: "first", + Literal: "literal", + }, + { + TagName: "first", + RequestHeaderName: ":path", + }, + }, + ExtensionService: "projectcontour/otel-collector", + } + require.Error(t, trace.Validate()) +} diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html index 9905210fff6..0b17d3871b2 100644 --- a/site/content/docs/main/config/api-reference.html +++ b/site/content/docs/main/config/api-reference.html @@ -4790,6 +4790,20 @@

ContourConfiguration

Contour’s default is { address: “0.0.0.0”, port: 8000 }.

+ + +tracing +
+ + +TracingConfig + + + + +

Tracing defines properties for exporting trace data to OpenTelemetry.

+ + @@ -5484,6 +5498,20 @@

ContourConfiguratio

Contour’s default is { address: “0.0.0.0”, port: 8000 }.

+ + +tracing +
+ + +TracingConfig + + + + +

Tracing defines properties for exporting trace data to OpenTelemetry.

+ +

ContourConfigurationStatus @@ -5757,6 +5785,61 @@

ContourSettings +

CustomTag +

+

+

CustomTag defines custom tags with unique tag name +to create tags for the active span.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+tagName +
+ +string + +
+

TagName is the unique name of the custom tag.

+
+literal +
+ +string + +
+(Optional) +

Literal is a static custom tag value.

+
+requestHeaderName +
+ +string + +
+(Optional) +

RequestHeaderName indicates which request header +the label value is obtained from.

+

DaemonSetSettings

@@ -7311,7 +7394,8 @@

NamespacedName EnvoyConfig, GatewayConfig, HTTPProxyConfig, -RateLimitServiceConfig) +RateLimitServiceConfig, +TracingConfig)

NamespacedName defines the namespace/name of the Kubernetes resource referred from the config file. @@ -7981,6 +8065,111 @@

TimeoutParameters +

TracingConfig +

+

+(Appears on: +ContourConfigurationSpec) +

+

+

TracingConfig defines properties for exporting trace data to OpenTelemetry.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+includePodDetail +
+ +bool + +
+(Optional) +

IncludePodDetail defines a flag. +If it is true, contour will add the pod name and namespace to the span of the trace. +the default is true.

+
+serviceName +
+ +string + +
+

ServiceName defines the name for the service. +contour’s default is contour

+
+overallSampling +
+ +string + +
+(Optional) +

OverallSampling defines the sampling rate of trace data. +contour’s default is 100

+
+maxPathTagLength +
+ +uint32 + +
+(Optional) +

OverallSampling defines maximum length of the request path +to extract and include in the HttpUrl tag. +contour’s default is 256.

+
+customTags +
+ + +[]*github.com/projectcontour/contour/apis/projectcontour/v1alpha1.CustomTag + + +
+(Optional) +

CustomTags defines a list of custom tags with unique tag name.

+
+extensionService +
+ + +NamespacedName + + +
+

ExtensionService identifies the extension service defining the otle-collector.

+

WorkloadType (string alias)