오픈 소스 관측 가능성, 그라파나

uchan·2024년 2월 27일
0

4.1 그라파나 관측 가능성

4.1.1 목적과 범위

LGTM

  • 로키: 로그 관리
  • 그라파나: 대시보드
  • 템포: 추적 관리
  • 미미르: 매트릭 관리 (프로메테우스 메트릭 관리)

위 관측 가능성 4가지 소프트웨어를 사용한다


ref: https://tindevops.medium.com/how-to-debug-an-issue-by-grafana-lab-mimir-loki-and-tempo-dd4a7edc532c

4.1.2 인프라 구성

eks 를 이용하여 쿠버네티스 환경을 띄우고 helm 을 설치하여 관측 가능성 툴을 설치할 준비를 한다. (eks 가 없다면 minikube 사용해도 된다)

4.1.3 애플리케이션 구성

ref: https://github.com/philllipjung/o11ybook/tree/main/4.1

미니오

  • 오픈소스 객체 스토리지
  • AWS S3 와 호환되는 API 제공
  • 그라파나 관측가능성은 디스크 IO 에서 성능 감소가 발생하는데, 미니오에서 제공하는 상세한 메트릭과 수치를 보고 성능 개선 작업을 할 수 있다
  • AWS 운영 환경이라면 S3 를 그대로 사용하는게 좋지만 클라우드 네이티브 환경으로서 다양한 플랫폼을 이용한다면 미니오를 사용하는 걸 권장한다
deployment 코드
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert -f docker-compose.yaml
    kompose.version: 1.26.1 (a9d05d509)
  creationTimestamp: null
  labels:
    io.kompose.service: minio
  name: minio
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: minio
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert -f docker-compose.yaml
        kompose.version: 1.26.1 (a9d05d509)
      creationTimestamp: null
      labels:
        io.kompose.service: minio
    spec:
      containers:
        - args:
            - server
            - --console-address
            - :9001
            - /export
          env:
            - name: MINIO_ACCESS_KEY
              value: 697d0993dd097f38d5b8
            - name: MINIO_SECRET_KEY
              value: 9f88738761b57c63f6a81bdfd471
            - name: MINIO_ROOT_USER # 로그인할 아이디와 패스워드
              value: admin
            - name: MINIO_ROOT_PASSWORD
              value: admin123
          image: minio/minio:latest
          livenessProbe:
            exec:
              command:
                - curl
                - -f
                - http://localhost:9000/minio/health/live
            failureThreshold: 3
            periodSeconds: 30
            timeoutSeconds: 20
          name: minio
          ports:
            - containerPort: 9000
            - containerPort: 9001
          resources: {}
          volumeMounts:
            - mountPath: /export
              name: minio-claim0
      restartPolicy: Always
      volumes:
        - name: minio-claim0
          persistentVolumeClaim:
            claimName: minio-claim0
status: {}
실행 화면


레디스

  • 오픈소스 캐시
  • NoSQL
  • 레디스를 통해 객체 스토리지부터 읽어오기 보다 캐시로부터 읽어옴으로써 빠른 쿼리 처리 등이 가능하다
  • 자바 오브 힙(off-heap) 보다 디버깅이 유용하다
deployment 코드
  • redis
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: ./kompose convert -f docker-compose.yaml
    kompose.version: 1.26.1 (a9d05d509)
  creationTimestamp: null
  labels:
    io.kompose.service: redis
  name: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: redis
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: ./kompose convert -f docker-compose.yaml
        kompose.version: 1.26.1 (a9d05d509)
      creationTimestamp: null
      labels:
        io.kompose.service: redis
    spec:
      containers:
        - env:
            - name: ALLOW_EMPTY_PASSWORD
              value: "yes"
          image: bitnami/redis:latest
          name: redis
          ports:
            - containerPort: 6379
          resources: {}
          volumeMounts:
            - mountPath: /data
              name: redis-claim0
      restartPolicy: Always
      volumes:
        - name: redis-claim0
          persistentVolumeClaim:
            claimName: redis-claim0
status: {}
  • redis commander
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: ./kompose convert -f docker-compose.yaml
    kompose.version: 1.26.1 (a9d05d509)
  creationTimestamp: null
  labels:
    io.kompose.service: redis-commander
  name: redis-commander
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: redis-commander
  strategy: {}
  template:
    metadata:
      annotations:
        kompose.cmd: ./kompose convert -f docker-compose.yaml
        kompose.version: 1.26.1 (a9d05d509)
      creationTimestamp: null
      labels:
        io.kompose.service: redis-commander
    spec:
      containers:
        - env:
            - name: REDIS_HOSTS
              value: local:redis:6379
          image: rediscommander/redis-commander:latest
          name: redis-commander
          ports:
            - containerPort: 8081
          resources: {}
      restartPolicy: Always
status: {}
실행화면

다음은 redis commander GUI 화면이다.

콘술

  • 하시코프에서 관리하는 오픈소스 서비스 레지스트리
  • 관측 가능성은 대부분 다수의 서버로 클러스터를 구성하기에 복잡한 네트워크 구성화 구성 정보 관리가 필요한데, 콘술을 통해 이를 쉽게 해결할 수 있다
deployment 코드
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert -f docker-compose.yaml
    kompose.version: 1.26.1 (a9d05d509)
  creationTimestamp: null
  labels:
    io.kompose.service: consul
  name: consul
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: consul
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert -f docker-compose.yaml
        kompose.version: 1.26.1 (a9d05d509)
      creationTimestamp: null
      labels:
        io.kompose.service: consul
    spec:
      containers:
        - args:
            - agent
            - -log-level=info
            - -dev
            - -ui
            - -client
            - 0.0.0.0
          image: consul:1.9
          name: consul
          ports:
            - containerPort: 8500
          resources: {}
          volumeMounts:
            - mountPath: /consul/config
              name: consul-claim0
            - mountPath: /consul/data
              name: consul-claim1
      restartPolicy: Always
      volumes:
        - name: consul-claim0
          persistentVolumeClaim:
            claimName: consul-claim0
        - name: consul-claim1
          persistentVolumeClaim:
            claimName: consul-claim1
status: {}
실행화면

초기에는 consul 서비스만 띄워져있다.
문서를 참고하여 consul API 를 사용하여 redis 를 별도로 등록한다.

curl --request PUT --data '{
  "name": "redis",
  "tags": ["database", "cache"],
  "address": "redis.ada-grafana",
  "port": 6379,
  "checks": [
    {
      "name": "Redis TCP Check",
      "tcp": "redis.ada-grafana:6379",
      "interval": "10s",
      "timeout": "1s"
    }
  ]
}' http://10.77.110.138:30885/v1/agent/service/register

카프카

  • 오픈소스 데이터 분석 파이프라인
  • 로키에서 로그 수집에 사용하는 프롬테일, 플루언트비트는 로키에 직접적으로 파이프라인을 생성하기 보다 중간에 카프카를 사용함으로써 안정적으로 데이터를 전달할 수 있다

멤캐시트

항목멤캐시트레디스
데이터 분할지원지원
다양한 데이터 구조 지원미지원지원
스레드 모델멀티스레드싱글스레드
데이터 저장미지원지원
데이터 복제미지원지원
트랜잭션 지원미지원지원
발행과 구독미지원지원

멤캐시트는 멀티 스레드를 지원하고, 레디스는 싱글 스레드이지만 스케일 아웃을 지원하고 있다.

최종 서비스 출력 화면

4.2 로키 로그 관리

4.2.1 로키 마이크로서비스 모드

각 컴포넌트를 개별 마이크로서비스로 실행하여 마이크로서비스의 개수를 늘려 확장시키는 모드이다. 설정과 유지 관리가 복잡하지만 대규모 로키 클러스터 또는 확장과 클러스터 작업에 대한 많은 제어가 필요한 경우 사용된다.

로키 마이크로서비스 모드에서 데이터가 저장되는 흐름

  • 쓰기 방식 (안정성 확보)

    • 디스트리뷰터는 인제스터로 스트림을 전달하는데 이때 데이터 유실 방지 및 안정성 확보를 위해 replication factor 개수(복제본 개수)에 맞춰 스트림 개수를 보낸다(예를 들어, 레플리케이션 팩터가 3이라면 3개의 복제본 개수를 생성하도록 3개의 스트림을 전달).
    • 인제스터에서 장애 발생 방지를 위해 WAL 을 사용한다 (handoff 사용하여 새로운 인제스터를 생성할 수 있지만 리밸런싱 등에 의해 지연이 발생한다)
    • 디스트리뷰터와 인제스터 간에는 쿼럼(정족수)를 사용해서 안정적으로 스트림을 전달하고, 각 샤드 간 가십을 사용해서 health check 를 수행한다
  • 읽기 방식

    • 쿼리어는 캐시 -> 인제스터 WAL 메모리 -> 블록 스토리지 순서대로 결과를 조회함으로써 빠른 처리를 한다

=> 전체적으로 로키 컴포넌트는 다수의 해시 링에 샤드를 구성한 다음 WAL, 쿼럼, 가십, 캐시, 레플리케이션 팩터를 향상시키고, 병렬 처리와 중복제거를 기본으로 한다

로키 컴포넌트

1. 디스트리뷰터

들어오는 스트림을 처리하는 역할로서 로그 데이터에 대한 쓰기 경로의 첫번 째 단계다. 스트림을 수신하면 각 스트림의 정확성 검증되고 및 구성된 테넌트의 제한 내에 있는지 확인한다. 그런 다음 유효한 청크를 배치로 분할하고 인제스터에 병렬로 보낸다. 디스트리뷰터는 상태 비저장 컴포넌트이며 스트림 검증 작업을 인제스터로부터 독립적으로 확장할 수 있기 때문에 로키가 수집기에 과부라흫 줄 수 있는 공격으로부터 보호할 수 있다.

  • 유효성
    • 들어오는 데이터가 사양에 맞는지 검사
      • 레이블이 유효한가?
      • 타임스탬프가 너무 오래되거나 새롭지 않나?
      • 로그가 너무 길지 않나?
  • 비율 제한
    • 테넌트 별로 속도 제한을 지정하여 수신 로그의 속도를 제한한다
    • 속도 제한 = 테넌트 속도 제한 / 디스트리뷰터 개수
  • 포워딩
    • 모든 유효성 검사가 완료되면 인제스터로 데이터를 전달한다
  • 레플리케이션 팩터 & 쿼럼(정족수)
    • 데이터 손실을 줄이기 위해 레플리케이션 팩터로 전달하며 일반적으로 이 팩터 값은 3이다
    • 그리고 쿼럼은 floor(replication factor/ 2) + 1 로 정의되어 보통 2개 이상의 쓰기 완료를 받으면 성공으로 간주한다
    • 레플리케이션 팩터의 주된 목적은 데이터 손실 방지보다도 롤아웃 및 재시작하는 동안 중단 없이 쓰기를 계속할 수 있도록 하기 위함이다. 인제스터에는 디스크가 손상되지 않는 한 디스크에 들어오는 쓰기를 지속하는 WAL 이 있는데, 레플리케이션 팩터와 WAL 을 이용하여 최대한 데이터 손실을 막는다.
  • 해싱
    • 스트림(테넌트 ID, 고유 레이블, 로그)를 해싱하여 나온 해시 값을 토대로 스트림을 보낼 인제스터를 찾는다
    • JOINING 또는 ACTIVE 상태인 인제스터는 쓰기 요청, ACTIVE 또는 LEAVING 상태인 인제스터는 읽기 요청을 수신할 수 있다. 디스트리뷰터는 해시 룩업을 수행하면서 요청에 적합한 상태에 있는 인제스터의 토큰을 사용한다

2. 인제스터

  • 인제스터 상태
    • PENDING
      • LEAVING 상태인 다른 인제스터로부터 핸드오프를 기다리고 있을 때 상태이다
    • JOINING
      • 현재 토큰을 링에 삽입하고 초기화 중일 떄 인제스터의 상태이다. 소유한 토큰에 대한 쓰기 요청을 받을 수 있다
    • ACTIVE
      • 완전히 초기화되었을 때 상태이다. 소유한 토큰에 대한 쓰기와 읽기 요청을 모두 수신할 수 잇다
    • LEAVING
      • 종료될 때 상태이다
      • 아직 메모리에 있는 데이터에 대한 읽기 요청을 수신할 수 잇다
    • UNHEALTHY
      • 콘술의 하트비트에 실패했을 때 상태이다
      • 디스트리뷰터가 주기적으로 링을 확인하고 설정한다
  • 타임스탬프 정렬
    • 로키는 out-of-order(비순차적 쓰기)를 허용하는 구성을 할수도 있다. 만약 그렇지 않다면 순서에 따르지 않는 로그라인은 거부되고 오류를 반환한다
      • 만약 비순차적 쓰기를 허용하지 않은 경우 나노세컨드가 다른 두 로그가 동일한 것으로 판단되어 무시할 수도 있고 내용이 다른 경우 허용할 수도 있다(즉, 동일한 타임스탬프에 대해 서로 다른 로그 행을 가질 수도 있다)
    • 로그 라인이 타임스탬프 오름차순으로 수신되었는지 확인한다
    • 각각 레이블 세트의 로그는 메모리의 청크로 작성된 다음 백엔드 스토리지로 플러시 된다
    • 인제스터 프로세스가 충돌하거나 갑자기 종료되면 아직 플러시되지 않은 모든 데이터가 솔실 될 수 있다. 이를 위해 일반적으로 다시 시작하면 WAL 와 로그의 레플리케이션 팩터를 사용하여 이러한 위험을 완화한다
  • 청크
    • 인제스터가 수신하는 각 로그 스트림은 메모리 내 다수의 청크 세트로 구성되고, 구성 가능한 주기로 백엔드 스토리지에 플러시 된다.
    • 다음과 같은 경우 청크가 압축되고 읽기 전용으로 표시된다
      • 현재 청크가 용량에 도달 했을 경우
      • 현재 청크가 많은 시간이 경과했을 경우
      • 플러시가 발생할 경우
    • 청크가 압축되고 읽기 전용으로 표시되면 쓰기 가능한 청크가 그 자리를 차지한다
  • 파일 시스템 지원
    • 볼드DB 를 통해 파일 시스템에 쓰기를 지원하지만, 쿼리어는 동일한 백엔드 저장소에 액세스해야 하고, 볼트DB 는 주어진 시간에 하나의 프로세스만 DB에 대한 잠금을 갖도록 허용하므로 단일 프로세스모드에서만 작동한다

3. 쿼리 프런트엔드

  • 쿼리 프런트엔드는 내부적으로 일부 쿼리 조정을 수행하고 쿼리를 내부 대기열에 보관한다. 그리고 쿼리어에서 대기열로부터 작업을 가져와 실행하고 쿼리 프런트엔드로 반환한다.
    • 쿼리어 설정할 때 -querier.frontend-address 플래그에 쿼리 프런트엔드 주소를 넣어 연결한다
  • 일반적으로 내부 대기열이 작동하는 방식으로 이점을 얻기 위해 다수의 쿼리 프런트엔드를 실행하는 것이 좋다 (대부분 2개)
  • 쿼리 플런트엔드는 상태 비저장 컴포넌트다
  • 큐잉 메커니즘
    • FIFO 대기열로 모든 쿼리어에게 배포한다
    • 테넌트 간 쿼리를 공정하게 예약하여 특정 테넌트의 과도한 시소스 사용을 제한한다
  • 스플리팅
    • OOM 을 일으킬 수 있는 대규모 쿼리를 확인하고 이를 더 작은 쿼리로 분할하여 다운스트림 쿼리에서 디러한 쿼리를 병렬로 실행하고 결과를 다시 결합한다
  • 캐싱 (멤캐시트, 레디스 등)
    • 메트릭 쿼리 결과 캐싱을 지원하고 후속 쿼리에서 이를 재사용한다
    • 캐시 결과가 불완전한 경우 쿼리 프런트엔드는 하위 쿼리를 계산하고 다시 병렬로 실행한다

4. 쿼리어

  • LogQL 쿼리 언어를 사용하여 쿼리를 처리하고 인제스터와 장기 저장소 모두에서 로그를 가져온다
  • 백엔드 저장소에 쿼리하기 전 메모리 내 데이터에 대한 모든 인제스터를 쿼리하는데, 이때 복제로 인해 중복 데이터를 받을 수 있다. 이를 해결하기 위해 나노초 타임스탬프, 레이블 집합, 로그메시지가 동일한 중복 데이터를 쿼리어에서 내부적으로 제거한다

일관된 해시링

고가용성 구현과, 클러스터 수평 확장 또는 축소를 지원하기 위해 일관된 해시링을 로키 클러스터 아키텍처와 통합하여 사용한다. 모든 로키 컴포넌트가 해시 링으로 자동 연결되는 것은 아니며 다음 컴포넌트는 해시 링에 연결해야 한다.

  • 디스트리뷰터
  • 인제스터
  • 쿼리 스케쥴러
  • 콤팩터
  • 룰러
    다음 컴포넌트는 선택적으로 해시링에 연결할 수 있다.
  • 인덱스 게이트웨이

해시 링에 등록된 각 노드마다 키-값 저장소를 다음과 같이 보유한다

  • 컴포넌트 노드 ID
  • 다른 노드에서 통신 채널로 사용하는 컴포넌트 주소
  • 컴포넌트 노드의 상태 표시

그라파나 로키 레이블

  • 로키와 프로메테우스 사이에 일관된 레이블을 갖는 것은 로키의 기능 중 하나이므로 어플리케이션 메트릭을 로그 데이터와 매우 쉽게 연관시킬 수 있다
  • Elastic Search 와 같이 보통 로그 솔루션은 주로 인덱스를 사용하기 때문에 로키에서도 효과적으로 쿼리하기 위해 레이블 정의가 필수라고 생각이 들 수 있다. 하지만 로키는 쿼리를 작은 조각으로 분할하고 병렬로 전달하여 짧은 시간에 방대한 양의 로그 데이터를 쿼리한다. 만약 로키에서 스트림 변동을 최소화하여 자업을 잘 수행한다면 수집된 로그에 비해 인덱스 크기는 매우 느리게 증가한다. 이를 통해 비용을 절감할 수 있다

4.2.3 로키 쿠버네티스 구성

4.2.2 로키 바이너리 구성은 스킵하고 바로 쿠버네티스에 설치한다

helm repo add grafana https://grafana.github.io/helm-charts
helm fetch grafana/loki-distributed
tar xvf loki-distributed-0.78.3.tgz
cd loki-distributed

로키 헬름차트를 가져왔으면 values.yaml 파일을 다음과 같이 수정한다

distributor:
  autoscaling:
    enabled: true # distributor.autoscaling 사용
    
querier:
  autoscaling:
    enabled: true # querier.autoscaling 사용
  persistence:
    enabled: true # querier.persistence 사용

queryFrontend:
  autoscaling:
    enabled: true # queryFrontend.autoscaling 사용

gateway:
  enabled: true # gateway.enabled 사용
  autoscaling:
    enabled: true # gateway.autoscaling 사용

compactor:
  enabled: true # compactor.enabled 사용

memcachedExporter:
  enabled: true # memcachedExporter 사용

memcachedChunks:
  enabled: true # memcachedChunk 사용

memcachedFrontend:
  enabled: true # memcachedFrontend 사용

memcachedIndexQueries:
  enabled: true # memcachedIndexQueries 사용

memcachedIndexWrites:
  enabled: true # memcachedIndexWrites 사용

그리고 로키를 설치하면 다음과 같이 뜬다

> loki-distributed % helm install loki .
NAME: loki
LAST DEPLOYED: Fri Mar  1 15:23:12 2024
NAMESPACE: ada-grafana
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
***********************************************************************
 Welcome to Grafana Loki
 Chart version: 0.78.3
 Loki version: 2.9.4
***********************************************************************

Installed components:
* gateway
* ingester
* distributor
* querier
* query-frontend
* compactor
* memcached-chunks
* memcached-frontend
* memcached-index-queries
* memcached-index-writes
> loki-distributed % k get pod | grep loki
loki-loki-distributed-compactor-0                       0/1     CrashLoopBackOff   7 (55s ago)   12m
loki-loki-distributed-distributor-6fb8d74fbb-p7v8s      1/1     Running            0             12m
loki-loki-distributed-gateway-87cd896d5-jzdtz           1/1     Running            0             12m
loki-loki-distributed-ingester-0                        1/1     Running            0             12m
loki-loki-distributed-memcached-chunks-0                2/2     Running            0             12m
loki-loki-distributed-memcached-frontend-0              2/2     Running            0             12m
loki-loki-distributed-memcached-index-queries-0         2/2     Running            0             12m
loki-loki-distributed-memcached-index-writes-0          2/2     Running            0             12m
loki-loki-distributed-querier-0                         1/1     Running            0             12m
loki-loki-distributed-query-frontend-67c5c65f5b-z7bcv   1/1     Running            0             12m

이후 그라파나 대시보드를 설치하여 로키를 확인한다

> helm fetch grafana/grafana
> tar xvf grafana-7.3.3.tgz 
> cd grafana
> helm install grafana .
NAME: grafana
LAST DEPLOYED: Fri Mar  1 15:38:39 2024
NAMESPACE: ada-grafana
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:

   kubectl get secret --namespace ada-grafana grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo


2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:

   grafana.ada-grafana.svc.cluster.local

   Get the Grafana URL to visit by running these commands in the same shell:
     export POD_NAME=$(kubectl get pods --namespace ada-grafana -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}")
     kubectl --namespace ada-grafana port-forward $POD_NAME 3000

3. Login with the password from step 1 and the username: admin
#################################################################################
######   WARNING: Persistence is disabled!!! You will lose your data when   #####
######            the Grafana pod is terminated.                            #####
#################################################################################

> kubectl get secret --namespace ada-grafana grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
# 어드민 비밀번호 출력

이후 그라파나에 접속하여 아이디 & 비밀번호에 admin, 위에서 출력된 비밀번호 입력 후 데이터 소스에 로키를 추가한다.

curl -v -H "Content-Type: application/json" -XPOST -s "http://10.77.110.164:32498/loki/api/v1/push" --data-raw \ 
  '{"streams": [{ "stream": { "foo": "bar2" }, "values": [ [ "1709276745000000000", "ada" ] ] }]}'
*   Trying 10.77.110.164:32498...
* Connected to 10.77.110.164 (10.77.110.164) port 32498 (#0)
> POST /loki/api/v1/push HTTP/1.1
> Host: 10.77.110.164:32498
> User-Agent: curl/7.79.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 94
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 204 No Content
< Server: nginx/1.20.2
< Date: Fri, 01 Mar 2024 07:08:01 GMT
< Connection: keep-alive
< 
* Connection #0 to host 10.77.110.164 left intact

4.3 미미르 메트릭 관리

장기 저장소 솔루션으로 타노스가 있지만, 타노스는 튜닝이 어렵고 다른 관측 가능 가능성과 연계 설정하는 것이 어렵다. 타노스를 대체할 수 있는 그라파나 미미르를 살펴본다.

4.3.1 미미르 기능

미미르 컴포넌트

미미르도 로키와 마찬가지로 병렬 실행과 수평 확장 가능한 마이크로서비스가 다수 존재한다. 프로메테우스 및 오픈 메트릭 등을 지원하며 이들로부터 대용량의 시계열 데이터를 저장한다.

쓰기 경로

  • 프로메테우스
    타깃으로부터 샘플을 스크랩하고 프로메테우스의 원격쓰기 API 를 사용하여 미미르에 push 한다. API 의 HTTP 요청에는 테넌트 ID 를 지정하는 헤더와 스내피 압축 프로토콜 버퍼 메시지를 본문에 담겨 있다.

  • 디스트리뷰터
    프로메테우스로부터 받은 샘플을 처리한다.

  • 인제스터
    디스트리뷰터를 통해 인제스터로 들어온 샘플은 메모리에 보관되고, WAL 에 기록된다. WAL 은 해당 샘플이 새 TSDB 블록으로 생성될 때 없어지고 기본적으로 이는 두 시간마다 실행된다. 새로 생성된 블록은 장기 저장소에 업로드되고 -blocks-storage.tsdb.retention-repiod 가 만료될때까지 인제스터에 보관된다. 만약 인제스터가 갑자기 종료된 경우 WAL 을 영구 디스크에 저장한다(AWS EBS 등). 그리고 k8s에서 미미르 클러스터를 실행하는 경우 PVC 와 함께 스테이트풀셋을 사용할 수도 있다.

  • 콤팩터
    여러 개로 복제된 인제스터로부터 쓰여진 블록을 단일 블록으로 병합하고 중복 샘플을 제거 및 압축하여 스토리지 절약하고 효율적인 읽기/쓰기를 가능하게 한다.

읽기 경로

  • 쿼리 프런트엔드
    미미르로 들어오는 쿼리는 쿼리 프런트엔드에 입수되고 여러 개의 작은 쿼리로 분할한다. 그런 다음 결과 캐시를 확인하여 캐시된 경우는 캐시된 결과를 반환하고, 캐시에서 응답할 수 없는 쿼리는 프런트엔드 내 메모리 큐에 삽입한다.

  • 쿼리어

  • 쿼리 프런트엔드 내 메모리 큐에서 쿼리를 가져와 작업한다. 스토어 게이트웨이와 인제스터에 연결하여 쿼리를 실행하는 데 필요한 모든 데이터를 가져온다. 이후 쿼리 결과를 프런트엔드에 반환한다

스토리지 방식

미미르는 각 테넌트의 시계열 데이터를 자체 TSDB 에 저장함으로써 블록의 시리즈를 유지한다. 기본적으로 블록의 범위는 두 시간이며, 각 블록 디렉토리는 인덱스 파일, 메타데이터, 청크가 존재한다.
미미르에서 사용하는 디스크로는 AWS, GCP 등의 스토리지, 로컬 파일시스템 단일 노드 등이 있다.

해시 링


인제스터가 각각 2, 5, 8 토큰에 순서대로 등록되면 시계방향을 다른 토큰들을 담당하게 된다. 예를 들어, 토큰 1에 스트림이 들어오면 인제스터1(토큰2)가 담당하고 스트림 쓰기 작업을 진행한다. 그리고 안정적으로 데이터를 전달하기 위해 복제본을 만드는데 이때도 마찬가지로 시계방향으로 가까운 인제스터를 찾아 복제를 한다.

토큰 3에 작업요청이 들어오면 인제스터2가 처리하고 복제본으로 인제스터 3에게도 전달하여 쓰기 요청을 한다

해시 링을 사용하는 미미르 컴포넌트는 다음과 같다

  • 인제스터: 샤딩과 시리즈를 복제한다
  • 디스트리뷰터: 비율 제한을 시행한다
  • 콤팩터: 압축 워크로드를 샤딩한다
  • 스토어 게이트웨이: 장기 저장소에서 쿼리하기 위해 블록을 샤딩한다
  • 룰러: 규칙 그룹을 평가하기 위해 샤딩한다

해시링 데이터 구조는 그라파나 미미르 인스턴스 간에 키-값 저장소를 사용하여 공유되며 이를 통해 구축한 기능은 다음과 같다

  • 서비스 디스커버리: 인스턴스는 링에 등록된 인스턴스를 조회하여 서로를 검색할 수 있다
  • 하트비팅: 인스턴스는 주기적으로 링에 하트비트를 전송하여 health-check 를 한다
  • 영역 인식 복제: 장애 도메인 간의 데이터 복제로, 도메인 중단 동안 데이터 손실을 방지한다
  • 셔플 샤팅: 서로 다른 테넌트의 워크로드를 격리하고 공유 클러스터에서 실행되는 경우에도 각 테넌트에 단일 테넌트 환경을 제공하기 위해 멀티 테넌트 클러스터에서 셔플 샤딩을 선택적으로 지원하여 중단의 폭발 반경을 줄이고 테넌트를 격리한다

4.3.2 미미르 구성

노드 익스포터 -> 프로메테우스 -> 미미르 순으로 메트릭이 전달된다.
1. 노드 익스포터에서 메트릭을 수집하여 프로메테우스에 보내고
2. 다수의 프로메테우스로부터 메트릭을 수집하여 미미르에 전달된다.

4.3.3 미미르 쿠버네티스 구성

로키와 같이 grafana 레포로부터 헬름차트를 인스톨한다.
ref: https://grafana.com/docs/mimir/latest/set-up/helm-chart

> helm fetch grafana/mimir-distributed
> tar xvf mimir-distributed-5.2.1.tgz 
> helm install mimir .
W0301 17:04:40.291057   12419 warnings.go:70] metadata.name: this is used in Pod names and hostnames, which can result in surprising behavior; a DNS label is recommended: [must not contain dots]
NAME: mimir
LAST DEPLOYED: Fri Mar  1 17:04:32 2024
NAMESPACE: ada-grafana
STATUS: deployed
REVISION: 1
NOTES:
Welcome to Grafana Mimir!
Remote write endpoints for Prometheus or Grafana Agent:
Ingress is not enabled, see the nginx.ingress values.
From inside the cluster:
  http://mimir-nginx.ada-grafana.svc:80/api/v1/push

Read address, Grafana data source (Prometheus) URL:
Ingress is not enabled, see the nginx.ingress values.
From inside the cluster:
  http://mimir-nginx.ada-grafana.svc:80/prometheus

**IMPORTANT**: Always consult CHANGELOG.md file at https://github.com/grafana/mimir/blob/main/operations/helm/charts/mimir-distributed/CHANGELOG.md and the deprecation list there to learn about breaking changes that require action during upgrade.

k get svc
NAME                             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                         AGE
mimir-alertmanager               ClusterIP   172.20.187.32    <none>        8080/TCP,9095/TCP               2m1s
mimir-alertmanager-headless      ClusterIP   None             <none>        8080/TCP,9095/TCP,9094/TCP      2m1s
mimir-compactor                  ClusterIP   172.20.138.0     <none>        8080/TCP,9095/TCP               2m1s
mimir-distributor                ClusterIP   172.20.180.42    <none>        8080/TCP,9095/TCP               2m1s
mimir-distributor-headless       ClusterIP   None             <none>        8080/TCP,9095/TCP               2m1s
mimir-gossip-ring                ClusterIP   None             <none>        7946/TCP                        2m1s
mimir-ingester-headless          ClusterIP   None             <none>        8080/TCP,9095/TCP               2m1s
mimir-ingester-zone-a            ClusterIP   172.20.231.78    <none>        8080/TCP,9095/TCP               2m1s
mimir-ingester-zone-b            ClusterIP   172.20.55.232    <none>        8080/TCP,9095/TCP               2m1s
mimir-ingester-zone-c            ClusterIP   172.20.227.182   <none>        8080/TCP,9095/TCP               2m1s
mimir-minio                      ClusterIP   172.20.81.199    <none>        9000/TCP                        2m1s
mimir-minio-console              ClusterIP   172.20.215.234   <none>        9001/TCP                        2m1s
mimir-nginx                      ClusterIP   172.20.201.56    <none>        80/TCP                          2m1s
mimir-overrides-exporter         ClusterIP   172.20.33.125    <none>        8080/TCP,9095/TCP               2m1s
mimir-querier                    ClusterIP   172.20.144.166   <none>        8080/TCP,9095/TCP               2m1s
mimir-query-frontend             ClusterIP   172.20.171.98    <none>        8080/TCP,9095/TCP               2m1s
mimir-query-scheduler            ClusterIP   172.20.115.148   <none>        8080/TCP,9095/TCP               2m1s
mimir-query-scheduler-headless   ClusterIP   None             <none>        8080/TCP,9095/TCP               2m1s
mimir-ruler                      ClusterIP   172.20.186.105   <none>        8080/TCP                        2m1s
mimir-store-gateway-headless     ClusterIP   None             <none>        8080/TCP,9095/TCP               2m1s
mimir-store-gateway-zone-a       ClusterIP   172.20.104.77    <none>        8080/TCP,9095/TCP               2m1s
mimir-store-gateway-zone-b       ClusterIP   172.20.43.107    <none>        8080/TCP,9095/TCP               2m1s
mimir-store-gateway-zone-c       ClusterIP   172.20.98.205    <none>        8080/TCP,9095/TCP               2m1s

MINIO 화면

미미르에서 미니오 객체스토리지가 초기화 된 것을 확인할 수 있다

그라파나 화면

ref: https://grafana.com/docs/mimir/latest/visualize/
이후 설치한 그라파나 대시보드에 데이터 소스 연결한다.
ref: https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/set-up/additionaldatasources/
1. 데이터 소스 추가에 프로메테우스 클릭

2. performance 에 mimir 선택하고 connection 에 mimir-nginx 엔드포인트 url을 넣는다(path 는 /prometheus 이다)

3. explore 에서 미미르를 통해 프로메테우스 지표를 갖고오는 걸 확인한다


정리하면 프로메테우스는 미미르에 지표를 보내고 미미르에서는 Minio 를 통해 객체스토리지로 데이터를 보내서 장기간동안 보관한다. 그리고 그라파나 대시보드에서 Mimir 에 접근하여 프로메테우스의 지표를 읽어와 시각화한다. 이렇게 함으로써 프로메테우스에서 장기간동안 대용량 데이터를 보관못하는 단점을 보완한다.

4.4 템포 추적관리

4.4.1 템포 아키텍쳐


ref: https://grafana.com/docs/tempo/latest/operations/architecture/

템포 또한 앞서 봤던 로키, 미미르와 마찬가지로 아키텍쳐 구조가 읽기와 쓰기가 나눠지고 내부 컴포넌트와 동작방식(해시 링 등)이 동일하다. 앞서 구체적으로 설명하였으니 아키텍쳐에 대한 내용은 스킵하고 바로 실습으로 넘어간다.

4.4.2 템포 바이너리 실행

쿠버네티스 환경에서 실습하기에 해당 스텝은 넘어간다

4.4.3 템포 쿠버네티스 구성

> helm fetch grafana/tempo-distributed
> tar xvf tempo-distributed-1.8.5.tgz 
> cd tempo-distributed
> helm install tempo .
NAME: tempo
LAST DEPLOYED: Sat Mar  9 16:46:37 2024
NAMESPACE: ada-grafana
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
***********************************************************************
 Welcome to Grafana Tempo
 Chart version: 1.8.5
 Tempo version: 2.3.1
***********************************************************************

Installed components:
* ingester
* distributor
* querier
* query-frontend
* compactor
* memcached
> k get pod
tempo-compactor-557d799589-r6wnl                1/1     Running     0          41s
tempo-distributor-566657f5b9-c75hk              1/1     Running     0          41s
tempo-ingester-0                                1/1     Running     0          41s
tempo-ingester-1                                1/1     Running     0          41s
tempo-ingester-2                                1/1     Running     0          41s
tempo-memcached-0                               1/1     Running     0          41s
tempo-querier-c854c9557-67qvf                   1/1     Running     0          41s
tempo-query-frontend-68c67ff65c-hjs4p           1/1     Running     0          41s

앞서 로키, 미미르와 마찬가지로 그라파나 대시보드에 데이터소스를 추가하여 템포를 시각화한다.

테스트

더미 데이터: https://grafana.com/docs/grafana/latest/datasources/tempo/json-trace-file/

위 링크에 있는 json 을 tempo 에 import trace 하면 다음과 같이 나온다.

추적은 시스템 전체적으로 실행의 흐름을 집계하고 세부 트랜잭션을 분석하는 데 사용된다. 스팬 사이의 인과관계를 통해 추적으로부터 요청의 세부적인 내용을 파악해야한다.

0개의 댓글