# Loki 3.x 설치하고 S3 기반 TSDB 설정하기

지금까지 배부른 회사에서 데이터독만 쓰면서 신경 끄며 살아와서 그런가, 모니터링에 대한 지식이 너무 빈약하다는 것을 많이 느끼는 요즘이네요... 😢

쿠버네티스에서 로그를 수집 및 저장하기 위해 로키(Loki)를 배포하기 위한 노력의 흔적을 공유합니다!

---

로키가 출시된지 꽤 시간이 지났지만 여전히 로키 설치에 관해 자세히 설명하는 글이 많지 않았고, `loki-stack`  헬름 차트가 더 이상 활발하게 개발되고 있지 않아 설정에 어려움을 많이 겪었답니다.

그러므로 이번 글에서는 `loki` , `promtail`  차트를 사용해서 로키를 배포하고, 최소한의 동작을 하도록 설정해 보도록 할게요!

# 1. 큰 그림

우선 프로메테우스(Prometheus)의 경우 `kube-prometheus-stack`  헬름 차트([차트 리포지토리 링크](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack))로 설치했다고 가정하겠습니다.

그라파나의 경우 프로메테우스를 예전에 설치해서 이미 사용하고 있었고, 이번에 설치해야 할 차트는 아래의 두 가지였어요.

- `loki` : 로그 소스로부터 로그를 push받아 로그를 처리, 저장하여 그라파나 등을 통해 시각화하기 위한 로그 시스템이에요. ([차트 리포지토리 링크](https://github.com/grafana/loki/tree/main/production/helm/loki%20))

- `promtail` : 각 쿠버네티스 노드로부터 컨테이너 로그를 가져와 로키로 push하기 위한 에이전트예요. ([차트 리포지토리 링크](https://github.com/grafana/helm-charts/tree/main/charts/promtail))

다시 말하면, 프롬테일로 로그를 긁어모아서 로키로 쏴 주는 구조예요.

# 2. 로키 차트 설정

## 2.1. 로키의 배포 모드

로키의 배포 모드([공식 문서 링크](https://grafana.com/docs/loki/latest/get-started/deployment-modes/))는 세 가지가 있어요. 이 중, 로키 차트에서 지정한 기본값은 단순 확장가능 모드([v3.3.2 밸류 참조](https://github.com/grafana/loki/blob/v3.3.2/production/helm/loki/values.yaml#L36))입니다.

우리 클러스터에는 **_단순 확장가능 모드_**로 로키를 설치하기로 결정했어요.

### **모놀리식 모드 (monolithic mode)**

- 로키의 모든 컴포넌트가 한 바이너리로 되어 있어요.

- 레플리카 확장이 가능하지만, 복제하지 않아도 되는 컴포넌트까지 모두 복제하게 돼요.

- 단순 개발 환경, PoC 환경이나 하루 최대 20GB 정도의 로그를 처리할 때 적합해요.

### 단순 확장가능 모드 (simple scalable mode)

- 로키의 컴포넌트를 의미 단위로 묶어 두었어요.

    - 쓰기 타겟(write target): distributor, ingester

    - 읽기 타겟(read target): querier, query frontend

    - 백엔드 타겟(backend target): compactor, query scheduler, index gateway, ruler

- 모놀리식과 마이크로서비스 모드 사이의 유연함과 복잡성을 가져요.

- 하루 최대 1TB 정도의 로그를 처리하기에 적합해요.

### 마이크로서비스 모드 (microservices mode)

- 모든 컴포넌트가 따로 배포돼요.

- 가장 유연하고 성능적으로 최적화하기 좋지만, 매우 복잡해요.

- 하루 1TB 이상의 로그를 처리하기에 적합해요.

## 2.2. 로키 스토리지 설정 - 오브젝트 스키마

로키 차트를 통해 설치할 때 기본적으로 파일시스템을 사용하게 됩니다. 이를 위해 [MinIO](https://min.io/product/overview)의 사용이 기본적으로 활성화되어 있어요. (v3.3.2 밸류 참조 - [모놀리식](https://github.com/grafana/loki/blob/v3.3.2/production/helm/loki/single-binary-values.yaml#L51-L52), [단순 확장가능](https://github.com/grafana/loki/blob/v3.3.2/production/helm/loki/simple-scalable-values.yaml#L39-L40), [마이크로서비스](https://github.com/grafana/loki/blob/v3.3.2/production/helm/loki/distributed-values.yaml#L59-L60))

미니오는 S3 API를 그대로 따르는 오브젝트 기반 스토리지이며, 차트 기본값을 사용한다면 미니오를 사용해 파일시스템에 로그를 저장하게 되어 있어요. 파일시스템을 사용하더라도 S3의 오브젝트 스키마를 그대로 사용하므로 스키마 설정은 아래와 같이 `s3` 를 지정하게 됩니다. ([공식 문서 참조](https://grafana.com/docs/loki/latest/configure/storage/#on-premise-deployment-minio-single-store))

> **키값의 형태에 주의하세요!**

카멜케이스를 채택한 `schemaConfig`와 다르게, 다른 밸류는 언더스코어 케이스를 채택한 경우가 많아요.

예시: `storage_config` 

```javascript
loki:
  schemaConfig:
    configs:
      - from: "2025-01-22"
        store: tsdb
        object_store: s3
        schema: v13
        index:
          prefix: loki_index_
          period: 24h
```

- `from` : 여기에 지정한 날짜부터 생산된 로그를 저장해요.

- `store` : 로키에서 사용할 스토리지 방식을 지정해요. 로키 2.8 버전 이후부터는 TSDB가 권장됩니다.

- `index.period` : 스토리지에 저장될 인덱스의 보관 기간이에요. **24시간 이외의 값을 사용할 수 없습니다**. ([공식 문서 참조](https://grafana.com/docs/loki/latest/operations/storage/retention/#retention-configuration))

하지만 우리는 오브젝트 스토어를 위해 **S3 버킷**을 사용할 생각이므로, 미니오를 명시적으로 비활성화해 줄게요.

> 미니오를 비활성화한 상태에서 파일시스템을 사용하기를 원한다면 `object_store` 를 `filesystem` 으로 지정해야 해요!

```javascript
loki:
  schemaConfig:
  # omit

minio:
  enabled: false
```

## 2.3. 로키 스토리지 설정 - S3 버킷 설정

그 다음 S3 버킷을 로키와 연결해야 합니다. 이를 위해서는 `loki.storage_config`  값과 `loki.storage`  값을 설정해 주어야 해요. 둘 중 하나라도 빠지면 정상적으로 동작하지 않아요!

우선 로키에서 저장하는 데이터 중 chunk와 ruler에 사용할 버킷을 반드시 생성해야 해요. ([v3.3.2 밸류 참조](https://github.com/grafana/loki/blob/v3.3.2/production/helm/loki/values.yaml#L327-L332)) 이를 위해 버킷을 서울 리전에 아래와 같이 생성했다고 가정하겠습니다.

- testtest-loki-chunk

- testtest-loki-ruler

그 다음 아래와 같이 차트 밸류를 설정해 줍니다.

```javascript
loki:
  schemaConfig:
  # omit
  storage_config:
    tsdb_shipper:
      active_index_directory: "/var/loki/index"
      cache_location: "/var/loki/index_cache"
      cache_ttl: 24h
    aws:
      region: ap-northeast-2
      bucketnames: testtest-loki-chunk
  storage:
    type: s3
    bucketNames:
      chunk: testtest-loki-chunk
      ruler: testtest-loki-ruler
    s3:
      region: ap-northeast-2
```

- `storage_config.tsdb_shipper` : TSDB shipper가 S3로부터 값을 읽고 쓰기 전에 값을 저장하거나 캐싱해 두는 곳을 지정합니다. ([공식 문서 참조](https://grafana.com/docs/loki/latest/configure/storage/#aws-deployment-s3-single-store))

    - 여기서 `/var/loki`  부분은 기본값입니다. `commonConfig.path_prefix` 로 변경할 수 있습니다. ([v3.3.2 밸류 참조](https://github.com/grafana/loki/blob/v3.3.2/production/helm/loki/values.yaml#L322))

    - `active_index_directory` 의 경우 쓰기 타겟에 생성돼요.

![Image](https://upload.cafenono.com/image/slashpagePost/20250122/174913_Say6UunS2a3x4x7UBC?q=80&s=1280x180&t=outside&f=webp)

    - `cache_location` 의 경우 백엔드 타겟에 생성돼요.

![Image](https://upload.cafenono.com/image/slashpagePost/20250122/174922_PwMCTqk9cJwfmpvsOf?q=80&s=1280x180&t=outside&f=webp)

- `storage_config.aws` : 스토리지 설정 첫 번째 부분

    - 이 부분에는 chunk 버킷만 지정합니다. 설명이 잘 된 공식 문서를 찾을 수 없어서 링크한 공식 문서에서 유추하였어요...😭 ([공식 문서 참조](https://grafana.com/docs/loki/latest/setup/install/helm/deployment-guides/aws/#loki-helm-chart-configuration))

- `storage` : 스토리지 설정 두 번째 부분

    - 여기서는 chunk와 ruler 모두 지정해 줍니다.

## 2.4. S3 접근을 위한 EKS IRSA 설정

이제 로키의 연관 파드가 S3로부터 읽고 쓸 수 있도록 권한을 설정해 줘야 해요. `storage_config` 나 `storage` 에서 액세스 키를 설정하는 방식도 사용할 수 있지만, AWS 관련 키를 평문으로 코드베이스에 기록하는 것은 너무나도 좋지 않은 습관이에요. 이런 방식을 피하기 위해서 S3 접근이 가능하도록 IAM 롤을 만들어 EKS OIDC 프로바이더에 연결하는 방식을 사용하겠습니다.

> EKS의 OIDC 프로바이더는 이미 가지고 있다고 가정할게요!

### 2.4.1. 퍼미션 생성

아래와 같은 퍼미션을 생성합시다.

```javascript
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::testtest-loki-chunk",
                "arn:aws:s3:::testtest-loki-chunk/*",
                "arn:aws:s3:::testtest-loki-ruler",
                "arn:aws:s3:::testtest-loki-ruler/*",
            ]
        }
    ]
}
```

### 2.4.2. 롤 생성

`testtest-loki`라는 이름의 롤을 생성한 뒤 위에서 생성한 퍼미션을 붙여 봅시다.

그 다음, EKS에서 사용할 수 있도록 신뢰 관계를 구축해 줍니다.

```javascript
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::<계정 아이디 숫자>:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/<OIDC 아이디>"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.eks.ap-northeast-2.amazonaws.com/id/<OIDC 아이디>:sub": "system:serviceaccount:<로키 네임스페이스>:<로키 서비스 어카운트 이름>",
                    "oidc.eks.ap-northeast-2.amazonaws.com/id/<OIDC 아이디>:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}
```

서비스 어카운트의 이름은 `testtest-loki-sa` 라고 가정할게요.

### 2.4.3. 헬름 밸류 설정

IRSA에 사용될 서비스 어카운트를 생성하고, `testtest-loki` 롤을 연결시켜 볼게요.

```javascript
loki:
# omit

serviceAccount:
  create: true
  name: testtest-loki-sa
  annotations:
    "eks.amazonaws.com/role-arn": "arn:aws:iam::<계정 아이디 숫자>:role/testtest-loki"
```

## 2.5. 마지막: 레플리카 수 조정

각 타깃의 레플리카 수는 기본적으로 세 개씩이에요. ([v3.3.2 밸류 참조](https://github.com/grafana/loki/blob/v3.3.2/production/helm/loki/simple-scalable-values.yaml#L31-L36)) 이를 두 개씩으로 바꾸기만 해 봅시다!

```javascript
backend:
  replicas: 2
read:
  replicas: 2
write:
  replicas: 2
```

이것으로 로키 헬름 밸류 설정은 마무리가 되었어요. 이제 프롬테일을 준비해 봅시다.

# 3. 프롬테일 차트 설정

다행히도 프롬테일 차트의 설정은 기본값에서 거의 바뀌지 않아요. 다만 프롬테일의 클라이언트로 로키를 가리키도록 설정해 주어야 해요.

```javascript
config:
  clients:
    - url: "http://loki-gateway/loki/api/v1/push"
      tenant_id: 1
```

여기서는 로키와 프롬테일의 네임스페이스가 동일하다고 가정하고, 곧바로 `loki-gateway`  서비스를 가리키도록 하였어요.

`tenant_id: 1` 의 경우 "no org id" 에러로 401이 발생하는 것 때문에 [링크된 이슈](https://github.com/grafana/loki/issues/7081)를 참조하여 추가해 준 것입니다. 이 부분은 workaround로 보여서, 나중에 원인을 명확하게 찾아서 보완할 생각이에요.

그리고 Fargate 노드에는 프롬테일 데몬셋이 띄워지면 안 되기 때문에 아래와 같은 affinity 설정을 해 줍니다.

```javascript
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
          - key: "eks.amazonaws.com/compute-type"
            operator: NotIn
            values:
              - fargate
```

# 4. 완성된 헬름 밸류

여기까지 정리된 헬름 밸류 설정입니다.

## 4.1. 로키

```javascript
loki:
  schemaConfig:
    configs:
      - from: "2025-01-22"
        store: tsdb
        object_store: s3
        schema: v13
        index:
          period: 24h
          prefix: loki_index_
  limits_config:
    allow_structured_metadata: true
    volume_enabled: true
  storage_config:
    tsdb_shipper:
      active_index_directory: "/var/loki/index"
      cache_location: "/var/loki/index_cache"
      cache_ttl: 24h
    aws:
      region: ap-northeast-2
      bucketnames: testtest-loki-chunk
  storage:
    type: s3
    bucketNames:
      chunk: testtest-loki-chunk
      ruler: testtest-loki-ruler
    s3:
      region: ap-northeast-2

serviceAccount:
  create: true
  name: testtest-loki-sa
  annotations:
    "eks.amazonaws.com/role-arn": "arn:aws:iam::<계정 아이디 숫자>:role/testtest-loki"

backend:
  replicas: 2
read:
  replicas: 2
write:
  replicas: 2

minio:
  enabled: false

```

## 4.2. 프롬테일

```javascript
config:
  clients:
    - url: "http://loki-gateway/loki/api/v1/push"
      tenant_id: 1
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
          - key: "eks.amazonaws.com/compute-type"
            operator: NotIn
            values:
              - fargate
```

## 4.3. 배포 결과

ArgoCD를 통하여 배포를 완료했습니다!

![Image](https://upload.cafenono.com/image/slashpagePost/20250122/182656_22lp5npFOz8EkZJ9AZ?q=80&s=1280x180&t=outside&f=webp)

S3에도 열심히 청크를 보내고 있는 것을 확인했습니다.

![Image](https://upload.cafenono.com/image/slashpagePost/20250122/182754_9XGEc0Whc3EY2erp9b?q=80&s=1280x180&t=outside&f=webp)

# 5. 그라파나와 연결

이 부분은 `kube-prometheus-stack` 의 밸류 수정이 필요하지만, 되는 것을 보기 위해서 우선 손으로 작업했어요.

![Image](https://upload.cafenono.com/image/slashpagePost/20250122/183057_RRCWeNSwqrNh01bcd3?q=80&s=1280x180&t=outside&f=webp)

현 상태에서는 인증을 두고 있지 않기 때문에 로키 게이트웨이의 서비스를 그대로 바라보게 하면 됩니다. 다만 위에 언급했던 `tenant_id: 1` 과 같은 이슈로, `X-Scope-OrgID: 1` 헤더를 설정해 주어야 해요. 이 부분은 차후 고도화하면서 개선하고, 이와 함께 헬름 밸류로 설정하도록 할 생각이에요.

# 6. 결과

![Image](https://upload.cafenono.com/image/slashpagePost/20250122/183335_0INHhrUIXZujlV8GOT?q=80&s=1280x180&t=outside&f=webp)

그라파나로 로그를 볼 수 있어요!

여기까지 로키를 배포하기 위한 과정을 가능한 자세하게 적어 보았는데요. 궁금한 점이 있으시면 언제든 질문해 주세요. 저도 설정을 좀 더 최적화해 나갈 예정입니다!

For the site tree, see the [root Markdown](https://slashpage.com/uniglot.md).
