개요
바로 이전 게시글에서 ELK + Filebeat로 로그 관리 시스템 구축을 진행했었다.
개인용 서버에서 무거운 ELK 스택을 쓸 이유가 굳이 있을까? 라는 생각이 들었다.
그리고 ELK 스택은 무엇보다 러닝 커브가 좀 있다.
노드 설정이나 logstash의 파이프 라이닝 등의 여러 가지 복잡한 설정들로 인해 시스템 구축이 그리 쉬운 편은 아닌 것 같다.
그렇게 해서 찾게된 것이 Grafana
, Promtail
, Loki
이다.
이번 포스팅에서는 grafana, promtail, loki를 사용해 로그 시스템을 구축해보려고 한다.
시스템 아키텍처
- Promtail: 로그가 발생하는 서버/애플리케이션에 에이전트로 설치된다. 로그 파일을 감시하고 레이블을 첨부한 후 Loki로 전송함.
- Loki: Promtail로부터 로그를 수신하여 저장하는 로그 집계 시스템이다. Prometheus와 유사하게 레이블을 기반으로 로그를 인덱싱하여 효율적인 검색을 지원한다.
- Grafana: Loki에 저장된 로그를 시각화하고 대시보드를 구축하는 데 사용되는 데이터 시각화 도구.
Promtail 란?
Promtail은 로컬 로그의 내용을 grafana loki 또는 grafana cloud로 전송하는 로그 수집 에이전트이다.
일반적으로 모니터링해야 하는 모든 머신에 배포된다.
Promtail이 타겟(특정 파일)을 바라보고 있고, promtail 내의 레이블(label) 설정이 올바르게 되었다면 해당 파일을 읽기 시작한다.
promtail은 파일을 일괄적으로 읽고 충분한 데이터를 읽었을 경우(설정한 timeout
, memory
를 기준으로 판단) flush가 일어나 Loki로 로그가 적재된다.
또한, promtail은 다음의 API 엔드포인트를 통해 접근 가능하다.
GET /ready
promtail이 정상적으로 구동되며 target을 하나 이상 바라보고 있을 경우 200
으로 응답한다.
GET /metrics
prometheus로 전송할 수 있는 promtail의 메트릭 정보를 담고 있으며, 어떤 메트릭 정보가 노출되는지는 https://grafana.com/docs/loki/latest/operations/meta-monitoring/에서 확인 가능하다.
Loki 란?
로키는 Grafana lab에서 만든 오픈소스 프로젝트이다.
수평 확장이 가능하며 가용성이 높은 여러 테넌트(사용자 그룹)를 지원하는 로그 집계 시스템이다.
자원을 굉장히 효율적으로 사용하며 운영이 비교적 쉽게 설계되어 있다.
또한, Elasticsearch와는 다르게 log의 컨텐츠를 인덱싱 하지는 않고, 로그 시스템의 레이블(label)을 설정하여 로그 집계가 가능하다.
PLG 설치해보기!
이제 본격적으로 PLG(Promtail
, Loki
, Grafana
)를 설치해보도록 하자.
위 세 개의 서비스 중 Grafana
와 Loki
는 모니터링 서버에 Docker로 배포할 것이고,
Promtail
은 클라이언트쪽에 도커가 아닌 네이티브로 설치할 예정이다.
Grafana, Loki 설치하기
- 해당 작업은 모니터링 서버에서 진행됩니다.
- Grafana: https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/
- Loki: https://grafana.com/docs/loki/latest/setup/install/docker/
프로젝트 디렉토리를 생성해주자.
mkdir -p docker-log/loki/{config,data}
도커 컴포즈 작성
docker-log
하위에 docker-compose.yaml
파일의 내용을 아래와 같이 작성해주자.
networks:
log-net:
driver: bridge
external: false
volumes:
grafana-data: {}
services:
grafana:
image: grafana/grafana
container_name: grafana
restart: always
environment:
- GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-worldmap-panel # 필요시 플러그인 설치
# - GF_SECURITY_ADMIN_USER=admin # 기본값 admin
# - GF_SECURITY_ADMIN_PASSWORD=yoursecurepassword # 초기 관리자 비밀번호 설정 권장
volumes:
- grafana-data:/var/lib/grafana
ports:
- "3000:3000"
networks:
- log-net
loki:
image: grafana/loki
container_name: loki
restart: always
volumes:
- ./loki/config/loki-config.yaml:/etc/loki/loki-config.yaml # Loki 설정 파일 마운트
- ./loki/data:/loki # Loki 데이터 저장 볼륨 (청크, 인덱스 등)
ports:
- "3100:3100"
networks:
- log-net
Loki 설정 파일 업데이트
- ./loki/config/loki-config.yml
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
instance_addr: 127.0.0.1
path_prefix: /loki # docker-compose.yml에서 ./loki/data 와 매핑된 경로
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
schema_config:
configs:
- from: 2020-10-24 # Loki 설치 날짜보다 이전으로 설정 권장
store: boltdb-shipper
object_store: filesystem
schema: v11 # 또는 v12, v13 등 최신 Loki 버전에 맞는 스키마 확인
index:
prefix: index_
period: 24h
ruler:
alertmanager_url: http://localhost:9093 # 필요시 Alertmanager 설정
compactor:
working_directory: /loki/compactor # path_prefix 하위 경로
shared_store: filesystem
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150
# 테이블 기반 보존 기간 설정 (Loki 2.0+ 권장)
# table_manager:
# retention_deletes_enabled: true
# retention_period: 168h # 7일 (7 * 24h)
ingester:
lifecycler:
address: 127.0.0.1
ring:
kvstore:
store: inmemory
replication_factor: 1
chunk_idle_period: 1h
max_chunk_age: 1h
chunk_target_size: 1048576
chunk_retain_period: 30s
max_transfer_retries: 0
distributor:
ring:
kvstore:
store: inmemory
주의: path_prefix
, chunks_directory
, rules_directory
, working_directory
는 docker-compose.yml
에서 loki
서비스의 볼륨으로 마운트한 /loki
디렉토리 내부 경로를 사용함.
Promtail 설치하기
이번 실습에서는 Promtail
은 도커가 아닌 네이티브로 설치할 예정입니다.
도커 또는 APT
나 RPM
패키지 매니저로 설치하시고 싶다면 아래 링크를 참조해주세요.
도커로 설치하는 경우
APT or RPM 패키지 매니저로 설치하는 경우
그럼 바로 설치해볼게요. 설치 환경은 Ubuntu 22.04
입니다.
Promtail 설치 방법이 시간이 지남에 따라 바뀔 수 있으므로, 링크를 참고해주세요.
# 최신 버전 확인 후 URL 변경 가능
PROMTAIL_VERSION="3.1.2" # 예시 버전, Loki와 동일 버전 권장
wget https://github.com/grafana/loki/releases/download/v$PROMTAIL_VERSION/promtail-linux-amd64.zip
unzip promtail-linux-amd64.zip
sudo mv promtail-linux-amd64 /usr/local/bin/promtail
sudo chmod +x /usr/local/bin/promtail
Promtail 설정 파일 업데이트
Promtail 설정 파일또한 YAML
파일을 사용합니다.
sudo mkdir -p /etc/promtail
sudo nano /etc/promtail/promtail-config.yaml
다음 내용을 promtail-config.yaml
파일에 추가해주세요.
server:
http_listen_port: 9080 # Promtail HTTP 서버 포트 (메트릭 제공 등)
grpc_listen_port: 0 # 사용 안 함 (0으로 설정)
positions: # Promtail이 마지막으로 읽은 로그 파일의 위치를 저장하는 파일 경로
filename: /tmp/positions.yaml # 실제 운영 환경에서는 변경 권장 (예: /var/promtail/positions.yaml)
clients: # Loki 서버 정보
- url: http://<LOKI_서버_IP>:3100/loki/api/v1/push # Loki 푸시 API 엔드포인트
scrape_configs: # 로그 수집 대상 설정
- job_name: [job_name] # 작업 이름 (임의 지정 가능 Ex. system)
static_configs:
- targets:
- localhost # Promtail이 실행되는 호스트
labels: # 이 작업에서 수집되는 모든 로그에 적용될 기본 레이블
job: [label_name] # 작업 레이블 (Grafana에서 필터링 시 사용), Ex. varlogs
__path__: /var/log/*log # 수집할 로그 파일 경로 패턴 (예: /var/log/syslog, /var/log/auth.log 등)
- job_name: myapp
static_configs:
- targets:
- localhost
labels:
job: applogs
app: my-sample-app
__path__: /path/to/your/app/logs/*.log # 애플리케이션 로그 경로 지정
# pipeline_stages: # 로그 파싱 및 레이블 추출 등 고급 설정 (선택 사항)
# - docker: {} # Docker 로그 형식일 경우
# - json:
# expressions:
# level: level
# message: message
# - labels:
# level:
설정 알아보기
positions.filename
:Promtail
이 로그 파일을 어디까지 읽었는지 기록하는 파일입니다.Promtail
재시작 시 이 파일을 참조하여 중복 수집을 방지합니다./tmp
대신 영구적인 경로를 사용하세요.clients.url
:Loki
서버의 푸시 API 엔드포인트 주소입니다. Loki 설정의http_listen_port
(기본값 3100)를 따릅니다.scrape_configs
: 어떤 로그를 수집할지 정의하는 부분입니다.job_name
: 수집 작업의 이름입니다. Grafana에서 이 이름으로 필터링할 수 있습니다.static_configs
: 정적으로 로그 수집 대상을 지정합니다.targets
: Promtail이 로그를 수집할 대상 호스트입니다. 로컬 파일을 수집하므로 localhost로 설정합니다.labels
: 이 job에서 수집되는 모든 로그에 첨부될 기본 레이블입니다.job
: 작업의 종류를 나타내는 레이블입니다.__path__
: Promtail이 감시할 로그 파일의 경로 패턴입니다. 와일드카드(*)를 사용할 수 있습니다. 매우 중요합니다.
pipeline_stages
(선택 사항): 로그 내용을 파싱하여 추가적인 레이블을 추출하거나 로그 메시지를 변형하는 등의 고급 처리를 할 수 있습니다. 예를 들어 JSON 형식의 로그에서 특정 필드를 레이블로 만들 수 있습니다.
Promtail 서비스 등록 (systemd)
sudo vim /etc/systemd/system/promtail.service
다음 내용을 promtail.service
파일에 추가해주세요.
[Unit]
Description=Promtail agent for Loki
Wants=network-online.target
After=network-online.target
[Service]
User=root # 또는 promtail 전용 유저 생성 후 지정
Group=root # 또는 promtail 전용 그룹 생성 후 지정
Type=simple
ExecStart=/usr/local/bin/promtail -config.file /etc/promtail/promtail-config.yaml
[Install]
WantedBy=multi-user.target
마지막으로 서비스 시작 및 활성화를 해줍시다.
sudo systemctl daemon-reload
sudo systemctl enable promtail
sudo systemctl start promtail
sudo systemctl status promtail
로깅 테스트
Grafana 웹 UI에 접속하여 Data Source
로 이동해 Loki를 추가해줍시다.
Expolore
-> Logs
로 들어가서 Loki로 넘어온 로그 데이터를 확인해보자!
job
, filename
별로 필터를 걸어서 로그를 확인할 수 있다.
알림 도입하기
Grafana의 경우 내장된 모듈을 통해 자체적인 알림 시스템도 제공한다.
가령, 특정 서버의 메트릭 중 CPU
이용률이 특정 % 이상으로 몇 분 이상 지속될 시 알림을 전송한다던지,
아니면 특정 서버의 로그 중 ERROR
라는 문구가 발생한 경우 알림을 전송한다던지 등등등 ...
필자는 Discord로 알림을 받을 수 있도록 설정해보려고 한다.
Discord의 Incomming Webhook
앱을 사용하여 알림 받을 채널의 주소를 확보한 뒤,
Contact Points
에 접근해 Webhook URL
만 입력해두면 알림을 전송할 곳에 대한 설정은 완료된다!
Alerting
- Alert rules
에 진입하여 New rule
버튼을 클릭하여 룰 생성 페이지로 진입하고
알림 조건을 설정해보자!
1. 쿼리 정의하기
위 쿼리문은 1분 이내에 들어온 로그들 중 error
발생 횟수를 알아내는 쿼리문이다.
2. 알람 조건 설정하기
이제 알림 행동에 대한 지침을 설정해보자.
오류가 1분 이내에 5번이상 발생하면 알람이 오도록 설정해두었다.
3. 알람 규칙 설정하기
Evaluataion Group 만들기
설정한 알림 규칙을 검사할 주기를 설정할 수 있다.
현재 1분 동안 발생한 오류를 검사하고 있으므로 1분으로 설정하였다.
Pending period 설정하기
알람 조건을 만족했을 때 보류 시간을 지정할 수 있다. 해당 시간이 지나도 여전히 조건을 만족하면 알림을 보낸다.
오류에 대해서는 굳이 보류 시간을 정하지 않아도 될 것 같아 None
으로 설정하였다.
알림 테스트
이제 클라이언트쪽에서 error
로그를 유도하여 알림이 정상적으로 발생하는지 테스트해보겠다.
먼저 아래 코드를 실행해 ERROR
로깅을 발생시켜주자.
for i in $(seq 1 10); do logger "ERROR: Critical test$i"; done
그리고 디스코드를 확인해보면 다음과 같은 알림을 볼 수 있을거다.
컨테이너 로그도 모니터링에 추가하기 (도커 환경 기반)
이 챕터는 Promtail
을 도커 기반 환경에서 배포했을 때, 컨테이너 로그를 Job에 등록하기 위해서 어떤 과정을 거쳐야하는지 알아보기 위한 챕터라고 생각해주세요.
단순히 컨테이너 로그만을 모니터링 하려면 promtail-config.yaml
에 다음과 같이 job을 등록해주면 된다.
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://모니터링서버IP:3100/loki/api/v1/push #모니터링 서버 IP
scrape_configs:
- job_name: docker-logs
static_configs:
- targets:
- localhost # 로그를 수집할 곳
labels:
job: docker-logs
__path__: /var/lib/docker/containers/*/*-json.log
이 때 만약에 클라이언트쪽에서 Promtail을 도커로 설치했다면 다음과 같이 도커 컴포즈를 수정해줘야 한다.
services:
promtail:
image: grafana/promtail:latest
container_name: promtail
restart: always
volumes:
- type: bind
source: /var/log
target: /mnt/logs
# ================
# 호스트의 도커 컨테이너 폴더를 마운트
- type: bind
source: /var/lib/docker/containers
target: /var/lib/docker/containers
# ================
- type: bind
source: promtail-config.yml
target: /etc/promtail/config.yml
command: -config.file=/etc/promtail/config.yml
위처럼 설정하고 Grafana로 다시 돌아가 Logs를 확인해보면?
음 .. 근데 컨테이너 ID로 되어있어서 어떤 컨테이너에 대한 로그인지 감이 안온다!
서비스명으로 컨테이너 Labels 불러오기!
위 사진처럼 컴포즈 서비스명, 도커 컨테이너명으로 Labels를 불러오는 방법에 대해 알아보자!
"서비스별 로깅 옵션(settings.tag) 없이 불필요한 로깅을 제거하고, 대신에 Docker가 스택, 서비스, Compose 배포 시 컨테이너에 자동으로 붙이는 라벨(com.docker.*
)을 이용하는 방법이 있다."
위 링크에 서술된 방법을 그대로 따라해볼거야!
먼저 클라이언트쪽에서 /etc/docker/daemon.json
파일을 다음과 같이 수정해야해!
{
"log-driver": "json-file",
"log-opts": {
"labels-regex": "^.+"
}
}
이렇게 설정한 후에는 다음 명령어로 새로운 라벨들이 로그에 추가되었는지 확인해볼 수 있어!
docker logs <container_id> --details
그리고 라벨이 로그에 나타나는 것을 확인했다면 아래와 같은 방식으로 기존의 promtail-config.yaml
을 수정해줘야 해!
server:
http_listen_address: 0.0.0.0
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: http://<loki_server_ip>:3100/loki/api/v1/push
scrape_configs:
- job_name: containers
static_configs:
- targets:
- localhost
labels:
job: containerlogs
__path__: /var/lib/docker/containers/*/*log
pipeline_stages:
- json:
expressions:
log: log
stream: stream
time: time
tag: attrs.tag
compose_project: attrs."com.docker.compose.project"
compose_service: attrs."com.docker.compose.service"
stack_name: attrs."com.docker.stack.namespace"
swarm_service_name: attrs."com.docker.swarm.service.name"
swarm_task_name: attrs."com.docker.swarm.task.name"
- regex:
expression: "^/var/lib/docker/containers/(?P<container_id>.{12}).+/.+-json.log$"
source: filename
- timestamp:
format: RFC3339Nano
source: time
- labels:
stream:
container_id:
tag:
compose_project:
compose_service:
stack_name:
swarm_service_name:
swarm_task_name:
- output:
source: log
옵션 설명
- 로그 파일 경로에서
container_id
를 추출하여 라벨로 설정함. - 시간(
timestamp
)을 올바르게 설정함. “tag”
를 그대로 전달하여 스택에서 정의된 추가 필터링에 사용할 수 있게 함.Docker Compose
로 배포된 컨테이너에 대해compose_project
,compose_service
라벨을 생성함.- Swarm 서비스에 대해서는
swarm_service_name
,swarm_task_name
라벨을 생성함. - 스택으로부터 배포된 컨테이너에는
stack_name
라벨을 부여함.
이렇게 변경된 설정으로 다시 배포한 후에 Grafana의 Logs 탭으로 들어가서 다시 확인해보면!
이렇게 깔끔하게 잘 보이게 될거야!
트러블슈팅
Structured Metadata 에러
Loki 컨테이너가 정상적으로 실행되지 않는 문제가 발생해
컨테이너 로그를 조회해보니 다음과 같은 에러 로그가 발생하였다.
loki | level=error ts=2025-02-05T12:39:59.903864665Z caller=main.go:70 msg="validating config" err="MULTIPLE CONFIG ERRORS FOUND, PLEASE READ CAREFULLY\nCONFIG ERROR: schema v13 is required to store Structured Metadata and use native OTLP ingestion, your schema version is v11. Set allow_structured_metadata: false in the limits_config section or set the command line argument -validation.allow-structured-metadata=false and restart Loki. Then proceed to update to schema v13 or newer before re-enabling this config, search for 'Storage Schema' in the docs for the schema update procedure\nCONFIG ERROR: tsdb index type is required to store Structured Metadata and use native OTLP ingestion, your index type is boltdb-shipper (defined in the store parameter of the schema_config). Set allow_structured_metadata: false in the limits_config section or set the command line argument -validation.allow-structured-metadata=false and restart Loki. Then proceed to update the schema to use index type tsdb before re-enabling this config, search for 'Storage Schema' in the docs for the schema update procedure"
loki | level=error ts=2025-02-05T12:40:06.624937114Z caller=main.go:70 msg="validating config" err="MULTIPLE CONFIG ERRORS FOUND, PLEASE READ CAREFULLY\nCONFIG ERROR: schema v13 is required to store Structured Metadata and use native OTLP ingestion, your schema version is v11. Set allow_structured_metadata: false in the limits_config section or set the command line argument -validation.allow-structured-metadata=false and restart Loki. Then proceed to update to schema v13 or newer before re-enabling this config, search for 'Storage Schema' in the docs for the schema update procedure\nCONFIG ERROR: tsdb index type is required to store Structured Metadata and use native OTLP ingestion, your index type is boltdb-shipper (defined in the store parameter of the schema_config). Set allow_structured_metadata: false in the limits_config section or set the command line argument -validation.allow-structured-metadata=false and restart Loki. Then proceed to update the schema to use index type tsdb before re-enabling this config, search for 'Storage Schema' in the docs for the s...
해당 에러 로그의 원인은 다음과 같다.
Loki에서 Structured Metadata(OTLP 등) 기능을 사용하려면 설정 및 스토리지 스키마가 v13 이상,
인덱스 타입이 tsdb여야 한다.
지금 설정에는 스키마가 v11
이고 인덱스 타입이 boltdb-shipper
로 되어 있기 때문에,
로키가 “OTLP/Structured Metadata를 지원할 수 없는 설정이니 끄거나 스키마를 업데이트해라”라고 에러를 뿜고 있는 것이다.
해결 방법 1: Structured Metadata 기능을 사용하지 않는 경우
만약 OTLP(Trace 등) 지원이나 Structured Metadata가 필요 없다면,
다음과 같이 limits_config
섹션에 allow_structured_metadata: false
를 명시해주자.
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
ruler:
alertmanager_url: http://localhost:9093
limits_config:
allow_structured_metadata: false
이렇게 설정해주면, Loki가 더 이상 Structured Metadata 관련 에러를 내지 않게 된다.
참고로 -validation.allow-structured-metadata=false
플래그를 Loki 실행 시점에 전달해주는 방식도 가능하지만,
일반적으로는 config에 명시하는 것이 추후 유지관리 시 편하다.