LGTM 구성해보기(테스트 및 작성중)

Cloud Native를 하다보면 여러 오픈소스들을 다루게 된다고 하지만,
내부 고객, 외부 고객사, 운영업체 등 요구하는게 너무 다양하다.

이번에는 LGTM을 해야한다…Loki, Grafana, Tempo, Mimir.

LGTM ( Loki Grafana Tempo Mimir )

# 기본 흐름
 ┌────────────┐     ┌────────────┐     ┌────────────┐
 │  Promtail  │────▶│    Loki    │────▶│  Grafana   │
 └────────────┘     └────────────┘     └────────────┘
 ┌────────────┐     ┌────────────┐     ┌────────────┐
 │ Prometheus │────▶│   Mimir    │────▶│  Grafana   │
 └────────────┘     └────────────┘     └────────────┘
 ┌───────────────┐     ┌────────────┐     ┌────────────┐
 │ OpenTelemetry │────▶│   Tempo    │────▶│  Grafana   │
 └───────────────┘     └────────────┘     └────────────┘

gpt를 통해 간단하게 리서치해본 결과.

LGTM 스택 구성요소와 대응 비교

구성요소역할유사한 도구 (EFK/Prometheus 계열)비교 설명
Loki로그 수집 및 저장Elasticsearch + Fluentd/Fluent BitLoki는 로그를 “인덱스 없이” 저장해 비용과 성능을 개선. Fluent Bit이나 Promtail을 통해 수집. 쿼리는 Grafana에서 LogQL로 수행.
Grafana시각화 및 대시보드Kibana / GrafanaKibana와 달리 Loki, Tempo, Mimir 모두를 통합 관리 가능. EFK에서 Kibana는 Elasticsearch만 시각화 가능.
Tempo트레이싱(Distributed Tracing)Jaeger / ZipkinJaeger와 유사하지만, 저장을 위한 백엔드(예: S3, GCS, MinIO 등)를 직접 사용해 더 단순하고 통합성이 좋음.
Mimir메트릭 저장 및 분석Prometheus / Thanos / CortexMimir은 Prometheus를 수평 확장(HA) 가능한 버전으로 발전시킨 것. Cortex 기반이며 장기 스토리지 및 멀티테넌시 지원.

기능 비교 요약

기능 영역LGTM 스택 (Loki, Grafana, Tempo, Mimir)EFK + Prometheus 스택
로그 수집Loki + PromtailFluent Bit / Fluentd + Elasticsearch
로그 저장 구조인덱스 최소화 (메타데이터 기반)인덱스 기반 (Elasticsearch)
메트릭Mimir (Prometheus 확장형)Prometheus
트레이스(분산 추적)TempoJaeger / Zipkin
시각화Grafana (중앙 통합)Kibana (로그), Grafana (메트릭)
확장성Mimir, Tempo 모두 수평 확장 및 오브젝트 스토리지 사용Prometheus/Elasticsearch 확장은 상대적으로 복잡
운영 비용상대적으로 낮음 (인덱스 없음, 오브젝트 스토리지 기반)높은 편 (Elasticsearch의 인덱싱, 클러스터 관리 필요)
통합성Grafana 하나로 모든 데이터(Log, Metric, Trace) 조회 가능Kibana + Grafana + Jaeger 등 분리

아키텍처 관정에서의 차이

비교 항목LGTMEFK + Prometheus
스토리지 구조오브젝트 스토리지(S3, MinIO 등)에 직접 저장로컬 디스크 또는 전용 DB (Elasticsearch, TSDB 등)
쿼리 언어LogQL / PromQL / TraceQL (Grafana 통합)Lucene / PromQL / Jaeger UI
운영 편의성Grafana Agent로 통합 수집 가능각각의 Agent (Fluentd, Node Exporter 등) 필요
유지보수 복잡도낮음 (Grafana Labs 통합 솔루션)높음 (각 구성요소 개별 관리 필요)
라이선스 및 상호 호환성Grafana OSS 기반 (AGPLv3)Elastic은 SSPL 이후 제한, Prometheus는 Apache 2.0

정리

목적LGTM 권장EFK+Prometheus 권장
클라우드 네이티브 환경 (K8s 등)
운영 단순화 / 통합 관찰성
기존 Elastic Stack 인프라 보유
대규모 로그 인덱싱 / 검색 중심
저비용, 오브젝트 스토리지 기반 로그

좀더 찾아보면 어느게 더 알맞는지 알수있겠지만 우선 동작부터 시켜보고 비교해봐야할듯..

1. logging ( Promtail, Loki, Grafana)

loki는 Log저장소로 사용되는 것으로 OpenSearch와 대비해서 생각해야할것 같다.
그렇다면 로그를 수집하는 것은 어떤 것이하는지 알아야하는데, fluentbit도 가능하고 grafana에서 공식지원하는 promtail도 가능하다.
promtail을 사용해보겠다.

 ┌────────────┐           ┌────────────┐     ┌────────────┐
   Promtail  │── Logs ──▶│    Loki    │────▶│  Grafana   
 └────────────┘           └────────────┘     └────────────┘

Promtail

https://grafana.com/docs/loki/latest/send-data/promtail

promtail로 해보려고했는데 Alloy라는것으로 변경되나보다…

간단하게 시작해보는 용도로 promtail로 구성해보고 Alloy는 일단 리서치만..

kubernetes에 promtail을 배포해본다.

Promtail을 배포하기위해서 Loki Endpoint가 필요로 한다. Loki를 선행 배포해야 할것으로 보여진다.

https://grafana.com/docs/loki/latest/send-data/promtail/installation

배포권장방법이라 표기된 Install as Kubernetes daemonSet(recommended)를 참고하여 구성한다.

--- # Daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: promtail-daemonset
spec:
  selector:
    matchLabels:
      name: promtail
  template:
    metadata:
      labels:
        name: promtail
    spec:
      serviceAccount: promtail-serviceaccount
      containers:
      - name: promtail-container
        image: grafana/promtail:3.5.8
        args:
        - -config.file=/etc/promtail/promtail.yaml
        env: 
        - name: 'HOSTNAME' # needed when using kubernetes_sd_configs
        # "Kubernetes Service Discovery Configs"
        # "쿠버네티스 서비스 디스커버리 설정"의 줄임말입니다.
          valueFrom:
            fieldRef:
              fieldPath: 'spec.nodeName' # Pod가 스케줄된 실제 노드 이름을 Kubernetes API에서 가져옴
        volumeMounts:
        - name: logs
          mountPath: /var/log
        - name: promtail-config
          mountPath: /etc/promtail
        - mountPath: /var/lib/docker/containers
          name: varlibdockercontainers
          readOnly: true
      volumes:
      - name: logs
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: promtail-config
        configMap:
          name: promtail-config
--- # configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: promtail-config
data:
  promtail.yaml: |
    server:
      http_listen_port: 9080
      grpc_listen_port: 0

    clients:
    #- url: https://{YOUR_LOKI_ENDPOINT}/loki/api/v1/push
    - url: http://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push
    # auth_enabled=true 일때 아래 설정
      headers:
        X-Scope-OrgID: tk8s
    positions:
      filename: /tmp/positions.yaml
    target_config:
      sync_period: 10s
    scrape_configs:
    - job_name: pod-logs
      kubernetes_sd_configs:
        - role: pod
      pipeline_stages:
        - docker: {}
      relabel_configs:
        - source_labels:
            - __meta_kubernetes_pod_node_name
          target_label: __host__
        - action: labelmap
          regex: __meta_kubernetes_pod_label_(.+)
        - action: replace
          replacement: $1
          separator: /
          source_labels:
            - __meta_kubernetes_namespace
            - __meta_kubernetes_pod_name
          target_label: job
        - action: replace
          source_labels:
            - __meta_kubernetes_namespace
          target_label: namespace
        - action: replace
          source_labels:
            - __meta_kubernetes_pod_name
          target_label: pod
        - action: replace
          source_labels:
            - __meta_kubernetes_pod_container_name
          target_label: container
        - replacement: /var/log/pods/*$1/*.log
          separator: /
          source_labels:
            - __meta_kubernetes_pod_uid
            - __meta_kubernetes_pod_container_name
          target_label: __path__

--- # Clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: promtail-clusterrole
rules:
  - apiGroups: [""]
    resources:
    - nodes
    - services
    - pods
    verbs:
    - get
    - watch
    - list

--- # ServiceAccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: promtail-serviceaccount

--- # Rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: promtail-clusterrolebinding
subjects:
    - kind: ServiceAccount
      name: promtail-serviceaccount
      namespace: default
roleRef:
    kind: ClusterRole
    name: promtail-clusterrole
    apiGroup: rbac.authorization.k8s.io

Alloy

https://grafana.com/docs/alloy/latest

구분PromtailGrafana Alloy
역할로그 수집 전용로그 + 메트릭 + 트레이스 통합
기반 기술Loki 고유 포맷OpenTelemetry Collector
개발 상태유지보수만, 기능 추가 중단Grafana Labs의 향후 표준
구성 난이도간단 (Prometheus 유사)복잡하지만 유연함
리소스 사용량낮음다소 높음 (CPU/RAM 증가)
마이그레이션 도구Alloy로 전환 도구 제공Promtail 설정 자동 변환 지원
적합한 환경단순 로그 수집 환경통합 관측 플랫폼 구축 환경

Loki

loki를 배포하려면 어떤 타입으로 배포할지 고민해봐야 한다.
이 부분은 운영환경에 따라 그리고 누가 운영하느냐에 따라 달라지는게 맞다.

https://grafana.com/docs/loki/latest/get-started/deployment-modes/?utm_source=chatgpt.com

공식문서에서도 ‘Simple Scalable’ 모드로 구성해도 하루 최대 몇 TB로그까지 확장된다고 적혀있는데, ‘microservice’로 기본배포되는 LGTM 스택으로 배포해달라고하는 고객을 만나면 글쎄…어떤 mode로 구성해야되는지 물어보는 순간 헬게이트가 열릴수도…

모드구성 특징Helm 차트권장 용도
Single Binary (Monolithic)모든 컴포넌트(ingester, querier, compactor 등)가 하나의 바이너리에서 동작grafana/loki개발/테스트
Simple ScalableRead / Write / Backend 3계층 분리, 간단한 수평 확장 가능grafana/loki (기본값)중형 규모 프로덕션
Distributed (Microservices)각 구성요소를 완전 분리 (Distributor, Ingester, Querier 등)grafana/loki-distributed대규모, 고가용성 운영

일단은 이런 구조로 구성해야할듯…보통은 다중 클러스터에 중앙 집중 구조를 원하는 형상이 많으므로…
Loki는 배포방식이좀 다르니 배포 구성은 달라질수 있다.

MinIO가 포함되어있는데 ObjectStorage운영도 하나 추가된다고 생각해야한다.
public SaaS를 이용하면 그 사용 방법을 적용하는 구성 필요.
(단순히 Loki만 배포하고 땡이 아님.)

https://grafana.com/docs/loki/latest/setup/install/helm/install-scalable

helm 등록

helm repo add grafana https://grafana.github.io/helm-charts
helm repo update grafana

helm search repo grafana

상당히 많은 종류의 차트가 나타난다.

helm chart를 다운받는다.

helm pull grafana/loki --untar

value.yaml을 작성한다.

loki:
  schemaConfig:
    configs:
      - from: "2025-11-08"
        store: tsdb
        object_store: s3
        schema: v13
        index:
          prefix: loki_index_
          period: 24h
  ingester:
    chunk_encoding: snappy
  querier:
    # Default is 4, if you have enough memory and CPU you can increase, reduce if OOMing
    max_concurrent: 4
  pattern_ingester:
    enabled: true
  limits_config:
    allow_structured_metadata: true
    volume_enabled: true

  storage:
    type: s3
    bucketNames:
      chunks: loki-chunks
      ruler: loki-ruler
      admin: loki-admin
    s3:
      endpoint: minio.default.svc.cluster.local:9000
      region: us-east-1
      accessKeyId: minioadmin
      secretAccessKey: minioadmin
      insecure: true

deploymentMode: SimpleScalable

backend:
  replicas: 2
read:
  replicas: 2
write:
  replicas: 3 # To ensure data durability with replication

# Enable minio for storage
minio:
  enabled: true

gateway:
  service:
    type: LoadBalancer

create namespace

kubectl create ns monitoring

deploy

helm install loki grafana/loki -n monitoring -f values.yaml

기본 구성으로 배포를 하고보니 리소스 이슈가 발생한다.

loki-chunks-cache는 기본 리소스 요구가 memory가 9gb로 설정되어 높게되어있다.
loki-write는 replicas가 기본 3인데 podAntiAffinity가 있어서 2개 워커노드로 운영시 1개가 pending으로 동작한다.

아래 내용으로 변경

loki:
  schemaConfig:
    configs:
      - from: "2025-11-08"
        store: tsdb
        object_store: s3
        schema: v13
        index:
          prefix: loki_index_
          period: 24h
  ingester:
    chunk_encoding: snappy
  querier:
    # Default is 4, if you have enough memory and CPU you can increase, reduce if OOMing
    max_concurrent: 4
  pattern_ingester:
    enabled: true
  limits_config:
    allow_structured_metadata: true
    volume_enabled: true

  storage:
    type: s3
    bucketNames:
      chunks: loki-chunks
      ruler: loki-ruler
      admin: loki-admin
    s3:
      endpoint: minio.default.svc.cluster.local:9000
      region: us-east-1
      accessKeyId: minioadmin
      secretAccessKey: minioadmin
      insecure: true

deploymentMode: SimpleScalable

backend:
  replicas: 2
read:
  replicas: 2
write:
  #replicas: 3 # To ensure data durability with replication
  replicas: 2 # To ensure data durability with replication

# 리소스 조절
chunksCache:
  enabled: true
  replicas: 1
  persistence:
    enabled: false
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 500m
      memory: 256Mi

# Enable minio for storage
minio:
  enabled: true

gateway:
  service:
    type: LoadBalancer

재배포

helm upgrade loki grafana/loki -n monitoring -f values.yaml

이제 loki엔드포인트가 생성되었으니 promtail로 돌아가 배포해보도록 한다.

참고로 Minio에 로그인을해서 Access Key를 생성하고 Loki연결부분을 교체해서 helm 재배포를 해야한다.

ingress

위 템플릿으로 배포하면 ingress가 빠져있다.
helm에 구성해도되고 직접 구성해도된다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: loki-minio-console
  namespace: monitoring
spec:
  ingressClassName: nginx
  rules:
    - host: minio.tk8s.test       # 원하는 도메인 이름으로 교체
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: loki-minio-console
                port:
                  number: 9001
  tls:
  - hosts:
    - minio.tk8s.test
    secretName: ssl-common
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: loki-ingress
  namespace: monitoring
spec:
  ingressClassName: nginx
  rules:
    - host: loki.tk8s.test
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: loki-gateway
                port:
                  number: 80
  tls:
  - hosts:
    - loki.tk8s.test
    secretName: ssl-common

Loki 삭제

helm list -n monitoring
helm uninstall loki -n monitoring

pvc

kubectl delete pvc -n monitoring -l app.kubernetes.io/name=loki

pv

kubectl get pv | grep monitoring | awk '{print $1}' | xargs kubectl delete pv

MinIO

배포를 하면 기본은 MinIO의 ingress정보가 없다. 생성해서 붙여주면된다.

Grafana

설정 항목설명
NameLoki원하는 이름
URLhttps://loki.tk8s.testLoki Ingress 주소
AccessServer (Default)Grafana 서버에서 Loki로 직접 요청
Custom HTTP HeadersX-Scope-OrgID: default(multi-tenancy 활성화된 Loki에서 필수)
Skip TLS Verify✅ (선택)자체 서명 인증서인 경우 반드시 켜기
Version자동 인식Loki 3.5.x 확인 가능

Loki배포할때 auth_enabled기본값이 true로 되어있으므로 Headers에 넣어주도록 한다.

promtail configmap과 맞춰줄것.


테스트 하면서 구조가 바뀔수도 있다. 다음은 tempo, mimir를 해봐야할듯…클러스터도 정리를 좀 해야되고..

Prometheus → Thanos → Cortex → Mimir