MSA 분산 추적

위 같이 마이크로서비스가 여러개 있을 경우, 각 서비스들간에 호출 체인이 일어납니다.
간단한 예로 이전 포스팅에서 통화 변환 서비스가 환율정보를 받아오기 위해서 환율 서비스의 API를 호출 했었죠.
마이크로 서비스들이 많아지면 많아질수록 이런 호출체인은 더 복잡해지겠죠. 그럼 상황에서 문제가 발생하기 마련입니다.
이렇게 발생하는 문제를 추적하고 분석할 수 있어야하는데 이를 위해 분산 추적을 사용합니다.

이미지와 같이 모든 마이크로서비스의 모든 정보를 분산 추적 서버로 보냅니다.
그리고 분산 추적 서버는 각 서비스에서 전송된 트레이스 데이터를 수집하고 이를 DB에 저장합니다.
이 데이터는 서비스 간 호출 정보, 요청의 시작 시간과 종료 시간, 응답 시간, 서비스 간의 관계, 그리고 각 단계에서 발생한 메타데이터(i.g. HTTP 상태 코드, 에러 등) 를 포함하고 있으며, 이를 기반으로 트랜잭션의 흐름이나 성능 병목 지점 등을 분석할 수 있습니다.
또한, 분산 추적 서버는 UI를 통해 모든 서비스의 요청에 대한 정보를 시각화하여 제공합니다.
이러한 분산추적 기능을 제공하고 스프링 클라우드와 통합되어 사용하기 편한 Zipkin을 사용해봅시다.
Zipkin을 사용하여 분산추적 시스템 구성하기
분산 추적 서버를 로컬에서 따로 만들지않고 Docker를 사용하여 서버를 컨테이너로 띄우겠습니다.
터미널을 열어 docker run -p 9411:9411 openzipkin/zipkin:latest 명령어를 입력해줍니다. 분산 추적 서버의 포트는 일반적으로 9411을 사용하기에 포트를 9411로 매핑해줍니다.

도커허브에서 이미지를 받아와 실행하게되고 정상적으로 실행이 되었다면 위와 같은 화면이 보일겁니다.
컨테이너 띄우는데 성공했으면 http://localhost:9411/ 로 접근을 해봅니다.

접속 결과, 아직은 연결된 서비스가 없어 아무것도 찾아볼 수 없는 상태입니다.
그럼 분산 추적서버에 마이크로서비스들을 연결해보겠습니다.
implementation 'io.micrometer:micrometer-observation'
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
implementation 'io.opentelemetry:opentelemetry-exporter-zipkin'
환율 서비스의 build.gradle로 들어가 위 3개 의존성을 추가해주도록 합니다.
각 의존성들에 알아보자면,
- micrometer-observation - 애플리케이션에서 발생하는 Observation을 수집하고 기록하는 기능을 제공합니다. 성능 데이터를 관찰하고 추적하는 데 사용됩니다.
- micrometer-tracing-bridge-otel - OpenTelemetry를 사용하여 Micrometer의 분산 추적 기능을 통합할 수 있는 브릿지 역할을 합니다. 이 의존성은 Micrometer Tracing API와 OpenTelemetry 간의 연결을 도와줍니다.
- OpenTelemetry와 Micrometer 란?
- OpenTelemetry는 애플리케이션에서 발생하는 트레이스와 메트릭 데이터, 로그를 수집하여 모니터링 및 성능 분석을 가능하게 하는 오픈소스 관측 프레임워크입니다.
-> 스프링부트 2.x 버전에서는 Brave를 사용 -> OpenTelemetry와 달리 트레이스만 추적함. - Micrometer는 애플리케이션에서 발생하는 메트릭 데이터를 수집하고 모니터링 시스템으로 전송하는 경량화된 Java 라이브러리입니다.
-> 스프링부트 2.x 버전에서는 Sleuth를 사용
- OpenTelemetry는 애플리케이션에서 발생하는 트레이스와 메트릭 데이터, 로그를 수집하여 모니터링 및 성능 분석을 가능하게 하는 오픈소스 관측 프레임워크입니다.
- OpenTelemetry와 Micrometer 란?
- opentelemetry-exporter-zipkin - OpenTelemetry 데이터를 Zipkin 서버로 내보내는 역할을 합니다. Zipkin을 분산 추적 서버로 사용하면서 OpenTelemetry로 수집한 데이터를 전송할 수 있습니다
의존성을 추가 했다면 application.yml을 열어줍니다.
management:
tracing:
sampling:
probability: 1.0
logging:
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
위와 같이 프로퍼티를 정의해줍니다.
- management.tracing.sampling.probability - 이 속성은 샘플링 확률을 설정하는 데 사용됩니다. 샘플링 확률은 애플리케이션에서 생성된 요청 중 얼마나 많은 트레이스를 실제로 기록할지 결정하는 값입니다.
- 값이 1.0일 경우 - 모든 요청이 추적됩니다.
- 값이 0.5일 경우 - 절반의 요청만 추적됩니다.
- 일반적으로 0~1 사이의 값으로 설정되며 1.0은 모든 요청을 추적하는 것을 의미하므로 100%의 트레이스가 기록됩니다. 이 설정은 1.0으로 했을 시, 성능에 영향을 미칠 수 있다는 점을 유의해야합니다.
- logging.pattern.level - 로그에 표시될 로그 레벨의 형식을 지정합니다.
- ${spring.application.name:} - 로그 항목에 애플리케이션 이름을 포함시킵니다.
- %X{traceId:-}: Trace ID를 로그 패턴에 포함시킵니다. Trace ID는 분산 추적에서 요청의 전체 흐름을 식별하는 값으로 사용됩니다. 값이 없을 때는 "-" 로 표시됩니다.
- %X{spanId:-}: Span ID는 특정 작업이나 요청의 하위 작업을 식별하는 데 사용됩니다. Trace ID와 함께 특정 요청이 각 서비스에서 어떻게 처리되는지 추적할 수 있습니다. 값이 없을 때는 "-" 로 표시됩니다.
이렇게 하면 환율 서비스와 분산 추적 서버와의 연결이 되었습니다. 바로 테스트해보겠습니다.
zipkin 서버는 컨테이너로 띄워 실행하고, 인텔리제이에서 유레카서버와 환율 서비스를 실행한 뒤,
환율서비스의 엔드포인트로 접근을 해보겠습니다.

접근이 정상적으로 되었고, 분산추적 서버에서 해당 요청데이터를 잘 받아왔는지 확인해보면,

http://localhost:9411 로 접근하여 RUN QUERY 를 눌러보면 이미지와 같이 데이터들이 쭉 나옵니다.
이미지의 첫째줄에는 제가 직접 테스트해보기로 한 환율서비스의 엔드포인트 요청에 대한 데이터이고,
그 아래의 여러개의 다른 요청들은 환율 서비스와 유레카 서버의 통신 데이터입니다.
/currency-exchange/from/{from}/to/{to} 의 요청 추적 데이터 행의 가장 오른쪽에 show를 눌러보면,

해당 요청의 HTTP 메서드 타입, URL, 요청 처리 시간, 서비스의 이름, 트레이스 및 스팬 고유식별자, 상태코드 등 여러 정보를 담고 있습니다.
여기서, 추가적으로 해당 엔드포인트 코드에 특정 로그를 출력하는 코드를 작성하고 다시 요청해보겠습니다.
@GetMapping("/currency-exchange/from/{from}/to/{to}")
public CurrencyExchange getExchangeValue(
@PathVariable("from") String from,
@PathVariable("to") String to) {
log.info("[CurrencyServiceController getExchangeValue] {} to {}", from, to); // 로그 추가
String property = environment.getProperty("local.server.port");
CurrencyExchange finded = currencyExchangeRepository.findByFromAndTo(from, to);
finded.setEnvironment(property);
if (finded == null) {
throw new RuntimeException(from + " & " + to + " 가 존재하지 않습니다.");
}
return finded;
}
위 코드와 같이 로그출력 코드를 입력하고 다시 요청을 해보면,
INFO [currency-exchange,f8dfe1c738767f0922a6a17bdf8aceaa,6269e83d8869740f] 8045 --- [currency-exchange] [nio-8000-exec-1] [f8dfe1c738767f0922a6a17bdf8aceaa-6269e83d8869740f] c.m.c.c.CurrencyExchangeController : [CurrencyServiceController getExchangeValue] USD to KRW
출력된 로그에서 f8dfe1c738767f0922a6a17bdf8aceaa 와 6269e83d8869740f 가 출력되는 것을 볼 수 있는데 이는 각각 Trace Id, Span Id 입니다.
- Trace ID - 해당 요청이 시작된 전체 트랜잭션을 추적할 수 있는 고유한 식별자입니다. 여러 마이크로서비스 간에 발생한 트랜잭션의 흐름을 확인할 때 유용합니다.
- Span ID - 해당 요청의 하위 작업을 나타내는 고유 식별자입니다. Trace ID와 함께 개별 작업이나 호출의 세부 정보를 파악할 수 있습니다.
이렇게 분산추적 기능을 통해 요청에 대한 정보(trace id, span id)가 함께 기록이 되어 로그가 나오는 것을 확인할 수 있습니다.
다음으로 통화변환 서비스와 API Gateway 서버도 분산추적 서버에 연결하겠습니다.
implementation 'io.micrometer:micrometer-observation'
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
implementation 'io.opentelemetry:opentelemetry-exporter-zipkin'
똑같이 통화변환 서비스와 API Gateway 각각의 build.gradle에도 아래 3가지 의존성을 추가합니다.
management:
tracing:
sampling:
probability: 1.0
logging:
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
그리고 각각 application.yml 에들어가 프로퍼티 지정도 똑같이 해주면 끝납니다.
이렇게 해주면 API Gateway, 환율 서비스, 통화변환 서비스 총 3개가 모두 분산추적 서버와 연결되었습니다. 그럼 연결되었는지 서버를 모두 실행시키고 각 서비스별로 엔드포인트 접근 후, 요청들을 올바르게 추적하는지 확인해보겠습니다.

통화변환 서비스의 /currency-conversion-feign/from/{from}/to/{to}/quantity/{quantity} 엔드포인트로 접근했을 때,
해당 환율변환 api는 환율정보를 받아오기 위해 환율서비스(currency-exchange)에 요청을 합니다. 그래서 해당 한 번 접근으로 두 가지 요청이 추적되는 것을 볼 수 있습니다.
이를 통화변환 api의 요청 추적 행에서 환율데이터 요청 api까지 함께 추적할 수 있어야 하는게 바람직해보입니다.
참고로 스프링부트 2.x 버전에서는 요청 체이닝이 하나의 행으로 보여주었지만, 스프링부트 3.x 버전에서는 추가적으로 설정을 해주어야합니다.
feign client로 환율 서비스에 보내는 요청 추적 데이터를 통합하기 위해서는 feign-micrometer 의존성을 추가해주기만 하면 끝납니다.
implementation 'io.github.openfeign:feign-micrometer'
위 의존성을 추가 해준 뒤, 다시 요청을 하고 추적데이터를 확인해봅시다.

)

위 두 이미지와 같이 요청 추적데이터가 잘 통합된 모습을 확인해볼 수 있습니다.
참고
https://www.udemy.com/course/microservices-with-spring-boot-and-spring-cloud