Add k8s resource attributes automatically
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
parent
7b78128d4e
commit
78cc85283c
15 changed files with 170 additions and 60 deletions
70
pkg/types/k8sdetector.go
Normal file
70
pkg/types/k8sdetector.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
kerror "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kclientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// K8sAttributesDetector detects the metadata of the Traefik pod running in a Kubernetes cluster.
|
||||
// It reads the pod name from the hostname file and the namespace from the service account namespace file and queries the Kubernetes API to get the pod's UID.
|
||||
type K8sAttributesDetector struct{}
|
||||
|
||||
func (K8sAttributesDetector) Detect(ctx context.Context) (*resource.Resource, error) {
|
||||
attrs := os.Getenv("OTEL_RESOURCE_ATTRIBUTES")
|
||||
if strings.Contains(attrs, string(semconv.K8SPodNameKey)) || strings.Contains(attrs, string(semconv.K8SPodUIDKey)) {
|
||||
return resource.Empty(), nil
|
||||
}
|
||||
|
||||
// The InClusterConfig function returns a config for the Kubernetes API server
|
||||
// when it is running inside a Kubernetes cluster.
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil && errors.Is(err, rest.ErrNotInCluster) {
|
||||
return resource.Empty(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating in cluster config: %w", err)
|
||||
}
|
||||
|
||||
client, err := kclientset.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating Kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
podName, err := os.Hostname()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting pod name: %w", err)
|
||||
}
|
||||
|
||||
podNamespaceBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting pod namespace: %w", err)
|
||||
}
|
||||
podNamespace := string(podNamespaceBytes)
|
||||
|
||||
pod, err := client.CoreV1().Pods(podNamespace).Get(ctx, podName, metav1.GetOptions{})
|
||||
if err != nil && kerror.IsForbidden(err) {
|
||||
log.Error().Err(err).Msg("Unable to build K8s resource attributes for Traefik pod")
|
||||
return resource.Empty(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting pod metadata: %w", err)
|
||||
}
|
||||
|
||||
// To avoid version conflicts with other detectors, we use a Schemaless resource.
|
||||
return resource.NewSchemaless(
|
||||
semconv.K8SPodUID(string(pod.UID)),
|
||||
semconv.K8SPodName(pod.Name),
|
||||
semconv.K8SNamespaceName(podNamespace),
|
||||
), nil
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ import (
|
|||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
|
||||
otelsdk "go.opentelemetry.io/otel/sdk/log"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
)
|
||||
|
|
@ -164,7 +164,7 @@ func (o *OTelLog) SetDefaults() {
|
|||
}
|
||||
|
||||
// NewLoggerProvider creates a new OpenTelemetry logger provider.
|
||||
func (o *OTelLog) NewLoggerProvider() (*otelsdk.LoggerProvider, error) {
|
||||
func (o *OTelLog) NewLoggerProvider(ctx context.Context) (*otelsdk.LoggerProvider, error) {
|
||||
var (
|
||||
err error
|
||||
exporter otelsdk.Exporter
|
||||
|
|
@ -178,23 +178,27 @@ func (o *OTelLog) NewLoggerProvider() (*otelsdk.LoggerProvider, error) {
|
|||
return nil, fmt.Errorf("setting up exporter: %w", err)
|
||||
}
|
||||
|
||||
attr := []attribute.KeyValue{
|
||||
semconv.ServiceNameKey.String(o.ServiceName),
|
||||
semconv.ServiceVersionKey.String(version.Version),
|
||||
}
|
||||
|
||||
var resAttrs []attribute.KeyValue
|
||||
for k, v := range o.ResourceAttributes {
|
||||
attr = append(attr, attribute.String(k, v))
|
||||
resAttrs = append(resAttrs, attribute.String(k, v))
|
||||
}
|
||||
|
||||
res, err := resource.New(context.Background(),
|
||||
resource.WithAttributes(attr...),
|
||||
res, err := resource.New(ctx,
|
||||
resource.WithContainer(),
|
||||
resource.WithFromEnv(),
|
||||
resource.WithHost(),
|
||||
resource.WithOS(),
|
||||
resource.WithProcess(),
|
||||
resource.WithTelemetrySDK(),
|
||||
resource.WithDetectors(K8sAttributesDetector{}),
|
||||
// The following order allows the user to override the service name and version,
|
||||
// as well as any other attributes set by the above detectors.
|
||||
resource.WithAttributes(
|
||||
semconv.ServiceName(o.ServiceName),
|
||||
semconv.ServiceVersion(version.Version),
|
||||
),
|
||||
resource.WithAttributes(resAttrs...),
|
||||
// Use the environment variables to allow overriding above resource attributes.
|
||||
resource.WithFromEnv(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building resource: %w", err)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import (
|
|||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
|
|
@ -52,7 +52,7 @@ func (c *OTelTracing) SetDefaults() {
|
|||
}
|
||||
|
||||
// Setup sets up the tracer.
|
||||
func (c *OTelTracing) Setup(serviceName string, sampleRate float64, resourceAttributes map[string]string) (trace.Tracer, io.Closer, error) {
|
||||
func (c *OTelTracing) Setup(ctx context.Context, serviceName string, sampleRate float64, resourceAttributes map[string]string) (trace.Tracer, io.Closer, error) {
|
||||
var (
|
||||
err error
|
||||
exporter *otlptrace.Exporter
|
||||
|
|
@ -66,23 +66,27 @@ func (c *OTelTracing) Setup(serviceName string, sampleRate float64, resourceAttr
|
|||
return nil, nil, fmt.Errorf("setting up exporter: %w", err)
|
||||
}
|
||||
|
||||
attr := []attribute.KeyValue{
|
||||
semconv.ServiceNameKey.String(serviceName),
|
||||
semconv.ServiceVersionKey.String(version.Version),
|
||||
}
|
||||
|
||||
var resAttrs []attribute.KeyValue
|
||||
for k, v := range resourceAttributes {
|
||||
attr = append(attr, attribute.String(k, v))
|
||||
resAttrs = append(resAttrs, attribute.String(k, v))
|
||||
}
|
||||
|
||||
res, err := resource.New(context.Background(),
|
||||
resource.WithAttributes(attr...),
|
||||
res, err := resource.New(ctx,
|
||||
resource.WithContainer(),
|
||||
resource.WithFromEnv(),
|
||||
resource.WithHost(),
|
||||
resource.WithOS(),
|
||||
resource.WithProcess(),
|
||||
resource.WithTelemetrySDK(),
|
||||
resource.WithDetectors(K8sAttributesDetector{}),
|
||||
// The following order allows the user to override the service name and version,
|
||||
// as well as any other attributes set by the above detectors.
|
||||
resource.WithAttributes(
|
||||
semconv.ServiceName(serviceName),
|
||||
semconv.ServiceVersion(version.Version),
|
||||
),
|
||||
resource.WithAttributes(resAttrs...),
|
||||
// Use the environment variables to allow overriding above resource attributes.
|
||||
resource.WithFromEnv(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("building resource: %w", err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue