OpenTelemetry Go로 마이그레이션 - AWS X-Ray

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

OpenTelemetry Go로 마이그레이션

다음 코드 예제를 사용하여 X-Ray에서 마이그레이션할 때 OpenTelemetry SDK를 사용하여 Go 애플리케이션을 수동으로 계측할 수 있습니다.

SDK를 사용한 수동 계측

Tracing setup with X-Ray SDK

Go용 X-Ray SDK를 사용하는 경우 코드를 계측하기 전에 서비스 플러그인 또는 로컬 샘플링 규칙을 구성해야 했습니다.

func init() { if os.Getenv("ENVIRONMENT") == "production" { ec2.Init() } xray.Configure(xray.Config{ DaemonAddr: "127.0.0.1:2000", ServiceVersion: "1.2.3", }) }
Set up tracing with OpenTelemetry SDK

TracerProvider를 인스턴스화하고 글로벌 추적기 공급자로 등록하여 OpenTelemetry SDK를 구성합니다. 다음 구성 요소를 구성하는 것이 좋습니다.

  • OTLP 추적 내보내기 - CloudWatch 에이전트 또는 OpenTelemetry Collector로 추적을 내보내는 데 필요합니다.

  • X-Ray 전파기 - 추적 컨텍스트를 X-Ray와 통합된 AWS 서비스로 전파하는 데 필요합니다.

  • X-Ray 원격 샘플러 - X-Ray 샘플링 규칙을 사용하여 요청을 샘플링하는 데 필요합니다.

  • 리소스 감지기 - 애플리케이션을 실행하는 호스트의 메타데이터 감지

import ( "go.opentelemetry.io/contrib/detectors/aws/ec2" "go.opentelemetry.io/contrib/propagators/aws/xray" "go.opentelemetry.io/contrib/samplers/aws/xray" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/sdk/trace" ) func setupTracing() error { ctx := context.Background() exporterEndpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") if exporterEndpoint == "" { exporterEndpoint = "localhost:4317" } traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(exporterEndpoint)) if err != nil { return fmt.Errorf("failed to create OTLP trace exporter: %v", err) } remoteSampler, err := xray.NewRemoteSampler(ctx, "my-service-name", "ec2") if err != nil { return fmt.Errorf("failed to create X-Ray Remote Sampler: %v", err) } ec2Resource, err := ec2.NewResourceDetector().Detect(ctx) if err != nil { return fmt.Errorf("failed to detect EC2 resource: %v", err) } tp := trace.NewTracerProvider( trace.WithSampler(remoteSampler), trace.WithBatcher(traceExporter), trace.WithResource(ec2Resource), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(xray.Propagator{}) return nil }

수신 요청 추적(HTTP 핸들러 계측)

With X-Ray SDK

X-Ray로 HTTP 핸들러를 계측하기 위해 X-Ray 핸들러 메서드를 사용하여 NewFixedSegmentNamer를 사용하여 세그먼트를 생성했습니다.

func main() { http.Handle("/", xray.Handler(xray.NewFixedSegmentNamer("myApp"), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello!")) }))) http.ListenAndServe(":8000", nil) }
With OpenTelemetry SDK

OpenTelemetry로 HTTP 핸들러를 계측하려면 OpenTelemetry의 newHandler 메서드를 사용하여 원래 핸들러 코드를 래핑합니다.

import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) helloHandler := func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() span := trace.SpanFromContext(ctx) span.SetAttributes(attribute.Bool("isHelloHandlerSpan", true), attribute.String("attrKey", "attrValue")) _, _ = io.WriteString(w, "Hello World!\n") } otelHandler := otelhttp.NewHandler(http.HandlerFunc(helloHandler), "Hello") http.Handle("/hello", otelHandler) err = http.ListenAndServe(":8080", nil) if err != nil { log.Fatal(err) }

AWS SDK for Go v2 계측

With X-Ray SDK

AWS SDK의 발신 AWS 요청을 계측하기 위해 클라이언트는 다음과 같이 계측되었습니다.

// Create a segment ctx, root := xray.BeginSegment(context.TODO(), "AWSSDKV2_Dynamodb") defer root.Close(nil) cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-west-2")) if err != nil { log.Fatalf("unable to load SDK config, %v", err) } // Instrumenting AWS SDK v2 awsv2.AWSV2Instrumentor(&cfg.APIOptions) // Using the Config value, create the DynamoDB client svc := dynamodb.NewFromConfig(cfg) // Build the request with its input parameters _, err = svc.ListTables(ctx, &dynamodb.ListTablesInput{ Limit: aws.Int32(5), }) if err != nil { log.Fatalf("failed to list tables, %v", err) }
With OpenTelemetry SDK

다운스트림 AWS SDK 호출에 대한 추적 지원은 OpenTelemetry의 AWS SDK for Go v2 Instrumentation에서 제공합니다. 다음은 S3 클라이언트 호출을 추적하는 예입니다.

import ( ... "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" "go.opentelemetry.io/otel" oteltrace "go.opentelemetry.io/otel/trace" awsConfig "github.com/aws/aws-sdk-go-v2/config" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" ) ... // init aws config cfg, err := awsConfig.LoadDefaultConfig(ctx) if err != nil { panic("configuration error, " + err.Error()) } // instrument all aws clients otelaws.AppendMiddlewares(&.APIOptions) // Call to S3 s3Client := s3.NewFromConfig(cfg) input := &s3.ListBucketsInput{} result, err := s3Client.ListBuckets(ctx, input) if err != nil { fmt.Printf("Got an error retrieving buckets, %v", err) return }

발신 HTTP 호출 구성

With X-Ray SDK

X-Ray를 사용하여 발신 HTTP 호출을 계측하기 위해 xray.Client를 사용하여 제공된 HTTP 클라이언트의 복사본을 생성했습니다.

myClient := xray.Client(http-client) resp, err := ctxhttp.Get(ctx, xray.Client(nil), url)
With OpenTelemetry SDK

OpenTelemetry를 사용하여 HTTP 클라이언트를 계측하려면 OpenTelemetry의 otelhttp.NewTransport 메서드를 사용하여 http.DefaultTransport를 래핑합니다.

import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Create an instrumented HTTP client. httpClient := &http.Client{ Transport: otelhttp.NewTransport( http.DefaultTransport, ), } req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/repos/aws-observability/aws-otel-go/releases/latest", nil) if err != nil { fmt.Printf("failed to create http request, %v\n", err) } res, err := httpClient.Do(req) if err != nil { fmt.Printf("failed to make http request, %v\n", err) } // Request body must be closed defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { fmt.Printf("failed to close http response body, %v\n", err) } }(res.Body)

다른 라이브러리에 대한 계측 지원

OpenTelemetry Go에 지원되는 라이브러리 계측의 전체 목록은 계측 패키지에서 확인할 수 있습니다.

또는 OpenTelemetry 레지스트리를 검색하여 OpenTelemetry가 레지스트리에서 라이브러리에 대한 계측을 지원하는지 확인할 수 있습니다.

추적 데이터 수동 생성

With X-Ray SDK

X-Ray SDK를 사용하면 X-Ray 세그먼트 및 하위 세그먼트를 수동으로 생성하려면 BeginSegment 및 BeginSubsegment 메서드가 필요했습니다.

// Start a segment ctx, seg := xray.BeginSegment(context.Background(), "service-name") // Start a subsegment subCtx, subSeg := xray.BeginSubsegment(ctx, "subsegment-name") // Add metadata or annotation here if necessary xray.AddAnnotation(subCtx, "annotationKey", "annotationValue") xray.AddMetadata(subCtx, "metadataKey", "metadataValue") subSeg.Close(nil) // Close the segment seg.Close(nil)
With OpenTelemetry SDK

사용자 지정 스팬을 사용하여 계측 라이브러리로 캡처되지 않은 내부 활동의 성능을 모니터링합니다. 참고로 서버는 X-Ray 세그먼트로 변환되고 다른 모든 스팬은 X-Ray 하위 세그먼트로 변환됩니다.

먼저 추적기를 생성하여 otel.Tracer 메서드를 통해 얻을 수 있는 스팬을 생성해야 합니다. 그러면 추적 설정 예제에서 전역적으로 등록된 TracerProvider 인스턴스가 제공됩니다. 필요한 만큼 Tracer 인스턴스를 생성할 수 있지만 전체 애플리케이션에 대해 하나의 Tracer를 사용하는 것이 일반적입니다.

tracer := otel.Tracer("application-tracer")
import ( ... oteltrace "go.opentelemetry.io/otel/trace" ) ... var attributes = []attribute.KeyValue{ attribute.KeyValue{Key: "metadataKey", Value: attribute.StringValue("metadataValue")}, attribute.KeyValue{Key: "annotationKey", Value: attribute.StringValue("annotationValue")}, attribute.KeyValue{Key: "aws.xray.annotations", Value: attribute.StringSliceValue([]string{"annotationKey"})}, } ctx := context.Background() parentSpanContext, parentSpan := tracer.Start(ctx, "ParentSpan", oteltrace.WithSpanKind(oteltrace.SpanKindServer), oteltrace.WithAttributes(attributes...)) _, childSpan := tracer.Start(parentSpanContext, "ChildSpan", oteltrace.WithSpanKind(oteltrace.SpanKindInternal)) // ... childSpan.End() parentSpan.End()

OpenTelemetry SDK를 사용하여 트레이스에 주석 및 메타데이터 추가

위 예제에서 WithAttributes 메서드는 각 스팬에 속성을 추가하는 데 사용됩니다. 기본적으로 모든 스팬 속성은 X-Ray 원시 데이터의 메타데이터로 변환됩니다. 속성이 메타데이터가 아닌 주석으로 변환되도록 하려면 속성 목록에 aws.xray.annotations 속성의 키를 추가합니다. 자세한 내용은 사용자 지정 X-Ray 주석 활성화를 참조하세요.

Lambda 수동 계측

With X-Ray SDK

X-Ray SDK를 사용하면 Lambda에 활성 추적이 활성화된 후 X-Ray SDK를 사용하는 데 추가 구성이 필요하지 않았습니다. Lambda는 Lambda 핸들러 호출을 나타내는 세그먼트를 생성했으며 추가 구성 없이 X-Ray SDK를 사용하여 하위 세그먼트를 생성했습니다.

With OpenTelemetry SDK

다음 Lambda 함수 코드(계측 없음)는 Amazon S3 ListBuckets 호출 및 발신 HTTP 요청을 수행합니다.

package main import ( "context" "encoding/json" "fmt" "io" "net/http" "os" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" ) func lambdaHandler(ctx context.Context) (interface{}, error) { // Initialize AWS config. cfg, err := awsconfig.LoadDefaultConfig(ctx) if err != nil { panic("configuration error, " + err.Error()) } s3Client := s3.NewFromConfig(cfg) // Create an HTTP client. httpClient := &http.Client{ Transport: http.DefaultTransport, } input := &s3.ListBucketsInput{} result, err := s3Client.ListBuckets(ctx, input) if err != nil { fmt.Printf("Got an error retrieving buckets, %v", err) } fmt.Println("Buckets:") for _, bucket := range result.Buckets { fmt.Println(*bucket.Name + ": " + bucket.CreationDate.Format("2006-01-02 15:04:05 Monday")) } fmt.Println("End Buckets.") req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/repos/aws-observability/aws-otel-go/releases/latest", nil) if err != nil { fmt.Printf("failed to create http request, %v\n", err) } res, err := httpClient.Do(req) if err != nil { fmt.Printf("failed to make http request, %v\n", err) } defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { fmt.Printf("failed to close http response body, %v\n", err) } }(res.Body) var data map[string]interface{} err = json.NewDecoder(res.Body).Decode(&data) if err != nil { fmt.Printf("failed to read http response body, %v\n", err) } fmt.Printf("Latest ADOT Go Release is '%s'\n", data["name"]) return events.APIGatewayProxyResponse{ StatusCode: http.StatusOK, Body: os.Getenv("_X_AMZN_TRACE_ID"), }, nil } func main() { lambda.Start(lambdaHandler) }

Lambda 핸들러와 Amazon S3 클라이언트를 수동으로 계측하려면 다음을 수행합니다.

  1. main()에서 TracerProvider(tp)를 인스턴스화하고 글로벌 추적기 공급자로 등록합니다. TracerProvider는 다음과 같이 구성하는 것이 좋습니다.

    1. 추적을 Lambda의 UDP X-Ray 엔드포인트로 전송하기 위한 X-Ray UDP 스팬 내보내기가 있는 Simple Span Processor

    2. service.name Lambda 함수 이름으로 설정된 리소스

  2. 의 사용량을 lambda.Start(lambdaHandler)로 변경합니다lambda.Start(otellambda.InstrumentHandler(lambdaHandler, xrayconfig.WithRecommendedOptions(tp)...)).

  3. 용 OpenTemetry AWS 미들웨어를 Amazon S3 클라이언트 구성에 추가하여 OpenTelemetry SDK 계측aws-sdk-go-v2으로 Amazon S3 클라이언트를 계측합니다.

  4. OpenTelemetry의 otelhttp.NewTransport 메서드를 사용하여를 래핑하여 http 클라이언트를 계측합니다http.DefaultTransport.

다음 코드는 변경 후 Lambda 함수가 어떻게 표시되는지 보여주는 예입니다. 자동으로 제공되는 스팬 외에도 추가 사용자 지정 스팬을 수동으로 생성할 수 있습니다.

package main import ( "context" "encoding/json" "fmt" "io" "net/http" "os" "github.com/aws-observability/aws-otel-go/exporters/xrayudp" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" lambdadetector "go.opentelemetry.io/contrib/detectors/aws/lambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/contrib/propagators/aws/xray" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) func lambdaHandler(ctx context.Context) (interface{}, error) { // Initialize AWS config. cfg, err := awsconfig.LoadDefaultConfig(ctx) if err != nil { panic("configuration error, " + err.Error()) } // Instrument all AWS clients. otelaws.AppendMiddlewares(&cfg.APIOptions) // Create an instrumented S3 client from the config. s3Client := s3.NewFromConfig(cfg) // Create an instrumented HTTP client. httpClient := &http.Client{ Transport: otelhttp.NewTransport( http.DefaultTransport, ), } // return func(ctx context.Context) (interface{}, error) { input := &s3.ListBucketsInput{} result, err := s3Client.ListBuckets(ctx, input) if err != nil { fmt.Printf("Got an error retrieving buckets, %v", err) } fmt.Println("Buckets:") for _, bucket := range result.Buckets { fmt.Println(*bucket.Name + ": " + bucket.CreationDate.Format("2006-01-02 15:04:05 Monday")) } fmt.Println("End Buckets.") req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/repos/aws-observability/aws-otel-go/releases/latest", nil) if err != nil { fmt.Printf("failed to create http request, %v\n", err) } res, err := httpClient.Do(req) if err != nil { fmt.Printf("failed to make http request, %v\n", err) } defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { fmt.Printf("failed to close http response body, %v\n", err) } }(res.Body) var data map[string]interface{} err = json.NewDecoder(res.Body).Decode(&data) if err != nil { fmt.Printf("failed to read http response body, %v\n", err) } fmt.Printf("Latest ADOT Go Release is '%s'\n", data["name"]) return events.APIGatewayProxyResponse{ StatusCode: http.StatusOK, Body: os.Getenv("_X_AMZN_TRACE_ID"), }, nil } func main() { ctx := context.Background() detector := lambdadetector.NewResourceDetector() lambdaResource, err := detector.Detect(context.Background()) if err != nil { fmt.Printf("failed to detect lambda resources: %v\n", err) } var attributes = []attribute.KeyValue{ attribute.KeyValue{Key: semconv.ServiceNameKey, Value: attribute.StringValue(os.Getenv("AWS_LAMBDA_FUNCTION_NAME"))}, } customResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) mergedResource, _ := resource.Merge(lambdaResource, customResource) xrayUdpExporter, _ := xrayudp.NewSpanExporter(ctx) tp := trace.NewTracerProvider( trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(xrayUdpExporter)), trace.WithResource(mergedResource), ) defer func(ctx context.Context) { err := tp.Shutdown(ctx) if err != nil { fmt.Printf("error shutting down tracer provider: %v", err) } }(ctx) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(xray.Propagator{}) lambda.Start(otellambda.InstrumentHandler(lambdaHandler, xrayconfig.WithRecommendedOptions(tp)...)) }

Lambda를 호출할 때 CloudWatch 콘솔의 Trace Map에 다음 추적이 표시됩니다.

Golang용 CloudWatch 콘솔의 트레이스 맵.