Zipkin 적용기

SeungHyuk Shin·2023년 4월 7일
0


이 포스트에서는 Sleuth와 Zipkin을 서비스에 적용하는데 있었던 문제점과 해결방안에 대해 설명합니다.

문제 상황

Kubernetes 클러스터에서 ingress-nginx를 사용하여 서비스를 노출하고 있습니다. Slueth의 로그를 Zipkin에서 확인할 수 있도록 분산 추적을 적용 하고 있는데 로컬에서 요청 시에는 잘 추적되는 API가 k8s 환경에서 들어오는 요청 중에 특정 API만 Zipkin에서 제대로 표현이 되고 있지 않았습니다.

콘솔에서 확인한 내용은 SpanId가 Undefiend라는 내용이었습니다.

원인 파악

1.

처음에는 1차적으로 Slueth와 Zipkin 설정이 원인이라고 생각하고 application.yaml의 Sleuth의 설정값을 다음과 같이 변경해 보았습니다.

spring.sleuth.sampler.probability=1.0 # 모든 span을 보내도록 설정
spring.sleuth.sampler.type=const
spring.sleuth.http.enabled=true # HTTP span 보내기 활성화
spring.sleuth.http.client.enabled=true # 클라이언트에서 HTTP span 보내기 활성화
spring.sleuth.http.server.enabled=true # 서버에서 HTTP span 보내기 활성화
spring.zipkin.sender.type= web
spring.zipkin.enabled=true

하지만 여전히 k8s 환경에서는 해당 요청이 Zipkin에 적재는 되지만 클릭시에 SpanId가 Undefiend 오류를 표시하고 있었습니다.

2.

다음으로는 Sleuth와 Zipkin 사이의 통신에 문제가 있을것이라고 생각했습니다. Zipkin에 적재되고 있는걸 봐서는 프록시 문제는 아니라고 생각했고, 보내는 데이터에 문제가 있을거라고 추측했습니다. 그래서 application.yaml에 해당 라이브러리 로깅을 Debug를 설정했습니다.

logging:
  level:
    org.springframework.cloud.sleuth: DEBUG
    zipkin2: DEBUG

이후 찍히는 로그를 추적해보니 Zipkin에 적재시 RealSpan이 아닌 NoopSpan을 적재하고 있었습니다. 매우 중요한 힌트를 얻게 되었고 해당 힌트를 토대로 추적해 나갔습니다.

3.

왜 Sleuth가 NoopSpan으로 생성되는지에 대해서 찾아보았고 Sleuth가 정확한 tracer를 사용하도록 구성되어 있지 않다고 생각했습니다. 하지만 설정은 제대로 되어있었고 라이브러리들끼리의 충돌이 있나라고 생각할 이쯤에서 Jaeger 라이브러리로 바꿔 사용해야 하나라는 고민을 시작했습니다...

4.

이어서 Zipkin에 적재되는 traceId를 확인하던 도중 특이한 점을 발견하게 됐습니다. 정상적으로 Zipkin에 적재되는 요청들은 TraceId와 ParentId가 같은데(즉, 동일요청이라는 뜻), 적재되지 않았던 요청들은 ParentId와 TraceId가 같지 않았습니다.

곧 바로 요청의 Request Header를 확인했고 envoy proxy를 통해 들어오는 요청들이x-b3-*의 헤더 이름으로 Slueth 관련 정보가 넘어오는것을 파악했습니다. 분산 서비스를 추적하기 위해 이전 서버에서 넣어준 해당 헤더 값을 우리 서버 Zipkin에 적재하고 있었던 것입니다.

하지만 우리 서버쪽 Zipkin의 입장에서는 ParentId의 정보가 존재하질 않으니 오류가 발생했던 것입니다.

만약 두 개의 서버가 같은 Zipkin을 사용했다면 발생하지 않았을 오류였습니다.

해결 방안

해결 방안으로는 해당 Header 값을 지우는것이 떠올랐는데 두가지 방법이 있었습니다. 서비스에서 요청이 들어왔을때 Servlet을 조작해 Header 값을 삭제 하는것과 nginx-ingress에서 해당 헤더 값을 지운채로 서비스로 전달해주는것이였는데 nginx의 역할과 서비스 안정성을 생각했을때 후자가 나을거라고 판단했고 ingress yaml에 다음 설정을 추가했습니다.

  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_input_headers "x-b3-traceid:";
      more_set_input_headers "x-b3-spanid:";
      more_set_input_headers "x-b3-parentspanid:";
      more_set_input_headers "x-b3-sampled:";
      more_set_input_headers "x-b3-flags:";

proxy_hide_header는 응답 헤더를 제거하는 데 사용되는 반면, 여기서는 요청 헤더를 제거해야 합니다. 따라서 more_set_input_headers 디렉티브를 사용하여 요청 헤더를 제거했습니다. more_set_input_headers 디렉티브는 와일드카드(*)를 지원하지 않으므로, 각 헤더를 개별적으로 명시했습니다.

결론

위와 같은 방법으로 문제를 해결하였고, 각각의 요청들이 Zipkin에서 제대로 추적되고 있는 것을 확인할 수 있었습니다. 이를 통해 분산 서비스에서 발생하는 문제점들을 해결하면서도, 서비스 안정성을 유지할 수 있도록 노력하는 중요성을 깨닫게 되었습니다.

분산 서비스에서는 많은 요청이 동시에 처리되므로, 문제가 발생할 가능성이 높습니다. 따라서, 문제를 빠르게 해결하고 안정성을 유지하기 위해서는 서비스 간 통신 방식과 환경 설정 등을 꼼꼼하게 확인하고, 가능한 미리 대비해야 합니다.

또한, 로그나 디버깅 정보 등을 적극 활용하여 문제의 원인을 파악하는 것이 중요합니다. 이를 통해 분산 서비스에서 발생하는 다양한 문제들을 빠르게 대처하고 안정성을 유지할 수 있을 것입니다.

0개의 댓글