Spring boot에 jaeger 도입 트러블 슈팅

코딩은 많은 시행착오·2024년 7월 31일
0

back-end

목록 보기
14/18
post-thumbnail

엊그제 일하던 중 책임님이 갑자기 나를 찾으셨다..

사내에서 모니터링 시스템을 구축하고 있는 중이셨는데 local 환경에선 jaeger가 잘 붙었지만 dev 환경에서는 안붙는다고 새로운 시선이 필요하시다며 도움을 요청하셨다.

처음에는 단순히 dev 환경의 ec2가 인터넷이 안돼서 그런줄로 알았지만, Spring Gateway로 라우팅이 돼있고, ec2의 보안그룹도 jaeger가 사용하는 port가 다 뚫려있는 상태라서 그런 문제는 아니였다.

한참을 헤메다가 결국 해결했고, 그 과정을 적어보겠다.

발생한 문제

ec2에서 jar 실행

java -javaagent:/path/to/opentelemetry-javaagent.jar \
-Dotel.traces.exporter=jaeger \
-Dotel.exporter.jaeger.endpoint=http://SpringGatewayUrl/trace \
-Dotel.resource.attributes=service.name=test,compose_service=test \
-Dotel.jaeger.service.name=test \
-jar test-0.0.1-SNAPSHOT.jar

OpenTelemetry Javaagent failed to start
io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException: otel.traces.exporter set to "jaeger" but opentelemetry-exporter-jaeger not found on classpath. Make sure to add it as a dependency.
        at io.opentelemetry.sdk.autoconfigure.SpanExporterConfiguration.configureExporter(SpanExporterConfiguration.java:96)
        at io.opentelemetry.sdk.autoconfigure.SpanExporterConfiguration.configureSpanExporters(SpanExporterConfiguration.java:68)
        at io.opentelemetry.sdk.autoconfigure.TracerProviderConfiguration.configureTracerProvider(TracerProviderConfiguration.java:58)
        at io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder.build(AutoConfiguredOpenTelemetrySdkBuilder.java:465)
        at io.opentelemetry.javaagent.tooling.OpenTelemetryInstaller.installOpenTelemetrySdk(OpenTelemetryInstaller.java:29)
        at io.opentelemetry.javaagent.tooling.AgentInstaller.installBytebuddyAgent(AgentInstaller.java:122)
        at io.opentelemetry.javaagent.tooling.AgentInstaller.installBytebuddyAgent(AgentInstaller.java:102)
        at io.opentelemetry.javaagent.tooling.AgentStarterImpl.start(AgentStarterImpl.java:99)
        at io.opentelemetry.javaagent.bootstrap.AgentInitializer$1.run(AgentInitializer.java:53)
        at io.opentelemetry.javaagent.bootstrap.AgentInitializer$1.run(AgentInitializer.java:47)
        at io.opentelemetry.javaagent.bootstrap.AgentInitializer.execute(AgentInitializer.java:68)
        at io.opentelemetry.javaagent.bootstrap.AgentInitializer.initialize(AgentInitializer.java:46)
        at io.opentelemetry.javaagent.OpenTelemetryAgent.startAgent(OpenTelemetryAgent.java:57)
        at io.opentelemetry.javaagent.OpenTelemetryAgent.premain(OpenTelemetryAgent.java:45)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:560)
        at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:572)

위와 같이 코드를 실행하면 프로젝트가 실행되면서 아래와 같은 에러를 내뿜었다.
opentelemetry-exporter-jaeger가 class-path에 포함되지 않았다고 했는데, 필요한 세팅은 다 해주어서 하나하나 체크해봤다.
jaeger에서 매트릭을 수집하는 14250 port는 gateway에서 라우팅을 해줬고 코드 상 문제가 없어 보였다.

implementation 'io.opentelemetry:opentelemetry-exporter-jaeger:1.34.1'

또한, 구글링을 해도 위와 같은 종속성을 넣으면 해결된다했는데 이미 책임님이 시도하셨던 방법이였고 역시나 되지않았다.

문제 해결 과정

opentelemetry-exporter-jaeger 이놈을 class-path에 추가하기 위해 여러가지 방법을 시도했다.
fat-jar 파일을 만들어보기도 했고 이것 저것 시도해보다가 안됐다.
그때 책임님이 local에서는 분명 잘됐다고 하신 말씀이 떠올랐다.

local에 jaeger container 구축 및 실행

local 환경에서 jaeger를 구축하고 테스트해보기로 했다.

일단 docker-compose를 이용해서 jaeger all-in-one을 설치했다.
이 이미지는 jaeger의 모든 구성요소를 단일 컨테이너에 넣은 것이다.

docker-compose.yml

version: '3.9'

services:
  jaeger:
    platform: linux/amd64
    image: jaegertracing/all-in-one:1.59
    container_name: jaeger_1_59
    volumes:
    ports:
      - "16686:16686"
      - "6831:6831/udp"
      - "6832:6832/udp"
      - "4317:4317"
      - "4318:4318"
      - "9411:9411"
      - "14250:14250"
    expose:
      - 16686
      - 6381
      - 6832
      - 4317
      - 4318
      - 9411
      - 14250
    environment:
  		...
    extra_hosts:
      - 'host.docker.internal:host-gateway'
    networks:
    	...
    mem_limit: "2g"
// 일부 내용 생략

Intellij VM option

-javaagent:/path/to/opentelemetry-javaagent.jar
-Dotel.traces.exporter=jaeger
-Dotel.exporter.jaeger.endpoint=http://localhost:14250
-Dotel.resource.attributes=service.name=test,compose_service=test
-Dotel.jaeger.service.name=test

localhost에 jaeger 컨테이너를 띄우고 vm option에 다음과 같이 넣어 실행하니 jaeger에 데이터가 전송됐다.
하지만 VM option에 넣어 실행하면 되는데 똑같은 옵션으로 build를 해서 -jar로 실행하면 맨처음에 오류와 같이 뜨면서 jaeger에 데이터를 전송하지 못하는 문제가 발생했다!

jar 파일 실행

java -javaagent:/path/to/opentelemetry-javaagent.jar \
-Dotel.traces.exporter=jaeger \
-Dotel.exporter.jaeger.endpoint=http://localhost:14250 \
-Dotel.resource.attributes=service.name=test,compose_service=test \
-Dotel.jaeger.service.name=test \
-jar test-0.0.1-SNAPSHOT.jar

여기서 두 가지의 차이점을 알면 해결할 수 있을까라고 생각했고 Intellij가 어떻게 프로젝트를 실행하는지 분석해봤다.

인텔리제이 분석

프로젝트 실행

프로젝트를 실행하니 맨 위에 실행구문 같은 게 있었다. 이 구문을 복사해서 메모장에 붙여서 분석하니 다음과 같은 구조로 프로젝트를 실행하고 있었다.

프로젝트 실행문

/Path/to/Library/Java/JavaVirtualMachines/corretto-21.0.3/Contents/Home/bin/java \
-javaagent:/Path/to/opentelemetry/opentelemetry-javaagent.jar \
-Dotel.traces.exporter=jaeger \
-Dotel.exporter.jaeger.endpoint=http://localhost:14250 \
-Dotel.resource.attributes=service.name=test,compose_service=test \
-Dotel.jaeger.service.name=test \
-XX:TieredStopAtLevel=1 \
-Dspring.output.ansi.enabled=always \
-Dcom.sun.management.jmxremote \
-Dspring.jmx.enabled=true \
-Dspring.liveBeansView.mbeanDomain \
-Dspring.application.admin.enabled=true \
-Dmanagement.endpoints.jmx.exposure.include=* \
-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=55792:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 \
-Dsun.stdout.encoding=UTF-8 \
-Dsun.stderr.encoding=UTF-8 \
-classpath /Path/to/IdeaProjects/test/build/classes/java/main:/Path/to/IdeaProjects/test/build/resources/main:각종외부종속성... \
com.package.TestApplication

구조는 java / VM option / 각종 intellij 옵션 / classpath로 외부 종속성 및 main calss / 메인 클래스 였다.
몇가지 실행 안되는 구문들을 빼고 실행하니 localhost jaeger에 데이터를 전송할 수 있었다.
여기서 어떤 차이가 있는지 분석했고, classpath 부분에서 힌트를 찾았다.

path/to/.gradle/caches/modules-2/files-2.1/io.opentelemetry.javaagent/opentelemetry-javaagent/1.28.0/dd05936291e121dfad1fd994f2b719be5898c238/opentelemetry-javaagent-1.28.0.jar

외부 종속성 부분의 코드를 하나씩 지우면서 어느 부분이 사라지면 똑같은 에러가 발생하는지 봤는데 이부분이 포함되어야 에러가 발생하지 않았다.
이 부분을 -jar 구문에 붙여넣기 하고 실행해봤는데, -jar 와 class-path와는 서로 연동되지 않는다고 해서 다른 방법을 찾았다.

근데 어딘가 익숙한 jar파일인가 했는데 vm option으로 넣어주는 opentelemetry-javaagent 였던 것이였다!
이 사실을 안 뒤에 해당 외부 종속성을 찾아갔고 경로를 열어서 파일을 추출했다.

해당 외부 종속성

파일 추출

jar파일로 javaagent 항목의 javaagent를 바꿔주니 jar 파일로도 실행이 가능했다.

java -javaagent:/path/to/opentelemetry-javaagent-1.28.0.jar \
-Dotel.traces.exporter=jaeger \
-Dotel.exporter.jaeger.endpoint=http://SpringGatewayUrl/trace \
-Dotel.resource.attributes=service.name=test,compose_service=test \
-Dotel.jaeger.service.name=test \
-jar test-0.0.1-SNAPSHOT.jar

그리고 ec2에 해당 jar를 sftp로 밀어넣고 실행하니 게이트웨이를 통해 jaeger에 연결됐다!

결론

결국 opentelemetry javaagent의 버전이 안맞아서 발생한 문제였다.
한국어로 된 메뉴얼도 많이 없어서 힘들었지만, 이슈를 해결하면서 짜릿한 감정과 업무에 도움이 된 것 같아 뿌듯함도 느꼈다.

이 글을 읽는 분들도 저런 에러를 마주친다면 위와 같은 방법으로 해결하길 바라며 블로그에 글을 남긴다.
별거 아닌 문제였지만, 에러가 불친절해서 오래 걸렸던 것 같다.
--끝--

0개의 댓글