개요
바로 이전 게시글에서 ELK + Filebeat로 로그 관리 시스템 구축을 진행했었다.
개인용 서버에서 무거운 ELK 스택을 쓸 이유가 굳이 있을까? 라는 생각이 들었다.
그리고 ELK 스택은 무엇보다 러닝 커브가 좀 있다.
노드 설정이나 logstash의 파이프 라이닝 등의 여러 가지 복잡한 설정들로 인해 시스템 구축이 그리 쉬운 편은 아닌 것 같다.
그렇게 해서 찾게된 것이 Grafana
, Promtail
, Loki
이다.
이번 포스팅에서는 grafana, promtail, loki를 사용해 로그 시스템을 구축해보려고 한다.
시스템 아키텍처
- 로그 파일들을 Promtail에서 읽어와 Loki로 전송
- Loki에서 로그들을 수집 및 저장하고 Grafana로 전송
- Grafana에서 로그들을 시각화
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)을 설정하여 로그 집계가 가능하다.
모니터링 서버
Grafana
와,Loki
는 모니터링 서버에 Docker로 배포된다.Promtail
은 클라이언트 서버 또는 PC에 설치된다.
[Docker] 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
도커 컴포즈 작성
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
volumes:
- type: volume
source: grafana-data
target: /var/lib/grafana
ports:
- "3000:3000"
networks:
- log-net
loki:
image: grafana/loki
container_name: loki
restart: always
volumes:
- type: bind
source: ./loki/config/local-config.yml
target: /etc/loki/local-config.yml
ports:
- "3100:3100"
networks:
- log-net
Loki 설정 파일 업데이트
- ./loki/config/local-config.yml
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
[Client] Promtail 설치
- 이번 실습에서는 Docker로 Promtail을 띄울 것임.
- 클라이언트 환경에 도커가 설치되어 있어야 함.
도커로 설치하는 경우
https://grafana.com/docs/loki/latest/send-data/promtail/installation/
APT or RPM 패키지 매니저로 설치하는 경우
https://grafana.com/docs/loki/latest/setup/install/local/#install-using-apt-or-rpm-package-manager
클라이언트에서 docker-promtail
이라는 디렉토리를 생성해주자.
mkdir -p docker-promtail
도커 컴포즈 작성
docker-compose.yaml
파일을 아래와 같이 작성
services:
promtail:
image: grafana/promtail:latest
container_name: promtail
restart: always
volumes:
- type: bind
source: /var/log # 원하는 로그 경로를 명시
target: /mnt/logs
- type: bind
source: promtail-config.yml
target: /etc/promtail/config.yml
command: -config.file=/etc/promtail/config.yml
promtail
의 경우 외부 접속을 허용하지 않을 것이기 때문에 port
속성은 따로 명시하지 않았음.
Promtail 설정 파일 업데이트
- ./promtail-config.yml
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: [job name]
static_configs:
- targets:
- localhost # 로그를 수집할 곳
labels:
job: [label name]
__path__: [log 파일 경로]
clients
의 url은 Promtail이 로그 데이터를 전송할 Loki 서버의 주소를 기입해주면 된다.
또한, 나는 하나의 Job만 등록시켰지만
로그별로 job을 여러개 등록시킬 수도 있다.
그리고 __path__
의 경우에는 도커 컴포즈 작성에서 작성했었던 타겟 경로의 /mnt/logs
로 잡아줘야 한다.
(호스트 /var/log
디렉토리의 파일들이 컨테이너의 /mnt/logs
로 바인드 되니까.)
그라파나에서 로그 확인하기
Expolore
-> Logs
로 들어가서 Loki로 넘어온 로그 데이터를 확인해보자!
로그가 정상적으로 보인다.
트러블슈팅
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에 명시하는 것이 추후 유지관리 시 편하다.
컨테이너 로그도 모니터링에 추가하기!
이번에는 컨테이너 로그를 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 탭으로 들어가서 다시 확인해보면!
이렇게 깔끔하게 잘 보이게 될거야!