콘텐츠로 이동

OpenTelemetry Go SDK

OpenTelemetry Go SDK는 API, SDK 및 프레임워크 계측 라이브러리를 통해 Go 애플리케이션에 대한 원격 측정 데이터를 수집합니다. Java Agent와 같은 런타임 자동 계측 솔루션과 달리 Go 애플리케이션은 일반적으로 코드에서 SDK를 초기화하고 웹 프레임워크, HTTP 클라이언트, 데이터베이스 또는 메시지 대기열을 해당 계측 라이브러리로 래핑해야 합니다.

이 문서에서는 DataKit의 OpenTelemetry 수집기를 사용하여 OTLP 데이터를 수신하고 Guance로 전달합니다.

Go 애플리케이션 + OpenTelemetry Go SDK -> OTLP -> DataKit -> Guance

이 문서의 예제는 OTLP/HTTP + Protobuf를 통해 Trace와 Metric을 보고합니다. OpenTelemetry Go의 Trace와 Metric은 안정화되었지만, Log SDK의 성숙도와 생태계 지원 수준은 계속 바뀔 수 있으므로 프로덕션 환경에 적용하기 전에 최신 공식 상태를 확인해야 합니다.

전제 조건

  • Go 1.23 이상;
  • DataKit이 설치되고 DataKit이 대상 Guance 작업 공간에 연결됩니다.
  • DataKit에 대한 Go 애플리케이션의 네트워크 연결성: OTLP/HTTP는 DataKit HTTP 포트 '9529'를 사용하고, OTLP/gRPC는 기본적으로 '4317'을 사용합니다.
  • 애플리케이션에서 사용하는 프레임워크나 구성요소에 해당 OpenTelemetry Go 계측 라이브러리 가 있는 것으로 확인되었거나, OpenTelemetry API를 통해 Span 및 Metric을 수동으로 생성할 계획입니다.

1. OpenTelemetry 수집기 활성화

DataKit 설치 디렉터리에 'conf.d/opentelemetry'를 입력하세요. 수집기 구성이 아직 생성되지 않은 경우 샘플 파일을 복사합니다.

cd /usr/local/datakit/conf.d/opentelemetry
sudo cp opentelemetry.conf.sample opentelemetry.conf

opentelemetry.conf에 최소한 다음 수신 구성이 포함되어 있는지 확인하세요.

[[inputs.opentelemetry]]
  # Guance에서 사용자 정의 속성을 태그로 유지하려면 이 화이트리스트에 추가하세요.
  # 속성 이름의 점은 밑줄로 변환됩니다. 예: team.name -> team_name.
  customer_tags = ["team", "project"]

  [inputs.opentelemetry.http]
    http_status_ok = 200
    trace_api = "/otel/v1/traces"
    metric_api = "/otel/v1/metrics"
    logs_api = "/otel/v1/logs"

  [inputs.opentelemetry.grpc]
    addr = "127.0.0.1:4317"
    max_payload = 16777216

위 구성을 사용하면 다음 수신 주소가 활성화됩니다.

프로토콜 데이터 유형 DataKit 수신 주소
OTLP/HTTP + 프로토부프 추적 http://<DataKit-IP>:9529/otel/v1/traces
OTLP/HTTP + 프로토부프 미터법 http://<DataKit-IP>:9529/otel/v1/metrics
OTLP/HTTP + 프로토부프 로그 http://<DataKit-IP>:9529/otel/v1/logs
OTLP/gRPC 추적, 측정항목, 로그 http://<DataKit-IP>:4317

애플리케이션과 DataKit이 동일한 호스트에 있지 않으면 DataKit HTTP 수신 주소, 방화벽 또는 기타 네트워크 액세스 제어를 실제 배포에 따라 조정해야 합니다. OTLP/gRPC를 사용하는 경우 addr을 0.0.0.0:4317과 같이 애플리케이션이 액세스할 수 있는 수신 주소로 변경해야 합니다. OTLP 수신 포트를 공용 네트워크에 직접 노출하지 마십시오.

구성을 적용하려면 DataKit을 다시 시작하십시오.

sudo datakit service restart

DataKit HTTP 서비스에 연결할 수 있는지 확인하세요.

curl http://127.0.0.1:9529/v1/ping

2. OpenTelemetry {#instrument-application}에 대한 애플리케이션 액세스

Go SDK 및 계측 라이브러리 {#install-sdk} 설치

Go 프로젝트 디렉터리에 공식 SDK, OTLP/HTTP 내보내기 및 net/http 계측 라이브러리를 설치합니다.

go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
go mod tidy

go.mod 및 go.sum은 의존성 버전을 기록합니다. 프로덕션 환경에서는 OpenTelemetry 의존성을 업그레이드한 뒤 이 두 파일을 커밋하고 컴파일, 단위 테스트, 트레이스 회귀 테스트를 수행해야 합니다.

SDK {#initialize-sdk} 초기화

다음 예제는 두 가지를 모두 완료합니다.

  1. OTEL_SERVICE_NAME 및 OTEL_RESOURCE_ATTRIBUTES에서 리소스 속성을 읽습니다.
  2. OTLP/HTTP 추적 내보내기 및 메트릭 내보내기를 생성합니다.
  3. 전역 TracerProvider, MeterProvider 및 W3C 컨텍스트 전파자를 등록합니다.
  4. otelhttp로 HTTP 핸들러를 감싸고 비즈니스 child Span 및 사용자 정의 Counter를 생성합니다.
  5. 버퍼의 데이터 손실을 방지하려면 프로세스가 종료될 때 공급자를 새로 고치고 닫습니다.
package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/propagation"
    sdkmetric "go.opentelemetry.io/otel/sdk/metric"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
    res, err := resource.New(
        ctx,
        resource.WithFromEnv(),
        resource.WithTelemetrySDK(),
        resource.WithHost(),
        resource.WithOS(),
        resource.WithProcess(),
    )
    if err != nil {
        return nil, fmt.Errorf("create resource: %w", err)
    }

    traceExporter, err := otlptracehttp.New(ctx)
    if err != nil {
        return nil, fmt.Errorf("create trace exporter: %w", err)
    }

    tracerProvider := sdktrace.NewTracerProvider(
        sdktrace.WithResource(res),
        sdktrace.WithSampler(
            sdktrace.ParentBased(sdktrace.TraceIDRatioBased(1.0)),
        ),
        sdktrace.WithBatcher(traceExporter),
    )

    metricExporter, err := otlpmetrichttp.New(ctx)
    if err != nil {
        _ = tracerProvider.Shutdown(ctx)
        return nil, fmt.Errorf("create metric exporter: %w", err)
    }

    meterProvider := sdkmetric.NewMeterProvider(
        sdkmetric.WithResource(res),
        sdkmetric.WithReader(
            sdkmetric.NewPeriodicReader(
                metricExporter,
                sdkmetric.WithInterval(30*time.Second),
            ),
        ),
    )

    otel.SetTracerProvider(tracerProvider)
    otel.SetMeterProvider(meterProvider)
    otel.SetTextMapPropagator(
        propagation.NewCompositeTextMapPropagator(
            propagation.TraceContext{},
            propagation.Baggage{},
        ),
    )

    shutdown := func(ctx context.Context) error {
        return errors.Join(
            meterProvider.Shutdown(ctx),
            tracerProvider.Shutdown(ctx),
        )
    }
    return shutdown, nil
}

func main() {
    ctx, stop := signal.NotifyContext(
        context.Background(),
        os.Interrupt,
        syscall.SIGTERM,
    )
    defer stop()

    shutdown, err := setupOTelSDK(ctx)
    if err != nil {
        log.Fatal(err)
    }
    defer func() {
        shutdownCtx, cancel := context.WithTimeout(
            context.Background(),
            5*time.Second,
        )
        defer cancel()
        if err := shutdown(shutdownCtx); err != nil {
            log.Printf("shutdown OpenTelemetry: %v", err)
        }
    }()

    meter := otel.Meter("example/order-service")
    requestCounter, err := meter.Int64Counter("app.request.count")
    if err != nil {
        log.Fatal(err)
    }

    mux := http.NewServeMux()
    mux.Handle("/hello", otelhttp.NewHandler(
        http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            requestCtx, span := otel.Tracer("example/order-service").Start(
                r.Context(),
                "prepare-response",
            )
            defer span.End()

            span.SetAttributes(attribute.String("app.route", "/hello"))
            requestCounter.Add(requestCtx, 1)
            _, _ = fmt.Fprintln(w, "hello from OpenTelemetry Go SDK")
        }),
        "GET /hello",
    ))

    server := &http.Server{
        Addr:              ":8080",
        Handler:           mux,
        ReadHeaderTimeout: 5 * time.Second,
    }

    go func() {
        <-ctx.Done()
        shutdownCtx, cancel := context.WithTimeout(
            context.Background(),
            5*time.Second,
        )
        defer cancel()
        if err := server.Shutdown(shutdownCtx); err != nil {
            log.Printf("shutdown HTTP server: %v", err)
        }
    }()

    log.Println("listening on http://127.0.0.1:8080")
    if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
        log.Fatal(err)
    }
}

실제 프로젝트에서는 사용되는 구성 요소에 해당하는 계측 라이브러리를 설치해야 합니다. 예를 들어, 표준 라이브러리 net/http는 otelhttp를 사용합니다. 다른 웹 프레임워크, 데이터베이스 또는 메시지 대기열은 OpenTelemetry Registry에서 일치하는 패키지를 선택하고 해당 패키지의 지침에 따라 핸들러, 전송, 클라이언트 또는 드라이버를 래핑해야 합니다.

보고된 주소를 구성하고 {#configure-and-run}를 시작합니다.

다음으로 OTLP/HTTP + Protobuf를 사용하여 로컬 DataKit에 보고합니다. OTEL_EXPORTER_OTLP_ENDPOINT는 기본 주소입니다. Trace 및 Metric 내보내기는 각각 /v1/traces 및 /v1/metrics를 추가하며 이는 궁극적으로 DataKit의 /otel/v1/* 경로에 해당합니다.

export OTEL_SERVICE_NAME="order-service"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment.name=prod,service.version=1.0.0,team=backend"

export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:9529/otel"
export OTEL_EXPORTER_OTLP_INSECURE="true"
export OTEL_EXPORTER_OTLP_COMPRESSION="gzip"

go run .

추적 및 측정항목 생성을 요청합니다.

curl http://127.0.0.1:8080/hello

메트릭은 기본적으로 30초마다 내보내지며, 이 예제에서는 sdkmetric.WithInterval(30*time.Second)로 제어합니다. 한 번의 내보내기 주기를 기다린 뒤 Guance에서 service=order-service의 트레이스를 확인하고 app.request.count 메트릭을 조회할 수 있습니다.

OTLP/gRPC {#use-grpc} 사용

Go SDK의 OTLP 전송은 코드에 사용된 내보내기 패키지에 의해 결정됩니다. 이 예에서는 otlptracehttp 및 otlpmetrichttp를 직접 사용합니다. OTEL_EXPORTER_OTLP_PROTOCOL=grpc 설정만으로는 gRPC로 전환되지 않습니다.

OTLP/gRPC를 사용하려면 gRPC 내보내기를 설치하세요.

go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
go mod tidy

그런 다음 코드의 내보내기 패키지 및 초기화 함수를 다음으로 바꿉니다.

traceExporter, err := otlptracegrpc.New(ctx)
metricExporter, err := otlpmetricgrpc.New(ctx)

그리고 /v1/traces, /v1/metrics 경로 없이 gRPC 주소를 사용하세요.

export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:4317"
export OTEL_EXPORTER_OTLP_INSECURE="true"

3. 데이터 보고 매개변수

리소스 매개변수

이 예에서는 resource.WithFromEnv()를 통해 다음 표준 환경 변수를 읽습니다.

환경 변수 설명 제안된 값 또는 예
OTEL_SERVICE_NAME 리소스 속성 service.name에 해당하는 서비스 이름입니다. 주문 서비스; 프로덕션 환경에서는 명시적으로 설정해야 합니다.
OTEL_RESOURCE_ATTRIBUTES 쉼표로 구분된 '키=값' 형식의 리소스 속성입니다. deployment.environment.name=prod,service.version=1.0.0,team=backend

Guance에서는 서비스 속성, 환경 필터링, 버전 분석을 위해 최소한 service.name, deployment.environment.name, service.version을 설정하는 것이 좋습니다. 사용자 정의 리소스 속성을 태그로 유지하려면 먼저 DataKit customer_tags 화이트리스트에 추가해야 합니다. 속성 이름의 .는 _로 변환됩니다.

OTLP/HTTP 매개변수

otlptracehttp.New() 및 otlpmetrichttp.New()는 다음 환경 변수를 직접 읽습니다. 신호별 매개변수는 일반 매개변수보다 우선합니다.

일반 환경 변수 신호별 환경 변수 설명 DataKit 예제
OTEL_EXPORTER_OTLP_ENDPOINT OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT 일반 매개변수는 기본 URL이며, 내보내기는 자동으로 신호 경로를 추가합니다. 신호별 매개변수는 원래 값에 따라 사용되는 전체 URL입니다. 유니버설: http://127.0.0.1:9529/otel;Trace:http://127.0.0.1:9529/otel/v1/traces;Metric:http://127.0.0.1:9529/otel/v1/metrics
OTEL_EXPORTER_OTLP_INSECURE OTEL_EXPORTER_OTLP_TRACES_INSECURE, OTEL_EXPORTER_OTLP_METRICS_INSECURE 전송 계층 TLS를 끌지 여부입니다. DataKit이 일반 텍스트 HTTP를 사용하는 경우 true로 설정합니다.
OTEL_EXPORTER_OTLP_HEADERS OTEL_EXPORTER_OTLP_TRACES_HEADERS, OTEL_EXPORTER_OTLP_METRICS_HEADERS 쉼표로 구분된 key=value 형식의 요청 헤더입니다. DataKit에서 expected_headers를 구성할 때 해당 값을 설정합니다.
OTEL_EXPORTER_OTLP_TIMEOUT OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, OTEL_EXPORTER_OTLP_METRICS_TIMEOUT 단일 내보내기 시간 초과, 값은 밀리초입니다. 10000 등 네트워크 상황에 따라 설정하세요.
OTEL_EXPORTER_OTLP_COMPRESSION OTEL_EXPORTER_OTLP_TRACES_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION OTLP 요청 압축 방법. gzip으로 설정할 수 있습니다. 압축하지 않으려면 비워두세요.
OTEL_EXPORTER_OTLP_CERTIFICATE OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE 서버 인증서를 확인하는 데 사용되는 PEM CA 파일의 경로입니다. HTTPS를 통해 OTLP를 수신할 때 인증서별로 설정을 배포합니다.

앱과 DataKit이 동일한 호스트에 있지 않으면 예제의 127.0.0.1을 앱이 액세스할 수 있는 DataKit 주소로 바꿔야 합니다.

SDK 코드 매개변수

다음 매개변수는 Go SDK 초기화 코드에 의해 제어되며 동일한 이름의 공통 환경 변수를 설정해도 자동으로 적용되지 않습니다.

구성 항목 샘플 코드 설명
추적 샘플링 sdktrace.ParentBased(sdktrace.TraceIDRatioBased(1.0)) '1.0'은 루트 추적 전체 샘플링을 의미합니다. 생산 환경은 용량에 따라 '0.1'과 같은 비율로 조정될 수 있으며, 'ParentBased'를 통해 업스트림 샘플링 결정을 따를 수 있습니다.
스팬 일괄 내보내기 sdktrace.WithBatcher(traceExporter) 프로덕션 환경에서는 일괄 내보내기가 권장됩니다. WithMaxQueueSize, WithMaxExportBatchSize, WithBatchTimeout 및 WithExportTimeout을 통해 추가로 조정할 수 있습니다.
측정항목 내보내기 기간 sdkmetric.WithInterval(30*time.Second) 주기적 내보내기 간격을 제어합니다. 너무 짧으면 애플리케이션, 네트워크 및 스토리지 오버헤드가 증가합니다.
컨텍스트 전파 TraceContext{}, 수하물{} W3C 'traceparent', 'tracestate' 및 'baggage'를 사용하세요. 콜 체인의 서비스는 전파 형식 호환성을 유지해야 합니다.
자원탐지 resource.WithHost(), WithOS(), WithProcess() 호스트, 운영 체제 및 프로세스 속성을 자동으로 보완합니다. 프로세스 매개변수 및 리소스 속성의 키와 같은 민감한 정보를 저장하지 마세요.

이 예에서는 OTLP 내보내기를 직접 생성하므로 특별한 주의가 필요합니다.- OTEL_EXPORTER_OTLP_PROTOCOL은 코드에서 이미 선택한 HTTP 또는 gRPC 내보내기를 변경하지 않습니다. - OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER는 이 예에서 직접 생성된 내보내기를 닫지 않습니다. - OpenTelemetry Go 핵심 SDK는 현재 모든 공통 SDK 환경 변수를 자동으로 적용하지 않습니다. 특히 'OTEL_SDK_DISABLED', 'OTEL_TRACES_SAMPLER' 또는 'OTEL_PROPAGATORS'가 사용자 지정 초기화 코드에 자동으로 적용된다고 가정하지 마세요. - 표준 환경 변수를 통해 내보내기를 동적으로 선택하고 초기화해야 하는 경우 공식 Contrib의 autoexport 패키지를 평가하고 테스트 환경에서 해당 동작을 확인할 수 있습니다.

확인 및 문제 해결

  1. curl http://127.0.0.1:9529/v1/ping 를 실행하여 애플리케이션이 DataKit에 접근할 수 있는지 확인합니다.
  2. 애플리케이션을 시작하고 /hello에 액세스하여 애플리케이션 로그에 추적 내보내기 생성, 메트릭 내보내기 생성 또는 OTLP 내보내기 오류가 없는지 확인합니다.
  3. 하나 이상의 메트릭 내보내기 주기를 기다립니다.
  4. Guance의 APM 서비스 목록에서 order-service의 Trace를 조회합니다.
  5. 데이터를 쿼리할 수 없는 경우 DataKit opentelemetry 수집기 구성, DataKit에 적용된 네트워크, 보고 URL 및 DataKit 로그를 확인합니다.
  6. 중복 Span이 발생하는 경우 동일한 Handler, Transport, Database Client 또는 Driver가 반복적으로 패키징되어 있는지 확인합니다.

참고자료

문서 평가

이 페이지가 도움이 되었나요?