Loki 아키텍처
이 문서는 Grafana Loki 공식 문서의 Loki architecture 페이지 (opens in a new tab)를 번역한 것입니다.
Grafana Loki는 마이크로서비스 기반 아키텍처를 가지고 있으며 수평적으로 확장 가능한 분산 시스템으로 실행되도록 설계되었습니다. 시스템에는 별도로 병렬로 실행할 수 있는 여러 구성 요소가 있습니다. Grafana Loki 디자인은 모든 구성 요소의 코드를 단일 바이너리 또는 Docker 이미지로 컴파일합니다. -target
명령줄 플래그는 해당 바이너리가 어떤 구성 요소로 작동할지를 제어합니다.
쉽게 시작하려면 모든 구성 요소가 하나의 프로세스에서 동시에 실행되는 "단일 바이너리" 모드 또는 구성 요소를 읽기, 쓰기 및 백엔드 부분으로 그룹화하는 "단순 확장 가능 배포" 모드로 Grafana Loki를 실행하세요.
Grafana Loki는 요구 사항이 변경됨에 따라 구성 변경 없이 또는 최소한의 구성 변경으로 다른 모드에서 클러스터를 쉽게 재배포할 수 있도록 설계되었습니다.
스토리지
Loki는 Amazon Simple Storage Service (S3), Google Cloud Storage (GCS), Azure Blob Storage 등과 같은 단일 객체 스토리지 백엔드에 모든 데이터를 저장합니다. 이 모드는 인덱스 시퍼(index shipper) (또는 줄여서 시퍼(shipper))라는 어댑터를 사용하여 객체 스토리지에 청크 파일을 저장하는 것과 동일한 방식으로 인덱스(TSDB 또는 BoltDB) 파일을 저장합니다. 이 작동 모드는 Loki 2.0에서 정식으로 사용 가능하게 되었으며 빠르고 비용 효율적이며 간단합니다. 현재 및 미래의 모든 개발이 여기에 있습니다.
2.0 이전에는 Loki가 인덱스와 청크에 대해 다른 스토리지 백엔드를 가졌습니다. 자세한 내용은 레거시 스토리지를 참조하세요.
데이터 포맷
Grafana Loki에는 **인덱스(index)**와 **청크(chunks)**라는 두 가지 주요 파일 유형이 있습니다.
위 다이어그램은 청크에 저장된 데이터와 인덱스에 저장된 데이터의 개요를 보여줍니다.
인덱스 포맷
현재 인덱스 시퍼와 함께 단일 저장소로 지원되는 두 가지 인덱스 형식이 있습니다.
-
TSDB (권장) Time Series Database(또는 줄여서 TSDB)는 Prometheus (opens in a new tab)의 유지 관리자가 시계열(메트릭) 데이터를 위해 원래 개발한 인덱스 형식 (opens in a new tab)입니다. 확장 가능하며 더 이상 사용되지 않는 BoltDB 인덱스에 비해 많은 장점이 있습니다. Loki의 새로운 스토리지 기능은 TSDB를 사용할 때만 사용할 수 있습니다.
-
BoltDB (더 이상 사용되지 않음) Bolt (opens in a new tab)는 Go로 작성된 저수준 트랜잭션 키-값 저장소입니다.
청크 포맷
청크는 특정 시간 범위의 스트림(고유한 레이블 세트)에 대한 로그 라인의 컨테이너입니다.
다음 ASCII 다이어그램은 청크 형식을 자세히 설명합니다.
----------------------------------------------------------------------------
| | | | |
| MagicNumber(4b) | version(1b) | encoding(1b) | |
| | | | |
----------------------------------------------------------------------------
| #structuredMetadata (uvarint) |
----------------------------------------------------------------------------
| len(label-1) (uvarint) | label-1 (bytes) |
----------------------------------------------------------------------------
| len(label-2) (uvarint) | label-2 (bytes) |
----------------------------------------------------------------------------
| len(label-n) (uvarint) | label-n (bytes) |
----------------------------------------------------------------------------
| checksum(from #structuredMetadata) |
----------------------------------------------------------------------------
| block-1 bytes | checksum (4b) |
----------------------------------------------------------------------------
| block-2 bytes | checksum (4b) |
----------------------------------------------------------------------------
| block-n bytes | checksum (4b) |
----------------------------------------------------------------------------
| #blocks (uvarint) |
----------------------------------------------------------------------------
| #entries(uvarint) | mint, maxt (varint) | offset, len (uvarint) |
----------------------------------------------------------------------------
| #entries(uvarint) | mint, maxt (varint) | offset, len (uvarint) |
----------------------------------------------------------------------------
| #entries(uvarint) | mint, maxt (varint) | offset, len (uvarint) |
----------------------------------------------------------------------------
| #entries(uvarint) | mint, maxt (varint) | offset, len (uvarint) |
----------------------------------------------------------------------------
| checksum(from #blocks) |
----------------------------------------------------------------------------
| #structuredMetadata len (uvarint) | #structuredMetadata offset (uvarint) |
----------------------------------------------------------------------------
| #blocks len (uvarint) | #blocks offset (uvarint) |
----------------------------------------------------------------------------
mint
와 maxt
는 각각 최소 및 최대 유닉스 나노초 타임스탬프를 설명합니다.
structuredMetadata
섹션은 반복되지 않는 문자열을 저장합니다. 구조화된 메타데이터의 레이블 이름과 레이블 값을 저장하는 데 사용됩니다. structuredMetadata
섹션 내의 레이블 문자열과 길이는 압축되어 저장됩니다.
블록 포맷
블록은 각각 개별 로그 라인인 일련의 항목으로 구성됩니다. 블록의 바이트는 압축되어 저장됩니다. 다음은 압축을 풀었을 때의 형태입니다.
-----------------------------------------------------------------------------------------------------------------------------------------------
| ts (varint) | len (uvarint) | log-1 bytes | len(from #symbols) | #symbols (uvarint) | symbol-1 (uvarint) | symbol-n*2 (uvarint) |
-----------------------------------------------------------------------------------------------------------------------------------------------
| ts (varint) | len (uvarint) | log-2 bytes | len(from #symbols) | #symbols (uvarint) | symbol-1 (uvarint) | symbol-n*2 (uvarint) |
-----------------------------------------------------------------------------------------------------------------------------------------------
| ts (varint) | len (uvarint) | log-3 bytes | len(from #symbols) | #symbols (uvarint) | symbol-1 (uvarint) | symbol-n*2 (uvarint) |
-----------------------------------------------------------------------------------------------------------------------------------------------
| ts (varint) | len (uvarint) | log-n bytes | len(from #symbols) | #symbols (uvarint) | symbol-1 (uvarint) | symbol-n*2 (uvarint) |
-----------------------------------------------------------------------------------------------------------------------------------------------
ts
는 로그의 유닉스 나노초 타임스탬프이고 len
은 로그 항목의 바이트 길이입니다.
심볼은 청크의 structuredMetadata
섹션에 있는 레이블 이름과 값을 포함하는 실제 문자열에 대한 참조를 저장합니다.
쓰기 경로
개략적으로 Loki의 쓰기 경로는 다음과 같이 작동합니다.
- Distributor는 스트림 및 로그 라인이 포함된 HTTP POST 요청을 받습니다.
- Distributor는 요청에 포함된 각 스트림을 해시하여 일관된 해시 링의 정보를 기반으로 전송해야 하는 인게스터 인스턴스를 결정합니다.
- Distributor는 각 스트림을 적절한 인게스터 및 해당 복제본(구성된 복제 인수에 따라)으로 보냅니다.
- 인게스터는 로그 라인이 있는 스트림을 수신하고 스트림의 데이터에 대한 청크를 생성하거나 기존 청크에 추가합니다. 청크는 테넌트 및 레이블 세트당 고유합니다.
- 인게스터는 쓰기를 승인합니다.
- Distributor는 인게스터의 과반수(쿼럼)가 쓰기를 승인할 때까지 기다립니다.
- Distributor는 최소한 쿼럼의 승인된 쓰기를 받은 경우 성공(2xx 상태 코드)으로 응답하거나 쓰기 작업이 실패한 경우 오류(4xx 또는 5xx 상태 코드)로 응답합니다.
쓰기 경로에 관련된 구성 요소에 대한 자세한 설명은 구성 요소를 참조하세요.
읽기 경로
개략적으로 Loki의 읽기 경로는 다음과 같이 작동합니다.
- Query Frontend는 LogQL 쿼리가 포함된 HTTP GET 요청을 받습니다.
- Query Frontend는 쿼리를 하위 쿼리로 분할하여 쿼리 스케줄러에 전달합니다.
- Querier는 스케줄러에서 하위 쿼리를 가져옵니다.
- Querier는 메모리 내 데이터에 대한 쿼리를 모든 인게스터에 전달합니다.
- 인게스터는 쿼리와 일치하는 메모리 내 데이터를 반환합니다(있는 경우).
- Querier는 인게스터가 데이터를 반환하지 않거나 불충분한 데이터를 반환한 경우 백업 저장소에서 데이터를 지연 로드하고 이에 대해 쿼리를 실행합니다.
- Querier는 수신된 모든 데이터를 반복하고 중복을 제거하여 하위 쿼리의 결과를 쿼리 프런트엔드에 반환합니다.
- Query Frontend는 쿼리의 모든 하위 쿼리가 완료되고 쿼리어에서 반환될 때까지 기다립니다.
- Query Frontend는 개별 결과를 최종 결과로 병합하여 클라이언트에 반환합니다.
읽기 경로에 관련된 구성 요소에 대한 자세한 설명은 구성 요소를 참조하세요.
멀티테넌시
메모리와 장기 저장소의 모든 데이터는 Grafana Loki가 다중 테넌트 모드에서 실행될 때 요청의 X-Scope-OrgID
HTTP 헤더에서 가져온 테넌트 ID로 분할될 수 있습니다. Loki가 다중 테넌트 모드가 아닌 경우 헤더는 무시되고 테넌트 ID는 fake
로 설정되어 인덱스와 저장된 청크에 나타납니다.