Grafana Loki란?
Grafana Loki는 Prometheus에서 영감을 받은 로그 집계 시스템으로, 로깅 및 이벤트 데이터를 수집, 저장 및 검색하기 위한 오픈 소스 플랫폼이다. 비용 효율적으로 운영하기 쉽게 설계되었으며 Grafana Labs에서 Loki 프로젝트 개발을 주도하고 있다.
Loki는 로그 전체 TEXT가 아닌 metadata만 인덱싱하는 방식을 취한다.
이런 최소 인덱싱 접근 방식은 다른 솔루션보다 적은 저장 공간이 필요함을 의미한다.
Grafana Loki는 아래와 같이 작동한다.
Loki를 위해 만들어진 로그 수집 도구인 Promtail을 통해 로그를 가져와 로그를 저장한다. 이후 Grafana에서 LogQL이라는 쿼리 언어를 통해 로그를 검색하게 된다. 또한 경고 규칙을 설정하여 Prometheus Alertmanager로 경고를 보낼 수 있다.
아래는 Garafana Loki의 주요 구성 요소이다.
다음은 구성 요소에 대한 설명이다.
Distributor
- 클라이언트로부터 수신한 로그 데이터를 검증 후 Ingester에게 전달하는 역할을 담당한다
Ingester
- Distributor로부터 받은 로그 데이터를 메모리에 압축하여 chunks 단위로 저장하고 일정 시간 후 장기 저장소 백엔드(DynamoDB, S3, Cassandra 등)에 기록하는 역할을 담당한다.
Querier
- Ingester의 in-memory 데이터를 쿼리 후 장기 저장소에서 쿼리 로그를 가져와 Query-Frontend에게 데이터를 반환한다. Ingester에서 복제 데이터를 가져올 수 있기 때문에 내부적으로 중복을 제거한다.
Query-Frontend
- 실제 쿼리 실행에 필요한 Querier의 역할을 보조하며 읽기 경로를 가속화한다. Query frontend는 내부적으로 쿼리를 조정하고 큐에 보관한다.
Compactor
- chunk 보관주기를 관리하고(retention), 테이블을 단일 인덱스 파일로 압축한다.
- Compactor를 통한 보존은 boltdb-shipper 또는 tsdb store에서만 지원된다.
- Loki 2.8부터는 tsdb store사용이 권장이다. 이전에 사용하던 boltdb-shipper보다 효율적이고 빠르며 확장성이 뛰어나다. (참조 : https://grafana.com/docs/loki/latest/storage/)
Ruler
- 사용자가 정의한 경고 규칙 기반으로 경고를 발생시키는 등 로그 데이터에 대한 경고를 관리한다.
아래는 데이터를 쓰고 읽을 때 흐름에 대한 내용이다.
Read
- Read 요청 시 querier에서 해당 요청을 수신한다.
- querier는 ingester의 in-memory를 조회한다.
- ingester에 캐시 된 데이터가 있는 경우 querier에게 반환하고, 데이터가 없는 경우 백업 저장소(S3)에서 데이터를 조회한다.
- querier는 수신된 데이터가 중복 됐는지 확인 후 중복제거 진행하여 log를 제공한다.
Write
- distributor가 데이터를 수신한다.
- 수신된 데이터는 해시된다.
- distributor는 해시된 데이터를 ingester에게 전달한다.
- ingester는 데이터에 대해 chunk를 생성하고 저장한다.
- distributor는 데이터를 저장 완료 여부를 성공 코드로 응답한다.
WAL(Write Ahead Log)
- Loki에서는 WAL(Write Ahead Log)이라는 걸 사용하는데, 아래와 같은 예기치 않은 장애 상황을 방지해 준다.
- 데이터(chunk)가 Ingesters로 들어오면, 먼저 이 데이터를 메모리 적재 및 WAL에 기록한다. WAL은 로컬 파일 시스템에 저장된다.
- 예기치 않은 장애로 프로세스가 갑자기 중단되거나 다운되면, 메모리에 있는 데이터가 손실된다.
- 장애가 복구되어 프로세스가 다시 시작되면 WAL에 저장된 데이터를 읽어와 메모리에 적재하여 장애 전의 상태로 복구된다.
본 글에서는 Promtail + Grafana Loki + Grafana 구성을 하여 로그 모니터링 환경을 구성 후 간단하게 로그를 조회하는 실습을 다룬다.
구성은 아래와 같다.
각 도구들의 역할은 다음과 같다.
- Promtail
- Pod의 로그가 저장되어 있는 각 노드 로그파일의 로그 내용을 수집하여 로그 집계 도구인 Grafana loki로 보낸다
- Grafana loki
- Promtail을 통해 수집된 로그 데이터(index, chunk)를 S3에 보관한다.
- Grafana
- Grafana loki에 저장되어 있는 로그 데이터를 data source로 등록하여 LogQL 쿼리 언어로 조회한다.
실습
전제 조건
- [Grafana 구성]
- AWS EKS 클러스터
- Helm CLI
- AWS CLI
- AWS S3 버킷
구성 환경
- AWS EKS : v1.24.17
- Helm : v3.8.2
- aws-cli : 2.13.25
설치 버전
- Promtail : 2.9.1
- Grafana Loki : 2.9.1
- Grafana : 10.1.4
1. Grafana loki 구성
Grafana loki가 AWS S3 버킷의 권한을 얻기 위해 IRSA를 설정한다.
IAM policy 생성한다.
cat >grafana-loki-s3-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LokiStorage",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::{bucket_name}",
"arn:aws:s3:::{bucket_name}/*"
]
}
]
}
EOF
$ aws iam create-policy --policy-name AWSLokiS3 --policy-document file://grafana-loki-s3-policy.json
IAM Role을 생성한다.
cat >trust-rs.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::{account}:oidc-provider/oidc.eks.{region}.amazonaws.com/id/{oidc}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.{region}.amazonaws.com/id/{oidc}:sub": "system:serviceaccount:{namespace}:{loki}",
"oidc.eks.{region}.amazonaws.com/id/{oidc}:aud": "sts.amazonaws.com"
}
}
},
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::{account}:oidc-provider/oidc.eks.{region}.amazonaws.com/id/{oidc}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.{region}.amazonaws.com/id/{oidc}:sub": "system:serviceaccount:{namespace}:{loki-compactor}",
"oidc.eks.{region}.amazonaws.com/id/{oidc}:aud": "sts.amazonaws.com"
}
}
}
]
}
EOF
$ aws iam create-role --role-name AWS-Loki-Role --assume-role-policy-document file://trust-rs.json --description "AWS-Loki-Role"
위에서 만든 IAM Policy를 IAM Role에 추가한다.
Loki Helm Chart를 등록한다.
$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm repo update
Loki Helm chart values 파일을 아래와 같이 작성한다. (loki-values.yaml)
## loki-values.yaml
loki:
server:
http_listen_port: 3100
grpc_server_max_recv_msg_size: 8938360
grpc_server_max_send_msg_size: 8938360
structuredConfig:
auth_enabled: false
compactor:
apply_retention_interval: 1h
compaction_interval: 10m
retention_delete_delay: 2h
retention_delete_worker_count: 150
retention_enabled: true
shared_store: s3
working_directory: /var/loki/compactor
limits_config:
max_global_streams_per_user: 100000
max_streams_per_user: 100000
reject_old_samples: false
retention_period: 30d
per_stream_rate_limit: 3MB
per_stream_rate_limit_burst: 10MB
max_query_parallelism: 90
ingestion_rate_mb: 512
ingestion_burst_size_mb: 1024
ingester:
max_transfer_retries: 0
chunk_idle_period: 1h
chunk_target_size: 1572864
max_chunk_age: 2h
chunk_encoding: snappy
lifecycler:
ring:
kvstore:
store: memberlist
replication_factor: 3
heartbeat_timeout: 10m
wal:
dir: /var/loki/wal
replay_memory_ceiling: 800mb
storage_config:
aws:
region: {region}
bucketnames: {버킷 이름}
s3forcepathstyle: false
insecure: false
tsdb_shipper:
shared_store: s3
active_index_directory: /var/loki/tsdb-index
cache_location: /var/loki/tsdb-cache
index_queries_cache_config:
memcached:
batch_size: 100
parallelism: 100
schema_config:
configs:
- from: 2023-10-31
store: tsdb
object_store: s3
schema: v12
index:
prefix: loki_index_
period: 24h
chunk_store_config:
max_look_back_period: 48h
chunk_cache_config:
memcached:
batch_size: 100
parallelism: 100
write_dedupe_cache_config:
memcached:
batch_size: 100
parallelism: 100
querier:
max_concurrent: 16
query_scheduler:
max_outstanding_requests_per_tenant: 32768
serviceAccount:
create: true
name: "loki"
annotations:
"eks.amazonaws.com/role-arn": "{위에서 생성한 IAM Role ARN}"
automountServiceAccountToken: true
ingester:
replicas: 3
maxUnavailable: 1
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 1Gi
persistence:
enabled: true
claims:
- name: data
size: 10Gi
storageClass: ebs-sc
distributor:
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 256Mi
querier:
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 512Mi
queryFrontend:
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 512Mi
gateway:
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 512Mi
compactor:
enabled: true
serviceAccount:
create: true
name: "loki-compactor"
annotations:
"eks.amazonaws.com/role-arn": "{위에서 생성한 IAM Role ARN}"
automountServiceAccountToken: true
indexGateway:
enabled: true
memcachedChunks:
enabled: true
extraArgs:
- -I 32m
memcachedFrontend:
enabled: true
extraArgs:
- -I 32m
memcachedIndexQueries:
enabled: true
extraArgs:
- -I 32m
튜닝 파라미터 값이 많기 때문에 내용은 아래 링크를 참조한다.
- Configuration : https://grafana.com/docs/loki/latest/configure/
Helm 명령어로 설치한다.
$ helm install loki -n logging grafana/loki-distributed -f loki-values.yaml --create-namespace
설치 후 정상으로 동작하는지 ingester의 로그를 확인한다.
$ kubectl logs -n logging sts/loki-loki-distributed-ingester
...
level=info ts=2023-10-31T09:11:34.892385377Z caller=loki.go:505 msg="Loki started"
level=info ts=2023-10-31T09:11:34.892405262Z caller=lifecycler.go:483 msg="auto-joining cluster after timeout" ring=ingester
level=info ts=2023-10-31T09:11:34.901529164Z caller=memberlist_client.go:592 msg="joining memberlist cluster succeeded" reached_nodes=3 elapsed_time=9.891838ms
...
2. Promtail 구성
Promtail Helm chart values 파일을 아래와 같이 작성한다. (Promtail.yaml)
config:
clients:
- url: http://loki-distributed-gateway/loki/api/v1/push
- 위에서 설치한 loki의 gateway k8s dns 주소를 넣어주면 된다.
위에 지정한 url에 loki-distributed-gateway는 nginx로 구동되며 "/loki/api/v1/push"로 요청이 오면 distributor로 proxy 한다.
## nginx.conf
location = /loki/api/v1/push {
set $loki_api_v1_push_backend http://loki-loki-distributed-distributor.logging.svc.cluster.local;
proxy_pass $loki_api_v1_push_backend:3100$request_uri;
proxy_http_version 1.1;
}
위에서 작성한 values 파일로 설치한다.
$ helm install promtail grafana/promtail -n logging -f promtail.yaml
3. S3로 저장된 파일 확인 (Chunk, Index)
위에서 설정한 1시간이 지나면 s3로 chunk 파일이 upload 되는 걸 확인할 수 있다.
chunk파일이 fake라는 디렉토리 아래에 생성되는 걸 확인할 수 있다.
ingester pod의 로그를 확인하면 시작 시 S3로 index 파일을 업로드하고 약 1시간(1h) 후 chunk 파일을 업로드하는 걸 확인할 수 있다.
$ kubectl logs -n logging loki-loki-distributed-ingester-0
... [ index upload ]
level=info ts=2023-11-02T03:34:00.198726757Z caller=table_manager.go:136 index-store=tsdb-2023-10-31 msg="uploading tables"
... [ chunk upload ]
level=info ts=2023-11-02T04:36:20.161238895Z caller=flush.go:167 msg="flushing stream" user=fake fp=b1a2b15ef3e71765 immediate=false num_chunks=1 labels="{app=\"aws-ebs-csi-driver\", component=\"csi-driver\", container=\"ebs-plugin\", filename=\"/var/log/pods/kube-system_ebs-csi-node-dh4k8_2eb18e4a-b29d-4ca3-998c-9f38574a28d5/ebs-plugin/3.log\", job=\"kube-system/aws-ebs-csi-driver\", namespace=\"kube-system\", node_name=\"ip-192-168-7-27.ap-northeast-2.compute.internal\", pod=\"ebs-csi-node-dh4k8\", stream=\"stderr\"}"
level=info ts=2023-11-02T04:36:20.161637895Z caller=flush.go:167 msg="flushing stream" user=fake fp=2d6f655e7cee44fc immediate=false num_chunks=1 labels="{app=\"loki-distributed\", component=\"ingester\", container=\"ingester\", filename=\"/var/log/pods/logging_loki-loki-distributed-ingester-0_d7b8c0ed-3439-4250-b21b-5e38749ac8d7/ingester/1.log\", instance=\"loki\", job=\"logging/loki-distributed\", namespace=\"logging\", node_name=\"ip-192-168-7-27.ap-northeast-2.compute.internal\", pod=\"loki-loki-distributed-ingester-0\", stream=\"stderr\"}"
...
S3에서 저장된 index 파일을 확인하면 압축된 index 파일을 확인할 수 있다.
compactor pod의 로그를 확인하면 아래와 같이 index 파일을 확인 후 압축하는 걸 확인할 수 있다.
$ kubectl logs -n logging deploy/loki-loki-distributed-compactor
...
level=info ts=2023-11-02T04:52:56.67481131Z caller=table.go:132 table-name=loki_index_19663 msg="listed files" count=3
level=info ts=2023-11-02T04:52:56.768179657Z caller=util.go:94 table-name=loki_index_19663 file-name=1698899458-loki-loki-distributed-ingester-1-1698895978474469189.tsdb.gz size="13 kB" msg="downloaded file" total_time=93.224083ms
level=info ts=2023-11-02T04:52:56.7700753Z caller=util.go:94 table-name=loki_index_19663 file-name=1698899400-loki-loki-distributed-ingester-0-1698895980198103840.tsdb.gz size="13 kB" msg="downloaded file" total_time=95.214667ms
level=info ts=2023-11-02T04:52:56.785549612Z caller=util.go:94 table-name=loki_index_19663 file-name=1698899454-loki-loki-distributed-ingester-2-1698895974989622962.tsdb.gz size="13 kB" msg="downloaded file" total_time=110.68938ms
level=info ts=2023-11-02T04:52:56.840658104Z caller=index_set.go:269 table-name=loki_index_19663 user-id=fake user-id=fake msg="removing source db files from storage" count=0
level=info ts=2023-11-02T04:52:56.840690395Z caller=index_set.go:269 table-name=loki_index_19663 msg="removing source db files from storage" count=3
level=info ts=2023-11-02T04:52:56.925566496Z caller=compactor.go:688 msg="finished compacting table" table-name=loki_index_19663
...
4. Grafana에서 로그 조회
설치가 완료되면 Grafana 대시보드로 들어가 Loki를 data sources로 등록한다.
- Home > Administration > Data sources
"Home > Dashboards > New dashboard"로 들어가 data source를 위에서 생성한 Loki로 지정하고 아래와 같이 쿼리 하여 로그를 조회한다. app이라는 필드 기반으로 로그를 조회하였다.
정상적으로 조회가 되는 걸 확인할 수 있다.
'Observability > Prometheus & Grafana' 카테고리의 다른 글
Grafana Mimir란? 개념부터 설치까지 (2) | 2023.11.17 |
---|---|
Grafana Tempo란? 개념부터 설치까지 (0) | 2023.11.06 |
Prometheus HA 구성2 (With 샤딩 + Thanos) (3) | 2023.05.10 |
Prometheus 란? (0) | 2023.05.10 |
Grafana + AWS CloudWatch를 이용한 AWS 모니터링 (0) | 2023.05.09 |
댓글