Files
higress/pkg/ingress/kube/configmap/tracing.go

409 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// 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 configmap
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"sync/atomic"
"github.com/alibaba/higress/pkg/ingress/kube/util"
. "github.com/alibaba/higress/pkg/ingress/log"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/schema/gvk"
)
const (
higressTracingEnvoyFilterName = "higress-config-tracing"
defaultTimeout = 500
defaultSampling = 100.0
)
type Tracing struct {
// Flag to control trace
Enable bool `json:"enable,omitempty"`
// The percentage of requests (0.0 - 100.0) that will be randomly selected for trace generation,
// if not requested by the client or not forced. Default is 100.0.
Sampling float64 `json:"sampling,omitempty"`
// The timeout for the gRPC request. Default is 500ms
Timeout int32 `json:"timeout,omitempty"`
// The tracer implementation to be used by Envoy.
//
// Types that are assignable to Tracer:
Zipkin *Zipkin `json:"zipkin,omitempty"`
Skywalking *Skywalking `json:"skywalking,omitempty"`
OpenTelemetry *OpenTelemetry `json:"opentelemetry,omitempty"`
}
// Zipkin defines configuration for a Zipkin tracer.
type Zipkin struct {
// Address of the Zipkin service (e.g. _zipkin:9411_).
Service string `json:"service,omitempty"`
Port string `json:"port,omitempty"`
}
// Skywalking Defines configuration for a Skywalking tracer.
type Skywalking struct {
// Address of the Skywalking tracer.
Service string `json:"service,omitempty"`
Port string `json:"port,omitempty"`
// The access token
AccessToken string `json:"access_token,omitempty"`
}
// OpenTelemetry Defines configuration for a OpenTelemetry tracer.
type OpenTelemetry struct {
// Address of OpenTelemetry tracer.
Service string `json:"service,omitempty"`
Port string `json:"port,omitempty"`
}
func validServiceAndPort(service string, port string) bool {
if len(service) == 0 || len(port) == 0 {
return false
}
return true
}
func validTracing(t *Tracing) error {
if t == nil {
return nil
}
if t.Timeout <= 0 {
return errors.New("timeout can not be less than zero")
}
if t.Sampling < 0 || t.Sampling > 100 {
return errors.New("sampling must be in (0.0 - 100.0)")
}
tracerNum := 0
if t.Zipkin != nil {
if validServiceAndPort(t.Zipkin.Service, t.Zipkin.Port) {
tracerNum++
} else {
return errors.New("zipkin service and port can not be empty")
}
}
if t.Skywalking != nil {
if validServiceAndPort(t.Skywalking.Service, t.Skywalking.Port) {
tracerNum++
} else {
return errors.New("skywalking service and port can not be empty")
}
}
if t.OpenTelemetry != nil {
if validServiceAndPort(t.OpenTelemetry.Service, t.OpenTelemetry.Port) {
tracerNum++
} else {
return errors.New("opentelemetry service and port can not be empty")
}
}
if tracerNum != 1 {
return errors.New("only one of skywalkingzipkin and opentelemetry configuration can be set")
}
return nil
}
func compareTracing(old *Tracing, new *Tracing) (Result, error) {
if old == nil && new == nil {
return ResultNothing, nil
}
if new == nil {
return ResultDelete, nil
}
if !reflect.DeepEqual(old, new) {
return ResultReplace, nil
}
return ResultNothing, nil
}
func deepCopyTracing(tracing *Tracing) (*Tracing, error) {
newTracing := NewDefaultTracing()
bytes, err := json.Marshal(tracing)
if err != nil {
return nil, err
}
err = json.Unmarshal(bytes, newTracing)
return newTracing, err
}
func NewDefaultTracing() *Tracing {
tracing := &Tracing{
Enable: false,
Timeout: defaultTimeout,
Sampling: defaultSampling,
}
return tracing
}
type TracingController struct {
Namespace string
tracing atomic.Value
Name string
eventHandler ItemEventHandler
}
func NewTracingController(namespace string) *TracingController {
tracingMgr := &TracingController{
Namespace: namespace,
tracing: atomic.Value{},
Name: "tracing",
}
tracingMgr.SetTracing(NewDefaultTracing())
return tracingMgr
}
func (t *TracingController) SetTracing(tracing *Tracing) {
t.tracing.Store(tracing)
}
func (t *TracingController) GetTracing() *Tracing {
value := t.tracing.Load()
if value != nil {
if tracing, ok := value.(*Tracing); ok {
return tracing
}
}
return nil
}
func (t *TracingController) GetName() string {
return t.Name
}
func (t *TracingController) AddOrUpdateHigressConfig(name util.ClusterNamespacedName, old *HigressConfig, new *HigressConfig) error {
if err := validTracing(new.Tracing); err != nil {
IngressLog.Errorf("data:%+v convert to tracing , error: %+v", new.Tracing, err)
return nil
}
result, _ := compareTracing(old.Tracing, new.Tracing)
switch result {
case ResultReplace:
if newTracing, err := deepCopyTracing(new.Tracing); err != nil {
IngressLog.Infof("tracing deepcopy error:%v", err)
} else {
t.SetTracing(newTracing)
IngressLog.Infof("AddOrUpdate Higress config tracing")
t.eventHandler(higressTracingEnvoyFilterName)
IngressLog.Infof("send event with filter name:%s", higressTracingEnvoyFilterName)
}
case ResultDelete:
t.SetTracing(NewDefaultTracing())
IngressLog.Infof("Delete Higress config tracing")
t.eventHandler(higressTracingEnvoyFilterName)
IngressLog.Infof("send event with filter name:%s", higressTracingEnvoyFilterName)
}
return nil
}
func (t *TracingController) ValidHigressConfig(higressConfig *HigressConfig) error {
if higressConfig == nil {
return nil
}
if higressConfig.Tracing == nil {
return nil
}
return validTracing(higressConfig.Tracing)
}
func (t *TracingController) RegisterItemEventHandler(eventHandler ItemEventHandler) {
t.eventHandler = eventHandler
}
func (t *TracingController) ConstructEnvoyFilters() ([]*config.Config, error) {
configs := make([]*config.Config, 0)
tracing := t.GetTracing()
namespace := t.Namespace
if tracing == nil {
return configs, nil
}
if tracing.Enable == false {
return configs, nil
}
tracingConfig := t.constructTracingTracer(tracing, namespace)
if len(tracingConfig) == 0 {
return configs, nil
}
config := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: higressTracingEnvoyFilterName,
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(tracingConfig),
},
},
{
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
Name: "envoy.filters.http.router",
},
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(`{
"name":"envoy.filters.http.router",
"typed_config":{
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router",
"start_child_span": true
}
}`),
},
},
},
},
}
configs = append(configs, config)
return configs, nil
}
func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace string) string {
tracingConfig := ""
timeout := float32(tracing.Timeout) / 1000
if tracing.Skywalking != nil {
skywalking := tracing.Skywalking
tracingConfig = fmt.Sprintf(`{
"name": "envoy.filters.network.http_connection_manager",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"tracing": {
"provider": {
"name": "envoy.tracers.skywalking",
"typed_config": {
"@type": "type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig",
"client_config": {
"service_name": "higress-gateway.%s",
"backend_token": "%s"
},
"grpc_service": {
"envoy_grpc": {
"cluster_name": "outbound|%s||%s"
},
"timeout": "%.3fs"
}
}
},
"random_sampling": {
"value": %.1f
}
}
}
}`, namespace, skywalking.AccessToken, skywalking.Port, skywalking.Service, timeout, tracing.Sampling)
}
if tracing.Zipkin != nil {
zipkin := tracing.Zipkin
tracingConfig = fmt.Sprintf(`{
"name": "envoy.filters.network.http_connection_manager",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"tracing": {
"provider": {
"name": "envoy.tracers.zipkin",
"typed_config": {
"@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
"collector_cluster": "outbound|%s||%s",
"collector_endpoint": "/api/v2/spans",
"collector_hostname": "higress-gateway",
"collector_endpoint_version": "HTTP_JSON",
"split_spans_for_request": true
}
},
"random_sampling": {
"value": %.1f
}
}
}
}`, zipkin.Port, zipkin.Service, tracing.Sampling)
}
if tracing.OpenTelemetry != nil {
opentelemetry := tracing.OpenTelemetry
tracingConfig = fmt.Sprintf(`{
"name": "envoy.filters.network.http_connection_manager",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"tracing": {
"provider": {
"name": "envoy.tracers.opentelemetry",
"typed_config": {
"@type": "type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig",
"service_name": "higress-gateway.%s"
"grpc_service": {
"envoy_grpc": {
"cluster_name": "outbound|%s||%s"
},
"timeout": "%.3fs"
}
}
},
"random_sampling": {
"value": %.1f
}
}
}
}`, namespace, opentelemetry.Port, opentelemetry.Service, timeout, tracing.Sampling)
}
return tracingConfig
}