๐Ÿณ Docker Compose๋ฅผ ํ™œ์šฉํ•œ Prometheus+Loki+Grafana ๊ตฌ์ถ• ๋ฐ ์„œ๋น„์Šค ์šด์˜

Dev96ยท2025๋…„ 3์›” 27์ผ
post-thumbnail

๐Ÿ“Œ ์›Œํฌ๋กœ๋“œ

1) docker-compse ์„ค์ •

services:

  prometheus:
    image: prom/prometheus:latest  												# Prometheus ์ตœ์‹  ์ด๋ฏธ์ง€ ์‚ฌ์šฉ
    container_name: prometheus     												# ์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„ ์ง€์ •
    ports:
      - "9090:9090"                												# ํ˜ธ์ŠคํŠธ ํฌํŠธ 9090 โ†’ ์ปจํ…Œ์ด๋„ˆ ํฌํŠธ 9090
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml   			# ํ˜ธ์ŠคํŠธ์˜ ์„ค์ • ํŒŒ์ผ์„ ์ปจํ…Œ์ด๋„ˆ๋กœ ๋งˆ์šดํŠธ   
    command:
      - --config.file=/etc/prometheus/prometheus.yml 							# ์„ค์ • ํŒŒ์ผ ๊ฒฝ๋กœ ์ง€์ • (์˜ต์…˜)                   
    networks:
      - prometheus               												# prometheus ๋„คํŠธ์›Œํฌ์— ์—ฐ๊ฒฐ
      
  loki:
    image: loki:latest            												# Loki ์ตœ์‹  ์ด๋ฏธ์ง€ ์‚ฌ์šฉ (๊ณต์‹ ์ด๋ฏธ์ง€๋Š” grafana/loki ์‚ฌ์šฉ ๊ถŒ์žฅ)
    container_name: loki          												# ์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„ ์ง€์ •
    user: "$UID:$GID"             												# ํ˜„์žฌ ์‚ฌ์šฉ์ž ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰ (๊ถŒํ•œ ๋ฌธ์ œ ๋ฐฉ์ง€)
    ports:
      - "3100:3100"               												# Loki ํฌํŠธ ๋งคํ•‘ (๊ธฐ๋ณธ ํฌํŠธ 3100)
    volumes:
      - ./loki/loki.yml:/etc/loki/local-config.yaml                   			# Loki ์„ค์ • ํŒŒ์ผ ๋งˆ์šดํŠธ       
      - ./loki:/var/loki    													# ๋กœ๊ทธ ๋ฐ์ดํ„ฐ ์ €์žฅ ๋””๋ ‰ํ† ๋ฆฌ ๋งˆ์šดํŠธ
    command: -config.file=/etc/loki/local-config.yaml 							# ์„ค์ • ํŒŒ์ผ ์ง€์ •                     
    networks:
      - loki                      												# loki ๋„คํŠธ์›Œํฌ์— ์—ฐ๊ฒฐ

  grafana:
    image: grafana:latest         												# Grafana ์ตœ์‹  ์ด๋ฏธ์ง€
    container_name: grafana      												# ์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„ ์ง€์ •
    user: "$UID:$GID"             												# ํ˜„์žฌ ์‚ฌ์šฉ์ž ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰
    ports:
      - "3000:3000"               												# Grafana ์›น ํฌํŠธ (๊ธฐ๋ณธ 3000)
    volumes:
      - ./grafana:/var/lib/grafana												# Grafana ๋ฐ์ดํ„ฐ ๋””๋ ‰ํ† ๋ฆฌ ๋งˆ์šดํŠธ (๋Œ€์‹œ๋ณด๋“œ, ์„ค์ • ๋“ฑ ์ €์žฅ)                             
    depends_on:
      - prometheus                												# Prometheus ์„œ๋น„์Šค๊ฐ€ ๋จผ์ € ์‹œ์ž‘๋˜๋„๋ก ์„ค์ •
      - loki                      												# Loki ์„œ๋น„์Šค๊ฐ€ ๋จผ์ € ์‹œ์ž‘๋˜๋„๋ก ์„ค์ •
    networks:
      - prometheus                												# Prometheus ๋„คํŠธ์›Œํฌ์— ์—ฐ๊ฒฐ (๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘์šฉ)
      - loki                      												# Loki ๋„คํŠธ์›Œํฌ์— ์—ฐ๊ฒฐ (๋กœ๊ทธ ์ˆ˜์ง‘์šฉ)
	
networks:																		# ์‚ฌ์šฉ์ž ์ •์˜ ๋„คํŠธ์›Œํฌ ์„ค์ •
  prometheus:
    driver: bridge                												# ๊ธฐ๋ณธ ๋ธŒ๋ฆฌ์ง€ ๋„คํŠธ์›Œํฌ ์‚ฌ์šฉ
  loki:
    driver: bridge               												# ๋กœ๊ทธ ๊ด€๋ จ ์ปจํ…Œ์ด๋„ˆ์šฉ ๋„คํŠธ์›Œํฌ

โœ… docker-compose down

  • ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ๋ชจ๋“  ์ปจํ…Œ์ด๋„ˆ, ๋„คํŠธ์›Œํฌ, ๋ณผ๋ฅจ ๋“ฑ์„ ์ข…๋ฃŒ ๋ฐ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ์ ์œผ๋กœ ๋„คํŠธ์›Œํฌ์™€ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ญ์ œํ•˜๊ณ , ๋ณผ๋ฅจ์€ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. (--volumes ์˜ต์…˜์„ ์ฃผ๋ฉด ๋ณผ๋ฅจ๋„ ์‚ญ์ œ๋จ)

โœ… docker-compose up -d

  • docker-compose.yml์— ์ •์˜๋œ ์„œ๋น„์Šค๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ(-d)๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ ๋‹ค์‹œ ๋นŒ๋“œํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

2) Spring Application ์—์„œ ํ”„๋กœ๋ฉ”ํ…Œ์šฐ์Šค ์„œ๋ฒ„๋กœ ๋ฉ”ํŠธ๋ฆญ ๋ฐ์ดํ„ฐ๋ฅผ Export

1. build.gradle ์˜์กด์„ฑ ์ถ”๊ฐ€

implementation 'io.micrometer:micrometer-registry-prometheus'

2. yml ํŒŒ์ผ ์„ค์ •

management:
  endpoints:
    web:
      exposure:
        include: prometheus
  endpoint:
    prometheus:
      enabled: true

3) Spring Application ์—์„œ Loki ์„œ๋ฒ„๋กœ ๋กœ๊ทธ๋ฅผ ์ „๋‹ฌํ•˜๋Š” Appender ์„ค์ •

1. build.gradle ์˜์กด์„ฑ ์ถ”๊ฐ€

implementation 'com.github.loki4j:loki-logback-appender:1.5.1'

2. logback ์„ค์ •

<!-- Loki๋กœ WARN๊ณผ ERROR ๋ ˆ๋ฒจ์˜ ๋กœ๊ทธ๋งŒ ์ „์†กํ•˜๋Š” appender ์„ค์ • -->
<appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
    
    <!-- ๋กœ๊ทธ ๋ ˆ๋ฒจ ํ•„ํ„ฐ ์„ค์ •: WARN ์ด์ƒ๋งŒ ์ „์†ก๋จ (WARN, ERROR) -->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>WARN</level> <!-- WARN๊ณผ ERROR ๋ ˆ๋ฒจ๋งŒ ์ „์†ก -->
    </filter>

    <!-- Loki HTTP Push ์„ค์ • -->
    <http>
        <!-- Loki ์ˆ˜์‹  URL (IP ๋˜๋Š” ๋„๋ฉ”์ธ) -->
        <url>http://{Loki ์„œ๋ฒ„ URL}:3100/loki/api/v1/push</url>
    </http>

    <!-- ๋กœ๊ทธ ํฌ๋งท ์„ค์ • -->
    <format>
        <!-- ๋ผ๋ฒจ(label): Loki์˜ ๋กœ๊ทธ ๋ถ„๋ฅ˜ ๊ธฐ์ค€์œผ๋กœ ์‚ฌ์šฉ๋จ -->
        <label>
            <!-- ์˜ˆ: appName, host, ๋กœ๊ทธ๋ ˆ๋ฒจ ๋“ฑ์„ ๋ผ๋ฒจ๋กœ ์ „์†ก -->
            <pattern>appName=weather-v2-dev,host=${HOSTNAME},level=%level</pattern>
        </label>

        <!-- ๋ฉ”์‹œ์ง€ ํฌ๋งท: ์‹ค์ œ ๋กœ๊ทธ ๋‚ด์šฉ -->
        <message>
            <!-- ๋กœ๊ทธ ์ถœ๋ ฅ ํ˜•์‹ ์„ค์ • (์Šค๋ ˆ๋“œ๋ช…, ๋กœ๊ทธ๋ ˆ๋ฒจ, ๋กœ๊ฑฐ, ๋ฉ”์‹œ์ง€) -->
            <pattern> [%thread] %-5level %logger{36} - %msg%n</pattern>
        </message>
    </format>
    
</appender>

4) preometheus.yml ์„ค์ •

๐ŸšจECS + FARGATE ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ ์—ฐ๋™ ์‹œ : dns_sd_config + Cloud Map ์‚ฌ์šฉ

global:
  scrape_interval: 1s             				# ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘ ์ฃผ๊ธฐ (๊ธฐ๋ณธ: 1์ดˆ)
  evaluation_interval: 1s         				# ์•Œ๋ฆผ ๋ฃฐ ํ‰๊ฐ€ ์ฃผ๊ธฐ (๊ธฐ๋ณธ: 1์ดˆ)
  
  
  
  - job_name: "test-one"              
    metrics_path: "/actuator/prometheus"
    static_configs:
      - targets: ["test.application.com"]
    relabel_configs:
      - source_labels: ['__address__']
        target_label: 'instance'
        replacement: '์ƒ˜ํ”Œํ…Œ์ŠคํŠธ(์ฒซ๋ฒˆ์งธ)'
    scheme: https

  - job_name: "test-two"                      
    metrics_path: "/actuator/prometheus"
    dns_sd_configs:                              # DNS ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ ์‚ฌ์šฉ
      - names:
          - 'test-two-api.test-two.dev'  	     # ์„œ๋น„์Šค ๋„๋ฉ”์ธ {
        type: 'A'                                # A ๋ ˆ์ฝ”๋“œ ์กฐํšŒ
        port: 80                                 # HTTP ํฌํŠธ
    relabel_configs:
      - source_labels: ['__address__']
        target_label: 'instance'
    scheme: https       

โœ… static_configs

์ •์ ์ธ ํƒ€๊ฒŸ ์„ค์ • โ€“ IP๋‚˜ ๋„๋ฉ”์ธ ์ฃผ์†Œ๋ฅผ ์ง์ ‘ ๋ช…์‹œ

  • ํƒ€๊ฒŸ์„ ์ˆ˜๋™์œผ๋กœ ์ž…๋ ฅ
  • IP ๋˜๋Š” ๋„๋ฉ”์ธ ์ฃผ์†Œ๊ฐ€ ๊ณ ์ •๋˜์–ด ์žˆ์„ ๋•Œ ์‚ฌ์šฉ
  • ๊ฐ„๋‹จํ•œ ๊ตฌ์กฐ, ํ…Œ์ŠคํŠธ/๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๋งŽ์ด ์‚ฌ์šฉ

์‚ฌ์šฉ ์˜ˆ

  • EC2, ECS, ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ ๋“ฑ์— ๋„๋ฉ”์ธ์ด๋‚˜ IP๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•  ๋•Œ
  • ์ˆ˜๊ฐ€ ๋งŽ์ง€ ์•Š๊ณ  ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ์„œ๋น„์Šค๋“ค

โœ… dns_sd_configs

DNS ๊ธฐ๋ฐ˜์˜ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ โ€“ DNS ์ฟผ๋ฆฌ๋ฅผ ํ†ตํ•ด ๋™์ ์œผ๋กœ ํƒ€๊ฒŸ์„ ์ฐพ์Œ

  • ์„œ๋น„์Šค๊ฐ€ ๋“ฑ๋ก๋œ ๋„๋ฉ”์ธ์„ ๊ธฐ์ค€์œผ๋กœ DNS A/AAAA ๋ ˆ์ฝ”๋“œ๋ฅผ ์กฐํšŒ
  • ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด๋ถ€ ์„œ๋น„์Šค(DNS๊ฐ€ ์ž๋™ ๊ด€๋ฆฌ๋˜๋Š” ํ™˜๊ฒฝ, ์˜ˆ: Kubernetes, ECS ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ ๋“ฑ)์— ์ ํ•ฉ
  • ๋Œ€์ƒ์ด ๋Š˜์–ด๋‚˜๊ฑฐ๋‚˜ ์ค„์–ด๋“œ๋Š” ๊ฒฝ์šฐ ์ž๋™ ๋ฐ˜์˜ ๊ฐ€๋Šฅ ( โ€ปํ•ต์‹ฌ )

์‚ฌ์šฉ ์˜ˆ

  • AWS ECS, Kubernetes ๋“ฑ ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ์„œ๋น„์Šค
  • DNS๋ฅผ ํ†ตํ•ด ํด๋Ÿฌ์Šคํ„ฐ ์„œ๋น„์Šค ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋…ธ์ถœํ•  ๋•Œ

5) Loki ์„ค์ •

auth_enabled: false  												# ์ธ์ฆ ๊ธฐ๋Šฅ ๋น„ํ™œ์„ฑํ™” (๊ธฐ๋ณธ์ ์œผ๋กœ ๋‚ด๋ถ€ ๋„คํŠธ์›Œํฌ์—์„œ ์‚ฌ์šฉ ์‹œ ์ธ์ฆ ์ƒ๋žต ๊ฐ€๋Šฅ)

server:
  http_listen_port: 3100  											# Loki ์„œ๋ฒ„๊ฐ€ HTTP ์š”์ฒญ์„ ์ˆ˜์‹ ํ•  ํฌํŠธ ์„ค์ •

common:
  instance_addr: 127.0.0.1  										# Loki ์ธ์Šคํ„ด์Šค์˜ ์ฃผ์†Œ (gossip ring์— ์‚ฌ์šฉ)
  path_prefix: /var/loki  											# Loki๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ํŒŒ์ผ์„ ์ €์žฅํ•  ๊ธฐ๋ณธ ๊ฒฝ๋กœ
  storage:
    filesystem:  													# ๋กœ์ปฌ ํŒŒ์ผ ์‹œ์Šคํ…œ ๊ธฐ๋ฐ˜ ์ €์žฅ์†Œ ์‚ฌ์šฉ
      chunks_directory: /var/loki/chunks  							# ๋กœ๊ทธ ์ฒญํฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๋””๋ ‰ํ† ๋ฆฌ
      rules_directory: /var/loki/rules 								# ๋ฃฐ ํŒŒ์ผ ์ €์žฅ ๊ฒฝ๋กœ (์•Œ๋ฆผ ๋ฃฐ ๋“ฑ)
  replication_factor: 1  											# ๋ณต์ œ ๊ฐœ์ˆ˜ ์„ค์ •, 1์€ ๋‹จ์ผ ๋…ธ๋“œ์—์„œ ์‚ฌ์šฉ
  ring:
    kvstore:
      store: inmemory  												# ํ‚ค-๊ฐ’ ์ €์žฅ์†Œ๋กœ ์ธ๋ฉ”๋ชจ๋ฆฌ ๋ฐฉ์‹ ์‚ฌ์šฉ (๋‹จ์ผ ๋…ธ๋“œ์— ์ ํ•ฉ)

query_range:
  results_cache:
    cache:
      embedded_cache:
        enabled: true  												# ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ์บ์‹œ ๊ธฐ๋Šฅ ํ™œ์„ฑํ™”
        max_size_mb: 100 										    # ์บ์‹œ ์ตœ๋Œ€ ํฌ๊ธฐ (MB ๋‹จ์œ„)

schema_config:
  configs:
    - from: 2020-10-24  											# ์ด ๋‚ ์งœ๋ถ€ํ„ฐ ์ ์šฉํ•  ์Šคํ‚ค๋งˆ ์„ค์ •
      store: tsdb  													# ์ €์žฅ ๋ฐฉ์‹์œผ๋กœ TSDB(Time Series Database) ์‚ฌ์šฉ
      object_store: s3  											# ์ฒญํฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์˜ค๋ธŒ์ ํŠธ ์Šคํ† ์–ด ํƒ€์ž…
      schema: v12  													# Loki ์Šคํ‚ค๋งˆ ๋ฒ„์ „ (์ตœ์‹  ๋ฒ„์ „ ์ค‘ ํ•˜๋‚˜)
      index:
        prefix: index_  											# ์ธ๋ฑ์Šค ํ”„๋ฆฌํ”ฝ์Šค (S3์— ์ €์žฅ๋  ํ‚ค ์ด๋ฆ„์˜ ์ ‘๋‘์–ด)
        period: 24h  												# ์ธ๋ฑ์Šค๋ฅผ ํ•˜๋ฃจ ๋‹จ์œ„๋กœ ์ƒ์„ฑ

storage_config:
  aws:
    s3: tnear-loki-log  											# ์‚ฌ์šฉํ•  S3 ๋ฒ„ํ‚ท ์ด๋ฆ„
    region: ap-northeast-2  										# S3 ๋ฆฌ์ „ (์„œ์šธ)
    access_key_id: {AWS_ACCESS_KEY}  								# AWS ์ ‘๊ทผ ํ‚ค (๋ณด์•ˆ์ƒ ์ฃผ์˜ ํ•„์š”)
    secret_access_key: {AWS_SECRET_ACCESS_KEY}  					# AWS ๋น„๋ฐ€ ์ ‘๊ทผ ํ‚ค (๋ณด์•ˆ์ƒ ์ฃผ์˜ ํ•„์š”)

6) Grafana ์ ‘์†


๐Ÿ”น ๊ฒฐ๋ก 

โญ Prometheus ๋ฉ”ํŠธ๋ฆญ ๋ฐ์ดํ„ฐ + Loki ๋กœ๊ทธ๋ฅผ ์—ฐ๋™ํ•˜์—ฌ ์‹œ๊ฐํ™”

โญ Loki๋Š” ๋ ˆ์ด๋ธ” ๊ธฐ๋ฐ˜์˜ ์ตœ์†Œ ์ธ๋ฑ์‹ฑ ๊ตฌ์กฐ ์‚ฌ์šฉ,

โญ Prometheus๋Š” ์ž์ฒด DB์™€ ์••์ถ• ๊ตฌ์กฐ๋กœ ๊ณ ์„ฑ๋Šฅ ๊ณ ํšจ์œจ์ ์ž„

โญ Loki์™€ Prometheus ์„ค์ • ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์šด์˜์ด ์‰ฌ์›€

โญ ์˜คํ”ˆ์†Œ์Šค ๊ธฐ๋ฐ˜์œผ๋กœ ๋ณ„๋„ ๋ผ์ด์„ ์Šค ๋น„์šฉ์ด ์—†๋‹ค.


์‹œ๊ฐํ™” ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์„ฑ์— ๊ด€ํ•ด์„œ๋Š” ๋‹ค์Œ ๊ธ€์—์„œ ์ง„ํ–‰ํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค. ๐Ÿ˜Š๐Ÿ˜Š๐Ÿ˜Š๐Ÿ˜Š๐Ÿ˜Š๐Ÿ˜Š๐Ÿ˜Š๐Ÿ˜Š

profile
๋‹ค์–‘ํ•œ ๊ฒฝํ—˜๊ณผ ์‹ค๋ฌด์˜ ๊นŠ์ด๋กœ ํ‰๊ฐ€๋ฐ›๊ณ  ์‹ถ์€ ์‚ฌ๋žŒ๋“ค์„ ์œ„ํ•ด ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ์‹ค๋ฌด์—์„œ ๋ถ€๋”ชํžˆ๋ฉฐ ๋ฐฐ์šด ๊ฒƒ๋“ค์ด ๊ฐ€์žฅ ์˜ค๋ž˜ ๋‚จ๋Š”๋‹ค๊ณ  ๋ฏฟ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€