[번역] Spring Cloud Sleuth (2) Additional Resources, Features

rin·2020년 8월 9일
0

Document 번역

목록 보기
17/22
post-thumbnail

Spring Colud Sleuth를 번역합니다.

특징

  • Slf4J MDC에 trace Id와 span Id를 추가함으로써 집계기에 지정된 trace와 span으로부터 모든 로그를 추출할 수 있다.
2016-02-02 15:30:57.902  INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
2016-02-02 15:30:58.372 ERROR [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
2016-02-02 15:31:01.936  INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...
  • MDC의 [appname,traceId,spanId,exportable] 항목은 다음과 같다.
    • appname: span이 기록된 어플리케이션 이름
    • traceId: span이 포함된 지연시간 그래프의 아이디
    • spanId: 발생한 특정 작업의 아이디
    • exportable: 로그를 Zipkin으로 전송할지에 대한 여부. 일부 작업을 span에 래핑하고 로그로만 기록하려는 경우에 전송하지 않을 수 있다.
  • traces, (DAG(???)을 형성하는) spans, 어노테이션, 키벨류 어노테이션과 같은 일반적인 분산 추적 데이터 모델에 대한 추상화를 제공한다. Spring Cloud Sleuth는 HTrace에 약하게 기반을 두고 있지만 Zipkin(Dapper)과 호환된다.
  • Sleuth는 지연 시간을 분석하기 위해 시간 정보를 기록하며, 이를 통해 사용자는 어플리케이션에서 시간이 지연되는 원인을 정확히 찾아낼 수 있다.
  • Sleuth는 필요 이상으로 로그를 남기지 않으며 응용 프로그램이 충돌하지 않도록 설계되었다.
    • Propagates structural data about your call graph in-band and the rest out-of-band.
    • HTTP 같은 계층에 대한 독자적인 기법(수단)을 포함한다.
    • 용량을 관리하기 위한 샘플링 정책을 포함한다.
    • 질의와 시각화를 위해 Zipkin 시스템에 데이터를 전달할 수 있다.(Can report ?? 뭘 보고한단건지 모르겠음)
  • 스프링 어플리케이션(서블릿 필터, 비동기 엔드포인트, Rest Template, 예약된 작업, 메세지 채널, Zuul filter 및 Feign 클라이언트)에서 일반적인 송수신 지점인 기구
  • HTTP 또는 메세징 경계를 넘어 trace를 결합하는 기본 로직을 포함한다. 예를 들어 Zipkin과 호환되는 request 헤더로도 HTTP의 전파가 가능하다.
  • 프로세스 간에 (baggage라고도 불리는) 컨텍스트를 전파 할 수 있다. 따라서 Span에 baggage를 추가하면 이는 HTTP나 메세징을 통해 다운스트림에서 다른 프로세스로 전달된다. Sleuth can propagate context (also known as baggage) between processes. Consequently, if you set a baggage element on a Span, it is sent downstream to other processes over either HTTP or messaging.
  • span을 생성하고 유지하는 방법과 어노테이션을 사용하여 태그와 로그를 추가하는 방법을 제공한다.
  • spring-cloud-sleuth-zipkin가 classpath에 있으면 Zipkin과 호환되는 trace를 생성하고 수집한다. 기본적으로 HTTP를 통해 로컬호스트(9411 포트)의 Zipkin 서버로 데이터를 전송한다. spring.zipkin.baseUrl를 설정하면 이를 변경할 수 있다.
    • spring-rabbit을 사용하는 경우 HTTP 대신 RabbitMQ 브로커에 trace를 전달한다.
    • spring-kafka를 사용하며 spring.zipkin.sender.type: kafka라는 설정을 추가하면 HTTP 대신 kafka 브로커에 trace를 전달한다.

spring-cloud-sleuth-stream는 스팩 아웃되었으므로 사용해선 안된다.

Slf4j MDC는 자동 설정되며 logback의 경우 이전에 보여준 예제처럼 log 내 trace와 span을 즉시 확인 할 수 있다. 만약 다른 로깅 시스템을 사용하는 경우 자체 포맷터를 정의해야만 동일한 결과(출력)를 얻을 수 있을 것이다.

기본값은 다음과 같다 :
logging.pattern.level%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]으로 설정한다. (이는 logback 사용자를 위한 스프링 부트가 제공하는 기능이다.)

Slf4j를 사용하지 않는다면 위 패턴은 자동으로 적용되지 않는다.

Brave 소개

❗️ NOTE
Spring Cloud Sleuth 2.0.0 버전부터 Brave를 추적 라이브러리로 사용한다. 편의를 위해 Brave document의 일부를 포함시킨다.

❗️ NOTE
대부분의 경우 Sleuth가 제공하는 Brave의 TracerSpanCustomizer 빈을 사용하면 된다. 아래의 document에는 Brave의 정의와 작동 방법에 대한 개요가 포함되어있다.

Brave는 분산 작업에 대한 지연 시간 정보를 포착하고 Zipkin에 보고하는데 사용되는 라이브러리이다. 대부분 사용자들은 Brave를 직접 사용하기보다 라이브러리나 프레임워크를 사용한다. (Most users do not use Brave directly. They use libraries or frameworks rather than employ Brave on their behalf.)

이 모듈은 잠재적인 분산 작업의 지연 시간을 모델링하는 span을 만들고 합치는 tracer를 포함한다. 또한 네트워트 경계를 넘어(예를 들어 HTTP 헤더를 사용한다.) trace의 context를 전파하는 라이브러리도 포함하고 있다.

Tracing

가장 중요한 것은 Zipkin에 보고하기 위한 brave.Tracer를 구성하는 것이다.

다음 예제는 추적 데이터(span)를 HTTP를 사용하여(Kafka와 반대됨) Zipkin에 전송하기 위한 설정을 보여준다.

class MyClass {

    private final Tracer tracer;

    // 트레이서는 자동주입된다.
    MyClass(Tracer tracer) {
        this.tracer = tracer;
    }

    void doSth() {
        Span span = tracer.newTrace().name("encode").start();
        // ...
    }
}

이름은 구체적이고 명확해야한다. 하지만 span이 50자보다 긴 이름을 가지는 경우 50자로 잘리게되며 긴 이름은 지연 시간 문제를 일으키거나 예외를 발생시키기도 한다.

tracer는 잠재적인 분산 작업의 지연 시간을 모델링하는 span을 만들고 합친다. "sampling"을 사용함으로써 일련의 과정 중에 발생하는 오버헤드나 Zipkin으로 전송되는 데이터를 감소시킬 수 있다.

tracer가 반환한 span은 작업이 끝나면 Zipkin에 데이터를 전달하나 샘플링되지 않았으면 아무일도 일어나지 않는다. span이 시작되면 관심있는 이벤트에 어노테이션을 추가하거나 세부 정보나 조회를 위한 키가 포함된 태그를 추가할 수 있다.

span의 컨텍스트는 분산 작업을 표현하는 트리에 해당 span을 올바르게 배치하기위한 trace 식별자를 포함한다.

Local Tracing

프로세스를 벗어나지 않는 코드를 추적 할 때는 span 영역 내부에서 실행하라

@Autowired Tracer tracer;

// 기존의 trace 내에서 새로운 trace나 span을 시작한다.
ScopedSpan span = tracer.startScopedSpan("encode");
try {
  // span이 "scope" 내에 있으므로 logger와 같은 다운스트림 코드가 trace Id를 알 수 있다.
  return encoder.encode();
} catch (RuntimeException | Error e) {
  span.error(e); // 예외를 핸들링하지 않으면 작업이 실패했음을 알 수 없을 수도 있다.
  throw e;
} finally {
  span.finish(); // span은 항상 종료된다.
}

더 많은 기능이나 세밀한 제어가 필요한 경우, 다음의 Span 유형을 사용한다.

@Autowired Tracer tracer;

// 기존의 trace 내에서 새로운 trace나 span을 시작한다.
Span span = tracer.nextSpan().name("encode").start();
// logger와 같은 다운스트림 코드가 trace Id에 접근할 수 있도록 span을 "scope"에 넣는다.
try (SpanInScope ws = tracer.withSpanInScope(span)) {
  return encoder.encode();
} catch (RuntimeException | Error e) {
  span.error(e); // Unless you handle exceptions, you might not know the operation failed!
  throw e;
} finally {
  span.finish(); // scope는 span에 독립적이다. span은 항상 종료된다.
}

위의 두 예는 완료시 동일한 span이 보고된다.

위 예에서 span은 새로운 root span이거나 기존 trace의 다음 하위 span이다.

Customizing Spans 📌 다시보기

span에는 조회를 위한 키나 세부 정보로 사용가능한 tag를 추가할 수 있다. 예를 들어 다음과 같이 런타임 버전에 대한 정보를 태그로 추가할 수 있다.

span.tag("clnt/finagle.version", "6.36.0");

제 3자(3rd party)에 사용자지정 span에 대한 기능을 노출하는 경우에는 brave.Span보다는 brave.SpanCustomizer를 사용하기를 추천한다. brave.SpanCustomizer는 이해하고 테스트하기에 더 간단하며 span의 lifecycle hook으로 사용자를 유도하지 않는다. ??? 머라는겨 (The former is simpler to understand and test and does not tempt users with span lifecycle hooks.)

interface MyTraceCallback {
  void request(Request request, SpanCustomizer customizer);
}

brave.Spanbrave.SpanCustomizer를 구현하기때문에 다음처럼 이를 사용자에게 전달할 수 있다.

for (MyTraceCallback callback : userCallbacks) {
  callback.request(request, span);
}

Implicitly Looking up the Current Span

종종 추적이 진행 중인지 아닌지를 알 수 없을 것이다. 또한 '사용자가 null을 확인하는 것'을 원하지 않을 수도있다. (Sometimes, you do not know if a trace is in progress or not, and you do not want users to do null checks. brave.CurrentSpanCustomizer handles this problem by adding data to any span that’s in progress or drops, as shown in the following example:)

brave.CurrentSpanCustomizer는 진행 중이거나 삭제된 모든 span에 데이터를 추가함으로써 이런 문제를 처리한다.

Ex.

// 사용자 코드에서 null일 가능성없이 주입할 수 있다.
@Autowired SpanCustomizer span;

void userCode() {
  span.annotate("tx.started");
  ...
}

RPC tracing

❗️ NOTE
RPC, Remote procedure call
네트워크로부터 떨어져 있는 컴퓨터에서 코드를 실행하는 방식. 프로그래머는 함수가 실행 프로그램의 로컬 위치에 있든 원격 위치에 있든 동일한 코드를 이용할 수 있다.

네트워크 서버 프로그램에서 작업 코드와 메인 코드를 독립 시킴으로써 경우에 따라서 Code1(네트워크 연결)와 Code2(코드의 실행을 담당하는 부분)를 실행시키고자 할 때 사용하는 개발도구이다.

RPC 추적은 종종 인터셉터에 의해 자동으로 수행된다. 뒤편에서 인터셉터는 RPC 작업에서 해당 작업의 역할과 관련된 tag와 이벤트를 추가한다.

다음 예는 client span을 추가하는 방법을 보여준다.

@Autowired Tracing tracing;
@Autowired Tracer tracer;

// 요청을 보내기전에 작업을 설명하는 메타데이터를 추가한다.
span = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
span.tag("myrpc.version", "1.0.0");
span.remoteServiceName("backend");
span.remoteIpAndPort("172.3.4.1", 8108);

// in-band내에 전파될 수 있도록 trace 컨텍스트를 요청에 추가한다. 
tracing.propagation().injector(Request::addHeader)
                     .inject(span.context(), request);

// 요청이 예약되면 span이 시작된다.
span.start();

// 에러가 있으면 이를 span의 tag로 추가한다.
span.tag("error", error.getCode());
// 혹은 예외를 발생시킨다.
span.error(exception);

// 응답이 완료되면 span을 종료한다.
span.finish();

One-Way tracing
요청은 있으나 응답이 없는 비동기 작업을 모델링해야하는 경우가 있다. 일반적인 경우에는 span.finish()을 사용하여 응답을 받았음을 나타낸다. 하지만 단방향 추적에서는 응답이 없으므로 span.flush()를 사용한다.

다음 예는 클라이언트가 단방향 작업을 모델링하는 방법을 보여준다.

@Autowired Tracing tracing;
@Autowired Tracer tracer;

// 클라이언트 요청을 나타내는 새로운 span을 시작한다.
oneWaySend = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);

// in-band에 전파하기 위해 trace 컨텍스트를 요청에 추가한다.
tracing.propagation().injector(Request::addHeader)
                     .inject(oneWaySend.context(), request);

// fire off the request asynchronously, totally dropping any response
request.execute();

// 클라이언트 측을 시작하고 finish 대신 flush를 사용한다.
oneWaySend.start().flush();

다음은 서버가 단방향 작업을 처리하는 방법을 보여준다.

@Autowired Tracing tracing;
@Autowired Tracer tracer;

// 들어오는 요청에서 컨텍스트를 가져온다.
extractor = tracing.propagation().extractor(Request::getHeader);

// 이 컨텍스트를 이름을 지정하고 태그를 추가할 수 있는 span으로 변환한다.
oneWayReceive = nextSpan(tracer, extractor.extract(request))
    .name("process-request")
    .kind(SERVER)
    ... add tags etc.

// 서버 측을 시작하고 finish대신 flush를 사용한다.
oneWayReceive.start().flush();

// 이 span이 완료되었으므로 더 이상 수정되서는 안되나 후처리를 위해 자식을 만들 수 있다.
next = tracer.newSpan(oneWayReceive.context()).name("step2").start();
profile
🌱 😈💻 🌱

0개의 댓글