본문 바로가기

Log,Monitorings

[Observability] fluentd 보다 더 좋은(?) Vector에 대해 알아보자.

반응형

개요

Toss slash 2023을 보다가 Logstash에서 Vector로 전환 후 상당히 만족한다는..

 

 

Star 수도 Fluentd를 넘었다고 한다.

 

작년에 Datadog에서 인수했다는 소식만 들었고 어떤 놈인지에 대해서는 관심이 없었는데, 마침 리서치를 해보자고 마음먹게 되었다.

 

허나, vector를 검색하니 기하와 벡터에서 배운 그 수학 용어만 나오던데.. 잘못 검색한 것인지 아직 한글로 정리된 글들이 없는 것인지..

 

그래서 아래 Docs를 보면서 내용들을 정리하였다. 상당히 깔끔히 정리되어있었다.


 

Vector란 ?

Observability data를 관리하는 도구이다.

: Logs, Metrics 등을 하나의 툴에서 수집하고, 변환하고, 라우팅하는 Observability pipeline이다.

 

특징

Rust로 만들어졌기 때문에 매우 빠르고 메모리 효율적이다.
-> Logstash는 JVM 기반이기 때문에 상당히 Memory를 많이 먹는데 반해 Rust 기반이기 때문에 덜 먹나 보다.

 

logs, metrics를 수집하고 처리하는 것을 지원한다.
-> 아직 Tracing은 지원하지 않는다고 한다. (아마 이후에 지원하지 않을까 생각된다.)

 

데몬, 사이드카, Aggregator 등으로 배포할 수 있다.
-> Helm chart를 봤을 땐 StatefulSet , Deployment , DaemonSet 3가지 중 하나의 타입을 선택할 수 있다.

# Agent = DaemonSet
# Aggregator = StatefulSet
# Stateless-Aggregator = Deployment

 

Vendor 중립적이다.

 

설정 파일을 관리하는데 있어 TOML, YAML, JSON 모두 사용이 가능하다.

 

어디에서든지 설치가 가능하다. 예를 들어, Single binary 라던가 X86_64, ARM64/v7 모두 호환이 된다.

-> fluentBit이나 fluentd에서 arm64를 지원하지 않아 docker buildx로 재빌드하는 경우가 있었는데 Vector는 모두 지원하나보다.

 

설정 예시

Case 1) Datadog log를 Datadog으로 전송

# TOML
[sources.datadog_agent]
type = "datadog_agent"
address = "0.0.0.0:80"

[transforms.remove_sensitive_user_info]
type = "remap"
inputs = ["datadog_agent"]
source = '''
  redact(., filters: ["us_social_security_number"])
'''

[sinks.datadog_backend]
type = "datadog_logs"
inputs = ["remove_sensitive_user_info"]
default_api_key = "${DATADOG_API_KEY}"

 

 

Case 2) Kafka topic으로부터 consuming하고, ES로 전송

# TOML
[sources.kafka_in]
type = "kafka"
bootstrap_servers = "10.14.22.123:9092,10.14.23.332:9092"
group_id = "vector-logs"
key_field = "message"
topics = ["logs-*"]

[transforms.json_parse]
type = "remap"
inputs = ["kafka_in"]
source = '''
  parsed, err = parse_json(.message)
  if err != null {
	log(err, level: "error")
  }
  . |= object(parsed) ?? {}
'''

[sinks.elasticsearch_out]
type = "elasticsearch"
inputs = ["json_parse"]
endpoint = "<http://10.24.32.122:9000>"
index = "logs-via-kafka"

 

 

Case 3) k8s logs to aws s3

[sources.k8s_in]
type = "kubernetes_logs"

[sinks.aws_s3_out]
type = "aws_s3"
inputs = ["k8s_in"]
bucket = "k8s-logs"
region = "us-east-1"
compression = "gzip"
encoding.codec = "json"

 

 

 


개념 (Concepts)

Events

Logs : 로그 이벤트는 일반적인 key/value 이벤트이다.

Metrics : 시계열에서 수행되는 수치 연산을 나타내는 이벤트이다. Vector의 metric 이벤트는 fully interoperable(상호 운용 가능)이다.

Components

Sources

Vector가 어디서 data를 Pull 하는지, 어떻게 이 Data를 받을지를 정의하는 컴포넌트이다.

 

Transforms

Vector에 의해 전송되는 이벤트를 변형시키는 역할을 수행한다.

Parsing , Filteriing , Sampling , Aggregating(집계) 등이 그 예이다.

파이프라인에 수많은 Transforms를 가질 수 있으며 어떻게 구성할지는 사용자에게 달려 있다.

 

Sinks

Sink는 event의 목적지이다.

Pipelines

Pipeline은 Component들의 directed acyclic graph(방향성 비순환 그래프)이다.

Data는 Sources → Sinks로 one direction(단방향)으로 흘러야 한다.

Buffers

Sinks는 가능한 빨리 events들을 보내려 시도한다. 만약 계속해서 보내지 못한다면 event들은 configurable buffer(설정 가능한 버퍼)에 잠시 머무르며 보내질 때 까지 대기한다.

Vector는 in-memory buffer를 사용하나, disk-buffer 또한 사용 가능하다.

Buffer가 가득 찰 떄 어떻게 behavior(행동)할 지 정할 수 있다.

 

buffer.when_full = block (default)

backpressure가 적용된다.

 

buffer.when_full = drop_newest

buffer가 가득차면 events를 버린다.

 

Backpressure

Sink에 버퍼가 가득 차서 Backpressure를 제공하도록 구성된 경우 Backpressure는 연결된 모든 Transforms에 전파되고 Source에도 전파된다.

 

Source는 데이터를 제공하려는 시스템에 Backpressure를 전파하려고 시도한다.

 

매커니즘은 Source에 따라 다른데, HTTP Source는 429 error (Too Many Requests)로 요청을 거부할 수도 있고 , Kafka 같은 Pull 기반의 Source는 fetching 하는 속도를 낮출 수도 있다.

 

Roles

배포 역할을 정할 수 있는데, Agent 모드와 Aggregator 모드가 있다.

 

Agent : Data를 수집하기 위한 용도이다.

Aggregator : Data를 수집하고 처리하기 위한 용도이다.

 

 


 

Architecture

 

 

Data Model

Vector를 통해 흐르는 데이터의 개별 단위를 이벤트 라고 한다.

이벤트 유형으로는 Logs와 Metrics 2가지를 지원한다. (아직 Tracing은 지원하지 않는다.)

 

Log event

Log event는 특정 Schema 중립적이라 특정 Schema가 필요하지 않다.

 

Metric event

Metric event는 시계열(time series)에서 수행되는 수치 연산을 나타낸다.

 

Counter : 증가하거나 0 값으로 재설정할 수 있지만 감소시킬 수 없는 단일 값이다. (value)

Distribution : 샘플링된 값의 분포를 나타낸다. 전역 히스토그램 및 요약을 지원하는 서비스와 함께 사용된다. (samples, statistics)

Guage : 증가 및 감소할 수 있는 특정 시점 값을 나타낸다. Vector의 내부 게이지 유형은 해당 값의 변경 사항을 나타낸다. 게이지는 현재 메모리 또는 CPU 사용량과 같은 값의 변동을 추적하는 데 사용해야 한다. (value)

Histogram : 타이머라고도 한다. 히스토그램은 관찰(일반적으로 요청 기간 또는 응답 크기와 같은 것)을 샘플링하고 구성 가능한 버킷에서 계산한다. 또한 관찰된 모든 값의 합계를 제공한다. (buckets , count , sum)

 

Pipeline Model

Vector의 Pipeline model은 독립적인 하위 그래프들을 포함하는 directed acylic graph의 component에 의존한다.

 

이벤트는 소스에서 싱크로 한 방향으로 흘러야 하며 Cycle(순환)을 만들 수 없다.

그래프의 각 구성 요소는 0개 이상의 이벤트를 생성할 수 있다.

 

 

Vector는 설정 변경을 적용하기 위해 Hot reloading을 지원한다. 이는 벡터의 프로세스에 SIGHUP 프로세스 신호를 전송하여 달성된다.

 

 

Concurrency Model

Vector는 위와 같이 들어오는 데이터 볼륨에 따라 자연스럽게 확장되는 동시성 모델을 구현한다.

 

각 벡터 Source 들은 동시성 단위를 정의하고 그에 따라 구현하는 일을 담당한다.

이를 통해 벡터가 사용되는 방식에 적응하는 자연스러운 동시성 모델이 가능하므로 동시성 조정 및 구성이 필요하지 않음.

 

예를 들어 file source는 테일링하는 파일 수 전체에서 동시성을 구현하고,

socket source는 유지하고 있는 활성 열린 연결 수 전체에서 동시성을 구현한다.

 

Task transforms(작업 변환)은 현재 병렬화할 수 없으며, 처리함에 있어 병목 현상이 발생할 수 있다.

 

 

Buffering Model

Vector는 Sink가 처리할 수 있는 것 이상의 초과 이벤트를 처리할 때 성능을 우선시할지, 내구성을 우선시할지 Operator(운영자)가 선택할 수 있도록 해준다.

어떤 버퍼링 모델을 선택하느냐에 따라 달라지기 때문이다.

Vector의 토폴로지 설계 일부는 Backpressure 전파를 포함하는데 , 이는 Event가 수신되는 것 만큼 빠르게 처리할 수 없음을 의미하고, 이를 간접적으로 알린다.

Backpressure는 sink에서 transforms를 거쳐 다시 source로,
궁극적으로 HTTP를 통해 로그를 보내는 Application과 같은 Client까지 이동할 수 있다.

Backpressure는 시스템이 더 많은 작업을 처리할 수 있는지 혹은 너무 바빠서 처리할 수 없는지 노출할 수 있는 수단이다.

Kafka와 같은 소스에서 이벤트를 가져오거나 HTTP와 같은 소켓을 통해 이벤트를 수락할 때와 같이 이벤트의 소비 또는 수락 속도를 늦출 수 있도록 Backpressure에 의존할 수 있다.

그러나 어떤 경우에는 Backpressure를 즉시 Propagating(전파)하는 것을 원하지 않을 수도 있다.

이렇게 하면 Upstream의 구성 요소와 Caller(호출자)의 속도 저하가 지속적으로 발생해 잠재적으로 Vector 외부에서 문제가 발생할 수 있기 때문이다.

Components가 완전히 임계값을 넘었을 때 속도가 완전히 느려지는 것을 피하고 Sink가 데이터를 보내는 외부 서비스(ex. Loki or ES)의 일시적인 속도 저하 및 중단을 처리하는 것을 위해, Buffering을 필요하게 되는 것이다.

구성 요소 간 버퍼링 (Between components)

모든 구성 요소에는 구성 요소 사이에 작은 인메모리 버퍼가 있다.

이 버퍼의 기본 목적은 두 구성 요소가 통신하는 채널 역할을 하지만 구성 요소가 통신하는 경우에도 이벤트를 보내는 데 사용할 수 있는 소량의 공간(일반적으로 100개 이벤트)이 있는지 확인하여 조금 더 확장한다.

이를 통해 워크로드가 완전히 균일하지 않은 경우 처리량을 최대화할 수 있다.

 

Sink에서 버퍼링

Vector 설정할 때, Sink에서 Buffer 설정을 하게 될 것이다.
Sink가 Topology에서 Backpressure의 주 원인이기 때문이다.

네트워크를 통해 서비스와 통신하면서 대기 시간이 발생하거나 일시적으로 Outage가 발생할 수 있다.

Sink는 다른 모든 구성 요소와 마찬가지로 In-memory Buffer를 사용하지만 기본 버퍼 크기는 500개 이벤트로 약간 증가한다.

더 큰 기본 Buffer 용량 외에도 버퍼 구성도 완전히 제어할 수 있다.

Vector는 버퍼링 제어를 위한 두 가지 기본 설정, 즉 사용할 버퍼 유형과 버퍼가 가득 찼을 때 수행할 작업을 설정할 수 있다.

 

Buffer types

In-memory buffers

빠른게 장점이지만, Buffer 크기에 비례하여 메모리를 소비할 수 있고 내구성이 없다는 주요 단점이 있다.
메모리 내 Buffer는 보유할 수 있는 Byte의 수가 아니라 Buffering 할 수 있는 Event 수에 따라 구성된다.

ex) 최대 이벤트 수 100,000으로 구성된 Memory 내 버퍼는 Event가 작은 경우 잠재적으로 몇 메가바이트만 소비할 수 있지만 이벤트가 킬로바이트 크기 범위에 있는 경우 수백 메가바이트로 팽창할 수 있다.

즉, Vector에서 처리중인 데이터가 Upstream에서 변경되고 예기치 않게 크기가 커지면 메모리 사용 프로필이 크게 변경될 수 있다.

또한 In-memory buffer는 내구성이 없다. 

Vector는 End-to-End 확인처럼 Source - Sink 간 잘 전송되었는지 확인하는 기능을 제공하여 이벤트가 처리될 때까지 소스가 이벤트를 확인하지 않도록 하지만 Vector를 실행하는 Host가 충돌하면 In-memory buffer event가 모두 손실된다.

S3 혹은 Kafka 기반 Pull source는 단순 이벤트 처리를 다시 시도하여 상관없지만 Push 기반 Source는 메시지를 재전송하지 못할 수 있다.

 

Disk buffers

데이터의 내구성이 Vector의 전체 성능보다 더 중요한 경우 Disk buffer를 사용하여 Vector가 충돌하는 경우를 포함하여 Vector를 중지하고 시작하는 동안 Buffering 된 이벤트를 유지할 수 있다.
Disk buffer를 사용하면 Vector가 기본적으로 다시 시작할 때 중단된 위치에서 다시 시작할 수 있다.

DIsk buffer는 모든 event가 먼저 buffer를 통해 전송되고 다시 읽기 전에 데이터 파일에 미리 기록되는 미리 쓰기 로그(write-ahead log, WAL)와 같은 기능을 한다.

기본적으로 모든 쓰기에 대해 데이터를 디스크에 동기화하지 않고 대신 데이터 손실을 줄이면서 높은 처리량을 허용하는 간격(500ms)로 동기화한다.

또한 메모리 내 버퍼와 마찬가지로 디스크 버퍼에는 구성 가능한 최대 크기가 있으므로 디스크 사용량 측면에서 제한할 수 있다.

이 최대 크기는 엄격하게 준수되므로 이를 초과하지 않는 Vector에 의존할 수 있다.

그러나 모든 버퍼에 대한 최소 크기는 현재 ~256MiB이며 이는 디스크 버퍼 구현의 요구 사항임.

디스크 버퍼는 디스크에 기록되는 모든 이벤트를 자동으로 체크섬하고 읽기 중에 손상이 감지되면 올바르게 디코딩할 수 있는 만큼의 이벤트를 자동으로 복구하며, 그러한 손상이 감지될 때 가능한 한 손실된 이벤트 수에 대한 정확한 보기를 제공하기 위해 메트릭을 내보낸다.

 

‘When full’ behavior

- Blocking : 차단하도록 구성된 경우 벡터는 현재 가득 찬 버퍼에 쓰기 위해 무기한 대기하며, 기본값임
- Drop the event (drop_newest) : Vector는 버퍼가 현재 가득 차 있는 경우 이벤트를 삭제한다.
- Overflow to another buffer (overflow) - Not recommended

Buffering 추천 설정

성능이 중요할 경우
→ In-memory buffer를 사용해야 한다..
drop_newest 모드는 가능한 가장 높은 성능을 보여주지만 더 많은 event들이 drop 될 수 있다.

내구성이 중요할 경우
→ Disk buffer를 사용해야 한다.
Source에 따라 Default 모드인 차단 동작을 유지하는 것이 좋을 수도 있고, Buffer가 가득 찼을 때 events를 drop 시킬 수도 있을 것이다.

End to end Acknowledgements

클라이언트가 데이터가 대상 싱크로 전달되었는지 확인할 수 있는 기능이다.

Runtime Model

Vector의 런타임은 벡터의 DAG 토폴로지 모델의 노드가 Channel을 통해 통신하는 비동기 작업에 대략적으로 매핑되는 Future-based 비동기 런타임이다.

 

 

지금까지 Vector에 대해 간단하게 정리해보았다.

 

만약 Vector를 계속 정리하게 된다면 .. 이후에는 아래의 주제로 글들을 더 작성하지 않을까 싶다.

 

- Vector를 Local에서 테스트하는 방법

- Vector를 Helm을 이용해 Kubernetes에 구성하는 방법

- Vector의 Source, Transforms, Sink를 활용해 Kubernetes log를 전송하는 방법

- 다양한 Source, Transforms에 대해서 Deepdive

- 여러가지 로그 부하테스트

 

등을 진행해볼까 고민을 하면서 글을 마무리.

반응형