diff --git a/k8sutils/pkg/container/container.go b/k8sutils/pkg/container/container.go index 602bb9a51..60cc911a3 100644 --- a/k8sutils/pkg/container/container.go +++ b/k8sutils/pkg/container/container.go @@ -1,12 +1,33 @@ package container import ( + "errors" "strings" "github.com/odigos-io/odigos/common" v1 "k8s.io/api/core/v1" ) +var ( + ErrDeviceNotDetected = errors.New("device not detected") + ErrContainerNotInPodSpec = errors.New("container not found in pod spec") +) + +func LanguageSdkFromPodContainer(pod *v1.Pod, containerName string) (common.ProgrammingLanguage, common.OtelSdk, error) { + for _, container := range pod.Spec.Containers { + if container.Name == containerName { + language, sdk, found := GetLanguageAndOtelSdk(container) + if !found { + return common.UnknownProgrammingLanguage, common.OtelSdk{}, ErrDeviceNotDetected + } + + return language, sdk, nil + } + } + + return common.UnknownProgrammingLanguage, common.OtelSdk{}, ErrContainerNotInPodSpec +} + func GetLanguageAndOtelSdk(container v1.Container) (common.ProgrammingLanguage, common.OtelSdk, bool) { deviceName := podContainerDeviceName(container) if deviceName == nil { diff --git a/k8sutils/pkg/workload/ownerreference.go b/k8sutils/pkg/workload/ownerreference.go index fbb061ee9..0f9795eaa 100644 --- a/k8sutils/pkg/workload/ownerreference.go +++ b/k8sutils/pkg/workload/ownerreference.go @@ -1,16 +1,55 @@ package workload import ( + "context" + "errors" "fmt" "strings" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// PodWorkloadObjectOrError is the same as PodWorkloadObject but returns an error if the workload is not found. +func PodWorkloadObjectOrError(ctx context.Context, pod *corev1.Pod) (*PodWorkload, error) { + pw, err := PodWorkloadObject(ctx, pod) + if err != nil { + return nil, err + } + + if pw == nil { + return nil, fmt.Errorf("workload not found for pod %s/%s", pod.Namespace, pod.Name) + } + + return pw, nil +} + +// PodWorkload returns the workload object that manages the provided pod. +// If the pod is not owned by a controller, it returns a nil workload with no error. +func PodWorkloadObject(ctx context.Context, pod *corev1.Pod) (*PodWorkload, error) { + for _, owner := range pod.OwnerReferences { + workloadName, workloadKind, err := GetWorkloadFromOwnerReference(owner) + if err != nil { + if errors.Is(err, ErrKindNotSupported) { + continue + } + return nil, IgnoreErrorKindNotSupported(err) + } + + return &PodWorkload{ + Name: workloadName, + Kind: workloadKind, + Namespace: pod.Namespace, + }, nil + } + + // Pod does not necessarily have to be managed by a controller + return nil, nil +} + // GetWorkloadFromOwnerReference retrieves both the workload name and workload kind // from the provided owner reference. func GetWorkloadFromOwnerReference(ownerReference metav1.OwnerReference) (workloadName string, workloadKind WorkloadKind, err error) { - return GetWorkloadNameAndKind(ownerReference.Name, ownerReference.Kind) } diff --git a/odiglet/cmd/main.go b/odiglet/cmd/main.go index 967ff0121..719d11feb 100644 --- a/odiglet/cmd/main.go +++ b/odiglet/cmd/main.go @@ -6,25 +6,22 @@ import ( "os" "sync" - detector "github.com/odigos-io/odigos/odiglet/pkg/detector" + "github.com/odigos-io/odigos/odiglet/pkg/ebpf" "github.com/odigos-io/odigos/odiglet/pkg/ebpf/sdks" "github.com/odigos-io/odigos/odiglet/pkg/instrumentation/fs" "github.com/kubevirt/device-plugin-manager/pkg/dpm" "github.com/odigos-io/odigos/common" k8senv "github.com/odigos-io/odigos/k8sutils/pkg/env" - "github.com/odigos-io/odigos/odiglet/pkg/ebpf" "github.com/odigos-io/odigos/odiglet/pkg/env" "github.com/odigos-io/odigos/odiglet/pkg/instrumentation" "github.com/odigos-io/odigos/odiglet/pkg/instrumentation/instrumentlang" "github.com/odigos-io/odigos/odiglet/pkg/kube" "github.com/odigos-io/odigos/odiglet/pkg/log" "github.com/odigos-io/odigos/opampserver/pkg/server" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager/signals" _ "net/http/pprof" @@ -45,8 +42,8 @@ func odigletInitPhase() { type odiglet struct { clientset *kubernetes.Clientset mgr ctrl.Manager - ctx context.Context - ebpfDirectors ebpf.DirectorsMap + ebpfManager *ebpf.Manager + configUpdates chan<- ebpf.ConfigUpdate } func newOdiglet() (*odiglet, error) { @@ -66,14 +63,22 @@ func newOdiglet() (*odiglet, error) { return nil, fmt.Errorf("Failed to create controller-runtime manager %w", err) } - ctx := signals.SetupSignalHandler() - - ebpfDirectors, err := initEbpf(ctx, mgr.GetClient(), mgr.GetScheme()) + ebpfManager, err := ebpf.NewManager( + mgr.GetClient(), + log.Logger, + map[ebpf.OtelDistribution]ebpf.Factory{ + ebpf.OtelDistribution{ + Language: common.GoProgrammingLanguage, + OtelSdk: common.OtelSdkEbpfCommunity, + }: sdks.NewGoInstrumentationFactory(), + }, + ) if err != nil { - return nil, fmt.Errorf("Failed to init eBPF director %w", err) + return nil, fmt.Errorf("Failed to create ebpf manager %w", err) } - err = kube.SetupWithManager(mgr, ebpfDirectors, clientset) + configUpdates := ebpfManager.ConfigUpdates() + err = kube.SetupWithManager(mgr, nil, clientset, configUpdates) if err != nil { return nil, fmt.Errorf("Failed to setup controller-runtime manager %w", err) } @@ -81,19 +86,19 @@ func newOdiglet() (*odiglet, error) { return &odiglet{ clientset: clientset, mgr: mgr, - ctx: ctx, - ebpfDirectors: ebpfDirectors, + ebpfManager: ebpfManager, + configUpdates: configUpdates, }, nil } -func (o *odiglet) run() { +func (o *odiglet) run(ctx context.Context) { var wg sync.WaitGroup // Start pprof server wg.Add(1) go func() { defer wg.Done() - err := common.StartPprofServer(o.ctx, log.Logger) + err := common.StartPprofServer(ctx, log.Logger) if err != nil { log.Logger.Error(err, "Failed to start pprof server") } else { @@ -111,16 +116,14 @@ func (o *odiglet) run() { log.Logger.V(0).Info("Device manager exited") }() - procEvents := make(chan detector.ProcessEvent) wg.Add(1) go func() { defer wg.Done() - err := detector.StartRuntimeDetector(o.ctx, log.Logger, procEvents) + err := o.ebpfManager.Run(ctx) if err != nil { - log.Logger.Error(err, "Failed to start runtime detector") - os.Exit(-1) + log.Logger.Error(err, "Failed to run ebpf manager") } - log.Logger.V(0).Info("Runtime detector exited") + log.Logger.V(0).Info("Ebpf manager exited") }() // start OpAmp server @@ -128,7 +131,7 @@ func (o *odiglet) run() { wg.Add(1) go func() { defer wg.Done() - err := server.StartOpAmpServer(o.ctx, log.Logger, o.mgr, o.clientset, env.Current.NodeName, odigosNs) + err := server.StartOpAmpServer(ctx, log.Logger, o.mgr, o.clientset, env.Current.NodeName, odigosNs) if err != nil { log.Logger.Error(err, "Failed to start opamp server") } @@ -139,17 +142,19 @@ func (o *odiglet) run() { wg.Add(1) go func() { defer wg.Done() - err := o.mgr.Start(o.ctx) + err := o.mgr.Start(ctx) if err != nil { log.Logger.Error(err, "error starting kube manager") + } else { + log.Logger.V(0).Info("Kube manager exited") + } + // the manager is stopped, it is now safe to close the config updates channel + if o.configUpdates != nil { + close(o.configUpdates) } - log.Logger.V(0).Info("Kube manager exited") }() - <-o.ctx.Done() - for _, director := range o.ebpfDirectors { - director.Shutdown() - } + <-ctx.Done() wg.Wait() } @@ -176,7 +181,9 @@ func main() { log.Logger.Error(err, "Failed to initialize odiglet") os.Exit(1) } - o.run() + + ctx := signals.SetupSignalHandler() + o.run(ctx) log.Logger.V(0).Info("odiglet exiting") } @@ -216,16 +223,3 @@ func runDeviceManager(clientset *kubernetes.Clientset) { manager := dpm.NewManager(lister) manager.Run() } - -func initEbpf(ctx context.Context, client client.Client, scheme *runtime.Scheme) (ebpf.DirectorsMap, error) { - goInstrumentationFactory := sdks.NewGoInstrumentationFactory(client) - goDirector := ebpf.NewEbpfDirector(ctx, client, scheme, common.GoProgrammingLanguage, goInstrumentationFactory) - goDirectorKey := ebpf.DirectorKey{ - Language: common.GoProgrammingLanguage, - OtelSdk: common.OtelSdkEbpfCommunity, - } - - return ebpf.DirectorsMap{ - goDirectorKey: goDirector, - }, nil -} diff --git a/odiglet/go.mod b/odiglet/go.mod index 84ab0cd62..b27b41467 100644 --- a/odiglet/go.mod +++ b/odiglet/go.mod @@ -1,6 +1,6 @@ module github.com/odigos-io/odigos/odiglet -go 1.22.0 +go 1.22.7 require ( github.com/go-logr/logr v1.4.2 @@ -14,13 +14,14 @@ require ( github.com/odigos-io/odigos/opampserver v0.0.0 github.com/odigos-io/odigos/procdiscovery v0.0.0 github.com/odigos-io/opentelemetry-zap-bridge v0.0.5 - github.com/odigos-io/runtime-detector v0.0.2 + github.com/odigos-io/runtime-detector v0.0.3 github.com/stretchr/testify v1.10.0 - go.opentelemetry.io/auto v0.17.0-alpha - go.opentelemetry.io/otel v1.31.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 + go.opentelemetry.io/auto v0.18.0-alpha + go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.67.1 + golang.org/x/sync v0.9.0 + google.golang.org/grpc v1.68.0 k8s.io/api v0.31.0 k8s.io/apimachinery v0.31.0 k8s.io/client-go v0.31.0 @@ -52,7 +53,7 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -66,45 +67,45 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.20.4 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.60.0 // indirect + github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/collector/pdata v1.18.0 // indirect - go.opentelemetry.io/contrib/bridges/prometheus v0.56.0 // indirect - go.opentelemetry.io/contrib/exporters/autoexport v0.56.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.7.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.53.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 // indirect - go.opentelemetry.io/otel/log v0.7.0 // indirect - go.opentelemetry.io/otel/metric v1.31.0 // indirect - go.opentelemetry.io/otel/sdk v1.31.0 // indirect - go.opentelemetry.io/otel/sdk/log v0.7.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect - go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.opentelemetry.io/collector/pdata v1.19.0 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect + go.opentelemetry.io/otel/log v0.8.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/arch v0.11.0 // indirect + golang.org/x/arch v0.12.0 // indirect golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.26.0 // indirect + golang.org/x/sys v0.27.0 // indirect golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/odiglet/go.sum b/odiglet/go.sum index cb986b4eb..49ee43d09 100644 --- a/odiglet/go.sum +++ b/odiglet/go.sum @@ -176,8 +176,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -256,8 +256,8 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/odigos-io/opentelemetry-zap-bridge v0.0.5 h1:UDEKtgab42nGVSvA/F3lLKUYFPETtpl7kpxM9BKBlWk= github.com/odigos-io/opentelemetry-zap-bridge v0.0.5/go.mod h1:K98wHhktQ6vCTqeFztcpBDtPTe88vxVgZFlbeGobt24= -github.com/odigos-io/runtime-detector v0.0.2 h1:8G6KOBxlmP0vu+/62lHbvjSDARP+DML0gk5ypcdZOpg= -github.com/odigos-io/runtime-detector v0.0.2/go.mod h1:m8GfdG6pET0K/8aoZwnX+MkYz8oVroKs8HzRnkuJWt4= +github.com/odigos-io/runtime-detector v0.0.3 h1:hUsdSmsPJ4ZrXofEzeNsfiids99RN+O6XcKxsgpZirE= +github.com/odigos-io/runtime-detector v0.0.3/go.mod h1:m8GfdG6pET0K/8aoZwnX+MkYz8oVroKs8HzRnkuJWt4= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -284,8 +284,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -294,8 +294,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= -github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -333,50 +333,50 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/auto v0.17.0-alpha h1:ppkt9JDK1nU9G2fERU9NbmQcm4tNzif8LC+8IteqBiI= -go.opentelemetry.io/auto v0.17.0-alpha/go.mod h1:x5tU1bUgUYMlfqfd7lWj9ZH7DbWG0opxfrejWmEAObY= -go.opentelemetry.io/collector/pdata v1.18.0 h1:/yg2rO2dxqDM2p6GutsMCxXN6sKlXwyIz/ZYyUPONBg= -go.opentelemetry.io/collector/pdata v1.18.0/go.mod h1:Ox1YVLe87cZDB/TL30i4SUz1cA5s6AM6SpFMfY61ICs= -go.opentelemetry.io/contrib/bridges/prometheus v0.56.0 h1:ax2MzrA26l3LTS2NRnagkbeKDrW4SM8VcAubasnpYqs= -go.opentelemetry.io/contrib/bridges/prometheus v0.56.0/go.mod h1:+aiuB6jaKqSb5xaY7sOpGZEMIgjL0sxXfIW1PQmp5d0= -go.opentelemetry.io/contrib/exporters/autoexport v0.56.0 h1:2k73WaZ+jHYcK3lLAC3CJ8viT/LqkIcDDUWpbbYbZK0= -go.opentelemetry.io/contrib/exporters/autoexport v0.56.0/go.mod h1:RAHAFqVEQ+iKEAPgm6z+Gnsi0Fd5MDuqnD5T3Ms6Kg4= -go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.7.0 h1:iNba3cIZTDPB2+IAbVY/3TUN+pCCLrNYo2GaGtsKBak= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.7.0/go.mod h1:l5BDPiZ9FbeejzWTAX6BowMzQOM/GeaUQ6lr3sOcSkc= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0 h1:mMOmtYie9Fx6TSVzw4W+NTpvoaS1JWWga37oI1a/4qQ= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0/go.mod h1:yy7nDsMMBUkD+jeekJ36ur5f3jJIrmCwUrY67VFhNpA= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 h1:ZsXq73BERAiNuuFXYqP4MR5hBrjXfMGSO+Cx7qoOZiM= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0/go.mod h1:hg1zaDMpyZJuUzjFxFsRYBoccE86tM9Uf4IqNMUxvrY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= -go.opentelemetry.io/otel/exporters/prometheus v0.53.0 h1:QXobPHrwiGLM4ufrY3EOmDPJpo2P90UuFau4CDPJA/I= -go.opentelemetry.io/otel/exporters/prometheus v0.53.0/go.mod h1:WOAXGr3D00CfzmFxtTV1eR0GpoHuPEu+HJT8UWW2SIU= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0 h1:TwmL3O3fRR80m8EshBrd8YydEZMcUCsZXzOUlnFohwM= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0/go.mod h1:tH98dDv5KPmPThswbXA0fr0Lwfs+OhK8HgaCo7PjRrk= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 h1:HZgBIps9wH0RDrwjrmNa3DVbNRW60HEhdzqZFyAp3fI= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0/go.mod h1:RDRhvt6TDG0eIXmonAx5bd9IcwpqCkziwkOClzWKwAQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= -go.opentelemetry.io/otel/log v0.7.0 h1:d1abJc0b1QQZADKvfe9JqqrfmPYQCz2tUSO+0XZmuV4= -go.opentelemetry.io/otel/log v0.7.0/go.mod h1:2jf2z7uVfnzDNknKTO9G+ahcOAyWcp1fJmk/wJjULRo= -go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= -go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= -go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/sdk/log v0.7.0 h1:dXkeI2S0MLc5g0/AwxTZv6EUEjctiH8aG14Am56NTmQ= -go.opentelemetry.io/otel/sdk/log v0.7.0/go.mod h1:oIRXpW+WD6M8BuGj5rtS0aRu/86cbDV/dAfNaZBIjYM= -go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= -go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= -go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= -go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/auto v0.18.0-alpha h1:9M1AGyBsuCxWQYVvBIib+R/xql75fWEtg/YIuBcLyjw= +go.opentelemetry.io/auto v0.18.0-alpha/go.mod h1:iQ+gXAztcOYqD7QgjvhRGTeO6b+6e8PUmSc4pS4uyF4= +go.opentelemetry.io/collector/pdata v1.19.0 h1:jmnU5R8TOCbwRr4B8sjdRxM7L5WnEKlQWX1dtLYxIbE= +go.opentelemetry.io/collector/pdata v1.19.0/go.mod h1:Ox1YVLe87cZDB/TL30i4SUz1cA5s6AM6SpFMfY61ICs= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -388,8 +388,8 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= -golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= +golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -457,8 +457,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -486,16 +486,16 @@ golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -555,10 +555,10 @@ google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBr google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200916143405-f6a2fa72f0c4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -567,8 +567,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/odiglet/pkg/detector/detector.go b/odiglet/pkg/detector/detector.go index c2f482395..d1d9a477c 100644 --- a/odiglet/pkg/detector/detector.go +++ b/odiglet/pkg/detector/detector.go @@ -2,9 +2,7 @@ package detector import ( "context" - "fmt" "log/slog" - "sync" "github.com/go-logr/logr" "github.com/odigos-io/odigos/common/envOverwrite" @@ -15,37 +13,20 @@ import ( type ProcessEvent = detector.ProcessEvent -func StartRuntimeDetector(ctx context.Context, logger logr.Logger, events chan ProcessEvent) error { - detector, err := newDetector(ctx, logger, events) - if err != nil { - return fmt.Errorf("failed to create runtime detector: %w", err) - } - - var wg sync.WaitGroup - var runError error - - wg.Add(1) - go func() { - defer wg.Done() - readProcEventsLoop(logger, events) - }() - - wg.Add(1) - go func() { - defer wg.Done() - runError = detector.Run(ctx) - }() +type Detector = detector.Detector - wg.Wait() - return runError -} +const ( + ProcessExecEvent = detector.ProcessExecEvent + ProcessExitEvent = detector.ProcessExitEvent +) -func newDetector(ctx context.Context, logger logr.Logger, events chan ProcessEvent) (*detector.Detector, error) { +func NewK8SProcDetector(ctx context.Context, logger logr.Logger, events chan<- ProcessEvent) (*detector.Detector, error) { sLogger := slog.New(logr.ToSlogHandler(logger)) opts := []detector.DetectorOption{ detector.WithLogger(sLogger), detector.WithEnvironments(relevantEnvVars()...), + detector.WithEnvPrefixFilter(consts.OdigosEnvVarPodName), } detector, err := detector.NewDetector(ctx, events, opts...) @@ -56,27 +37,6 @@ func newDetector(ctx context.Context, logger logr.Logger, events chan ProcessEve return detector, nil } -func readProcEventsLoop(l logr.Logger, events chan ProcessEvent) { - l = l.WithName("process detector") - for e := range events { - switch e.EventType { - case detector.ProcessExecEvent: - l.Info("detected new process", - "pid", e.PID, - "cmd", e.ExecDetails.CmdLine, - "exeName", e.ExecDetails.ExeName, - "exeLink", e.ExecDetails.ExeLink, - "envs", e.ExecDetails.Environments, - "container PID", e.ExecDetails.ContainerProcessID, - ) - case detector.ProcessExitEvent: - l.Info("detected process exit", - "pid", e.PID, - ) - } - } -} - func relevantEnvVars() []string { // env vars related to language versions versionEnvs := process.LangsVersionEnvs diff --git a/odiglet/pkg/ebpf/director.go b/odiglet/pkg/ebpf/director.go index 0d508056c..625c5b0c4 100644 --- a/odiglet/pkg/ebpf/director.go +++ b/odiglet/pkg/ebpf/director.go @@ -47,6 +47,8 @@ type InstrumentationFactory[T OtelEbpfSdk] interface { } // Director manages the instrumentation for a specific SDK in a specific language +// +// Deprecated: this will be removed once we fully move to the process event based approach type Director interface { Language() common.ProgrammingLanguage Instrument(ctx context.Context, pid int, podDetails types.NamespacedName, podWorkload *workload.PodWorkload, appName string, containerName string) error @@ -71,14 +73,6 @@ type podDetails[T OtelEbpfSdk] struct { InstrumentedProcesses []*InstrumentedProcess[T] } -type InstrumentationStatusReason string - -const ( - FailedToLoad InstrumentationStatusReason = "FailedToLoad" - FailedToInitialize InstrumentationStatusReason = "FailedToInitialize" - LoadedSuccessfully InstrumentationStatusReason = "LoadedSuccessfully" -) - // CleanupInterval is the interval in which the director will check if the instrumented processes are still running // and clean up the resources associated to the ones that are not. // It is not const for testing purposes. @@ -94,6 +88,7 @@ type instrumentationStatus struct { Pid int } +// Deprecated: this will be removed once we fully move to the process event based approach type EbpfDirector[T OtelEbpfSdk] struct { mux sync.Mutex @@ -127,6 +122,7 @@ type DirectorKey struct { common.OtelSdk } +// Deprecated: this will be removed once we fully move to the process event based approach type DirectorsMap map[DirectorKey]Director func NewEbpfDirector[T OtelEbpfSdk](ctx context.Context, client client.Client, scheme *runtime.Scheme, language common.ProgrammingLanguage, instrumentationFactory InstrumentationFactory[T]) *EbpfDirector[T] { diff --git a/odiglet/pkg/ebpf/factory.go b/odiglet/pkg/ebpf/factory.go new file mode 100644 index 000000000..4d0098b60 --- /dev/null +++ b/odiglet/pkg/ebpf/factory.go @@ -0,0 +1,61 @@ +package ebpf + +import ( + "context" + + odigosv1 "github.com/odigos-io/odigos/api/odigos/v1alpha1" + "github.com/odigos-io/odigos/common" + + "go.opentelemetry.io/otel/attribute" +) + +// Settings is used to pass initial configuration to the instrumentation +type Settings struct { + // ServiceName is the name of the service that is being instrumented + // It will be used to populate the service.name resource attribute. + ServiceName string + // ResourceAttributes can be used to pass additional resource attributes to the instrumentation + // These attributes will be added to the resource attributes of the telemetry data. + ResourceAttributes []attribute.KeyValue + // InitialConfig is the initial configuration that should be applied to the instrumentation, + // it can be used to enable/disable specific instrumentation libraries, configure sampling, etc. + InitialConfig *odigosv1.SdkConfig +} + +// Factory is used to create an Instrumentation +type Factory interface { + // CreateInstrumentation will initialize the instrumentation for the given process. + // Setting can be used to pass initial configuration to the instrumentation. + CreateInstrumentation(ctx context.Context, pid int, settings Settings) (Instrumentation, error) +} + +// OtelDistribution is a customized version of an OpenTelemetry component. +// see https://opentelemetry.io/docs/concepts/distributions and https://github.com/odigos-io/odigos/pull/1776#discussion_r1853367917 for more information. +// TODO: This should be moved to a common package, since it will require a bigger refactor across multiple components, +// we use this local definition for now. +type OtelDistribution struct { + Language common.ProgrammingLanguage + OtelSdk common.OtelSdk +} + +// Instrumentation is used to instrument a running process +type Instrumentation interface { + // Loads the relevant probes, and will perform any initialization required + // for the instrumentation to be ready to run. + // For eBPF, this will load the probes into the kernel + // In case of a failure, an error will be returned and all the resources will be cleaned up. + Load(ctx context.Context) error + + // Run will attach the probes to the relevant process, and will start the instrumentation. + // It is a blocking call, and will return only when the instrumentation is stopped. + // During the run, telemetry will be collected from the probes and sent with the configured exporter. + // Run will return when either a fatal error occurs, the context is canceled, or Close is called. + Run(ctx context.Context) error + + // Close will stop the instrumentation (Stop the Run function) and clean up all the resources associated with it. + // When it returns, the instrumentation is stopped and all resources are cleaned up. + Close(ctx context.Context) error + + // ApplyConfig will send a configuration update to the instrumentation. + ApplyConfig(ctx context.Context, config *odigosv1.SdkConfig) error +} diff --git a/odiglet/pkg/ebpf/manager.go b/odiglet/pkg/ebpf/manager.go new file mode 100644 index 000000000..bc31f596c --- /dev/null +++ b/odiglet/pkg/ebpf/manager.go @@ -0,0 +1,434 @@ +package ebpf + +import ( + "context" + "errors" + "fmt" + + "golang.org/x/sync/errgroup" + + "github.com/go-logr/logr" + odigosv1 "github.com/odigos-io/odigos/api/odigos/v1alpha1" + "github.com/odigos-io/odigos/common" + "github.com/odigos-io/odigos/k8sutils/pkg/consts" + odgiosK8s "github.com/odigos-io/odigos/k8sutils/pkg/container" + instance "github.com/odigos-io/odigos/k8sutils/pkg/instrumentation_instance" + "github.com/odigos-io/odigos/k8sutils/pkg/workload" + workloadUtils "github.com/odigos-io/odigos/k8sutils/pkg/workload" + "github.com/odigos-io/odigos/odiglet/pkg/detector" + "github.com/odigos-io/odigos/odiglet/pkg/kube/utils" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + ErrNoInstrumentationFactory = errors.New("no ebpf factory found") +) + +const ( + configUpdatesBufferSize = 10 +) + +type errRequiredEnvVarNotFound struct { + envVarName string +} + +func (e *errRequiredEnvVarNotFound) Error() string { + return fmt.Sprintf("required environment variable not found: %s", e.envVarName) +} + +var _ error = &errRequiredEnvVarNotFound{} + +var ( + errContainerNameNotReported = &errRequiredEnvVarNotFound{envVarName: consts.OdigosEnvVarContainerName} + errPodNameNotReported = &errRequiredEnvVarNotFound{envVarName: consts.OdigosEnvVarPodName} + errPodNameSpaceNotReported = &errRequiredEnvVarNotFound{envVarName: consts.OdigosEnvVarNamespace} +) + +type InstrumentationStatusReason string + +const ( + FailedToLoad InstrumentationStatusReason = "FailedToLoad" + FailedToInitialize InstrumentationStatusReason = "FailedToInitialize" + LoadedSuccessfully InstrumentationStatusReason = "LoadedSuccessfully" + FailedToRun InstrumentationStatusReason = "FailedToRun" +) + +type InstrumentationHealth bool + +const ( + InstrumentationHealthy InstrumentationHealth = true + InstrumentationUnhealthy InstrumentationHealth = false +) + +type ConfigUpdate struct { + PodWorkload workload.PodWorkload + Config *odigosv1.InstrumentationConfig +} + +type instrumentationDetails struct { + inst Instrumentation + pod types.NamespacedName + configID workloadConfigID +} + +// workloadConfigID is used to identify a workload and its language for configuration updates +type workloadConfigID struct { + podWorkload workload.PodWorkload + lang common.ProgrammingLanguage +} + +type Manager struct { + // channel for receiving process events, + // used to detect new processes and process exits, and handle their instrumentation accordingly. + procEvents <-chan detector.ProcessEvent + detector *detector.Detector + client client.Client + factories map[OtelDistribution]Factory + logger logr.Logger + + // all the active instrumentations by pid, + // this map is not concurrent safe, so it should be accessed only from the main event loop + detailsByPid map[int]*instrumentationDetails + + // active instrumentations by workload, and aggregated by pid + // this map is not concurrent safe, so it should be accessed only from the main event loop + detailsByWorkload map[workloadConfigID]map[int]*instrumentationDetails + + configUpdates chan ConfigUpdate +} + +func NewManager(client client.Client, logger logr.Logger, factories map[OtelDistribution]Factory) (*Manager, error) { + procEvents := make(chan detector.ProcessEvent) + detector, err := detector.NewK8SProcDetector(context.Background(), logger, procEvents) + if err != nil { + return nil, fmt.Errorf("failed to create process detector: %w", err) + } + + return &Manager{ + procEvents: procEvents, + detector: detector, + client: client, + factories: factories, + logger: logger.WithName("ebpf-instrumentation-manager"), + detailsByPid: make(map[int]*instrumentationDetails), + detailsByWorkload: map[workloadConfigID]map[int]*instrumentationDetails{}, + configUpdates: make(chan ConfigUpdate, configUpdatesBufferSize), + }, nil +} + +// ConfigUpdates returns a channel for receiving configuration updates for instrumentations +// sending on the channel will add an event to the main event loop to apply the configuration. +// closing this channel is in the responsibility of the caller. +func (m *Manager) ConfigUpdates() chan<- ConfigUpdate { + return m.configUpdates +} + +func (m *Manager) runEventLoop(ctx context.Context) { + // main event loop for handling instrumentations + for { + select { + case <-ctx.Done(): + m.logger.Info("stopping Odiglet instrumentation manager") + for pid, details := range m.detailsByPid { + err := details.inst.Close(ctx) + if err != nil { + m.logger.Error(err, "failed to close instrumentation", "pid", pid) + } + // probably shouldn't remove instrumentation instance here + // as this flow is happening when Odiglet is shutting down + } + m.detailsByPid = nil + m.detailsByWorkload = nil + return + case e := <-m.procEvents: + switch e.EventType { + case detector.ProcessExecEvent: + m.logger.V(1).Info("detected new process", "pid", e.PID, "cmd", e.ExecDetails.CmdLine) + err := m.handleProcessExecEvent(ctx, e) + // ignore the error if no instrumentation factory is found, + // as this is expected for some language and sdk combinations + if err != nil && !errors.Is(err, ErrNoInstrumentationFactory) { + m.logger.Error(err, "failed to handle process exec event") + } + case detector.ProcessExitEvent: + m.cleanInstrumentation(ctx, e.PID) + } + case configUpdate := <-m.configUpdates: + if configUpdate.Config == nil { + m.logger.Info("received nil config update, skipping") + break + } + for _, sdkConfig := range configUpdate.Config.Spec.SdkConfigs { + err := m.applyInstrumentationConfigurationForSDK(ctx, configUpdate.PodWorkload, &sdkConfig) + if err != nil { + m.logger.Error(err, "failed to apply instrumentation configuration") + } + } + } + } +} + +func (m *Manager) Run(ctx context.Context) error { + g, errCtx := errgroup.WithContext(ctx) + + g.Go(func() error { + return m.detector.Run(errCtx) + }) + + g.Go(func() error { + m.runEventLoop(errCtx) + return nil + }) + + err := g.Wait() + return err +} + +func (m *Manager) cleanInstrumentation(ctx context.Context, pid int) { + details, found := m.detailsByPid[pid] + if !found { + m.logger.V(3).Info("no instrumentation found for exiting pid, nothing to clean", "pid", pid) + return + } + + m.logger.Info("cleaning instrumentation resources", "pid", pid) + + err := details.inst.Close(ctx) + if err != nil { + m.logger.Error(err, "failed to close instrumentation") + } + + // remove instrumentation instance + if err = m.client.Delete(ctx, &odigosv1.InstrumentationInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.InstrumentationInstanceName(details.pod.Name, pid), + Namespace: details.pod.Namespace, + }, + }); err != nil && !apierrors.IsNotFound(err) { + m.logger.Error(err, "error deleting instrumentation instance", "pod", details.pod.Name, "pid", pid) + } + + m.stopTrackInstrumentation(pid) +} + +func (m *Manager) handleProcessExecEvent(ctx context.Context, e detector.ProcessEvent) error { + if _, found := m.detailsByPid[e.PID]; found { + // this can happen if we have multiple exec events for the same pid (chain loading) + // TODO: better handle this? + // this can be done by first closing the existing instrumentation, + // and then creating a new one + m.logger.Info("received exec event for process id which is already instrumented with ebpf, skipping it", "pid", e.PID) + return nil + } + + // get the corresponding pod object for this process event + pod, err := m.podFromProcEvent(ctx, e) + if err != nil { + return fmt.Errorf("failed to get pod from process event: %w", err) + } + + containerName, found := containerNameFromProcEvent(e) + if !found { + return errContainerNameNotReported + } + + // get the language and sdk for this process event + // based on the pod spec and the container name from the process event + // TODO: We should have all the required information in the process event + // to determine the language - hence in the future we can improve this + lang, sdk, err := odgiosK8s.LanguageSdkFromPodContainer(pod, containerName) + if err != nil { + return fmt.Errorf("failed to get language and sdk: %w", err) + } + + factory, found := m.factories[OtelDistribution{Language: lang, OtelSdk: sdk}] + if !found { + return ErrNoInstrumentationFactory + } + + podWorkload, err := workloadUtils.PodWorkloadObjectOrError(ctx, pod) + if err != nil { + return fmt.Errorf("failed to find workload object from pod manifest owners references: %w", err) + } + + // Fetch initial config based on the InstrumentationConfig CR + sdkConfig := m.instrumentationSDKConfig(ctx, podWorkload, lang, types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}) + // we should always have config for this event. + // if missing, it means that either: + // - the config will be generated later due to reconciliation timing in instrumentor + // - just got deleted and the pod (and the process) will go down soon + // TODO: sync reconcilers so inst config is guaranteed be created before the webhook is enabled + // + // if sdkConfig == nil { + // m.Logger.Info("no sdk config found for language", "language", lang, "pod", pod.Name) + // return nil + // } + + settings := Settings{ + // TODO: respect reported name annotation (if present) - to override the service name + // refactor from opAmp code + ServiceName: podWorkload.Name, + // TODO: add container name + ResourceAttributes: utils.GetResourceAttributes(podWorkload, pod.Name), + InitialConfig: sdkConfig, + } + + inst, err := factory.CreateInstrumentation(ctx, e.PID, settings) + if err != nil { + m.logger.Error(err, "failed to initialize instrumentation", "language", lang, "sdk", sdk) + + // write instrumentation instance CR with error status + err = m.updateInstrumentationInstanceStatus(ctx, pod, containerName, podWorkload, e.PID, InstrumentationUnhealthy, FailedToInitialize, err.Error()) + // TODO: should we return here the initialize error? or the instance write error? or both? + return err + } + + err = inst.Load(ctx) + if err != nil { + m.logger.Error(err, "failed to load instrumentation", "language", lang, "sdk", sdk) + + // write instrumentation instance CR with error status + err = m.updateInstrumentationInstanceStatus(ctx, pod, containerName, podWorkload, e.PID, InstrumentationUnhealthy, FailedToLoad, err.Error()) + // TODO: should we return here the load error? or the instance write error? or both? + return err + } + + m.startTrackInstrumentation(e.PID, lang, inst, types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}, *podWorkload) + + m.logger.Info("instrumentation loaded", "pid", e.PID, "pod", pod.Name, "container", containerName, "language", lang, "sdk", sdk) + + // write instrumentation instance CR with success status + msg := fmt.Sprintf("Successfully loaded eBPF probes to pod: %s container: %s", pod.Name, containerName) + err = m.updateInstrumentationInstanceStatus(ctx, pod, containerName, podWorkload, e.PID, InstrumentationHealthy, LoadedSuccessfully, msg) + if err != nil { + m.logger.Error(err, "failed to update instrumentation instance for successful load") + } + + go func() { + err := inst.Run(ctx) + if err != nil && !errors.Is(err, context.Canceled) { + m.logger.Error(err, "failed to run instrumentation") + err = m.updateInstrumentationInstanceStatus(ctx, pod, containerName, podWorkload, e.PID, InstrumentationUnhealthy, FailedToRun, err.Error()) + if err != nil { + m.logger.Error(err, "failed to update instrumentation instance for failed instrumentation run") + } + } + }() + + return nil +} + +func (m *Manager) startTrackInstrumentation(pid int, lang common.ProgrammingLanguage, inst Instrumentation, pod types.NamespacedName, podWorkload workload.PodWorkload) { + workloadConfigID := workloadConfigID{ + podWorkload: podWorkload, + lang: lang, + } + + details := &instrumentationDetails{ + inst: inst, + pod: pod, + configID: workloadConfigID, + } + m.detailsByPid[pid] = details + + if _, found := m.detailsByWorkload[workloadConfigID]; !found { + // first instrumentation for this workload + m.detailsByWorkload[workloadConfigID] = map[int]*instrumentationDetails{pid: details} + } else { + m.detailsByWorkload[workloadConfigID][pid] = details + } +} + +func (m *Manager) stopTrackInstrumentation(pid int) { + details, ok := m.detailsByPid[pid] + if !ok { + return + } + workloadConfigID := details.configID + + delete(m.detailsByPid, pid) + delete(m.detailsByWorkload[workloadConfigID], pid) + + if len(m.detailsByWorkload[workloadConfigID]) == 0 { + delete(m.detailsByWorkload, workloadConfigID) + } +} + +func (m *Manager) instrumentationSDKConfig(ctx context.Context, w *workloadUtils.PodWorkload, lang common.ProgrammingLanguage, podKey types.NamespacedName) *odigosv1.SdkConfig { + instrumentationConfig := odigosv1.InstrumentationConfig{} + instrumentationConfigKey := client.ObjectKey{ + Namespace: w.Namespace, + Name: workloadUtils.CalculateWorkloadRuntimeObjectName(w.Name, w.Kind), + } + if err := m.client.Get(ctx, instrumentationConfigKey, &instrumentationConfig); err != nil { + // this can be valid when the instrumentation config is deleted and current pods will go down soon + m.logger.Error(err, "failed to get initial instrumentation config for instrumented pod", "pod", podKey.Name, "namespace", podKey.Namespace) + return nil + } + for _, config := range instrumentationConfig.Spec.SdkConfigs { + if config.Language == lang { + return &config + } + } + return nil +} + +func (m *Manager) applyInstrumentationConfigurationForSDK(ctx context.Context, podWorkload workload.PodWorkload, sdkConfig *odigosv1.SdkConfig) error { + var err error + + configID := workloadConfigID{ + podWorkload: podWorkload, + lang: sdkConfig.Language, + } + + workloadInstrumentations, ok := m.detailsByWorkload[configID] + if !ok { + return nil + } + + for _, instDetails := range workloadInstrumentations { + m.logger.Info("applying configuration to instrumentation", "podWorkload", podWorkload, "pod", instDetails.pod, "language", sdkConfig.Language) + applyErr := instDetails.inst.ApplyConfig(ctx, sdkConfig) + err = errors.Join(err, applyErr) + } + return err +} + +func (m *Manager) updateInstrumentationInstanceStatus(ctx context.Context, pod *corev1.Pod, containerName string, w *workloadUtils.PodWorkload, pid int, health InstrumentationHealth, reason InstrumentationStatusReason, msg string) error { + instrumentedAppName := workloadUtils.CalculateWorkloadRuntimeObjectName(w.Name, w.Kind) + healthy := bool(health) + return instance.UpdateInstrumentationInstanceStatus(ctx, pod, containerName, m.client, instrumentedAppName, pid, m.client.Scheme(), + instance.WithHealthy(&healthy, string(reason), &msg), + ) +} + +func (m *Manager) podFromProcEvent(ctx context.Context, event detector.ProcessEvent) (*corev1.Pod, error) { + eventEnvs := event.ExecDetails.Environments + + podName, ok := eventEnvs[consts.OdigosEnvVarPodName] + if !ok { + return nil, errPodNameNotReported + } + + podNamespace, ok := eventEnvs[consts.OdigosEnvVarNamespace] + if !ok { + return nil, errPodNameSpaceNotReported + } + + pod := corev1.Pod{} + err := m.client.Get(ctx, client.ObjectKey{Namespace: podNamespace, Name: podName}, &pod) + if err != nil { + return nil, fmt.Errorf("error fetching pod object: %w", err) + } + + return &pod, nil +} + +func containerNameFromProcEvent(event detector.ProcessEvent) (string, bool) { + containerName, ok := event.ExecDetails.Environments[consts.OdigosEnvVarContainerName] + return containerName, ok +} diff --git a/odiglet/pkg/ebpf/sdks/go.go b/odiglet/pkg/ebpf/sdks/go.go index d0338a2d0..e22a576c7 100644 --- a/odiglet/pkg/ebpf/sdks/go.go +++ b/odiglet/pkg/ebpf/sdks/go.go @@ -6,10 +6,7 @@ import ( odigosv1 "github.com/odigos-io/odigos/api/odigos/v1alpha1" "github.com/odigos-io/odigos/common" - "github.com/odigos-io/odigos/k8sutils/pkg/workload" "github.com/odigos-io/odigos/odiglet/pkg/ebpf" - "github.com/odigos-io/odigos/odiglet/pkg/kube/utils" - "sigs.k8s.io/controller-runtime/pkg/client" "github.com/odigos-io/odigos/odiglet/pkg/env" "github.com/odigos-io/odigos/odiglet/pkg/instrumentation/consts" @@ -26,52 +23,33 @@ type GoOtelEbpfSdk struct { // compile-time check that configProvider[auto.InstrumentationConfig] implements auto.Provider var _ auto.ConfigProvider = (*ebpf.ConfigProvider[auto.InstrumentationConfig])(nil) -// compile-time check that GoOtelEbpfSdk implements ConfigurableOtelEbpfSdk -var _ ebpf.ConfigurableOtelEbpfSdk = (*GoOtelEbpfSdk)(nil) - -type GoInstrumentationFactory struct{ - kubeclient client.Client +type GoInstrumentationFactory struct { } -func NewGoInstrumentationFactory(kubeclient client.Client) ebpf.InstrumentationFactory[*GoOtelEbpfSdk] { - return &GoInstrumentationFactory{ - kubeclient: kubeclient, - } +func NewGoInstrumentationFactory() ebpf.Factory { + return &GoInstrumentationFactory{} } -func (g *GoInstrumentationFactory) CreateEbpfInstrumentation(ctx context.Context, pid int, serviceName string, podWorkload *workload.PodWorkload, containerName string, podName string, loadedIndicator chan struct{}) (*GoOtelEbpfSdk, error) { +func (g *GoInstrumentationFactory) CreateInstrumentation(ctx context.Context, pid int, settings ebpf.Settings) (ebpf.Instrumentation, error) { defaultExporter, err := otlptracegrpc.New( ctx, otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(fmt.Sprintf("%s:%d", env.Current.NodeIP, consts.OTLPPort)), ) if err != nil { - log.Logger.Error(err, "failed to create exporter") - return nil, err - } - - // Fetch initial config based on the InstrumentationConfig CR - instrumentationConfig := &odigosv1.InstrumentationConfig{} - initialConfig := auto.InstrumentationConfig{} - instrumentationConfigKey := client.ObjectKey{ - Namespace: podWorkload.Namespace, - Name: workload.CalculateWorkloadRuntimeObjectName(podWorkload.Name, podWorkload.Kind), - } - if err := g.kubeclient.Get(ctx, instrumentationConfigKey, instrumentationConfig); err == nil { - initialConfig = convertToGoInstrumentationConfig(instrumentationConfig) + return nil, fmt.Errorf("failed to create exporter: %w", err) } - cp := ebpf.NewConfigProvider(initialConfig) + cp := ebpf.NewConfigProvider(convertToGoInstrumentationConfig(settings.InitialConfig)) inst, err := auto.NewInstrumentation( ctx, auto.WithEnv(), // for OTEL_LOG_LEVEL auto.WithPID(pid), - auto.WithResourceAttributes(utils.GetResourceAttributes(podWorkload, podName)...), - auto.WithServiceName(serviceName), + auto.WithResourceAttributes(settings.ResourceAttributes...), + auto.WithServiceName(settings.ServiceName), auto.WithTraceExporter(defaultExporter), auto.WithGlobal(), - auto.WithLoadedIndicator(loadedIndicator), auto.WithConfigProvider(cp), ) if err != nil { @@ -86,36 +64,37 @@ func (g *GoOtelEbpfSdk) Run(ctx context.Context) error { return g.inst.Run(ctx) } -func (g *GoOtelEbpfSdk) Close(ctx context.Context) error { +func (g *GoOtelEbpfSdk) Load(ctx context.Context) error { + return g.inst.Load(ctx) +} + +func (g *GoOtelEbpfSdk) Close(_ context.Context) error { return g.inst.Close() } -func (g *GoOtelEbpfSdk) ApplyConfig(ctx context.Context, instConfig *odigosv1.InstrumentationConfig) error { - return g.cp.SendConfig(ctx, convertToGoInstrumentationConfig(instConfig)) +func (g *GoOtelEbpfSdk) ApplyConfig(ctx context.Context, sdkConfig *odigosv1.SdkConfig) error { + return g.cp.SendConfig(ctx, convertToGoInstrumentationConfig(sdkConfig)) } -func convertToGoInstrumentationConfig(instConfig *odigosv1.InstrumentationConfig) auto.InstrumentationConfig { +func convertToGoInstrumentationConfig(sdkConfig *odigosv1.SdkConfig) auto.InstrumentationConfig { ic := auto.InstrumentationConfig{} + if sdkConfig == nil { + log.Logger.V(0).Info("No SDK config provided for Go instrumentation, using default") + return ic + } ic.InstrumentationLibraryConfigs = make(map[auto.InstrumentationLibraryID]auto.InstrumentationLibrary) - for _, sdkConfig := range instConfig.Spec.SdkConfigs { - if sdkConfig.Language != common.GoProgrammingLanguage { - continue + for _, ilc := range sdkConfig.InstrumentationLibraryConfigs { + libID := auto.InstrumentationLibraryID{ + InstrumentedPkg: ilc.InstrumentationLibraryId.InstrumentationLibraryName, + SpanKind: common.SpanKindOdigosToOtel(ilc.InstrumentationLibraryId.SpanKind), } - for _, ilc := range sdkConfig.InstrumentationLibraryConfigs { - libID := auto.InstrumentationLibraryID{ - InstrumentedPkg: ilc.InstrumentationLibraryId.InstrumentationLibraryName, - SpanKind: common.SpanKindOdigosToOtel(ilc.InstrumentationLibraryId.SpanKind), - } - var tracesEnabled *bool - if ilc.TraceConfig != nil { - tracesEnabled = ilc.TraceConfig.Enabled - } - ic.InstrumentationLibraryConfigs[libID] = auto.InstrumentationLibrary{ - TracesEnabled: tracesEnabled, - } + var tracesEnabled *bool + if ilc.TraceConfig != nil { + tracesEnabled = ilc.TraceConfig.Enabled + } + ic.InstrumentationLibraryConfigs[libID] = auto.InstrumentationLibrary{ + TracesEnabled: tracesEnabled, } - - // TODO: sampling config } // TODO: take sampling config from the CR diff --git a/odiglet/pkg/kube/instrumentation_ebpf/instrumentationconfig.go b/odiglet/pkg/kube/instrumentation_ebpf/instrumentationconfig.go index 65169ac52..e067d9f2c 100644 --- a/odiglet/pkg/kube/instrumentation_ebpf/instrumentationconfig.go +++ b/odiglet/pkg/kube/instrumentation_ebpf/instrumentationconfig.go @@ -2,6 +2,8 @@ package instrumentation_ebpf import ( "context" + "errors" + "time" odigosv1 "github.com/odigos-io/odigos/api/odigos/v1alpha1" "github.com/odigos-io/odigos/k8sutils/pkg/workload" @@ -14,17 +16,23 @@ import ( type InstrumentationConfigReconciler struct { client.Client - Scheme *runtime.Scheme - Directors ebpf.DirectorsMap + Scheme *runtime.Scheme + Directors ebpf.DirectorsMap + ConfigUpdates chan<- ebpf.ConfigUpdate } +var ( + configUpdateTimeout = 1 * time.Second + errConfigUpdateTimeout = errors.New("failed to update config of workload: timeout waiting for config update") +) + func (i *InstrumentationConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { workloadName, workloadKind, err := workload.ExtractWorkloadInfoFromRuntimeObjectName(req.Name) if err != nil { return ctrl.Result{}, err } - podWorkload := &workload.PodWorkload{ + podWorkload := workload.PodWorkload{ Namespace: req.Namespace, Kind: workloadKind, Name: workloadName, @@ -46,12 +54,32 @@ func (i *InstrumentationConfigReconciler) Reconcile(ctx context.Context, req ctr for key, director := range i.Directors { // Apply the configuration only for languages specified in the InstrumentationConfig if _, ok := langs[key.Language]; ok { - err = director.ApplyInstrumentationConfiguration(ctx, podWorkload, instrumentationConfig) + err = director.ApplyInstrumentationConfiguration(ctx, &podWorkload, instrumentationConfig) if err != nil { return ctrl.Result{}, err } } } + if i.ConfigUpdates != nil { + // send a config update request for all the instrumentation which are part of the workload. + // if the config request is sent, the configuration updates will occur asynchronously. + ctx, cancel := context.WithTimeout(ctx, configUpdateTimeout) + defer cancel() + + select { + case i.ConfigUpdates <- ebpf.ConfigUpdate{ + PodWorkload: podWorkload, + Config: instrumentationConfig}: + return ctrl.Result{}, nil + case <-ctx.Done(): + if ctx.Err() == context.DeadlineExceeded { + // returning the error to retry the reconciliation + return ctrl.Result{}, errConfigUpdateTimeout + } + return ctrl.Result{}, ctx.Err() + } + } + return ctrl.Result{}, nil } diff --git a/odiglet/pkg/kube/instrumentation_ebpf/manager.go b/odiglet/pkg/kube/instrumentation_ebpf/manager.go index 003a60ad0..57c95f16b 100644 --- a/odiglet/pkg/kube/instrumentation_ebpf/manager.go +++ b/odiglet/pkg/kube/instrumentation_ebpf/manager.go @@ -12,26 +12,30 @@ import ( "sigs.k8s.io/controller-runtime/pkg/builder" ) -func SetupWithManager(mgr ctrl.Manager, ebpfDirectors ebpf.DirectorsMap) error { - +func SetupWithManager(mgr ctrl.Manager, ebpfDirectors ebpf.DirectorsMap, configUpdates chan<- ebpf.ConfigUpdate) error { log.Logger.V(0).Info("Starting reconcileres for ebpf instrumentation") + var err error - err := builder. - ControllerManagedBy(mgr). - Named("PodReconciler_ebpf"). - For(&corev1.Pod{}). - // trigger the reconcile when either: - // 1. A Create event is accepted for a pod with all containers ready (this is relevant when Odiglet is restarted) - // 2. All containers become ready in a running pod - // 3. Pod is deleted - WithEventFilter(predicate.Or(&odigospredicate.AllContainersReadyPredicate{}, &odigospredicate.DeletionPredicate{})). - Complete(&PodsReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Directors: ebpfDirectors, - }) - if err != nil { - return err + // TODO: once we fully move to the new approach of triggering instrumentations based on the + // process events, we can remove the PodReconciler entirely. + if ebpfDirectors != nil { + err = builder. + ControllerManagedBy(mgr). + Named("PodReconciler_ebpf"). + For(&corev1.Pod{}). + // trigger the reconcile when either: + // 1. A Create event is accepted for a pod with all containers ready (this is relevant when Odiglet is restarted) + // 2. All containers become ready in a running pod + // 3. Pod is deleted + WithEventFilter(predicate.Or(&odigospredicate.AllContainersReadyPredicate{}, &odigospredicate.DeletionPredicate{})). + Complete(&PodsReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Directors: ebpfDirectors, + }) + if err != nil { + return err + } } err = builder. @@ -40,9 +44,10 @@ func SetupWithManager(mgr ctrl.Manager, ebpfDirectors ebpf.DirectorsMap) error { For(&odigosv1.InstrumentationConfig{}). WithEventFilter(predicate.GenerationChangedPredicate{}). Complete(&InstrumentationConfigReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Directors: ebpfDirectors, + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Directors: ebpfDirectors, + ConfigUpdates: configUpdates, }) if err != nil { return err diff --git a/odiglet/pkg/kube/instrumentation_ebpf/pods.go b/odiglet/pkg/kube/instrumentation_ebpf/pods.go index 36cd73845..4deba0c47 100644 --- a/odiglet/pkg/kube/instrumentation_ebpf/pods.go +++ b/odiglet/pkg/kube/instrumentation_ebpf/pods.go @@ -68,7 +68,7 @@ func (p *PodsReconciler) Reconcile(ctx context.Context, request ctrl.Request) (c return ctrl.Result{}, nil } - podWorkload, err := p.getPodWorkloadObject(ctx, &pod) + podWorkload, err := workload.PodWorkloadObject(ctx, &pod) if err != nil { logger.Error(err, "error getting pod workload object") return ctrl.Result{}, err diff --git a/odiglet/pkg/kube/manager.go b/odiglet/pkg/kube/manager.go index 9ddc334f6..530a48c2a 100644 --- a/odiglet/pkg/kube/manager.go +++ b/odiglet/pkg/kube/manager.go @@ -59,13 +59,13 @@ func CreateManager() (ctrl.Manager, error) { }) } -func SetupWithManager(mgr ctrl.Manager, ebpfDirectors ebpf.DirectorsMap, clientset *kubernetes.Clientset) error { +func SetupWithManager(mgr ctrl.Manager, ebpfDirectors ebpf.DirectorsMap, clientset *kubernetes.Clientset, configUpdates chan<- ebpf.ConfigUpdate) error { err := runtime_details.SetupWithManager(mgr, clientset) if err != nil { return err } - err = instrumentation_ebpf.SetupWithManager(mgr, ebpfDirectors) + err = instrumentation_ebpf.SetupWithManager(mgr, ebpfDirectors, configUpdates) if err != nil { return err }