현대적 애플리케이션 참조 아키텍처에 OpenTelemetry 통합하기 – 진행 상황 보고

NGINX | 2022년 3월 08일

지난가을 Sprint 2.0에서 NGINX 최신 앱 참조 아키텍처(MARA) 프로젝트를 소개하면서, 일부 아키텍처처럼 단순한 ‘장난감’이 아니라 ‘견고하고 검증되었으며 Kubernetes 환경에서 실행되는 실제 프로덕션 애플리케이션에 배포할 준비가 된’ 솔루션을 만들겠다는 의도를 강조했습니다. 이러한 프로젝트에는 관측 가능성 도구가 반드시 필요합니다. MARA 팀원 모두가 상태와 성능을 파악할 수 없으면 애플리케이션 개발과 제공 과정이 얼마나 답답해지는지 직접 경험했습니다. 이에 따라 MARA에 프로덕션 환경에서 디버깅과 추적을 수행하기 위한 계측 기능을 반드시 포함해야 한다는 데 즉시 의견을 모았습니다.

MARA의 또 다른 기본 원칙은 오픈 소스 솔루션을 우선시하는 것입니다. 이 게시물에서는 다기능 오픈 소스 관측성 도구를 찾는 과정에서 어떻게 OpenTelemetry를 선택하게 되었는지 설명하고, OpenTelemetry를 Python, Java 및 NGINX로 구축된 마이크로서비스 애플리케이션과 통합하는 데 적용한 절충안, 설계 결정, 기법 및 기술을 자세히 살펴봅니다.

저희의 경험이 잠재적인 함정을 피하고 OpenTelemetry 도입을 앞당기는 데 도움이 되기를 바랍니다. 이 글은 시의성이 중요한 진행 상황 보고서이며, 여기서 다루는 기술들은 1년 이내에 성숙할 것으로 예상합니다. 또한 일부 프로젝트의 현재 한계를 지적하기도 하지만, 진행 중인 모든 오픈 소스 작업에 깊이 감사드리며 앞으로의 발전을 기대합니다.

애플리케이션 소개

옵저버빌리티 솔루션과 통합할 애플리케이션으로 Bank of Sirius를 선택했습니다. 이는 Google의 Bank of Anthos 샘플 앱을 포크한 것입니다. 이 웹 앱은 마이크로서비스 아키텍처로 구성되어 있으며 코드형 인프라를 통해 배포할 수 있습니다. 성능과 안정성 측면에서 개선할 부분이 많지만, 브라운필드 애플리케이션으로 보기에 충분히 성숙했습니다. 따라서 OpenTelemetry를 애플리케이션에 통합하는 방법을 보여 주는 좋은 예라고 생각합니다. 이론적으로 분산 추적을 통해 애플리케이션 아키텍처의 단점에 관한 유용한 인사이트를 얻을 수 있기 때문입니다.

다이어그램에서 볼 수 있듯이, 애플리케이션을 지원하는 서비스는 비교적 단순합니다.

이미지 제공: Google

OpenTelemetry를 선택한 과정

OpenTelemetry를 선택하기까지의 과정은 꽤 복잡했고 여러 단계를 거쳤습니다.

기능 위시리스트 만들기

사용 가능한 오픈 소스 옵저버빌리티 도구 자체를 평가하기에 앞서, 옵저버빌리티의 어떤 측면을 중요하게 생각하는지 파악했습니다. 과거 경험을 바탕으로 다음과 같은 목록을 작성했습니다.

  • 로깅 – 여기서 이 용어는 애플리케이션이 줄바꿈으로 구분된 전통적인 메시지 집합을 생성하는 것을 의미합니다. Bank of Sirius 앱은 로그를 Bunyan 형식으로 구조화합니다.
  • 분산 추적 – 전체 애플리케이션 내 각 구성 요소의 처리 시간과 메타데이터로, 애플리케이션 성능 관리(APM) 공급업체가 제공하는 것과 유사합니다
  • 메트릭 – 일정 기간 동안 수집되어 시계열 데이터로 그래프에 표시되는 측정값
  • 예외/오류 집계 및 알림 – 가장 자주 발생하는 예외와 오류를 집계한 모음으로, 어떤 애플리케이션 오류가 가장 흔한지 파악할 수 있도록 검색이 가능해야 합니다
  • 상태 확인 – 애플리케이션 내에서 서비스가 정상적으로 작동하는지 확인하기 위해 주기적으로 전송되는 프로브
  • 런타임 상태 검사 – 관리자에게만 표시되며 애플리케이션의 런타임 상태에 대한 정보를 반환하는 API 집합
  • 힙/코어 덤프 – 서비스의 런타임 상태를 포괄적으로 캡처한 스냅샷입니다. 여기서 중요한 것은 필요할 때나 서비스가 비정상 종료될 때 이러한 덤프를 얼마나 쉽게 생성할 수 있는지입니다.

희망 기능 목록과 도구 기능 비교

물론 단일 오픈 소스 도구나 접근 방식이 이 모든 기능을 제공할 것이라고 기대하지는 않았지만, 적어도 사용 가능한 도구를 비교할 수 있는 이상적인 기준은 마련할 수 있었습니다. 각 도구의 문서를 살펴보며 우리가 바라는 일곱 가지 기능 중 어떤 기능을 지원하는지 확인했습니다. 아래 표는 그 결과를 요약한 것입니다.

기술 로깅 분산 추적 메트릭 오류 집계 상태 확인 런타임 내부 검사 힙/코어 덤프ELK + Elastic APM ✅ ✅ ✅ ✅ ✅ ❌ ❌Grafana ✅ ✅ ✅ ✅ ✅ ❌ ❌Graylog ✅ ❌ ❌ ❌ ❌ ❌ ❌Jaeger ❌ ✅ ❌ ✅ ❌ ❌ ❌OpenCensus ❌ ❌ ✅ ❌ ❌ ❌ ❌OpenTelemetry 베타 ✅ ✅ ✅ ✅ * ❌ ❌Prometheus ❌ ❌ ✅ ❌ ❌ ❌ ❌StatsD ❌ ❌ ✅ ❌ ❌ ❌ ❌Zipkin ❌ ✅ ❌ ✅ ❌ ❌ ❌

* 확장 기능으로

이 표를 만들면서 현실을 뼈저리게 깨달았습니다. 각 도구는 기능과 용도가 너무나 달라서 모두 같은 범주에 속한다고 볼 수 없었습니다. 마치 사과와 토스터를 비교하는 것과 같았죠!

예를 들어, ELK(Elasticsearch-Logstash-Kibana에 Filebeat를 더한 것)와 Zipkin은 하는 일이 근본적으로 너무 달라서 서로 비교하려 하면 오히려 혼란만 커집니다. 안타깝게도 ‘기능 범위의 확장’은 상황을 더욱 복잡하게 만듭니다. 아마도 사용자 요청에 대응한 결과겠지만, 도구의 주된 목적과는 거리가 있는 부차적인 기능들이 추가되면서 다른 도구와 기능이 겹치게 되었습니다. 표면적으로 ELK는 로그 저장 및 시각화를 담당하고 Zipkin은 분산 추적을 수행합니다. 하지만 Elastic 제품군을 조금만 더 자세히 살펴보면 곧 Elastic APM을 발견하게 되는데, 이 제품은 분산 추적을 지원할 뿐만 아니라 Jaeger와도 호환됩니다.

기능 범위가 본래 목적을 넘어 확장되는 문제 외에도, 많은 도구를 서로 통합하여 수집기, 집계기, 대시보드 등을 다양하게 조합할 수 있습니다. 일부 기술은 서로 호환되지만 그렇지 않은 기술도 있습니다.

정성적 조사 수행하기

따라서 이 비교표만으로는 선택의 근거로 삼을 만큼 정확한 상황을 파악할 수 없었습니다. 프로젝트의 가치가 우리의 가치와 비슷할수록 장기적으로 호환성을 유지할 가능성이 높다고 판단하여, 각 프로젝트의 목표와 기본 원칙, 향후 나아갈 수 있는 방향을 정성적으로 조사해야 했습니다. 프로젝트 페이지를 살펴보던 중, OpenCensus 페이지에서 이를 바로 확인할 수 있었습니다.

OpenCensus와 OpenTracing은 통합되어 OpenTelemetry가 되었으며, 이는 OpenCensus와 OpenTracing의 차세대 주요 버전 역할을 합니다. OpenTelemetry는 기존 OpenCensus 통합과의 하위 호환성을 제공하며, 향후 2년 동안 기존 OpenCensus 라이브러리에 대한 보안 패치를 계속 제공할 예정입니다.

이는 저희에게 핵심적인 판단 근거였습니다. 저희의 선택이 미래에도 유효할 것이라고 장담할 수는 없었지만, 적어도 앞으로 탄탄한 커뮤니티의 지원을 받을 수 있는지는 확인하고 싶었습니다. 이 정보를 바탕으로 후보 목록에서 OpenCensus를 제외할 수 있었습니다. Jaeger 역시 사용하는 것이 좋은 선택은 아니었을 것입니다. 현재 공식적으로 지원이 중단된 OpenTracing 프로젝트의 참조 구현체이기 때문입니다. 앞으로 새로운 기여의 대부분은 OpenTelemetry에 반영될 것입니다.

그런 다음 OpenTelemetry Collector에 대해 자세히 알아보았습니다.

OpenTelemetry Collector는 텔레메트리 데이터를 수신, 처리 및 내보내는 방법을 공급업체에 종속되지 않는 방식으로 구현합니다. 또한 여러 오픈 소스 또는 상용 백엔드로 전송되는 오픈 소스 텔레메트리 데이터 형식(예: Jaeger, Prometheus 등)을 지원하기 위해 여러 에이전트/수집기를 실행, 운영 및 유지 관리할 필요가 없습니다.

OpenTelemetry Collector는 다양한 관측 가능성 데이터 수집 및 계측 방법을 여러 백엔드와 자유롭게 조합할 수 있게 해주는 애그리게이터 역할을 합니다. 기본적으로 애플리케이션은 Zipkin으로 트레이스를 수집하고 Prometheus에서 메트릭을 수집한 다음, 이를 구성 가능한 백엔드로 전송하여 Grafana로 시각화할 수 있습니다. 이 설계는 이외에도 여러 방식으로 조합할 수 있으므로, 다양한 접근 방식을 시도해 보면서 사용 사례에 적합한 기술을 찾을 수 있습니다.

향후 방향으로 OpenTelemetry 통합하기

기본적으로 저희가 OpenTelemetry Collector를 선택한 이유는 이론상 여러 옵저버빌리티 기술 간에 전환할 수 있기 때문입니다. 프로젝트가 아직 비교적 성숙하지 않았음에도, 이 분야의 판도를 이해하는 유일한 방법은 해당 기술을 직접 사용해 보는 것이라고 판단해 과감히 뛰어들었고, 오픈 소스 통합만으로 OpenTelemetry를 사용하기로 했습니다.

하지만 OpenTelemetry Collector에는 일부 기능이 빠져 있어, 해당 기능을 위해 다른 기술에 의존해야 합니다. 다음 섹션에서는 저희가 선택한 기술과 그 선택의 근거를 요약합니다.

로깅 구현

로깅은 언뜻 단순해 보이지만 금세 복잡한 결정을 내려야 하는 관측 가능성의 한 부분입니다. 컨테이너에서 로그 출력을 수집하기만 하면 된다는 점에서는 단순하지만, 이후 데이터를 어디에 저장할지, 해당 저장소로 어떻게 전송할지, 유용하게 활용할 수 있도록 어떻게 인덱싱할지, 얼마나 오래 보관할지를 결정해야 한다는 점에서는 복잡합니다. 로그 파일이 유용하려면 다양한 검색자의 요구를 충족할 수 있도록 충분히 여러 기준으로 쉽게 검색할 수 있어야 합니다.

OpenTelemetry Collector의 로깅 지원을 살펴보았지만, 이 글을 작성하는 시점에는 아직 베타 초기 단계였습니다. 다른 옵션을 계속 검토하는 한편, 단기적으로는 로깅에 ELK를 사용하기로 결정했습니다.

달리 사용하지 않을 만한 특별한 이유가 없었기 때문에 기본적으로 Elasticsearch 도구를 사용했으며, 이를 통해 배포를 수집, 조정, 마스터 및 데이터 노드로 분리할 수 있었습니다. 배포를 간소화하기 위해 Bitnami 차트를 사용했습니다. 데이터를 전송하기 위해 Kubernetes DaemonSet의 일부로 Filebeat를 배포했습니다. 또한 Kibana를 배포하고 사전 로드된 인덱스, 검색, 시각화 및 대시보드를 활용해 검색 기능을 추가했습니다.

이 솔루션이 작동하기는 했지만 기본 구성에서 상당히 많은 리소스를 사용하기 때문에 K3S 또는 Microk8s처럼 리소스가 제한된 환경에서는 실행하기 어렵다는 사실이 금세 드러났습니다. 각 구성 요소의 복제본 수를 조정할 수 있도록 하여 이 문제를 해결했지만, 리소스 고갈이나 과도한 데이터 양으로 인한 것으로 추정되는 일부 장애가 발생하기도 했습니다.

저희는 이에 실망하기는커녕, 다양한 구성에서 로깅 시스템의 성능을 벤치마킹하고 Grafana Loki 및 Graylog 같은 다른 옵션을 검토할 기회로 보고 있습니다. 경량 로깅 솔루션은 일부 사용자가 필요로 하며 리소스를 더 많이 사용하는 도구에서는 제공되는 모든 기능을 지원하지 못할 수도 있습니다. MARA는 모듈식 구조이므로, 사용자에게 더 다양한 선택지를 제공하기 위해 이러한 옵션을 위한 추가 모듈도 개발하게 될 가능성이 큽니다.

분산 추적 구현

원하는 추적 기능을 제공하는 도구를 결정하는 것뿐만 아니라, 솔루션을 구현하는 방법과 어떤 기술을 연동해야 하는지도 고려해야 했습니다.

먼저, 계측 작업이 애플리케이션 자체의 서비스 품질에 부정적인 영향을 미치지 않도록 해야 했습니다. 우리 모두 로그를 내보낼 때마다 한 시간에 한 번씩 성능이 어김없이 저하되는 시스템을 다뤄 본 경험이 있었고, 그런 일을 다시 겪고 싶지는 않았습니다. 이런 측면에서 호스트마다 하나의 인스턴스를 실행할 수 있는 OpenTelemetry Collector의 아키텍처는 매우 매력적이었습니다. 각 Collector는 호스트에서 실행 중인 여러 애플리케이션(컨테이너화 여부와 관계없이)의 클라이언트와 에이전트로부터 데이터를 수신합니다. 그런 다음 데이터를 집계하고 필요에 따라 압축한 뒤 스토리지 백엔드로 전송하므로, 이상적인 방식으로 보였습니다.

다음으로 애플리케이션에서 사용 중인 여러 프로그래밍 언어와 프레임워크가 OpenTelemetry를 지원하는지 평가했습니다. 이 부분에서는 상황이 조금 더 까다로워졌습니다. 표에 나온 두 가지 프로그래밍 언어와 관련 프레임워크만 사용했음에도 복잡성은 놀라울 정도로 높았습니다.

언어 프레임워크 서비스 수Java Spring Boot 3Python Flask 3

언어 수준의 추적 기능을 추가하기 위해 먼저 OpenTelemetry의 자동 계측 에이전트를 사용해 보았지만, 출력이 지나치게 복잡하고 이해하기 어렵다는 것을 알게 되었습니다. 자동 계측 라이브러리가 성숙해지면 개선될 것이라 확신하지만, 당분간은 OpenTelemetry 에이전트를 사용하지 않고 코드에 추적 기능을 직접 통합하기로 했습니다.

코드에 트레이싱을 직접 구현하기에 앞서, 먼저 모든 트레이스 데이터를 로컬에서 실행 중인 Jaeger 인스턴스로 출력하도록 OpenTelemetry Collector를 연결해 결과를 더 쉽게 확인할 수 있게 했습니다. OpenTelemetry를 완전히 통합하는 방법을 모색하면서 트레이싱 데이터가 시각적으로 표시되는 방식을 다양하게 시험해 볼 수 있어 매우 유용했습니다. 예를 들어 HTTP 클라이언트 라이브러리가 종속 서비스 호출 시 트레이싱 데이터를 포함하지 않는다는 사실을 발견하면, 해당 문제를 즉시 수정 목록에 추가했습니다. Jaeger에서는 단일 트레이스에 포함된 여러 스팬을 한눈에 보기 좋게 확인할 수 있습니다:

Python용 분산 추적

Python 코드에 추적 기능을 추가하는 작업은 비교적 간단했습니다. 모든 서비스에서 참조하는 두 개의 Python 소스 파일을 추가하고, 관련 requirements.txt 파일을 업데이트하여 필요한 opentelemetry-instrumentation-* 종속성을 포함했습니다. 덕분에 모든 Python 서비스에서 동일한 추적 구성을 사용할 수 있었으며, 로그 메시지에 각 요청의 추적 ID를 포함하고 종속 서비스로 보내는 요청에도 추적 ID를 삽입할 수 있었습니다.

Java용 분산 추적

다음으로 Java 서비스에 주목했습니다. 신규 프로젝트에서 OpenTelemetry Java 라이브러리를 직접 사용하는 것은 비교적 간단합니다. 필요한 라이브러리를 가져오고 트레이싱 API를 직접 사용하기만 하면 됩니다. 하지만 저희처럼 Spring을 사용한다면 추가로 결정해야 할 사항이 있습니다.

Spring에는 이미 분산 추적 API인 Spring Cloud Sleuth가 있습니다. 이 API는 기반이 되는 분산 추적 구현을 추상화하는 퍼사드를 제공하며, 문서에 설명된 대로 다음과 같은 기능을 수행합니다:

  • 추적 및 스팬 ID를 Slf4J MDC에 추가하므로, 로그 집계기에서 특정 추적 또는 스팬의 모든 로그를 추출할 수 있습니다.
  • Spring 애플리케이션의 일반적인 인그레스 및 이그레스 지점(서블릿 필터, RestTemplate, 예약 작업, 메시지 채널, Feign 클라이언트)을 계측합니다.
  • If spring-cloud-sleuth-zipkin을 사용할 수 있는 경우, … HTTP를 통해 Zipkin 호환 추적 정보를 [생성하고 보고합니다]. 기본적으로 localhost(포트 9411)의 Zipkin 수집기 서비스로 전송합니다. 다음을 사용하여 서비스 위치를 구성하세요: spring.zipkin.baseUrl.

또한 이 API를 사용하면 @Scheduled로 어노테이션된 작업에 추적 정보를 추가할 수 있습니다.

즉, Spring Cloud Sleuth만 사용해도 별도의 설정 없이 HTTP 서비스 엔드포인트 수준의 트레이스를 얻을 수 있다는 점은 큰 장점입니다. 저희 프로젝트는 이미 Spring을 사용하고 있었기 때문에 모든 것을 해당 프레임워크 내에서 유지하면서 제공되는 기능을 활용하기로 했습니다. 하지만 모든 요소를 Maven으로 통합하는 과정에서 몇 가지 문제를 발견했습니다:

  • The Spring Cloud Sleuth Autoconfigure 모듈은 아직 마일스톤 릴리스 단계입니다.
  • Spring Cloud Sleuth Autoconfigure 모듈은 오래된 알파 버전의 opentelemetry-instrumentation-api에 의존합니다. 현재 이 라이브러리에는 최신 비알파 1.x 릴리스가 없습니다.
  • Spring Cloud Sleuth가 마일스톤 릴리스의 종속성 참조를 코딩하는 방식 때문에 프로젝트는 Spring Snapshot 리포지토리에서 상위 프로젝트 객체 모델(POM)인 spring-cloud-build를 가져와야 합니다.

이로 인해 Maven이 Maven Central뿐만 아니라 Spring 저장소에서도 항목을 가져오도록 해야 했기 때문에 Maven 프로젝트 정의가 다소 복잡해졌으며, 이는 당시 Spring Cloud의 OpenTelemetry 지원이 아직 초기 단계였음을 분명히 보여 주었습니다. 그럼에도 불구하고 작업을 계속 진행하여 공통 텔레메트리 모듈을 만들고, 다양한 텔레메트리 관련 도우미 함수와 확장 기능을 포함해 Spring Cloud Sleuth와 OpenTelemetry를 사용한 분산 추적을 구성했습니다.

공통 텔레메트리 모듈에서는 다음 기능을 제공하여 Spring Cloud Sleuth 및 OpenTelemetry 라이브러리의 추적 기능을 확장합니다:

  • Spring이 활성화된 Autoconfiguration 클래스로, 프로젝트의 추적 및 확장 기능을 설정하고 추가 추적 리소스 속성을 로드합니다.
  • NoOp 인터페이스 구현체를 사용하면 시작 시 모든 Spring Cloud Sleuth 인터페이스에 NoOp 인스턴스를 주입하여 추적을 비활성화할 수 있습니다.
  • 추적 이름을 정규화하기 위한 추적 이름 지정 인터셉터입니다.
  • An slf4j 및 Spring Cloud Sleuth 추적을 통해 오류를 출력하는 오류 처리기입니다.
  • 각각의 내보낸 추적에 서비스 버전, 서비스 인스턴스 ID, 머신 ID, 파드 이름, 컨테이너 ID, 컨테이너 이름, 네임스페이스 이름 등의 추가 정보를 인코딩하는 향상된 구현인 추적 속성입니다.
  • Hibernate에서 실행하는 SQL 문 앞의 주석에 추적 ID를 삽입하는 추적 문 검사기입니다. 이제는 SQLCommenter로도 이 작업을 수행할 수 있는 것 같지만, 아직 마이그레이션하지 않았습니다.

또한 서비스 간에 HTTP 호출을 수행할 때 더 많은 메트릭과 사용자 지정 기능을 활용하기 위해 Apache HTTP Client를 기반으로 하는 Spring 호환 HTTP 클라이언트를 구현했습니다. 이 구현에서는 종속 서비스를 호출할 때 추적 및 스팬 식별자가 헤더로 전달되므로 추적 출력에 포함될 수 있습니다. 또한 이 구현은 OpenTelemetry에서 집계되는 HTTP 연결 풀 메트릭을 제공합니다.

결론적으로 Spring Cloud Sleuth와 OpenTelemetry를 연동해 추적 기능을 구성하는 과정은 고된 작업이었지만, 그만한 가치가 있었다고 생각합니다. 이 프로젝트와 게시물이 같은 길을 가고자 하는 다른 분들에게 길잡이가 되기를 바랍니다.

NGINX를 위한 분산 추적

요청의 전체 수명 주기에 걸쳐 모든 서비스를 연결하는 추적 데이터를 얻기 위해 NGINX에 OpenTelemetry를 통합해야 했습니다. 이를 위해 OpenTelemetry NGINX 모듈(현재 베타 버전)을 사용했습니다. 모든 NGINX 버전에서 작동하는 모듈 바이너리를 구하기가 어려울 수 있다고 예상하여, 지원되지 않는 NGINX 모듈을 포함한 컨테이너 이미지의 GitHub 저장소를 만들었습니다. 매일 밤 빌드를 실행하고, 손쉽게 가져올 수 있는 Docker 이미지를 통해 모듈 바이너리를 배포합니다.

아직 이 프로세스를 빌드 프로세스에 통합하지는 않았습니다. 대상은 NGINX Ingress Controller의 MARA 프로젝트이지만, 조만간 통합할 계획입니다.

메트릭 수집 구현

추적을 위한 OpenTelemetry 통합을 완료한 후, 다음으로 메트릭에 집중했습니다. Python 기반 애플리케이션에는 기존 메트릭이 없었기 때문에 당분간 추가를 미루기로 했습니다. Java 애플리케이션의 경우, 원본 Bank of Anthos 소스 코드는 Google Cloud의 Micrometer와 Stackdriver를 함께 사용하여 메트릭을 지원합니다. 하지만 이 코드는 메트릭 백엔드를 구성할 수 없었기 때문에 Bank of Anthos를 포크한 후 Bank of Sirius에서 제거했습니다. 그럼에도 메트릭 후크가 이미 존재했다는 사실은 적절한 메트릭 통합이 필요하다는 점을 보여주었습니다.

구성 가능하면서도 실용적인 솔루션을 마련하기 위해 먼저 OpenTelemetry Java 라이브러리와 Micrometer의 메트릭 지원을 살펴보았습니다. 두 기술을 비교한 자료를 검색해 보니, 작성 당시 OpenTelemetry 메트릭이 아직 알파 단계임에도 JVM에서 메트릭 API로 사용할 경우 OpenTelemetry의 단점을 열거한 결과가 다수 있었습니다. Micrometer는 JVM용으로 성숙한 메트릭 퍼사드 계층입니다. 자체적인 메트릭 구현체라기보다는 구성 가능한 메트릭 구현체를 앞단에서 감싸는 공통 API 래퍼를 제공한다는 점에서 slf4j와 유사합니다. 흥미롭게도 Micrometer는 Spring의 기본 메트릭 API입니다.

이 시점에서 저희는 다음과 같은 사실들을 고려하고 있었습니다:

  • OpenTelemetry Collector는 거의 모든 소스에서 메트릭을 수집할 수 있으며, 여기에는 Prometheus, StatsD, 그리고 네이티브 OpenTelemetry Protocol(OTLP)이 포함됩니다.
  • Micrometer는 Prometheus와 StatsD를 비롯한 다양한 메트릭 백엔드도 지원합니다.
  • JVM용 OpenTelemetry Metrics SDK는 OTLP를 통한 메트릭 전송을 지원합니다

몇 차례 실험한 끝에, Micrometer 퍼사드와 Prometheus 백엔드 구현을 사용하고 OpenTelemetry Collector가 Prometheus API를 통해 애플리케이션에서 메트릭을 가져오도록 구성하는 것이 가장 실용적인 접근 방식이라고 판단했습니다. 여러 글을 통해 OpenTelemetry에 일부 메트릭 유형이 없으면 문제가 발생할 수 있다는 점은 알고 있었지만, 저희 사용 사례에는 해당 유형이 필요하지 않았기 때문에 이러한 절충안을 받아들일 수 있었습니다.

OpenTelemetry Collector에 관해 한 가지 흥미로운 사실을 발견했습니다. OTLP를 통해 트레이스를 수신하고 Prometheus API를 통해 메트릭을 수신하도록 구성했더라도, OTLP나 지원되는 다른 프로토콜을 사용해 두 유형의 데이터를 모두 외부 데이터 수신기로 전송하도록 구성할 수 있다는 점입니다. 덕분에 애플리케이션을 LightStep과 손쉽게 연동해 볼 수 있었습니다.

전반적으로 Java에서 메트릭을 코딩하는 작업은 비교적 간단했습니다. 다양한 예제와 튜토리얼이 제공되는 Micrometer API를 따르도록 작성했기 때문입니다. 메트릭과 트레이싱 모두에서 가장 어려웠던 부분은 아마도 pom.xml 파일에서 telemetry-common의 Maven 종속성 그래프를 정확하게 구성하는 일이었을 것입니다.

오류 집계 구현

OpenTelemetry 프로젝트의 사명 자체에는 오류 집계가 포함되어 있지 않으며, 다음과 같은 솔루션만큼 세련된 오류 태깅 구현을 제공하지도 않습니다. Sentry 또는 Honeybadger.io 하지만 다른 도구를 추가하는 대신, 단기적으로는 OpenTelemetry를 오류 집계에 사용하기로 했습니다. Jaeger와 같은 도구에서는 error=true를 검색하여 오류가 발생한 모든 트레이스를 찾을 수 있습니다. 이를 통해 적어도 어떤 문제가 자주 발생하는지 파악할 수 있습니다. 향후에는 Sentry 연동을 추가하는 방안을 검토할 수도 있습니다.

상태 확인 및 런타임 내부 검사 구현

우리 애플리케이션에서 상태 확인은 서비스가 정상인지 또는 시작 단계를 완료했는지를 Kubernetes에 알려줍니다. 서비스가 정상 상태가 아닌 경우 Kubernetes가 인스턴스를 종료하거나 다시 시작하도록 구성할 수 있습니다. 우리 애플리케이션에서는 관련 문서가 충분하지 않다고 판단하여 OpenTelemetry 상태 확인을 사용하지 않기로 했습니다.

대신 JVM 서비스에는 Spring Boot Actuator라는 Spring 프로젝트를 사용합니다. 이 프로젝트는 상태 확인 엔드포인트뿐만 아니라 런타임 내부 검사 및 힙 덤프 엔드포인트도 제공합니다. Python 서비스에는 Spring Boot Actuator 기능의 일부를 제공하는 Flask Management Endpoints 모듈을 사용합니다. 현재는 사용자 지정이 가능한 애플리케이션 정보와 상태 확인 기능만 제공합니다.

Spring Boot Actuator는 JVM 및 Spring과 연동하여 내부 검사, 모니터링 및 상태 확인 엔드포인트를 제공합니다. 또한 엔드포인트에서 제공하는 기본 데이터에 사용자 지정 정보를 추가할 수 있는 프레임워크를 제공합니다. 엔드포인트를 통해 캐시 상태, 런타임 환경, 데이터베이스 마이그레이션, 상태 확인, 사용자 지정 가능한 애플리케이션 정보, 메트릭, 주기적 작업, HTTP 세션 상태, 스레드 덤프 등의 런타임 내부 정보를 확인할 수 있습니다.

Spring Boot Actuator에서 구현하는 상태 확인 엔드포인트는 모듈식으로 구성되어 있어, 서비스의 상태를 활성 상태 또는 준비 상태로 분류되는 여러 개별 검사로 구성할 수 있습니다. 모든 검사 모듈을 표시하는 전체 상태 확인도 제공되며, 일반적으로 다음과 같은 형태입니다.

정보 제공 엔드포인트는 JSON 문서에서 하나의 최상위 JSON 객체와 일련의 계층적 키 및 값으로 정의됩니다. 일반적으로 이 문서에는 서비스 이름과 버전, 아키텍처, 호스트 이름, OS 정보, 프로세스 ID, 실행 파일 이름, 그리고 머신 ID나 고유 서비스 ID와 같은 호스트 세부 정보가 명시됩니다.

힙 덤프 및 코어 덤프 구현

다음 표에서 기억하실 수도 있겠지만, 희망 기능 목록과 도구 기능 비교 어떤 도구도 런타임 내부 검사나 힙/코어 덤프를 지원하지 않습니다. 하지만 기반 프레임워크인 Spring은 두 기능을 모두 지원합니다. 다만 관측성 기능을 애플리케이션에 연동하려면 다소 작업이 필요했습니다. 이전 섹션에서 자세히 설명했듯이 런타임 내부 검사에는 Python 모듈과 Spring Boot Actuator를 함께 사용합니다.

마찬가지로 힙 덤프의 경우, Spring Boot Actuator에서 제공하는 스레드 덤프 엔드포인트를 사용하여 원했던 기능 중 일부를 구현합니다. 필요할 때 코어 덤프를 생성하거나 JVM의 힙 덤프를 이상적인 수준으로 세밀하게 생성할 수는 없지만, 약간의 추가 작업만으로 원하는 기능 중 일부를 사용할 수 있습니다. 안타깝게도 Python 서비스의 코어 덤프를 지원하려면 상당한 추가 작업이 필요하므로 추후로 미뤄 두었습니다.

요약

수없이 좌절하고 우리의 선택이 옳았는지 고민한 끝에, MARA의 관측 가능성을 위해 다음 기술을 사용하기로 의견을 모았습니다(이하 OTel은 OpenTelemetry를 의미합니다):

  • 로깅(모든 컨테이너 대상) – Filebeat → Elasticsearch / Kibana
  • 분산 추적
    • Java – Spring Cloud Sleuth → OTel용 Spring Cloud Sleuth 익스포터 → OTel Collector → Jaeger, Lightstep, Splunk 등의 플러그형 익스포터
    • Python – OTel Python 라이브러리 → OTel Collector → 플러그형 저장소
    • NGINX 및 NGINX Plus(NGINX Ingress Controller는 아직 지원되지 않음) – NGINX OTel 모듈 → OTel Collector → 플러그형 익스포터
  • 메트릭 수집
    • Java – Spring을 통한 Micrometer → Prometheus 익스포터 → OTel Collector
    • Python – 아직 구현되지 않음
    • Python WSGI – GUnicorn StatsD → Prometheus(StatsD/ServiceMonitor 경유)
    • NGINX – Prometheus 엔드포인트 → Prometheus(ServiceMonitor를 통해)
  • Error Aggregation – OTel distributed traces → pluggable exporter → exporter’s search functionality for finding traces marked with error
  • 상태 확인
    • Java – Spring Boot Actuator → Kubernetes
    • Python – Flask 관리 엔드포인트 모듈 → Kubernetes
  • 런타임 내부 검사
    • Java – Spring Boot Actuator
    • Python – Flask 관리 엔드포인트 모듈
  • 힙/코어 덤프
    • Java – 스레드 덤프를 위한 Spring Boot Actuator 지원
    • Python – 아직 지원되지 않음

이 구현은 현재 시점의 상태를 담은 스냅샷입니다. 개발이 계속됨에 따라 분명히 변경되고 발전할 것입니다. 조만간 애플리케이션을 대상으로 광범위한 부하 테스트를 진행할 예정입니다. 이를 통해 관측 가능성 접근 방식의 한계를 많이 파악하고, 관측 가능성 기능을 추가할 수 있을 것으로 기대합니다.

Please try out the Modern Apps 참조 아키텍처와 샘플 애플리케이션(Bank of Sirius)을 사용해 보세요. 더 나은 개선 방안에 대한 아이디어가 있다면 GitHub 저장소에 기여해 주세요!

관련 게시물

이 게시물은 연재 시리즈의 일부입니다. 앞으로 MARA에 새로운 기능을 추가할 때마다 자세한 내용을 블로그에 게시할 예정입니다:


공유

관련 블로그 게시물

시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
NGINX | 2023. 11. 30.

시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric

이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.

F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
NGINX | 2023. 03. 23.

F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법

앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.

Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
NGINX | 2021. 09. 27.

Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션

개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.

진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX | 2021. 07. 06.

진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법

NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.

NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX | 2021. 06. 30.

NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리

NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.

Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
NGINX | 2015. 03. 25.

Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유

Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.