ããã«ã¡ã¯ïŒ ã¿ã€ããŒã§Platform EngineerãããŠãã @MoneyForest ã§ãã ãã¡ã㯠Timee Product Advent Calendar 2024 ã®10æ¥ç®ã®èšäºã§ãã 2024幎8æã«å
¥ç€ŸããŠã幞ãã«ãããŒã ã¡ã³ããŒã«ãæµãŸããŠæ¥œããåããŠããŸãã å人çã«ãã£ããã¢ãããããŸãã§ããŠããªãã£ã OpenTelemetry ã顿ã«ããŠå®è£
ãããŠã¿ãã®ã§ãããããåŸãããæ°ã¥ããç¥èŠãå
±æããããšæããŸãã ã¯ããã« ã¢ããªã±ãŒã·ã§ã³ã®å¯èŠ³æž¬æ§ïŒObservabilityïŒãæ
ä¿ããäžã§ãAPM(Application Performance Monitoring)ã¯éèŠã§ãã çŸåšãJaegerãPrometheusã®ãããªéåçšã®ãã®ãããDatadogãNew RelicãSplunkãªã©æ§ã
ãªAPMãå©çšã§ããããŒã«ãååšããŸããïŒæ¬èšäºã§ã¯ãããããObservability BackendããšåŒç§°ããŸããïŒ ãããã®Observability Backendã§APMãå©çšããã«ã¯ãåã
ãæäŸããç¬èªã®SDKã䜿çšããŠTraceãSpanãéä¿¡ããå¿
èŠãããã以äžã®ãããªèª²é¡ããããŸããã ã¢ããªã±ãŒã·ã§ã³ã®å®è£
ã«åSDKã®ä»æ§ã滲ã¿åºã Observability Backendã®åãæ¿ããå°é£ OpenTelemetryã®ç»å Žã«ããããªãã¶ãŒãããªãã£ã«é¢ããæ§ã
ãªæŠå¿µãæœè±¡åãããçµ±äžããã仿§ãæäŸãããããã«ãªããŸããã ããã«ãããã¢ããªã±ãŒã·ã§ã³ã³ãŒããšObservability Backendã®å®è£
ãåé¢ããç¶æ³ã«å¿ããŠé©åãªããŒã«ãéžæã§ããããã«ãªããŸããã ã€ãŸããããŒã«ã«ã§ã¯Jaegerã®ãããªéåçšã®ããŒã«ã§éçºäžã«APMãå©çšãããããã€ããã¢ããªã±ãŒã·ã§ã³ã§ã¯Datadogã®ãããªåçšããŒã«ã§ãªãããªUIã§APMãå©çšããããã¢ãã¿ãªã³ã°ã¢ã©ãŒããçµãã ããªã©ãã§ãããšããããšã§ãã æ¬èšäºã§ã¯ãGoèšèªã§OpenTelemetryãæŽ»çšããŠãç°å¢ããšã«Observability BackendïŒJaegerãDatadogïŒãåãæ¿ããå®è£
ãè¡ãããšã§ããããã®ã¡ãªãããäœæããŠãããããšæããŸãã å®è£
ããŠã¿ã å®è£
ã®å
šéã¯ãã¡ãã«ãããŸãã https://github.com/MoneyForest/advent-calender-2024 å
±éã€ã³ã¿ãŒãã§ãŒã¹ã®å®çŸ©ã»åæååŠç OpenTelemetryã«æºæ ããå
±éã€ã³ã¿ãŒãã§ãŒã¹ãå®çŸ©ããããšã§ãåããã¯ãšã³ãã®å®è£
ãæœè±¡åããŸããïŒãã®å®è£
ã§ã¯ infrastructure/tracer.go ã«é
眮ããŠããŸããïŒ å
±éã€ã³ã¿ãŒãã§ãŒã¹ã¯OpenTelemetryã®SDKã®ä»æ§ã«åããŠã€ã³ã¿ãŒãã§ãŒã¹ãå®çŸ©ããŸãã äŸãã°Shutdownã¡ãœããã¯ãOpenTelemetryã®ä»æ§ã«åºã¥ãå®ããããŠãããã®ã§ãããã¬ãŒã¹ããŒã¿ã®æ¬ æãé²ãããã«å¿
èŠãªæšæºä»æ§ãšãªã£ãŠããŸãã åç
§ïŒ https://opentelemetry.io/docs/specs/otel/trace/sdk/#shutdown ãŸããä»åã¯ç°å¢ã«å¿ããŠObservability Backendãåãæ¿ããå®è£
ã®ãããenvãæ¡ä»¶ã«åæåããProviderãåãæ¿ããŠããŸãã // infrastructure/tracer.go package infrastructure import "context" type TracerProviderWrapper interface { Shutdown(context.Context) error } func InitTracer(env string ) (TracerProviderWrapper, error ) { switch env { case "dev" : return InitDatadog() default : return InitJaeger() } } Observability Backendåºæã®åæååŠç 次ã«åObservability Backendããšã®åæååŠçãæžããŠãããŸãã Datadog Datadogã¯2ã€ã®æ¹æ³ã§éä¿¡ã§ããŸãã ddotelã¢ããã¿ãŒçµç± åç
§ïŒ https://docs.datadoghq.com/ja/tracing/trace_collection/custom_instrumentation/go/otel/ ãã¡ãã§ã¯Datadogã®SDKã§ããdd-trace-goããddotelãšããã¢ããã¿ãŒãéããŠOtelã«æºæ ããTraceProviderãçæããŸãã éä¿¡ããããŒãã¯Datadog Agentã®ããã©ã«ãããŒãã§ãã8126ã«ãªããŸãã // infrastructure/datadog.go package infrastructure import ( "context" "os" "go.opentelemetry.io/otel" ddotel "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/opentelemetry" ddtracer "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) type ddTracerProvider struct { provider *ddotel.TracerProvider } func InitDatadog() (*ddTracerProvider, error ) { tp := ddotel.NewTracerProvider( ddtracer.WithService(os.Getenv( "SERVICE" )), // MoneyForest ddtracer.WithEnv(os.Getenv( "ENV" )), ddtracer.WithServiceVersion( "1.0.0" ), ddtracer.WithAgentAddr( "localhost:8126" ), ) otel.SetTracerProvider(tp) return &ddTracerProvider{provider: tp}, nil } func (p *ddTracerProvider) Shutdown(ctx context.Context) error { return p.provider.Shutdown() } Otel SDKçµç± ãã¡ãã§ã¯Otel SDKã䜿çšããŠTraceProviderãçæããŸãã åç
§ïŒ https://docs.datadoghq.com/ja/opentelemetry/interoperability/otlp_ingest_in_the_agent/?tab=host#enabling-otlp-ingestion-on-the-datadog-agent éä¿¡ããããŒãã¯OTLP HTTPã®ããã©ã«ãããŒãã§ãã4318ã«ãªããŸãã // infrastructure/datadog.go package infrastructure import ( "context" "os" "go.opentelemetry.io/otel" ddotel "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/opentelemetry" ddtracer "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) type ddTracerProvider struct { provider *sdktrace.TracerProvider } func InitDatadog() (*ddTracerProvider, error ) { exporter, err := otlptracehttp.New( context.Background(), otlptracehttp.WithEndpoint( "localhost:4318" ), otlptracehttp.WithInsecure(), otlptracehttp.WithURLPath( "/v1/traces" ), ) if err != nil { return nil , err } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String(os.Getenv( "SERVICE" )), // MoneyForest semconv.DeploymentEnvironmentKey.String(os.Getenv( "ENV" )), )), ) otel.SetTracerProvider(tp) return &ddTracerProvider{provider: tp}, nil } func (p *ddTracerProvider) Shutdown(ctx context.Context) error { return p.provider.Shutdown(ctx) } Jaeger Jaeger㯠jaegar-client-go ã®SDKããããŸãããã2024/5ã«éæšå¥šãšãªã£ãŠããŸãã ä»ã¯OpenTelemetryã«æºæ ããããšãæšå¥šãããŠãããããOtel SDKã«ããå®è£
ãè¡ããŸãã // infrastructure/jaeger.go package infrastructure import ( "context" "os" "go.opentelemetry.io/otel" "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.4.0" ) type jaegerTracerProvider struct { provider *sdktrace.TracerProvider } func InitJaeger() (jaegerTracerProvider, error ) { exporter, err := otlptracehttp.New( context.Background(), otlptracehttp.WithEndpoint( "localhost:4318" ), otlptracehttp.WithInsecure(), ) if err != nil { return jaegerTracerProvider{}, err } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String(os.Getenv( "SERVICE" )), semconv.DeploymentEnvironmentKey.String(os.Getenv( "ENV" )), )), ) otel.SetTracerProvider(tp) return jaegerTracerProvider{provider: tp}, nil } func (p jaegerTracerProvider) Shutdown(ctx context.Context) error { return p.provider.Shutdown(ctx) } ã¢ããªã±ãŒã·ã§ã³ã§ã®å©çš ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãOpenTelemetryã®æšæºAPIãéããŠTracerã䜿çšããããšã§ãObservability Backendã«äŸåããªãäžè²«ããå®è£
ãå¯èœã«ãªããŸãã package main import ( "context" "crypto/rand" "fmt" "log" "os" _ "github.com/go-sql-driver/mysql" "github.com/MoneyForest/timee-advent-calender-2024/internal" "github.com/MoneyForest/timee-advent-calender-2024/internal/handler" "github.com/MoneyForest/timee-advent-calender-2024/internal/infrastructure" ) func main() { // Initialize tracer tp, err := infrastructure.InitTracer(os.Getenv( "ENV" )) if err != nil { log.Fatal(err) } defer tp.Shutdown(context.Background()) // ... } func (h *UserHandler) CreateUser(ctx context.Context, email string ) error { tracer := otel.Tracer(os.Getenv( "SERVICE" )) ctx, span := tracer.Start(ctx, "UserHandler.CreateUser" ) defer span.End() span.SetAttributes( attribute.String( "email" , email), attribute.String( "handler" , "UserHandler" ), attribute.String( "method" , "CreateUser" ), ) return h.userUsecase.CreateUser(ctx, email) } åãããŠã¿ã ãããŸã§ã®å®è£
ãããŸããã£ãŠããã°ãJaegerãšDatadogã§åããããªAPMãèŠãããšãã§ããæ³å®ã§ãã確èªããŠã¿ãŸãããã Docker Composeã§ gcr.io/datadoghq/agent ãš jaegertracing/all-in-one ã®ãµã€ãã«ãŒãç«ãŠãŸãã æ¬æ¥ã¯datadog-agentã¯devç°å¢ä»¥äžã«å¿
èŠã§ãECSã®ãµã€ãã«ãŒãªã©ã«å®çŸ©ããã°ããã§ãã ïŒãªããžããªã§ã¯MySQLãRedisã®ã³ã³ããã建ãŠãŠããŸããçç¥ããŸããïŒ Jaegarã¯UIãŸã§ããã±ãŒãžã³ã°ãããã€ã¡ãŒãžãå
¬åŒããæäŸãããŠããããã¡ãã«ãã¬ãŒã¹ãéä¿¡ããããšã§ãlocalhostã®UIããAPMã確èªã§ããŸãã datadog-agent : image : gcr.io/datadoghq/agent:latest environment : - DD_API_KEY=${DD_API_KEY} - DD_SITE=datadoghq.com - DD_APM_ENABLED= true - DD_APM_NON_LOCAL_TRAFFIC= true - DD_OTLP_CONFIG_RECEIVER_PROTOCOLS_HTTP_ENDPOINT=0.0.0.0:4318 ports : - "8126:8126" - "4319:4318" # OTLP HTTP (ããŒã«ã«ãã·ã³ã®ããŒãã¯Jaegarãšè¢«ãã®ã§ããããŠãã) volumes : - /var/run/docker.sock:/var/run/docker.sock - /proc/:/host/proc/:ro - /sys/fs/cgroup:/host/sys/fs/cgroup:ro jaeger : image : jaegertracing/all-in-one:latest ports : - "16686:16686" # UI - "14250:14250" - "4318:4318" # OTLP HTTP environment : - COLLECTOR_OTLP_ENABLED= true devç°å¢ïŒDatadogïŒ https://app.datadoghq.com/apm/traces ã§ãã¬ãŒã¹æ
å ±ãèŠãããšãã§ããŸãã 以äžã¯ddotelã䜿çšããŠéä¿¡ããã¡ããªã¯ã¹ã§ãã 以äžã¯Otel SDKã䜿çšããŠéä¿¡ããã¡ããªã¯ã¹ã§ãã 1. ãšç°ãªãç¹ãšããŠãSpanã®Attributeã« otel ã®æ§é äœããããŸãããŸãã process_id ã runtime-id ã¯ååšããŸããã localç°å¢ïŒJaegarïŒ http://localhost:16686/search ã§ãã¬ãŒã¹æ
å ±ãèŠãããšãã§ããŸããDatadogãšäžéšã®ã¡ã¿ããŒã¿ãç°ãªã£ãŠããããšãå®å
šã«åäžã§ã¯ãªããã®ã®ãæŠãåãå
容ãèŠãããšãã§ããŸãã Datadogã®ãã¬ãŒã¹ãšç°ãªãç¹ãšããŠã¯ããµãŒãã¹åãšããŠäžããŠããMoneyForestãotel.libraryã«ãããã³ã°ãããŠããŸãããããã¡ãã§ã¯otel.scopeã«ãããã³ã°ãããŠãããšããç¹ã§ãã å®è£
ããŠããã£ãããš Goã®Otel SDKããçæããã sdktrace.TracerProvider ã§ã¯ Shutdown ã¯contextãåŒæ°ã«åããã®ã®ã ddotel.TracerProvider ã® Shutdown ã¯contentãåŒæ°ã«åããªãããªã©ã®çްããªå·®ç°ããããå
±éã€ã³ã¿ãŒãã§ãŒã¹ã®æ¹ã§åžåããå¿
èŠããããŸããã Datadogã®å®è£
ã§Otel SDKãçŽæ¥äœ¿ãæ¹ãæ¡çšããã°ãå
±éã€ã³ã¿ãŒãã§ãŒã¹ãçšæããå¿
èŠããªãã以äžã®ããã« InitTracer ã¯åæåã®æ¡ä»¶åå²ã®ã¿ãæžããå®è£
ãå¯èœã§ãã func InitTracer(env string ) (*sdktrace.TracerProvider, error ) { switch env { case "dev" : return initDatadog() default : return initJaeger() } } ãŸãddotelã®ã©ã€ãã©ãªã«ã¯ added to the OpenTelemetry API will default to being a no-op until implemented by this library. ã®ã³ã¡ã³ãããããæ°æ©èœã®å©çšã«ã¯Goã®Otel SDKã§ã¯ãªãDatadog AgentåŽã§ã®å®è£
ãåŸ
ã€å¿
èŠãããããã§ããã ãã®ãããDatadogã®ç¬èªæ©èœãéèŠããå Žåã¯ddotelãæ¡çšããOtelãžã®æºæ ãéèŠããå Žåã¯Otel SDKã§å®è£
ãããªã©ãå®è£
ã®éã«æææ±ºå®ãå¿
èŠã«ãªãããã§ãã ãŸãã现ããªãããã³ã°ã®éããååšããããªãããTraceã䜿ã£ãŠã¢ãã¿ãªã³ã°ã¢ã©ãŒããçµãã§ããå Žåã¯æ³šæããŠåãæ¿ããå¿
èŠãããããã§ããïŒäŸãã°DatadogããNew Relicã«å€æŽããå Žåãªã©ïŒ åçšã®ããŒã«ã§ã¯æéäœç³»ãªã©ãç°ãªãããã仿§ãåãã§ããµã³ããªã³ã°ã¬ãŒãã®å€ã®èª¿æŽãå¿
èŠã«ãªãããšãããã§ãããã ãŸãšã OpenTelemetryãæŽ»çšããããšã§ããã¬ãŒã·ã³ã°å®è£
ã§ããäžè²«æ§ã®ããã³ãŒããå®çŸã§ããããšãããããŸããã ããã«ãããç°å¢ã«å¿ããŠé©åãªObservability Backendãå©çšããããç§»è¡ããããšã容æã«ãªã£ããããã®ã¯ééããªãããã§ãã äžæ¹ã§æšæºä»æ§ã«ã€ããŠã¯ãŸã ãŸã çè§£ãæµ
ãã®ã§ãOpenTelemetryèªäœã®çè§£ãæ·±ããŠãããã«æ¥œãããšã³ãžãã¢ã©ã€ããéããããšæããŸãïŒãŸããã