로깅 (Loki & Promtail )

1
2
Production Kubernetes Online Study (=PKOS)는 쿠버네티스 실무 실습 스터디입니다.
CloudNet@ Gasida(가시다)님이 진행하시며, 책 "24단계 실습으로 정복하는 쿠버네티스"을 기반으로 진행하고 있습니다.

Logging?

애플리케이션 실행 중 발생하는 이벤트, 작업, 오류 등의 정보를 기록하는 프로세스이다. 로깅의 주요 목적은 프로그램의 실행 상태를 추적하고, 문제 발생 시 원인을 찾기고, 내부 감사를 기록하기 위함이다. 로그 파일은 시간 순서대로 저장되며, 대부분의 경우 텍스트 파일 또는 데이터베이스에 저장된다.

컨테이너화된 애플리케이션에 가장 쉽고 가장 널리 사용되는 로깅 방법은 표준 출력(stdout)과 표준 에러(stderr) 스트림에 작성하는 것이다. 이를 이용하면 로깅 명령어를 통해 조회가 가능하다.

1
2
3
4
5
6
# 로그 확인 예 
kubectl logs  metrics-server-5f65d889cd-znqw5 -n kube-system

I0328 00:14:31.072509       1 serving.go:342] Generated self-signed cert (/tmp/apiserver.crt, /tmp/apiserver.key)
I0328 00:14:31.477085       1 requestheader_controller.go:169] Starting RequestHeaderAuthRequestController
..

쿠버네티스 환경에서도 컨테이너 엔진이나 런타임이 제공하는 기본적인 로깅 기능이 있으나 충분하지 않다. 컨테이너가 크래시되거나, 파드가 축출되거나, 노드가 종료된 경우에도 애플리케이션의 로그에 접근할 수 없기 때문이다.

따라서, 쿠버네티스에서 로그는 노드, 파드 또는 컨테이너와는 독립적으로 별도의 스토리지와 라이프사이클을 가져야 한다. 이 개념을 클러스터-레벨-로깅 이라 하며 이를 위해 별도의 벡엔드 솔루션이 필요하다. 쿠버네티스에 사용할 수 있는 로깅 솔루션은 3가가지 오픈소스 프로젝트를 결합한 PLG 스택(Promtail, Loki, Grafana) 또는 ELK(Elasticsearch, Logstash, Kibana)있다. 이번 블로그 글에서는 PLG 스택을 알아볼 것이며 로깅 시스템인 Loki 와 로그 수집 에이전트인 Promtail 을 설치하하여 클러스터-레벨-로깅을 테스트해보겠다.

Loki

Loki는 Grafana Labs에서 개발한 경량 로깅 시스템으로, 쿠버네티스 환경에서 메타데이터를 기반으로 로그를 수집하고 빠르게 처리할 수 있다. 그리고 Prometheus와 호환되는 레이블 기반 질의 및 필터링 기능을 제공하며, Grafana와 통합을 통해 로깅 데이터를 대시보드에서 확인이 가능하다. 로깅 수집 에이전트인 Promtail을 사용하여 로깅을 수집하며 이를 통해 쿠버네티스 리소스(노드, 파드 또는 컨테이너)와는 독립적으로 별도의 스토리지와 라이프사이클을 가진다.

<a href="https://grafana.com/blog/2018/12/12/loki-prometheus-inspired-open-source-logging-for-cloud-natives/">https://grafana.com/blog/2018/12/12/loki-prometheus-inspired-open-source-logging-for-cloud-natives/</a>

https://grafana.com/blog/2018/12/12/loki-prometheus-inspired-open-source-logging-for-cloud-natives/

메타데이터 인덱싱?

<a href="https://grafana.com/oss/loki/">https://grafana.com/oss/loki/</a>

https://grafana.com/oss/loki/

로키는 메타데이터를 인덱싱을 통해 경량화 및 빠른 쿼리 성능을 가진다. 메타인덱싱 원리는 위의 그림과 같은데 로그 전체 텍스트를 저장하는 것이 아니라 타임스탬프와 라벨을 묶어 인덱스(index) 로 그 외 나머지 텍스트를 청크(chunk)로 나눠 저장된다.

  • 인덱스: 로그 시간과 레이블을 묶어 해싱을 통해 고유한 식별자를 만든다. 이 식별자를 통해 로그 스트림을 참조하고 검색하는데 사용된다. 인덱스는 일반적으로 NoSQL DB에 저장하는데 Key 값에는 인덱스를 values 값에는 해당 청크의 데이터를 저장한다.
  • 청크: 청크는 실제 로그 데이터이다. 청크는 데이터를 압축 및 저장하기 위해 여러 압축 알고리즘을 사용할 수 있으며, 기본적으로 압축이 적용되어 저장 공간을 최적화합니다. 일반적으로 오브젝트 스토리지에 저장한다.

아키텍처

<a href="https://grafana.com/blog/2018/12/12/loki-prometheus-inspired-open-source-logging-for-cloud-natives/">https://grafana.com/blog/2018/12/12/loki-prometheus-inspired-open-source-logging-for-cloud-natives/</a>

https://grafana.com/blog/2018/12/12/loki-prometheus-inspired-open-source-logging-for-cloud-natives/

그림에서 화살표 빨강은 로그 Write, 파랑은 Read를 의미한다. 또한, 각 구성 컴포넌트들은 HA를 지원하여 컴포넌트 내부 구성 하나에 장애가 발생하더라도 서비스가 중단되지 않는다.

  • Your Jobs : 로그 수집 에이전트로 사용자 정의에 맞게 로그를 수집하여 로키 서버(Distributor)에 전달한다. 로그 수집 에이전트로 Promtail를 사용하나 fluent, fluentbit 과 호환이 가능하다.
  • Distributor(디스트리뷰터): Distributor는 로그 데이터를 수신하고, 해당 데이터를 인제스터(Ingester)에 분산시키는 역할을 한다. 또한, 레이블의 해시 값을 사용하여 데이터를 적절한 인제스터에 전달하며 로드밸런싱을 통해 로그 데이터를 여러 인제스터에 고르게 분산시켜준다.
  • Ingester(인제스터): Ingester는 Distributor로부터 로그 데이터를 받아서, 로그 스트림을 청크로 나누고 압축한 후 저장시킨다. 인제스터는 메모리 또는 영구 스토리지에 청크를 저장할 수 있으며, 쿼리어(Querier)에게 저장된 청크에 대한 질의 결과를 제공한다. 청크가 일정 시간 또는 크기에 도달하면 인제스터는 이를 영구 스토리지에 저장시킨다.
  • Querier(쿼리어): Querier는 사용자로부터 질의를 받아 처리한다. 질의를 처리할 때, 쿼리어는 인덱스를 사용하여 관련된 청크를 찾고, 인제스터 및 영구 스토리지에서 해당 청크를 가져와 질의 결과를 반환한다.

loki 버전 3.0 이상부터는 loki, loki-distributed가 통합되었다.

<a href="https://grafana.com/docs/loki/latest/getting-started/">https://grafana.com/docs/loki/latest/getting-started/</a>

https://grafana.com/docs/loki/latest/getting-started/

  • Loki Write component : 로그 데이터를 수신하고 저장시켜주는 컴포넌트이다. 앞서 아키텍처의 빨간 flow를 담당하는 Distributor와 Ingester 로 구성되어 있다.
  • Loki Read component: 로그 데이터를 조회하고 처리하는데 사용된다. 앞서 아키텍처의 파랑 flow를 담당하는 Querier와 Query Frontend 로 구성되어 있다.
  • gateway : Loki 구성요소에 대한 프록시 서버이다. 로그를 까보면 NGINX 게이트웨이가 설치되며 로드밸런싱 기능을 수행하여 각 컴포넌트에 트래픽을 분산시킨다.

설치

설치 환경 : kops 클러스터 (k8s 1.24), AWS Ubuntu 인스턴스

설치는 다음과 같이 진행할 예정이다.

  • Loki (helm grafana/loki 4.8.0)
  • Promtail (helm grafana/promtail 6.9.2)

사족이지만, 로그 저장소로 mongoDB를 활용하려 했으나 안 된다! 로키 호환 저장소가 정해져있기 때문이다. 온프로미스에서 구성시 참고하자.

본 블로그에서는 S3에 오브젝트 데이터를 DynamoDB에 인덱스 데이터를 저장시키겠다. (230402. DynamoDB 인덱스 연동문제로 S3에 인덱스, 오브젝트 데이터 저장)

<a href="https://grafana.com/docs/loki/latest/operations/storage/">https://grafana.com/docs/loki/latest/operations/storage/</a>

https://grafana.com/docs/loki/latest/operations/storage/

Loki 설치

Loki 저장소로 S3 와 DyanmoDB 스토리지 생성과 IAM role 권한 연결이 필요하다. 본 테스트에서는 S3 이름을 han-loki , DyanmoDB 이름은 loki_ 로 생성하였다. DynamoDB 사용시 주의해야할 점은 다음과 같다.

  • DynamoDB 테이블 이름을 헬름 차트의 schema_config.config
  • DynamoDB 파티션 키 & 정렬 키를 (문자열, 바이너리)로 지정해야 한다.

IAM 권한은 S3, DyanmoDB에 대한 정책을 부여했다.

  • AmazonS3FullAccess
  • AmazonDynamoDBFullAccess

필자는 사용자에 IAM 권한를 부여 후 access-key 와 secret-key를 가져왔다. 해당 키는 밑의 헬름 차트 버킷 연동에서 사용된다. 우선 헬름을 통해 로키 차트를 가져오겠다.

1
2
3
4
kubectl create ns loki
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm fetch grafana/loki --untar --version 4.8.0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#values-loki.yaml 
schema_config:
  configs:
  - from: 2020-05-15
    store: aws
    object_store: s3
    schema: v11
    index:
      prefix: loki
      period: 0

storage_config:
  aws:
    s3: s3://ap-northeast-2/han-loki
    dynamodb:
      dynamodb_url: dynamodb://ap-northeast-2

table_manager:
  retention_deletes_enabled: true
  retention_period: 336h
  index_tables_provisioning:
    write_scale:
      enabled: false
    read_scale:
      enabled: false
  chunk_tables_provisioning:
    write_scale:
      enabled: false
    read_scale:
      enabled: false
  table_prefix: "loki"

tableManager:
  enabled: true

monitoring:
  lokiCanary:
    enabled: true
  selfMonitoring:
    enabled: false

loki: 
  auth_enabled: false
  storage:
    bucketNames:
      chunks: han-loki
      ruler: han-loki
      admin: han-loki
    type: s3
    s3:
      s3: han-loki
      endpoint: s3.ap-northeast-2.amazonaws.com
      region: ap-northeast-2
      secretAccessKey: {SECRET KEY} 
      accessKeyId: {ACCESS KEY} 
      s3ForcePathStyle: false
      insecure: false
      http_config: {}
  • access_key 와 Secret_access_key 노출에 주의하자!
  • schema_config : 인덱스와 청크 데이터에 대한 저장 스키마를 정의한다.
  • storage_config: 데이터 저장할 스토리지 정보를 입력한다.
  • table_manager, tableManager : 테이블 기반 데이터 저장소에 인덱스 및 청크를 지원하는데 버전 호환 문제로 작동이 안되어 dynamodb 에 인덱스가 저장이 안된다. (현재 S3에 저장됨)
  • monitoring.lokicanary: 시스템 검증에 사용된다. true 설정시 별도의 카나리 파드가 생성되어 일정시간마다 테스트 로그를 전달한다.
  • monitoring.selfMonitoring : 대시보드에 Loki 관련 대시보드가 업로드된다하지만,
  • loki: 로키 서버 설정을 정의한다. 최신 버전에는 스토리지 연동을 여기서 하는데 dynamodb에 대한 설정 예시가 없고 테스트가 안되서 s3 만 정의하였다.
1
helm install loki grafana/loki -f values-loki.yaml -n loki --version 4.8.0

파드 배포는 완료되었으나, 로키 스토리지 연동으로 안정화 작업이 필요하다.

안정화 작업는 다음과 같다.

storage_config 스토리지 연동 문제

1
2
level=error ts=2023-04-01T03:18:30.427197283Z caller=flush.go:144 org_id=self-monitoring msg="failed to flush" err="failed to flush chunks: store put chunk: NoCredentialProviders: no valid providers in chain. Deprecated.\n\tFor verbose messaging see aws.Config.CredentialsChainVerboseErrors, num_chunks: 1, labels: {app_kubernetes_io_component=\"read\", app_kubernetes_io_instance=\"loki\", app_kubernetes_io_name=\"loki\", app_kubernetes_io_part_of=\"memberlist\", cluster=\"loki\", container=\"loki\", controller_revision_hash=\"loki-read-7749df4969\", filename=\"/var/log/pods/loki_loki-read-2_a8fab5a2-e78b-4cff-9f8d-ba0ee5952a3c/loki/0.log\", job=\"loki/loki-read\", namespace=\"loki\", pod=\"loki-read-2\", statefulset_kubernetes_io_pod_name=\"loki-read-2\", stream=\"stderr\"}"
level=info ts=2023-04-01T03:18:30.427244754Z caller=flush.go:168 msg="flushing stream" user=self-monitoring fp=33d14d11bfd98f55 immediate=false num_chunks=1 labels="{app_kubernetes_io_component=\"read\", app_kubernetes_io_instance=\"loki\", app_kubernetes_io_name=\"loki\", app_kubernetes_io_part_of=\"memberlist\", cluster=\"loki\", container=\"loki\", controller_revision_hash=\"loki-read-7749df4969\", filename=\"/var/log/pods/loki_loki-read-2_a8fab5a2-e78b-4cff-9f8d-ba0ee5952a3c/loki/0.log\", job=\"loki/loki-read\", namespace=\"loki\", pod=\"loki-read-2\", statefulset_kubernetes_io_pod_name=\"loki-read-2\", stream=\"stderr\"}"

공식문서 예제 storage_config 가 최신 기준으로 업데이트된 것 같지 않다. 필자의 경우 배포 yaml로 구성을 설정하니 정상적으로 작동했다.

s3-loki.png

S3 디렉토리에 chunk 폴더가 없다? 이슈 에 따르면 멀티테넌트 구성에서 loki를 실행할때 인증이 비활성화하면 기본적으로 fake 폴더에 저장된다고 한다. fake폴더 안에는 정상적으로 chunk가 들어가있는 것을 확인할 수 있으나 다중 클러스터에서 배포시 loki 별로 인증이 필요할 것 같다.

fake.png

dynamoDB에 인덱스를 저장하고 싶어요.

해결해야할 문제다. 현재 S3에 index값이 들어가는데 dynamodb에 옮겨야 한다. 헬름차트에 table-manager 설정이 두개(table-manager, tableManager)여서 설정이 안 먹힌다. 추가 원인으로는 깃허브 이슈( https://github.com/grafana/loki/issues/5070) 설정값( extraArgs)인데 적용이 안된다. 추후 해결시 업데이트하겠다.

배포를 완료하면 로키 파드가 정상적으로 작동하는 것을 알 수 있다.

loki-result.png

loki6.png

http://loki-read-headless.loki.svc.cluster.local:3100

Promtail 설치

1
2
3
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm fetch grafana/promtail --untar --version 6.9.2

Promtail 차트에서 따로 설정할 것은 없지만, 로키 연결과 로그 라인 파이프라인 구성 확인을 위해 집어넣는다.

promtail1.png

  • config.clients.url : 로그 수집 후 전달한 로그 서버를 입력한다. 보통 로키 서버 설치시 설정되는 url로 지정된다.
  • config.snippets: 로그 라인 분석과 추출, 필터링하는 스테이지들의 작업을 정의한다. 원하는 로그 데이터 형식을 정의해서 입력하면 된다. scraping 구문은 프로메테우스와 동일하다.
1
helm install promtail grafana/promtail -n loki --version 6.9.2

로그 수집 테스트

Promtail-Loki-Grafana 까지의 로그 수집을 테스트하겠다. 테스트를 위해 nginx 를 배포하고 파드 로그 확인과 그라파나(로키)에서 로그를 확인한다.

1
2
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install nginx bitnami/nginx --version 13.2.23 -f nginx-values.yaml

파드 로그는 파드가 올라간 노드 /var/log/pods 에 저장되어 있다. 파드의 로그를 확인하고 그라파나 대시보드에서 로키가 해당 로그를 긁어오는지 확인하자.

nginx1.png

nginx2.png

  • [Explorer] → Job = default/nginx 설정 후 로그 확인

잘 들어온다! 이어서 파드 라이프사이클과 독립적으로 로그가 관리되는 지 확인하겠다. nginx 파드를 삭제하고 파드 로그와 로키를 확인하겠다.

1
helm uninstall nginx

아래 디렉토리를 확인하면 nginx 파드가 삭제되어 로그 디렉토리가 삭제된 것을 확인할 수 있다.

nginx3.png

nginx4.png

파드가 삭제되었지만 그라파나(로키) 에서 nginx 로그를 확인할 수 있다.

마치며

그라파나 공식 문서를 참고하니 엔터프라이즈에 대한 지원만 활발한 느낌이다. 오픈소스로 설치시 연동 부분과 최신 버전 호환 문제로 테스트하는 데도 오랜 시간이 걸렸다. 특히 인덱스 데이터를 dynamodb 에 연동해야 했지만 설정 문제로 실패했고 S3에 인덱스, 청크 데이터를 저장시켰다. 이 부분은 공식 문서를 최신 버전으로 업데이트를 하거나 예가 나오면 업데이트하겠다.