왜 오픈텔레메트리(OpenTelemetry)를 사용해야 할까요? 옵저버빌리티의 세상에서 왜 또 다른 새로운 영역으로 들어가야 할까요? 오픈텔레메트리란 도대체 무엇일까요?

커뮤니티의 정의를 보면, 오픈텔레메트리(OpenTelemetry)는 트레이스, 메트릭, 로그 같은 텔레메트리 데이터를 생성하고 관리하도록 설계된 옵저버빌리티 프레임워크이자 툴 키트입니다. 중요한 것은 오픈텔레메트리는 공급업체나 툴에 구애받지 않는다는 것입니다. 예거(Jaeger), 프로메테우스(Prometheus) 같은 오픈소스 툴은 물론 상용 제품까지 다양한 옵저버빌리티 백엔드와 함께 사용할 수 있다는 의미입니다. 줄여서 OTel이라고도 불리는 오픈텔레메트리는 클라우드 네이티브 컴퓨팅 재단(CNCF)의 프로젝트 중 하나입니다.

근사합니다. 커뮤니티의 사명, 비전, 가치를 보면 훨씬 더 근사합니다. 하지만 유사한 목표를 가진 다른 CNCF 샌드박스 프로젝트들도 많이 있습니다. 그렇다면 오픈텔레메트리가 어떤 면에서 특별할까요?

이 프로젝트가 시간이 지나면서 어떻게 진화해왔고 어떤 기업들이 적극적으로 기여하고 있는지 확인해보면, 성장해 온 과정과 앞으로 발휘될 역량을 가늠해 볼 수 있습니다.

그렇다면 지금 당장 프로젝트에 참여해 보는 것이 어떨까요?

쿠버네티스에 대한 이해

이 블로그는 쿠버네티스에 대해 이미 잘 알려져 있는 내용을 재차 설명하지는 않겠지만, 약간의 맥락을 제공하는 것이 도움이 될 거라고 생각합니다.

쿠버네티스는 가용성이 높은 플랫폼으로 구축되었고, 이를 달성하기 위해 확장 가능한 여러 구성 요소들로 다양한 책임을 오프로드하도록 설계되었습니다. 공식 문서에 요약된 내용을 살펴보겠습니다.

변화할 수 있는 요소들이 꽤 많이 있으며, 워크로드가 원활하게 실행되려면 이들이 성공적으로 작동해야 합니다. 다행히도 이러한 구성 요소들은 클러스터에서 무슨 일이 일어나고 있는지 더 폭넓은 관점을 가질 수 있도록 세부적인 성능 인사이트를 제공합니다. 무엇보다 좋은 점은 모두 프로메테우스를 공통 언어로 사용한다는 것입니다.

클러스터 전반의 메트릭 수집

쿠버네티스가 프로메테우스로 소통을 하니 우리도 똑같이 대답을 해줘야 하지 않을까요? 간단한 것처럼 들리지만 전혀 그렇지 않습니다.

우선, 프로메테우스는 풀(pull) 메커니즘을 기반으로 작동합니다. 프로메테우스가 노출시키는 메트릭을 찾고 스크레이핑하는 데 필요한 엔드포인트를 정의하는 책임은 사용자에게 있다는 말입니다. 쿠버네티스가 컨테이너를 빠르게 생성하고 제거를 한다는 점에서 본질적으로 매우 동적이라는 점을 고려하면 이는 상당히 어려운 일입니다.

다행히 프로메테우스에는 쿠버네티스용 서비스 검색이 내장되어 있어서 프로메테우스 인스턴스가 확인할 위치를 결정할 수 있도록 사전에 다양한 스크레이핑 작업을 구성할 수 있습니다.

확장성은 어떨까요?

클러스터가 점점 더 커지면 하나의 프로메테우스 인스턴스에 엄청난 양의 리소스가 필요할 수 있으며, 이로 인해 실행할 거대한 시스템이 필요할 수 있습니다. 우리는 더 작은 시스템에서 여러 개의 작은 인스턴스를 실행하는 것을 선호합니다. 그러나 메트릭이 풀링되기 때문에, 여러 인스턴스가 있다는 것은 동일한 엔드포인트를 여러 번 스크레이핑 하면 된다는 의미입니다.

이 시점에서 오픈텔레메트리 컬렉터가 활용됩니다. 간단히 요약하면 OTel 컬렉터는 리시버(receiver), 프로세서(processor), 엑스포터(exporter)의 세 가지 주요 구성 요소로 구성됩니다. 이 세 가지 구성 요소의 여러 옵션을 사용하면, 다양한 파이프라인을 구축하여 텔레메트리 데이터를 처리하고 필요에 따라 이를 다른 백엔드로 보낼 수 있습니다.

어떻게 OTel 컬렉터를 활용해 쿠버네티스와 통신할까요?

OTel 컬렉터에는 프로메테우스 리시버가 포함되어 있습니다. 이를 프로메테우스 서버를 구성할 수 있는 것처럼 구성할 수 있습니다. 간단하게 같은 스크레이핑 작업 정의를 사용할 수 있다는 의미입니다. 좋은 점은 가져온 메트릭을 로컬(메모리 제외)에 저장할 필요가 없다는 것입니다. 에이전트 모드에 있는 프로메테우스라고 생각하면 됩니다.

좋습니다. 그러나 아직 샤딩(sharding) 문제가 남아 있습니다. 여전히 OTel 컬렉터를 수평으로 확장할 수 없습니다. 바로 이 부분에서 타깃 얼로케이터(Target Allocator), 즉 대상 할당자가 도움을 줍니다. 타깃 얼로케이터는 상태 저장 OTel 컬렉터 앞에 위치하며 스크레이핑 할 대상에 따라 인스턴스 전체에 배포하여 프로메테우스 구성을 조작합니다. 모든 엔드포인트는 한 번에 하나의 OTel 컬렉터 인스턴스에 의해 스크레이핑됩니다. 스크레이퍼 확장이 어떻게 작동하는지 자세한 내용은 공식 문서를 참조하십시오.

프로메테우스의 작동 방식, OTel 컬렉터를 활용하는 방법, 확장 방법에 대해 많은 이야기를 했지만 실제로 수집해야 할 것이 무엇인지에 대해서는 언급하지 않았습니다. 쿠버네티스가 어떤 역할을 하는지 살펴보겠습니다.

먼저, 모든 컨트롤 플레인 구성 요소는 전용 포트에 자신을 노출하고 /metrics 엔드포인트 아래에 메트릭을 제공합니다. 예를 들어 kube-api-server의 스크레이핑 작업 구성을 살펴보겠습니다.

scrape_configs:
- job_name: 'kubernetes-apiservers'
  scrape_interval: 60s
  kubernetes_sd_configs:
    - role: endpoints
  scheme: https
  tls_config:
    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    insecure_skip_verify: true
  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
  relabel_configs:
    - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
      action: keep
      regex: default;kubernetes;https

두 번째로, 머신(노드)에 대한 가시성이 필요합니다. 노드 메트릭을 수집하고 노출하는 데 보편적으로 노드 엑스포터(Node Exporter)가 사용됩니다. 이를 모든 (Linux) 노드에서 데몬셋으로 실행하고 다음과 같이 기저 머신에서 수집한 내용을 스크레이핑할 수 있습니다.

scrape_configs:
- job_name: 'kubernetes-node-exporter'
  scrape_interval: 60s
  honor_labels: true
  kubernetes_sd_configs:
    - role: endpoints
  relabel_configs:
    - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
      action: keep
      regex: true
    - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape_slow]
      action: drop
      regex: true
    - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
      action: replace
      target_label: __scheme__
      regex: (https?)
    - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
      action: replace
      target_label: __metrics_path__
      regex: (.+)
    - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
      action: replace
      target_label: __address__
      regex: (.+?)(?::\d+)?;(\d+)
      replacement: $$1:$$2
    - source_labels: [__meta_kubernetes_service_name]
      action: keep
      regex: <NODE_EXPORTER_SERVICE_NAME>
    - source_labels: [__meta_kubernetes_namespace]
      action: replace
      target_label: namespace
    - source_labels: [__meta_kubernetes_service_name]
      action: replace
      target_label: service
    - source_labels: [__meta_kubernetes_pod_node_name]
      action: replace
      target_label: node

이제 머신 레벨 메트릭을 확보했으므로 다른 레벨로 올라가 컨테이너를 모니터링할 수 있습니다. 컨테이너 메트릭은 모든 노드에서 실행되는 cAdvisor(Container Advisor) 툴로 수집됩니다. 다음과 같이 스크레이핑할 수 있습니다.

scrape_configs:
- job_name: 'kubernetes-nodes-cadvisor'
  scrape_interval: 60s
  scheme: https
  tls_config:
    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    insecure_skip_verify: true
  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
  kubernetes_sd_configs:
    - role: node
  relabel_configs:
    - target_label: __address__
      replacement: kubernetes.default.svc:443
    - source_labels: [__meta_kubernetes_node_name]
      regex: (.+)
      target_label: __metrics_path__
      replacement: /api/v1/nodes/$$1/proxy/metrics/cadvisor

마지막으로 이제 클러스터에서 구성 요소의 상태를 알아야 합니다. kube-state-metrics 툴은 다양한 엔드포인트에 해당 정보를 요청하고 메트릭을 생성합니다. 이를 다시 OTel 컬렉터의 프로메테우스 리시버로 스크레이핑할 수 있습니다.

scrape_configs:
- job_name: 'kubernetes-kube-state-metrics'
  scrape_interval: 60s
  honor_labels: true
  kubernetes_sd_configs:
    - role: endpoints
  relabel_configs:
    - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
      action: keep
      regex: true
    - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape_slow]
      action: drop
      regex: true
    - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
      action: replace
      target_label: __scheme__
      regex: (https?)
    - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
      action: replace
      target_label: __metrics_path__
      regex: (.+)
    - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
      action: replace
      target_label: __address__
      regex: (.+?)(?::\d+)?;(\d+)
      replacement: $$1:$$2
    - source_labels: [__meta_kubernetes_service_name]
      action: keep
      regex: <KUBE_STATE_METRICS_SERVICE_NAME>
    - source_labels: [__meta_kubernetes_namespace]
      action: replace
      target_label: namespace
    - source_labels: [__meta_kubernetes_service_name]
      action: replace
      target_label: service
    - source_labels: [__meta_kubernetes_pod_node_name]
      action: replace
      target_label: node

이 시점에서는 서술하지 않고도 모니터링하려는 모든 단일 구성 요소에 대해 전용 스크레이핑 작업을 만들 수 있습니다.

클러스터에서 또 무엇을 얻을 수 있을까요?

프로메테우스 리시버로 수집한 메트릭 외에도 클러스터에서 이벤트와 로그를 수집할 수도 있습니다.(참고: 이 블로그에서는 트레이스에 대해 다루지 않습니다.)

이벤트부터 시작하겠습니다. 쿠버네티스는 클러스터에서 생성, 삭제 또는 변형이 발생할 때마다 이벤트를 발생시킵니다. 이러한 이벤트는 kube-api-server를 쿼리하여 가져올 수 있습니다. 다행스럽게도 OTel 컬렉터에는 이를 위한 리시버인 k8sevents receiver가 있습니다. (참고: 이 리시버의 지원 중단이 논의 중이므로 앞으로는 k8sobject 리시버를 사용하시기 바랍니다.)

단일 컬렉터 인스턴스를 실행하여 쿠버네티스 이벤트를 가져와 뉴렐릭에 로그로 전달할 수 있습니다. 주의할 것은 k8sevent 리시버의 여러 인스턴스는 동일한 이벤트들을 가져와 뉴렐릭으로 보낸다는 겁니다.

로그는 어떨까요? OTel 컬렉터에는 파일로그 리시버가 포함되어 있습니다. 이 리시버는 데몬셋으로 실행하고 노드의 로그 디렉터리를 여기에 마운트할 수 있습니다. 다양한 정규식(regex)을 사용해 클러스터의 모든 구성 요소에서 로그를 수집, 구문 분석, 필터링 및 강화할 수 있습니다.

뉴렐릭을 사용하여 OTel 컬렉터 배포 및 모니터링

필요한 텔레메트리 데이터를 찾을 수 있는 위치와 수집 방법을 알게 되었으니 이제 다음을 수행해야 합니다.

  1. 필요한 구성으로 OTel 컬렉터 인스턴스를 배포하기 위한 전략을 구축합니다.
  2. 포괄적인 NRQL(뉴렐릭 쿼리 언어) 쿼리를 사용해 적절한 대시보드와 알림을 구축하여 클러스터를 모니터링합니다.

지금까지 설명한 모든 내용은 리포지터리에서 확인할 수 있습니다.

  • OTel 컬렉터를 롤아웃하는 데 사용할 수 있는 공개 Helm 차트
  • 뉴렐릭 계정에 사전 구축된 모니터링을 부트스트랩하는 데 사용할 수 있는 Terraform 배포

보다 자세한 내용은 다음 자료를 확인하십시오.

  • 이 문서를 확인하시면 다양한 유형의 컬렉터(데몬셋, 배포, 스테이트풀세트, 싱글톤)가 필요한 이유를 이해할 수 있습니다.
  • 이 문서를 참조하여 클러스터에 모니터링 솔루션을 배포할 수 있습니다.
  • 이 문서를 확인하시면 수집된 프로메테우스 데이터를 올바르게 쿼리하고 이해하는 방법을 알 수 있습니다.

이 문서를 참조해 뉴렐릭 계정에 대시보드와 알림을 생성할 수 있습니다.