이번 포스트에서는 Grafana Alloy를 활용해 애플리케이션의 메트릭(metrics)과 로그(logs)를 수집하는 방법에 대해 정리하였다.
Grafana Alloy는 Grafana Labs에서 개발한 멀티 소스 원격 측정 수집기(Telemetry Collector)다.
Prometheus, Loki, Tempo, OpenTelemetry 등 다양한 원격 측정 데이터를 하나의 바이너리로 수집할 수 있는 통합형 오픈소스 에이전트다.
Prometheus, Loki, Tempo 등을 개별적으로 운영할 필요 없이 Alloy 하나로 통합 관리가 가능하다.
필요한 기능만 선택적으로 구성할 수 있으며, 모듈 간 연결도 명확하게 설정 가능하다.
구성 파일이 직관적이며 확장성이 뛰어나다.
Alloy는 단일 바이너리로 동작하므로 설치 및 배포가 매우 간편하다.
설정 변경 시에도 재시작 없이 적용할 수 있어 운영 중에도 안정적으로 설정을 변경할 수 있다.
Grafana Cloud를 비롯하여 Prometheus, Loki, Tempo, OTLP 등 다양한 백엔드 시스템과 통합이 가능하다.
SaaS 환경에서도 손쉽게 연동할 수 있다.
수집기 자체의 상태, 성능, 오류 등의 정보를 메트릭으로 제공하여, Alloy 자체도 Prometheus를 통해 모니터링할 수 있다.
이전에 구성했던 CDC 파이프라인에서 사용된 인스턴스와 애플리케이션들을 Grafana Alloy를 이용해 모니터링해보았다.
docker-compose.yaml
services:
zookeeper:
image: confluentinc/cp-zookeeper:7.4.0
container_name: zookeeper
ports:
- "9998:9998"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
kafka:
image: confluentinc/cp-kafka:7.4.0
container_name: kafka
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
mysql:
image: mysql:8.0
container_name: mysql
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: user
TZ: Asia/Seoul
volumes:
- ./mysql/db:/var/lib/mysql
command: [
"--server-id=1",
"--log-bin=mysql-bin",
"--binlog-format=ROW",
"--binlog-row-image=FULL"
]
mongodb:
image: mongodb/mongodb-community-server:latest
container_name: mongodb
ports:
- "27017:27017"
environment:
MONGODB_INITDB_ROOT_USERNAME: root
MONGODB_INITDB_ROOT_PASSWORD: password
restart: unless-stopped
connect:
image: debezium/connect:2.5
container_name: connect
depends_on:
- kafka
- mysql
ports:
- "8083:8083"
- "9999:9999"
environment:
BOOTSTRAP_SERVERS: kafka:9092
GROUP_ID: 1
CONFIG_STORAGE_TOPIC: connect-configs
OFFSET_STORAGE_TOPIC: connect-offsets
STATUS_STORAGE_TOPIC: connect-status
CONNECT_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter
CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
CONNECT_REST_ADVERTISED_HOST_NAME: connect
CONNECT_PLUGIN_PATH: /kafka/connect,/usr/share/java
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
container_name: elasticsearch
ports:
- "9200:9200"
- "9300:9300"
environment:
discovery.type: single-node
ES_JAVA_OPTS: -Xms16g -Xmx16g
ingest.geoip.downloader.enabled: false
xpack.security.enabled: false
xpack.security.transport.ssl.enabled: false
xpack.security.http.ssl.enabled: false
xpack.monitoring.collection.enabled: true
ulimits:
memlock:
soft: -1
hard: -1
kibana:
image: docker.elastic.co/kibana/kibana:8.12.0
container_name: kibana
depends_on:
- elasticsearch
ports:
- "5601:5601"
environment:
ELASTICSEARCH_HOSTS: "http://elasticsearch:9200"
XPACK_SECURITY_ENABLED: "false"
alloy:
image: grafana/alloy:latest
container_name: alloy
privileged: true
volumes:
- ./alloy:/etc/alloy
- /var/run/docker.sock:/var/run/docker.sock
- /:/rootfs:ro
- /proc:/rootfs/proc:ro
- /sys:/rootfs/sys:ro
- /var/run/docker.sock:/rootfs/var/run/docker.sock:ro
- /run/udev:/run/udev:ro
ports:
- "12345:12345"
command: >
run --server.http.listen-addr=0.0.0.0:12345
--storage.path=/var/lib/alloy/data
/etc/alloy/config/config.alloy
mem_limit: 512m
cpus: 1.0
restart: unless-stopped
grafana:
image: grafana/grafana
container_name: grafana
ports:
- 3000:3000
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning/:/etc/grafana/provisioning/
restart: always
depends_on:
- alloy
- loki
prometheus:
image: prom/prometheus
container_name: prometheus
volumes:
- ./prometheus:/etc/prometheus
- prometheus-data:/prometheus
ports:
- 9090:9090
command:
- '--web.enable-remote-write-receiver'
- '--web.enable-admin-api'
- '--storage.tsdb.path=/prometheus'
- '--config.file=/etc/prometheus/prometheus.yml'
restart: always
loki:
image: grafana/loki:3.4.1
container_name: loki
user: "$UID:$GID"
ports:
- 3100:3100
command: -config.file=/etc/loki/local-config.yaml
volumes:
- ./loki:/etc/loki
- ./loki-data/tsdb:/tmp/loki/tsdb
- ./loki-data/chunks:/tmp/loki/chunks
- ./loki-data/rules:/tmp/loki/rules
- ./loki-data/compactor:/tmp/loki/compactor
restart: always
kafka-ui:
image: provectuslabs/kafka-ui:latest
container_name: kafka-ui
ports:
- "8080:8080"
environment:
- KAFKA_CLUSTERS_0_NAME=CDC
- KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092
- KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper:2181
depends_on:
- kafka
- zookeeper
각 서비스는 다음과 같은 기능을 담당한다:
Grafana Alloy에 대한 설정은 다음과 같이 설정했다:
privileged
: 호스트의 시스템 리소스에 대한 접근 권한을 부여 여부mem_limit
: 최대 사용 가능한 메모리 용량cpus
: 최대 사용 가능한 CPU 코어volumes
: 다음과 같은 이유로 alloy 컨테이너에 마운트 했다../alloy
-> /etc/alloy
: 구성 파일 위치 (예: config.alloy)/var/run/docker.sock
-> /var/run/docker.sock
: Docker 데몬 접근 (컨테이너 메트릭 수집 등)/
-> /rootfs:ro
: 전체 루트 파일 시스템을 읽기 전용으로 마운트→ node-level 시스템 정보 수집/proc
-> /rootfs/proc:ro
: 프로세스 관련 정보 접근 (/proc/meminfo
, /proc/stat
)/sys
-> /rootfs/sys:ro
: 커널/하드웨어 정보 수집 (CPU, Memory 등)/var/run/docker.sock
-> /rootfs/var/run/docker.sock:ro
: Docker 메타 정보 수집 (read-only)/run/udev
-> /run/udev:ro
: 장치 정보 (udev) 접근, 하드웨어 메트릭 수집용command
: Grafana Alloy를 실행하기 위한 명령어run
: Alloy 실행 명령--server.http.listen-addr=0.0.0.0:12345
: Alloy의 HTTP 서버를 모든 인터페이스에서 12345 포트로 개방--storage.path=/var/lib/alloy/data
: 내부에서 사용하는 임시/지속 데이터 저장 경로/etc/alloy/config/config.alloy
: 실제 실행할 설정 파일 경로Alloy 컨테이너는 ./alloy
디렉토리를 /etc/alloy
로 마운트하여 구성 파일을 외부에서 관리할 수 있도록 하였다.
설정 파일(config.alloy
)이 없으면 Alloy는 정상적으로 실행되지 않으므로 반드시 작성해야 한다.
필자는 docker 환경에서 사용하기 위한 설정을 구성하였다.
공식 문서를 참고하면 다른 환경에 대한 설정에 대한 설명도 있으니 참고하면 좋다.
config.alloy
// livedebugging 설정
livedebugging {
enabled = true
}
// Logging 설정
logging {
level = "info"
format = "logfmt"
}
// Docker 컨테이너 메타데이터 수집 (모든 컨테이너 대상)
discovery.docker "docker" {
host = "unix:///var/run/docker.sock"
}
// 컨테이너 이름에서 서비스 이름 추출 (Loki 로그용)
discovery.relabel "docker_relabel" {
targets = discovery.docker.docker.targets
rule {
source_labels = ["__meta_docker_container_name"]
regex = "/(.*)"
target_label = "service_name"
}
}
// Docker 로그 수집 및 Loki로 전송
loki.source.docker "docker_logs" {
host = "unix:///var/run/docker.sock"
targets = discovery.relabel.docker_relabel.output
labels = { platform = "docker" }
forward_to = [loki.write.DB_Server.receiver]
}
// Loki에 로그 전송
loki.write "server_name" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
// Prometheus로 지표 전송
prometheus.remote_write "server_name" {
endpoint {
url = "http://prometheus:9090/api/v1/write"
}
}
// MySQL metrics 추출
prometheus.exporter.mysql "mysql_exporter" {
data_source_name = "root:password@(mysql:3306)/"
enable_collectors = ["info_schema.processlist"]
}
// MongoDB metrics 추출
prometheus.exporter.mongodb "mongodb_exporter" {
mongodb_uri = "mongodb://root:password@mongodb:27017"
}
// Node metrics 추출
prometheus.exporter.unix "node_exporter" {
rootfs_path = "/rootfs"
procfs_path = "/rootfs/proc"
sysfs_path = "/rootfs/sys"
}
// Kafka metrics 추출
prometheus.exporter.kafka "kafka_exporter" {
kafka_uris = ["kafka:9092"]
}
// ElasticSearch metrics 추출
prometheus.exporter.elasticsearch "elasticsearch_exporter" {
address = "http://elasticsearch:9200"
}
// Zookeeper JMX Exporter 지표 수집
prometheus.scrape "zookeeper_metrics" {
targets = [{ __address__ = "zookeeper:9998" }]
forward_to = [prometheus.remote_write.server_name.receiver]
job_name = "zookeeper"
}
// Debezium JMX Exporter 지표 수집
prometheus.scrape "debezium_metrics" {
targets = [{ __address__ = "connect:9999" }]
forward_to = [prometheus.remote_write.server_name.receiver]
job_name = "connect"
}
// MySQL Exporter 지표 수집
prometheus.scrape "mysql_metrics" {
targets = prometheus.exporter.mysql.mysql_exporter.targets
scrape_interval = "15s"
forward_to = [prometheus.remote_write.server_name.receiver]
}
// MongoDB Exporter 지표 수집
prometheus.scrape "mongodb_metrics" {
targets = prometheus.exporter.mongodb.mongodb_exporter.targets
scrape_interval = "15s"
forward_to = [prometheus.remote_write.server_name.receiver]
}
// Node Exporter 지표 수집
prometheus.scrape "node_metrics" {
targets = prometheus.exporter.unix.node_exporter.targets
scrape_interval = "15s"
forward_to = [prometheus.remote_write.server_name.receiver]
}
// Kafka Exporter 지표 수집
prometheus.scrape "kafka_metrics" {
targets = prometheus.exporter.kafka.kafka_exporter.targets
scrape_interval = "15s"
forward_to = [prometheus.remote_write.server_name.receiver]
}
// Elasticsearch Exporter 지표 수집
prometheus.scrape "elasticsearch_metrics" {
targets = prometheus.exporter.elasticsearch.elasticsearch_exporter.targets
scrape_interval = "15s"
forward_to = [prometheus.remote_write.server_name.receiver]
}
Prometheus는 Alloy에서 수집한 지표를 저장하고 Grafana를 통해 시각화하는 역할을 한다.
remote_write
설정을 통해 외부에서 전송된 지표를 수신할 수 있도록 하였다.
Prometheus 또한 설정 파일을 외부에서 관리할 수 있다.
proemtheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
remote_write: ## 해당 설정을 추가하여, 외부에서 metrics 수집한 정보를 저장할 수 있도록 한다.
- url: "http://localhost:9090/api/v1/write"
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
로그 수집은 Grafana Alloy에서 Loki로 전송되며, Loki는 해당 로그를 저장하고 Grafana를 통해 시각화할 수 있다.
Loki는 외부에서 로그를 Push 받을 수 있는 설정이 기본적으로 가능하므로 간단한 설정으로 운영할 수 있다.
필자는 다음과 같이 설정을 하였다.
Loki 또한 설정 파일을 외부에서 관리할 수 있다.
local-config.yaml
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9095
ingester:
lifecycler:
ring:
kvstore:
store: inmemory
replication_factor: 1
final_sleep: 0s
chunk_idle_period: 5m
max_chunk_age: 1h
chunk_target_size: 1048576
limits_config:
enforce_metric_name: false
reject_old_samples: true
reject_old_samples_max_age: 168h
max_streams_per_user: 10000
schema_config:
configs:
- from: 2022-01-01
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
storage_config:
boltdb_shipper:
active_index_directory: /tmp/loki/index
cache_location: /tmp/loki/cache
shared_store: filesystem
filesystem:
directory: /tmp/loki/chunks
compactor:
working_directory: /tmp/loki/compactor
shared_store: filesystem
ruler:
storage:
type: local
local:
directory: /tmp/loki/rules
rule_path: /tmp/loki/rules-temp
ring:
kvstore:
store: inmemory
enable_api: true
volumes:
- ./loki/data:/tmp/loki
모든 설정이 완료되면 다음 명령어로 컨테이너들을 실행할 수 있다.
sudo docker compose up -d
Grafana Alloy는 자체 웹 UI를 제공하여 구성된 모듈들을 시각적으로 확인할 수 있다.
Grafana Dashboard를 구성하면 실제로 메트릭과 로그가 정상적으로 수집되고 있는지 시각적으로 확인할 수 있다.
Grafana Alloy를 활용하면 기존 Prometheus, Loki, Tempo 등 개별 도구들을 하나의 수집기로 통합할 수 있어 매우 효율적인 운영이 가능하다.
Docker Compose 환경에서 손쉽게 설치 및 구성이 가능하며, Grafana를 통해 시각적으로 직관적인 대시보드까지 구성할 수 있다.
추가적인 구성 요소 및 기능은 공식 문서를 참고하면 더욱 다양한 활용이 가능하다.