콘텐츠로 이동

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 워크스페이스에 연결되어 있어야 함
  • Go 애플리케이션에서 DataKit까지 네트워크 접근 가능: 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 + Protobuf Trace http://<DataKit-IP>:9529/otel/v1/traces
OTLP/HTTP + Protobuf Metric http://<DataKit-IP>:9529/otel/v1/metrics
OTLP/HTTP + Protobuf Log http://<DataKit-IP>:9529/otel/v1/logs
OTLP/gRPC Trace, Metric, Log 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 연결

Go SDK 및 계측 라이브러리 설치

Go 프로젝트 디렉터리에서 공식 SDK, OTLP/HTTP Exporter 및 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.modgo.sum은 종속성 버전을 기록합니다. 프로덕션 환경에서는 이 두 파일을 커밋하고, OpenTelemetry 종속성을 업그레이드한 후 컴파일, 단위 테스트 및 분산 추적 회귀 테스트를 수행해야 합니다.

SDK 초기화

다음 예제에서는 다음 작업을 동시에 수행합니다.

  1. OTEL_SERVICE_NAMEOTEL_RESOURCE_ATTRIBUTES에서 리소스 속성을 읽습니다.
  2. OTLP/HTTP Trace Exporter 및 Metric Exporter를 생성합니다.
  3. 전역 TracerProvider, MeterProvider 및 W3C 컨텍스트 전파자를 등록합니다.
  4. otelhttp를 사용하여 HTTP Handler를 래핑하고 비즈니스 하위 Span 및 사용자 정의 Counter를 생성합니다.
  5. 프로세스 종료 시 Provider를 플러시하고 종료하여 버퍼의 데이터 손실을 방지합니다.
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("리소스 생성 실패: %w", err)
    }

    traceExporter, err := otlptracehttp.New(ctx)
    if err != nil {
        return nil, fmt.Errorf("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("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("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("HTTP 서버 종료 중 오류: %v", err)
        }
    }()

    log.Println("http://127.0.0.1:8080 수신 대기 중")
    if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
        log.Fatal(err)
    }
}

실제 프로젝트에서는 사용하는 구성 요소에 해당하는 계측 라이브러리를 설치해야 합니다. 예를 들어, 표준 라이브러리 net/httpotelhttp를 사용합니다. 다른 웹 프레임워크, 데이터베이스 또는 메시지 큐는 OpenTelemetry Registry에서 일치하는 패키지를 선택하고 해당 패키지의 지침에 따라 Handler, Transport, Client 또는 Driver를 래핑해야 합니다.

전송 주소 구성 및 실행

다음은 OTLP/HTTP + Protobuf를 사용하여 로컬 DataKit으로 전송합니다. OTEL_EXPORTER_OTLP_ENDPOINT는 기본 주소이며, Trace 및 Metric Exporter는 각각 /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 .

요청을 보내 Trace 및 Metric을 생성합니다.

curl http://127.0.0.1:8080/hello

Metric은 기본적으로 30초마다 내보내집니다. 이 예제에서는 sdkmetric.WithInterval(30*time.Second)로 제어합니다. 한 번의 내보내기 주기 후에 Guance에서 service=order-service로 분산 추적을 확인하고 app.request.count 메트릭을 조회할 수 있습니다.

OTLP/gRPC 사용

Go SDK의 OTLP 전송은 코드에서 사용하는 Exporter 패키지에 의해 결정됩니다. 이 예제에서는 otlptracehttpotlpmetrichttp를 직접 사용하므로 OTEL_EXPORTER_OTLP_PROTOCOL=grpc를 설정해도 gRPC로 전환되지 않습니다.

OTLP/gRPC를 사용하려면 gRPC Exporter를 설치합니다.

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

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

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에 해당 order-service; 프로덕션 환경에서는 명시적으로 설정해야 함
OTEL_RESOURCE_ATTRIBUTES 리소스 속성, 쉼표로 구분된 key=value 형식 deployment.environment.name=prod,service.version=1.0.0,team=backend

최소한 service.name, deployment.environment.nameservice.version을 설정하는 것이 좋습니다. 이는 Guance에서 서비스 소속, 환경 필터링 및 버전 분석에 사용됩니다. 사용자 정의 리소스 속성은 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이며, Exporter가 자동으로 신호 경로를 추가함; 신호별 매개변수는 전체 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 초기화 코드에 의해 제어되며, 동일한 이름의 일반 환경 변수를 설정해도 자동으로 적용되지 않습니다.

구성 항목 예제 코드 설명
Trace 샘플링 sdktrace.ParentBased(sdktrace.TraceIDRatioBased(1.0)) 1.0은 루트 Trace 전체 샘플링을 의미함; 프로덕션 환경에서는 용량에 따라 0.1 등의 비율로 조정하고 ParentBased를 통해 상위 샘플링 결정을 따름
Span 일괄 내보내기 sdktrace.WithBatcher(traceExporter) 프로덕션 환경에서는 일괄 내보내기 사용 권장; WithMaxQueueSize, WithMaxExportBatchSize, WithBatchTimeoutWithExportTimeout으로 추가 조정 가능
Metric 내보내기 주기 sdkmetric.WithInterval(30*time.Second) 주기적 내보내기 간격 제어; 너무 짧으면 애플리케이션, 네트워크 및 스토리지 오버헤드 증가
컨텍스트 전파 TraceContext{}, Baggage{} W3C traceparent, tracestatebaggage 사용; 호출 체인의 서비스는 전파 형식 호환성을 유지해야 함
리소스 탐지 resource.WithHost(), WithOS(), WithProcess() 호스트, 운영 체제 및 프로세스 속성을 자동으로 추가; 프로세스 매개변수 및 리소스 속성에 비밀 키와 같은 민감한 정보를 저장하지 않도록 주의

이 예제는 OTLP Exporter를 직접 생성하므로 다음 사항에 특히 주의해야 합니다.

  • OTEL_EXPORTER_OTLP_PROTOCOL은 코드에서 이미 선택한 HTTP 또는 gRPC Exporter를 변경하지 않습니다.
  • OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER는 이 예제에서 직접 생성한 Exporter를 비활성화하지 않습니다.
  • OpenTelemetry Go 핵심 SDK는 현재 모든 일반 SDK 환경 변수를 자동으로 적용하지 않습니다. 특히 OTEL_SDK_DISABLED, OTEL_TRACES_SAMPLER 또는 OTEL_PROPAGATORS가 사용자 정의 초기화 코드에서 자동으로 적용된다고 가정하지 마십시오.
  • 표준 환경 변수를 통해 Exporter를 동적으로 선택하고 초기화해야 하는 경우 공식 Contrib의 autoexport 패키지를 평가하고 테스트 환경에서 동작을 확인하십시오.

검증 및 문제 해결

  1. curl http://127.0.0.1:9529/v1/ping을 실행하여 애플리케이션이 DataKit에 액세스할 수 있는지 확인합니다.
  2. 애플리케이션을 시작하고 /hello에 액세스하여 애플리케이션 로그에 create trace exporter, create metric exporter 또는 OTLP 내보내기 오류가 없는지 확인합니다.
  3. 최소한 한 번의 Metric 내보내기 주기를 기다립니다.
  4. Guance의 APM 서비스 목록에서 order-service로 Trace를 조회합니다.
  5. 데이터를 찾을 수 없는 경우 DataKit opentelemetry 수집기 구성, 애플리케이션에서 DataKit까지의 네트워크, 전송 URL 및 DataKit 로그를 확인합니다.
  6. 중복 Span이 발생하는 경우 동일한 Handler, Transport, 데이터베이스 Client 또는 Driver가 중복으로 래핑되었는지 확인합니다.

참고 자료

문서 평가

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