이번 포스트에서는 Sampling을 사용해서 내가 원하는 http요청만 Zipkin으로 추적해보도록 하자
MSA에서 요청 추적은 시스템의 성능과 안정성을 모니터링하는 데 중요한 역할을 한다. 최근 프로젝트에서 우리는 Zipkin 로그에 ALB의 Health Check 요청이 과도하게 기록되는 문제에 직면했다. 이 글에서는 이 문제를 인식하고 해결하기 위한 우리 팀의 접근 방법을 공유하려 한다.
이 포스트는 아래에 링크걸어놓은 이전 포스트에서부터 이어지는 내용입니다!
시작 전에 이전 글의 서론을 통해 이번 포스트에서는 어떤 것을 진행할지 파악해보자
Zipkin을 적용한 후에, 분산 추적 로그를 확인하기 위해 Zipkin 웹 페이지에 접속했는데 내가 직접 보낸 요청한 HTTP 요청으로 인해 발생한 추적 로그보다는, ALB가 상태 검사를 위해 보낸 HTTP 요청으로 인한 로그가 훨씬 많이 남아 있는 상황이 발생했다. ALB가 서버 상태를 지속적으로 체크하기 위해 요청을 보내는 과정에서 이렇게 로그가 쌓인 것이다. 이 문제를 해결하기 위해서 ALB의 요청이 Zipkin에 기록되는 것을 최소화하려고 두 가지 방법을 시도해 봤다.
1. Ping을 던지는 주기를 늘린다.
2. 기존에는 /test/ping이라는 url에 요청을 보냈지만 이것을 루트경로인 "/" 로 변경한다.
이전 포스트에서 1번을 진행해 봤는데 이것이 근본적인 해결방법이 아니었다. 그 이유는 ALB는 지속적으로 ping을 던져서 검사를 하는것이 옳은 동작방식이기 때문에 주기를 늘린다면 그 사이에 서버가 고장나면 그만큼 사용성이 안좋아지기 때문이다.
그래서 이번에는 2번 방식으로 ALB의 url을 설정해서 한번 해결을 해보고자 한다.
우리팀은 ALB의 HealthCheck요청 url을 변경하기 전에 애초에 Zipkin에서 HealthCheck검사를 하는것이 맞는지에 대한 회의를 진행했다. 그 결과 아래와 같이 결론이 났다.
1. Zipkin으로 ALB의 HealthCheck를 추적하는것이 좋은 방법일까?
1-1. GPT-4와의 문답결과
AWS의 Application Load Balancer(ALB)의 헬스 체크 로그가 Zipkin에 남는 문제에 대해 말하자면, Zipkin은 분산 시스템에서 서비스 간의 요청을 추적하는데 사용되는 도구야. 이런 도구의 주 목적은 서비스 간의 상호작용을 추적하고 문제를 진단하는 건데, ALB의 헬스 체크 같은 내부적인 요청까지 모두 로그로 남기면 불필요한 데이터가 많아지고, 실제로 중요한 트래픽 분석이 어려워질 수 있어.
1-2. Zipkin으로 ALB의 HealthCheck를 추적하는 것에 대해
- 일반적으로는 추천하지 않는다. Zipkin의 주요 목적은 서비스 간의 요청을 추적하는 것이기 때문에 ALB의 헬스 체크 요청은 이 목적에 맞지 않을 수 있다. 이런 요청들은 분산 추적 시스템의 로그를 불필요하게 늘리고, 실제 중요한 데이터 분석을 방해할 수 있다.
1-3. ALB의 헬스 체크 로그 관리
- ALB의 헬스 체크 요청을 Zipkin에서 제외하는 방법을 찾는 게 좋을것 같다. 예를 들어, Zipkin 클라이언트 구성에 Sampling을 추가해서 특정 요청 패턴(예: 헬스 체크 URL)을 무시하도록 설정할 수 있다. 이렇게 하면 중요한 서비스 간의 요청에만 집중할 수 있고, 로그 데이터도 더 관리하기 쉬워질 것이다.
1-4. 팀 회의 결과
- 분산 추적 시스템을 효율적으로 관리하려면 실제 서비스 간의 상호작용에 집중하는 것이 중요하다고 판단하여 불필요한 로그 데이터는 분석을 방해하고 시스템의 성능에도 영향을 줄 수 있으니까, ALB 헬스 체크 로그는 Zipkin에서 제외하는 것이 좋을 것 같다.
2. ALB의 Health Check를 무시하는 방법 소개
ALB의 헬스 체크 요청을 Zipkin에서 제외하는 방법에 대해서는 몇 가지 접근 방식이 있다. 이는 샘플링을 통해 수행될 수 있고, 이 방법은 로그의 양을 관리하면서 중요한 데이터를 수집하는 데 유용하다.
2-1. 샘플링 설정을 통한 제외
- Spring Cloud와 Zipkin을 사용할 때, spring.sleuth.sampler.percentage를 설정하여 샘플링 비율을 정할 수 있다. 예를 들어, 100%로 설정하면 모든 요청을 추적하지만, 이 값을 낮추면 적은 수의 요청만 추적할 수 있다. 또한, spring.sleuth.web.skipPattern을 사용하여 특정 패턴의 요청을 제외할 수도 있다. 이는 정규식을 사용하여 이름이 일치하는 스팬(Span)을 제외하는 방법이다.
2-2. 커스텀 샘플링 로직
- 더 세부적인 제어가 필요한 경우, 커스텀 ZipkinSpanReporter를 구성하여 특정 요청을 제외할 수 있다. 예를 들어, Zipkin 서버의 URL을 Eureka에서 가져오고, URL이 변경될 때만 HttpZipkinSpanReporter를 업데이트하는 방식으로 구성할 수 있다.
이 방법들을 사용하면 ALB의 헬스 체크 요청과 같은 불필요한 요청을 효과적으로 제외하면서 중요한 서비스 간의 상호작용에 집중할 수 있게 된다. 이렇게 하면 로그의 양을 줄이고, 분석의 질을 높일 수 있다.
3. application.yml설정과 Sampler코드 설정에 대해 알아야 할 내용
가장 효율적으로 추적에 대한 Sampling을 적용시키 위해서 Zipkin을 처음 설정하면서 application.yml에 작성했던 tracing:sampling:probability: 1.0과 앞으로 자바로 작성하게될 Sampler 클래스의 역할과 작동 방식에 대해서 알아보고 넘어가도록 하자
3-1. application.yml은 다음과 같이 작성했다.
management:
tracing:
sampling:
probability: 1.0
propagation:
consume: B3
produce: B3
zipkin:
tracing:
endpoint: http://${내가 올린 서버주소 넣기}:9411/api/v2/spans
3-2. application.yml 설정의 역할
- management.tracing.sampling.probability: 이 값은 추적할 전체 요청의 비율을 설정한다. 예를 들어 이 값이 0.5로 설정되면, 시스템은 전체 요청 중 대략 절반(50%)만 추적한다.
3-3. 커스텀 Sampler의 우선순위와 작동 방식
- 커스텀 Sampler를 구현하면, 이것이 application.yml의 샘플링 설정보다 높은 우선순위를 가진다. 즉, 커스텀 Sampler에 정의된 조건(예: 특정 URL)을 만족하는 요청은 언제나 추적 대상이 된다.
3-4. 유연성과 추적 제어
- 사용자 정의 Sampler 설정은 application.yml의 기본 샘플링 설정을 Override한다. 이를 통해 추적 시스템은 더 유연하게 운영되며, 특정 요청의 추적을 보장할 수 있다.
3-5. 성능 최적화의 중요성
- 전체 추적 비율을 낮추려면 management.tracing.sampling.probability 값을 조절하여, 네트워크 트래픽과 서버 리소스 사용을 최적화할 수 있다. 이 방식은 시스템 성능에 긍정적인 영향을 미친다.
간단하게 요약하면, application.yml의 샘플링 설정은 전체적인 추적 비율을 관리하는 반면, 커스텀 Sampler는 특정 요청의 추적을 세밀하게 제어한다. 이 두 설정을 조합함으로써, 추적 시스템의 유연성을 높이고 성능을 최적화할 수 있다.
시스템에서 이 두 가지를 같이 사용하는 것은, 전체적인 추적 데이터의 양을 조절하면서도 특정 요청에 대한 세부적인 제어를 하고 싶을 때 유용하다. 하지만 단순히 특정 요청만 제외하고자 한다면 사용자 정의 Sampler만 사용해도 충분하다. sleuth.sampler.probability 설정은 전체적인 샘플링 비율에 영향을 미치므로, 필요에 따라 선택적으로 사용할 수 있다.
자 그럼 이제 자바코드로 Sampling 클래스를 만들어보자
4. Sampling을 코드로 설정하기 위해 클래스 생성
프로젝트에는 Zipkin사용을 위해 다음과 같은 의존성 설정이 되어있어야 한다. (SpringBoot3 기준)
dependencies {
// zipkin
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-tracing-bridge-brave'
implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
// 기타 필요한 의존성
}
4-1. Sampler 인터페이스 구현하기
- 우선, Sampler 인터페이스를 구현하는 새로운 클래스를 만들어야 한다. 이 클래스는 isSampled 메서드를 오버라이드하여 특정 요청이 추적 대상인지를 결정하게 될 것이다.
import brave.sampler.Sampler;
public class CustomSampler extends Sampler {
@Override
public boolean isSampled(long traceId) {
// 여기에 HTTP 요청 분석 로직을 구현할 것이다.
return true;
}
}
4-2. HTTP 요청 분석
- isSampled 메서드 내에서, RequestContextHolder를 사용해 현재 HTTP 요청의 정보를 가져올 수 있어. 예를 들어, HttpServletRequest를 통해 요청의 URL, 헤더, 메서드 등을 확인할 수 있어.
// 이전에 정의한 CustomSampler 클래스 내부
@Override
public boolean isSampled(long traceId) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 요청 URL 분석 로직 추가
return !request.getRequestURI().startsWith("/health-check");
}
return true;
}
4-3. 특정 요청 제외
- ALB의 Health Check 요청을 제외하려면, 이 메서드 내에서 요청 URL이 Health Check URL과 일치하는지 확인하고, 일치한다면 이 요청을 추적하지 않도록 설정해야 해. 예를 들어, Health Check URL이 /health-check라면, 요청 URL이 이 경로와 일치하는지 확인하고, 일치하면 false를 반환하도록 할 수 있다.
- 위의 코드에서 이미 !request.getRequestURI().startsWith("/health-check") 부분이 헬스 체크 요청을 제외하는 로직을 포함하고 있다.
if (requestAttributes instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 요청 URL 분석 로직 추가
return !request.getRequestURI().startsWith("/health-check");
}
4-4. Sampler 등록
- 위에서 커스텀 Sampler 클래스를 정의한 후, 이것을 스프링 빈으로 등록해야 한다. 이렇게 함으로써 스프링 부트가 이 Sampler 클래스를 자동으로 찾아 Zipkin과 연결해줄 것이다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SamplerConfig {
@Bean
public Sampler customSampler() {
return new CustomSampler();
}
}
4-5. 전체코드
import brave.sampler.Sampler;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Configuration
public class SamplerConfig {
@Bean
public Sampler customSampler() {
return new Sampler() {
@Override
public boolean isSampled(long traceId) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// '/health-check' URL로 시작하는 요청은 추적에서 제외
return !request.getRequestURI().startsWith("/health-check");
}
return true; // 그 외 요청은 추적
}
};
}
}
4-6. 번외: 모든 추적 요청을 허용하거나 거절하는 방법
1. 모든 요청 추적하기
@Bean
public Sampler customSampler() {
// 모든 요청을 추적한다.
return Sampler.ALWAYS_SAMPLE;
}
2. 모든 요청 거절하기
@Bean
public Sampler neverSampler() {
// 모든 추적 요청을 거절한다.
return Sampler.NEVER_SAMPLE;
}
이제 내가 프로젝트에 추가한 코드를 설명하겠다.
5. SpringBoot에 Sampler코드 추가하기
5-1 최종 완성된 Sampler 코드를 SpringBoot 프로젝트에 추가한다.
- 위에서 작성한 예시코드랑 다른점은 이 코드에서 나는 List를 통해 여러 url을 받아주도록 작성했다는 점이다.
// 빈 등록을 위해 @Configuration을 선언한다.
@Configuration
public class ZipkinSamplerConfig {
private static final List<String> ALLOWED_PATHS = Arrays.asList(
"/member/nicknameChange", // 허용할 첫 번째 URL 패턴
"/feign/member/getNickname", // 허용할 두 번째 URL 패턴
// "/api/moreCheck" // 추가로 허용할 URL 패턴
);
@Bean
public Sampler customSampler() {
return new Sampler() {
@Override
public boolean isSampled(long traceId) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 허용할 URL리스트의 경로만 추적을 설정해 준다.
return ALLOWED_PATHS.stream().anyMatch(path -> request.getRequestURI().startsWith(path));
}
// HTTP 요청 정보가 없는 경우에는 기본적으로 추적하지 않도록 한다.
return false;
}
};
}
}
5-2. 헬스체크를 위한 컨트롤러 코드 작성
- 아래와 같이 엄청 간단하게 작성했다.
/**
* ALB에서 상태 검사 요청용 URL
*/
@RestController
public class HealthCheckController {
@GetMapping("/health")
public ResponseEntity<String> healthCheck() {
return ResponseEntity.ok("Healthy");
}
}
6. ZIpkin UI에서 적용되었는지 검증하기
6-1. Zipkin UI에 "/health" 경로로 들어오는 요청 확인하기
- Zipkin UI를 확인해 보면 이제 더 이상 /health 요청은 더이상 추적되지 않는 모습을 확인할 수 있다. (37분 전이 마지막인 이미지)
6-2. 허용한 url에 대한 요청 추적 확인하기
- 아래와 같이 내가 원하는 경로에 대한 추적은 제대로 진행한다.
이렇게 다 하고나서 조금 더 알아보니 자바코드말고 application.yml에서 설정을 해서 url에 대한 Sampling처리를 하는 방법도 존재했다.
7. application.yml수정만으로 특정 url의 Sampling을 처리하는 방법
7-1. application.yml에 skip-pattern을 설정해서 특정 url은 제외할 수 있는 방법이 있었다.
spring:
sleuth:
web:
servlet:
skip-pattern: /health
- 자바 코드가 아니라 application.yml을 통해서 적용했을때도 아래와 같이 27분전 추적만이 마지막으로 기록에 남은것을 확인할 수 있었다. (즉, /health 경로에 대한 추적을 하지 않았다.)
ALB가 보내는 요청에 대해서는 Zipkin이 추적하지 않도록 설정을 완료했다. 이것을 적용시키기 위해서 팀 내에서도 많은 회의를 거쳤다. 같이 해결방안을 찾으며 여러가지 방법을 다 해보다보니 여기까지 오게 된것 같다. 이전에 진행했던 1번 방안은 기록으로만 남겨두고 사용하지 않을 예정이고 지금처럼 Sampling을 사용하여 적용하는것이 최적의 방법이라고 결론을 지었다.
다음으로 진행할 내용은 Spring Batch이다. 우선 어떻게 설정하는지에 대해서부터 알아보자
이 포스트는 Team chillwave에서 사이드 프로젝트 중 적용했던 부분을 다시 공부하며 기록한 것입니다.
시간이 괜찮다면 팀원 '평양냉면7'님의 블로그도 한번 봐주세요 :)
'Spring MSA' 카테고리의 다른 글
MSA 환경에서 SNS 메시지 재발행을 위한 스프링 배치 및 스케쥴러 구현 (1) | 2023.11.28 |
---|---|
Spring MSA 프로젝트에서 단일 책임 원칙을 지키기 위한 리팩토링 (3) | 2023.11.24 |
Zipkin 로그 최적화: AWS ALB 헬스 체크 설정과 로그 추적 간소화 (2) | 2023.11.22 |
SpringBoot MSA 로깅: Zipkin을 사용한 분산 추적에서 예외상황을 다루는 방법 (1) | 2023.11.22 |
SpringBoot MSA 로깅: Zipkin으로 서버간 SNS, SQS, Feign 통신의 분산 로그 추적 하기 (1) | 2023.11.20 |