ELK Stack 아키텍처 가이드 – Elasticsearch·Logstash·Kibana 구조 쉽게 이해하기


ELK Stack 아키텍처가 무엇인지 들어봤지만 막상 설명하려면 말문이 막히셨나요? “Elasticsearch는 검색 엔진이고, Logstash는 파이프라인이고, Kibana는 대시보드인데… 이게 어떻게 연결되는 거지?”라는 의문, 처음 공부할 때 누구나 갖습니다. 수백만 건의 로그가 쏟아지는 운영 환경에서 “언제 어디서 무슨 오류가 났는지”를 grep 명령어로 찾는 것은 한계가 있습니다. ELK Stack은 분산된 여러 서버의 로그를 한 곳에 모으고, 실시간으로 검색하며, 시각화 대시보드로 한눈에 파악할 수 있는 오픈소스 로그 분석 플랫폼입니다. 이 글에서는 ELK Stack이 왜 필요한지부터 Elasticsearch의 역색인 구조, Logstash 파이프라인, Kibana 시각화, Beats 경량 수집기, 그리고 Docker Compose로 실제 구성하는 방법까지 그림과 예제 코드로 쉽게 정리합니다.


목차

  1. ELK Stack이란? 왜 필요하고 무엇을 해결하는가
  2. Elasticsearch – 검색 엔진의 핵심 구조와 역색인 원리
  3. Logstash – 데이터 수집·변환·전송 파이프라인
  4. Kibana – 데이터 시각화와 대시보드 설계
  5. Beats – 경량 데이터 수집기와 전체 아키텍처 흐름
  6. 전문가 관점 – 실무 구성·성능 최적화·운영 전략

1. ELK Stack이란? 왜 필요하고 무엇을 해결하는가

로그 관리의 현실 – ELK Stack이 없다면

현대 서비스는 수십~수백 대의 서버가 동시에 동작합니다. 각 서버는 매 초마다 수천 줄의 로그를 생성합니다. 이 환경에서 전통적인 로그 관리가 얼마나 고통스러운지 먼저 살펴보겠습니다.

[ELK Stack 없이 장애 대응하는 현실]

오전 2시 – "결제 오류 발생" 슬랙 알림 수신

① 서버 A 접속 → /var/log/app.log grep 검색
   $ ssh server-A
   $ grep "ERROR" /var/log/app.log | tail -100
   → 오류 발견 안 됨

② 서버 B 접속 → 같은 작업 반복
   $ ssh server-B
   → 오류 발견! 하지만 원인 파악 불가

③ 서버 C, D, E... 반복 (총 20대)
   → 수동으로 각 서버 접속·검색
   → 각기 다른 로그 형식 → 비교 어려움
   → 타임스탬프 시간대 불일치

④ 30분 후 원인 파악 시작
   → 이미 피해 확산
   → 로그 보존 기간 짧아 이미 삭제됨

결론: 수십 대 서버의 로그를 수동으로 grep하는 것은
     규모가 커질수록 사실상 불가능

ELK Stack이 해결하는 3가지 핵심 문제

[ELK Stack 도입 전후 비교]

문제 1: 로그가 여러 서버에 분산
  Before: 서버 20대 × 개별 접속 → 수동 검색
  After:  Logstash/Beats가 모든 로그를 Elasticsearch로 중앙 집중
          → 한 곳에서 전체 서버 로그 검색

문제 2: 로그 검색이 느리고 불편
  Before: grep, awk, sed → 대용량에서 수 분 소요
  After:  Elasticsearch 역색인 → 수억 건 로그도 수 초 내 검색
          → "2026-05-18 결제 ERROR" 즉시 검색

문제 3: 로그 패턴 파악 어려움
  Before: 텍스트 파일 → 시각적 파악 불가
  After:  Kibana 대시보드 → 시간대별 오류 급증 즉시 시각화
          → "오전 2시에 결제 오류 급증" 패턴 한눈에 파악

ELK Stack의 이름과 구성

ELK는 세 가지 오픈소스 도구의 앞 글자를 딴 이름입니다. 현재는 Beats가 추가되어 Elastic Stack이라고도 부릅니다.

[ELK Stack 구성 요소]

E – Elasticsearch : 분산 검색·분석 엔진 (저장·검색·집계)
L – Logstash      : 데이터 수집·변환·전송 파이프라인
K – Kibana        : 데이터 시각화·대시보드

+ Beats           : 경량 데이터 수집기 (서버에 직접 설치)
  ├── Filebeat    : 로그 파일 수집
  ├── Metricbeat  : 시스템 메트릭 수집 (CPU·메모리·디스크)
  ├── Packetbeat  : 네트워크 패킷 수집
  ├── Heartbeat   : 업타임·가용성 모니터링
  └── Auditbeat   : 보안 감사 데이터 수집

전체 아키텍처 조감도

[ELK Stack 전체 데이터 흐름]

데이터 소스 (로그 생성)
  ┌─────────────────────────────────────────────────┐
  │  앱 서버 A  │  앱 서버 B  │  앱 서버 C  │  DB  │
  └──────┬──────┴──────┬──────┴──────┬──────┴──┬───┘
         │              │              │           │
    Filebeat       Filebeat       Filebeat   Metricbeat
    (로그 수집)   (로그 수집)   (로그 수집) (메트릭 수집)
         │              │              │           │
         └──────────────┴──────────────┴───────────┘
                               │
                         메시지 큐 (선택적)
                      Apache Kafka / Redis
                               │
                          Logstash
                     (파싱·변환·필터링)
                               │
                       Elasticsearch
                    (인덱싱·저장·검색·집계)
                               │
                           Kibana
                     (시각화·대시보드·알림)
                               │
                        운영팀·개발팀
                      (대시보드 모니터링)

2. Elasticsearch – 검색 엔진의 핵심 구조와 역색인 원리

Elasticsearch란

Elasticsearch는 Apache Lucene을 기반으로 구축된 분산 RESTful 검색·분석 엔진입니다. JSON 형식으로 데이터를 저장하고, REST API로 데이터를 조작하며, 역색인(Inverted Index) 구조로 대용량 텍스트를 초고속으로 검색합니다.

[Elasticsearch를 관계형 DB와 비교]

관계형 DB (MySQL)      Elasticsearch
─────────────────────────────────────
Database          →   Index
Table             →   (Type, 현재 폐기됨)
Row               →   Document
Column            →   Field
Schema            →   Mapping
SQL               →   Query DSL (JSON)
JOIN              →   Nested / Parent-Child

역색인 – Elasticsearch가 빠른 이유

일반 데이터베이스는 “이 책의 5페이지에 무슨 내용이 있나?”를 찾는 방식(순방향 색인)입니다. Elasticsearch의 역색인은 “이 단어는 몇 페이지에 있나?”를 미리 만들어두는 방식입니다.

[역색인(Inverted Index) 동작 원리]

원본 로그 문서:
  Document 1: "ERROR payment service connection timeout"
  Document 2: "INFO user login success"
  Document 3: "ERROR database connection refused"
  Document 4: "WARN payment service slow response"

역색인 테이블 (자동 생성):
  단어(Term)          │ 문서 ID(Document IDs)
  ──────────────────────────────────────────
  ERROR               │ [1, 3]
  payment             │ [1, 4]
  service             │ [1, 4]
  connection          │ [1, 3]
  timeout             │ [1]
  INFO                │ [2]
  user                │ [2]
  login               │ [2]
  database            │ [3]
  refused             │ [3]
  WARN                │ [4]
  slow                │ [4]

검색: "ERROR payment"
  → ERROR 역색인: [1, 3]
  → payment 역색인: [1, 4]
  → 교집합: [1]  ← 즉시 결과!

→ 전체 문서를 읽지 않고 역색인만 조회
→ 수억 건 문서도 수 밀리초 내 검색 가능

Elasticsearch 분산 구조 – 노드·인덱스·샤드·레플리카

[Elasticsearch 분산 아키텍처]

클러스터 (Cluster): "my-elk-cluster"
  │
  ├── 노드(Node) 1 (Master + Data)
  │     ├── 샤드 0 (Primary) ← Index "logs-2026.05.18"
  │     └── 샤드 2 (Replica) ← 샤드 2의 복제본
  │
  ├── 노드(Node) 2 (Data)
  │     ├── 샤드 1 (Primary)
  │     └── 샤드 0 (Replica) ← 샤드 0의 복제본
  │
  └── 노드(Node) 3 (Data)
        ├── 샤드 2 (Primary)
        └── 샤드 1 (Replica) ← 샤드 1의 복제본

핵심 개념:
  클러스터: 여러 노드가 하나의 논리적 단위로 묶인 것
  노드:     Elasticsearch 프로세스 하나 (서버 1대)
  인덱스:   관련 Document의 모음 (DB의 테이블 개념)
  샤드:     인덱스를 물리적으로 나눈 조각 (수평 확장 단위)
  레플리카: 샤드의 복사본 (장애 대비 + 읽기 성능 향상)

yaml

# Elasticsearch 인덱스 생성 예시
# PUT /logs-2026.05.18
{
  "settings": {
    "number_of_shards": 3,      # 샤드 3개로 분산
    "number_of_replicas": 1,    # 각 샤드마다 복제본 1개
    "refresh_interval": "5s"    # 5초마다 검색 가능하도록 갱신
  },
  "mappings": {
    "properties": {
      "timestamp":  { "type": "date" },
      "level":      { "type": "keyword" },   # 정확히 일치 검색
      "service":    { "type": "keyword" },
      "message":    { "type": "text" },      # 전문 검색 (역색인 적용)
      "host":       { "type": "keyword" },
      "duration_ms":{ "type": "long" }
    }
  }
}

Elasticsearch 주요 쿼리 DSL

json

// 특정 서비스의 ERROR 로그 검색
// GET /logs-*/_search
{
  "query": {
    "bool": {
      "must": [
        { "term":  { "level":   "ERROR" }},
        { "term":  { "service": "payment-service" }}
      ],
      "filter": [
        { "range": {
          "timestamp": {
            "gte": "2026-05-18T00:00:00",
            "lte": "2026-05-18T23:59:59"
          }
        }}
      ]
    }
  },
  "aggs": {
    "errors_per_hour": {
      "date_histogram": {
        "field": "timestamp",
        "calendar_interval": "1h"
      }
    }
  },
  "sort": [{ "timestamp": { "order": "desc" }}],
  "size": 100
}

// 응답 예시
{
  "hits": {
    "total": { "value": 1523 },
    "hits": [
      {
        "_index": "logs-2026.05.18",
        "_source": {
          "timestamp": "2026-05-18T02:15:33.412Z",
          "level":     "ERROR",
          "service":   "payment-service",
          "message":   "Connection timeout to payment gateway",
          "host":      "server-A",
          "duration_ms": 30042
        }
      }
    ]
  },
  "aggregations": {
    "errors_per_hour": {
      "buckets": [
        { "key_as_string": "2026-05-18T02:00:00", "doc_count": 847 },
        { "key_as_string": "2026-05-18T03:00:00", "doc_count": 12  }
      ]
    }
  }
}

3. Logstash – 데이터 수집·변환·전송 파이프라인

Logstash란

Logstash는 다양한 소스에서 데이터를 수집하고, 파싱·변환·필터링 처리를 거쳐, 지정된 목적지로 전송하는 데이터 파이프라인 엔진입니다. 마치 공장의 컨베이어 벨트처럼 원시 데이터(원자재)를 받아 가공된 데이터(완성품)로 만들어 Elasticsearch(창고)에 저장합니다.

[Logstash 파이프라인 3단계]

INPUT(입력)  →  FILTER(필터)  →  OUTPUT(출력)

INPUT:  데이터를 어디서 받을 것인가
  - Beats (Filebeat, Metricbeat 등)
  - Kafka, RabbitMQ (메시지 큐)
  - TCP/UDP (직접 수신)
  - JDBC (DB 쿼리 결과)
  - HTTP, Syslog, S3 등

FILTER: 받은 데이터를 어떻게 처리할 것인가
  - Grok: 로그 문자열을 필드로 파싱
  - Date: 날짜 형식 변환
  - Mutate: 필드 추가·삭제·이름 변경
  - GeoIP: IP → 지리 정보 변환
  - Ruby: 커스텀 로직 처리

OUTPUT: 처리된 데이터를 어디로 보낼 것인가
  - Elasticsearch (주 목적지)
  - Kafka (재분배)
  - File, S3 (백업·아카이브)
  - Slack, Email (알림)

Logstash 설정 파일 실전 예시

ruby

# /etc/logstash/conf.d/application-logs.conf

# ── INPUT ──────────────────────────────────────────────
input {
  beats {
    port => 5044        # Filebeat로부터 수신
    ssl => true         # TLS 암호화
    ssl_certificate => "/etc/logstash/certs/logstash.crt"
    ssl_key         => "/etc/logstash/certs/logstash.key"
  }

  kafka {
    bootstrap_servers => "kafka1:9092,kafka2:9092"
    topics            => ["app-logs", "access-logs"]
    group_id          => "logstash-consumer"
    codec             => "json"
  }
}

# ── FILTER ─────────────────────────────────────────────
filter {

  # Spring Boot 로그 Grok 파싱
  if [fields][log_type] == "spring-boot" {

    grok {
      match => {
        "message" => "%{TIMESTAMP_ISO8601:log_timestamp}%{SPACE}%{LOGLEVEL:level}%{SPACE}%{NUMBER:pid}%{SPACE}---%{SPACE}\[%{DATA:thread}\]%{SPACE}%{DATA:class}%{SPACE}:%{SPACE}%{GREEDYDATA:log_message}"
      }
      # 매칭 실패 시 태그 추가 (디버깅용)
      add_tag    => ["parsed"]
      tag_on_failure => ["_grokparsefailure"]
    }

    # 타임스탬프 정규화 (KST → UTC)
    date {
      match    => ["log_timestamp", "yyyy-MM-dd HH:mm:ss.SSS"]
      timezone => "Asia/Seoul"
      target   => "@timestamp"
    }

    # 응답 시간 파싱 (숫자형으로 변환)
    if [log_message] =~ /duration=(\d+)ms/ {
      grok {
        match => { "log_message" => "duration=%{NUMBER:duration_ms}ms" }
      }
      mutate {
        convert => { "duration_ms" => "integer" }
      }
    }
  }

  # Nginx 액세스 로그 파싱
  if [fields][log_type] == "nginx-access" {

    grok {
      match => {
        "message" => "%{IPORHOST:client_ip} - %{USER:auth} \[%{HTTPDATE:timestamp}\] \"%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}\" %{NUMBER:response_code:integer} %{NUMBER:bytes:integer} \"%{DATA:referrer}\" \"%{DATA:user_agent}\" %{NUMBER:response_time:float}"
      }
    }

    # IP를 지리 정보로 변환
    geoip {
      source => "client_ip"
      target => "geoip"
    }
  }

  # 공통 필드 처리
  mutate {
    # 불필요한 필드 제거 (저장 용량 절약)
    remove_field => ["message", "agent", "ecs", "input"]

    # 환경 정보 추가
    add_field => {
      "environment" => "%{[fields][env]}"
      "datacenter"  => "seoul-dc-1"
    }

    # 필드명 표준화
    rename => {
      "host.name" => "hostname"
      "log.file.path" => "log_path"
    }
  }

  # 개인정보 마스킹 (보안 준수)
  mutate {
    gsub => [
      "log_message", "\b\d{4}-\d{4}-\d{4}-\d{4}\b", "[CARD_MASKED]",
      "log_message", "\b01[0-9]-\d{3,4}-\d{4}\b",   "[PHONE_MASKED]"
    ]
  }
}

# ── OUTPUT ─────────────────────────────────────────────
output {

  # Elasticsearch로 전송
  elasticsearch {
    hosts     => ["https://es-node1:9200", "https://es-node2:9200"]
    user      => "logstash_writer"
    password  => "${LOGSTASH_ES_PASSWORD}"

    # 날짜 기반 인덱스 (일별 롤오버)
    index     => "logs-%{[fields][service]}-%{+YYYY.MM.dd}"

    # 파이프라인 처리
    pipeline  => "enrich-pipeline"

    # 재시도 설정
    retry_on_conflict => 3
  }

  # 파싱 실패 로그 별도 저장 (디버깅)
  if "_grokparsefailure" in [tags] {
    elasticsearch {
      hosts => ["https://es-node1:9200"]
      index => "logs-parse-failure-%{+YYYY.MM.dd}"
    }

    # 슬랙 알림 (파싱 오류 모니터링)
    http {
      url    => "${SLACK_WEBHOOK_URL}"
      method => "POST"
      format => "json"
      mapping => { "text" => "⚠️ Logstash 파싱 오류 발생: %{[host][name]}" }
    }
  }
}

Grok 패턴 – 로그 파싱의 핵심

[Grok 패턴 이해하기]

원본 로그:
  2026-05-18 02:15:33.412 ERROR 12345 --- [http-nio-8080-exec-1] c.e.PaymentService : Connection timeout

Grok 패턴:
  %{TIMESTAMP_ISO8601:log_timestamp}  → 2026-05-18 02:15:33.412
  %{LOGLEVEL:level}                   → ERROR
  %{NUMBER:pid}                       → 12345
  \[%{DATA:thread}\]                  → http-nio-8080-exec-1
  %{DATA:class}                       → c.e.PaymentService
  %{GREEDYDATA:log_message}           → Connection timeout

파싱 결과 (JSON):
  {
    "log_timestamp": "2026-05-18 02:15:33.412",
    "level":         "ERROR",
    "pid":           "12345",
    "thread":        "http-nio-8080-exec-1",
    "class":         "c.e.PaymentService",
    "log_message":   "Connection timeout"
  }

4. Kibana – 데이터 시각화와 대시보드 설계

Kibana란

Kibana는 Elasticsearch에 저장된 데이터를 시각화하고 탐색하는 웹 UI입니다. 코딩 없이 드래그&드롭으로 차트·그래프·맵·대시보드를 만들 수 있으며, 실시간 데이터 변화를 모니터링할 수 있습니다.

[Kibana 주요 기능]

① Discover  : 로그 원본 데이터 탐색·검색·필터링
② Dashboard : 여러 시각화를 조합한 대시보드 구성
③ Lens      : 드래그&드롭 기반 시각화 도구 (최신 표준)
④ Canvas    : 커스텀 픽셀 퍼펙트 보고서 제작
⑤ Maps      : 지리 데이터 시각화 (GeoIP 기반)
⑥ ML        : 머신러닝 기반 이상 탐지 (유료)
⑦ APM       : 애플리케이션 성능 모니터링
⑧ SIEM      : 보안 이벤트 모니터링
⑨ Alerting  : 조건 기반 알림 (이메일·슬랙·PagerDuty)
⑩ Dev Tools : Elasticsearch 쿼리 실행 콘솔

KQL – Kibana Query Language

[KQL 쿼리 예시 – Kibana 검색창에서 직접 사용]

# 특정 서비스의 ERROR 로그
level: ERROR AND service: "payment-service"

# 오늘 응답 시간 1초 초과 요청
response_time > 1000 AND @timestamp >= now-24h

# 특정 사용자 또는 IP의 요청
client_ip: "192.168.1.*" OR user_id: "user-12345"

# 특정 메시지 포함 (와일드카드)
log_message: "*timeout*" OR log_message: "*connection refused*"

# 복합 조건
(level: ERROR OR level: WARN) AND environment: "production"
  AND NOT service: "batch-service"

# 숫자 범위
duration_ms >= 3000 AND response_code: 500

실무 대시보드 구성 예시

[운영 팀을 위한 실시간 모니터링 대시보드 구성]

┌──────────────────────────────────────────────────────────────┐
│  🚨 서비스 상태 현황 대시보드  (최근 1시간, 30초 자동 새로고침) │
├────────────────────┬─────────────────────────────────────────┤
│  📊 지표 카드       │  📈 시계열 차트                          │
│                    │                                          │
│  전체 요청: 142,831 │  ─── ERROR ─── WARN ─── INFO           │
│  ERROR율: 0.8%     │  ▲                                      │
│  평균 응답: 245ms   │  │  ⚠️ 오류 급증                        │
│  P99 응답: 1,230ms │  │    (02:15)                           │
│                    │  └──────────────────────────            │
├────────────────────┴─────────────────────────────────────────┤
│  🗺️ 서비스별 오류 히트맵   │  🍩 HTTP 상태 코드 분포          │
│                           │                                  │
│  payment   ████████ 42건  │      200: 91.2%                  │
│  auth      ██ 8건          │      500: 0.8%                   │
│  order     █ 3건           │      404: 5.1%                   │
│  product   0건             │      302: 2.9%                   │
├───────────────────────────┴──────────────────────────────────┤
│  📋 최근 ERROR 로그 실시간 목록                                │
│  02:15:33 [payment-service] ERROR Connection timeout          │
│  02:15:31 [payment-service] ERROR Connection timeout          │
│  02:14:58 [auth-service]    ERROR JWT token expired           │
└──────────────────────────────────────────────────────────────┘

Kibana 알림 설정

yaml

# Kibana Alerting 규칙 예시 (Watcher/Alerting)

# 알림 규칙: ERROR 로그가 1분에 100건 초과 시 슬랙 알림
rule:
  name: "ERROR 급증 알림"
  schedule:
    interval: "1m"        # 1분마다 체크
  conditions:
    - type: "count"
      threshold: 100      # 100건 초과 시
      time_window: "1m"   # 최근 1분 기준
      filter:
        level: "ERROR"
        environment: "production"

  actions:
    - type: "slack"
      webhook_url: "${SLACK_WEBHOOK}"
      message: |
        🚨 *ERROR 급증 감지*
        발생 건수: {{ctx.payload.hits.total}}건/분
        기준 시각: {{ctx.execution_time}}
        즉시 확인 필요: https://kibana.example.com/dashboard/error-monitoring

    - type: "email"
      to: ["oncall@example.com"]
      subject: "[URGENT] 프로덕션 ERROR 급증 - {{ctx.payload.hits.total}}건/분"

5. Beats – 경량 데이터 수집기와 전체 아키텍처 흐름

Beats란 – 왜 Logstash 대신 Beats를 서버에 설치하는가

Logstash는 강력하지만 JVM 기반으로 메모리를 많이 사용합니다(기본 1GB+). 서비스가 동작하는 애플리케이션 서버에 Logstash를 직접 설치하면 서비스 성능에 영향을 줍니다. Beats는 Go 언어로 만들어진 경량(10~50MB 메모리) 데이터 수집기로, 서버에 에이전트로 설치해 데이터를 수집하고 Logstash 또는 Elasticsearch로 전송합니다.

[Logstash vs Beats 역할 분리]

[애플리케이션 서버]           [중앙 수집 서버]
  ┌─────────────┐              ┌─────────────┐
  │  Spring App │              │  Logstash   │
  │  생성 로그   │              │  파싱·변환   │
  └──────┬──────┘              │  필터링      │
         │                     └──────┬──────┘
  ┌──────▼──────┐                     │
  │  Filebeat   │──────────────────────┘
  │  (경량 수집) │  로그 파일 tail
  │  메모리: 50MB│  → Logstash로 전송
  └─────────────┘

→ 서버: Filebeat만 설치 (가볍고 리소스 영향 최소)
→ 중앙: Logstash에서 무거운 파싱·처리 담당
→ 역할 분리 → 서비스 성능 보호

Filebeat 설정 예시

yaml

# /etc/filebeat/filebeat.yml

# ── 입력 설정 ───────────────────────────────────────────
filebeat.inputs:

  # Spring Boot 애플리케이션 로그
  - type: log
    enabled: true
    paths:
      - /var/log/app/*.log
      - /var/log/app/*.log.*   # 롤링 로그 파일 포함
    fields:
      log_type: "spring-boot"
      service:  "order-service"
      env:      "production"
    fields_under_root: true   # fields를 최상위 레벨에 배치

    # 멀티라인 처리 (Java 스택트레이스)
    multiline.pattern:    '^\d{4}-\d{2}-\d{2}'
    multiline.negate:     true
    multiline.match:      after
    # → 날짜로 시작하지 않는 줄은 이전 줄에 합침 (스택트레이스 한 건으로 처리)

  # Nginx 액세스 로그
  - type: log
    enabled: true
    paths:
      - /var/log/nginx/access.log
    fields:
      log_type: "nginx-access"
      service:  "api-gateway"
      env:      "production"
    fields_under_root: true

# ── 처리기 설정 (간단한 전처리) ──────────────────────────
processors:
  - add_host_metadata:    # 호스트 정보 자동 추가 (IP, OS, 호스트명)
      when.not.contains.tags: forwarded
  - add_cloud_metadata:   # AWS/GCP/Azure 클라우드 메타데이터 추가
  - drop_fields:          # 불필요한 필드 제거
      fields: ["agent.ephemeral_id", "ecs.version"]
      ignore_missing: true

# ── 출력 설정 ───────────────────────────────────────────
output.logstash:
  hosts: ["logstash-server:5044"]
  ssl.certificate_authorities: ["/etc/filebeat/certs/ca.crt"]

  # 재시도 설정
  backoff.init: 1s
  backoff.max:  60s
  worker: 2           # 병렬 전송 워커 수
  bulk_max_size: 2048 # 한 번에 전송할 최대 이벤트 수

# ── 레지스트리 설정 (읽은 위치 기억) ──────────────────────
filebeat.registry.path: /var/lib/filebeat/registry
# → 서버 재시작 후에도 마지막으로 읽은 위치부터 재개
# → 로그 중복 전송 방지

ELK Stack 전체 데이터 흐름 상세 시나리오

[실제 오류 발생부터 Kibana 확인까지 전체 흐름]

① 오전 2:15:33 – 결제 서비스에서 타임아웃 발생
   서버 A: /var/log/app/order-service.log에 ERROR 기록
   "2026-05-18 02:15:33.412 ERROR ... Connection timeout"

② 오전 2:15:33 – Filebeat 감지 (tail 방식)
   파일 변경 감지 → 새 로그 라인 읽기
   → 메타데이터 추가 (호스트명, 서비스명, 환경)
   → Logstash로 전송 (수 밀리초 내)

③ 오전 2:15:33 – Logstash 처리
   Grok 패턴으로 파싱:
     level:       "ERROR"
     service:     "order-service"
     thread:      "http-nio-8080-exec-1"
     log_message: "Connection timeout"
   → 타임스탬프 UTC 변환
   → 개인정보 마스킹
   → Elasticsearch로 전송

④ 오전 2:15:34 – Elasticsearch 인덱싱
   인덱스: "logs-order-service-2026.05.18"
   역색인 업데이트:
     "ERROR" → [doc1, doc2, ..., docN]
     "timeout" → [doc1, doc5, ...]
   → 검색 가능 상태

⑤ 오전 2:15:35 – Kibana 알림 발동
   1분간 ERROR 100건 초과 감지
   → 슬랙 알림 발송: "🚨 ERROR 급증: 847건/분"
   → 담당자 모바일 알림 수신

⑥ 오전 2:16 – 담당자 Kibana 대시보드 확인
   KQL 검색: level: ERROR AND service: "order-service"
   → 오전 2:15분부터 타임아웃 급증 확인
   → 특정 서버(server-A)에서만 발생 확인
   → 5분 내 원인 파악 및 대응 시작

6. 전문가 관점 – 실무 구성·성능 최적화·운영 전략

Docker Compose로 ELK Stack 구성하기

yaml

# docker-compose.yml – 로컬 개발용 ELK Stack

version: '3.8'

services:

  # ── Elasticsearch ─────────────────────────────────────
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
    container_name: elasticsearch
    environment:
      - node.name=es-node-1
      - cluster.name=dev-elk-cluster
      - discovery.type=single-node    # 단일 노드 (개발용)
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - xpack.security.enabled=false  # 개발 환경에서 보안 비활성화
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - elasticsearch-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"   # REST API
      - "9300:9300"   # 클러스터 내부 통신
    networks:
      - elk-network
    healthcheck:
      test: ["CMD-SHELL",
             "curl -f http://localhost:9200/_cluster/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3

  # ── Logstash ──────────────────────────────────────────
  logstash:
    image: docker.elastic.co/logstash/logstash:8.13.0
    container_name: logstash
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline   # 파이프라인 설정
      - ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml
    ports:
      - "5044:5044"   # Beats 수신
      - "5000:5000"   # TCP 수신
      - "9600:9600"   # Logstash 모니터링 API
    environment:
      - "LS_JAVA_OPTS=-Xms256m -Xmx256m"
    networks:
      - elk-network
    depends_on:
      elasticsearch:
        condition: service_healthy

  # ── Kibana ────────────────────────────────────────────
  kibana:
    image: docker.elastic.co/kibana/kibana:8.13.0
    container_name: kibana
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
      - KIBANA_SYSTEM_PASSWORD=changeme
    ports:
      - "5601:5601"   # Kibana 웹 UI
    networks:
      - elk-network
    depends_on:
      elasticsearch:
        condition: service_healthy

  # ── Filebeat ──────────────────────────────────────────
  filebeat:
    image: docker.elastic.co/beats/filebeat:8.13.0
    container_name: filebeat
    user: root    # 로그 파일 읽기 권한
    volumes:
      - ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - /var/log:/var/log:ro    # 호스트 로그 디렉토리 마운트
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
    networks:
      - elk-network
    depends_on:
      - logstash

networks:
  elk-network:
    driver: bridge

volumes:
  elasticsearch-data:
    driver: local

실무 성능 최적화 전략

yaml

# Elasticsearch 운영 환경 성능 설정

# 1. JVM 힙 메모리 설정 (물리 메모리의 50%, 최대 32GB)
# jvm.options
-Xms16g
-Xmx16g

# 2. 인덱스 수명 관리 (ILM: Index Lifecycle Management)
# 로그 인덱스 자동 관리 정책
PUT _ilm/policy/logs-policy
{
  "policy": {
    "phases": {
      "hot": {                    # 활성 데이터 (최근 7일)
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50GB",   # 50GB 초과 시 새 인덱스 생성
            "max_age": "1d"       # 1일 후 롤오버
          }
        }
      },
      "warm": {                   # 조회 빈도 감소 (7~30일)
        "min_age": "7d",
        "actions": {
          "shrink": {
            "number_of_shards": 1 # 샤드 1개로 축소 (조회 최적화)
          },
          "forcemerge": {
            "max_num_segments": 1 # 세그먼트 병합 (성능 최적화)
          }
        }
      },
      "cold": {                   # 거의 조회 안 함 (30~90일)
        "min_age": "30d",
        "actions": {
          "freeze": {}            # 메모리 해제 (읽기 전용)
        }
      },
      "delete": {                 # 자동 삭제 (90일 후)
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

ELK Stack 운영 아키텍처 – 대규모 환경

[프로덕션 ELK Stack 고가용성 아키텍처]

애플리케이션 서버 (N대)
  각 서버에 Filebeat 설치
        │
        ▼
  Apache Kafka (메시지 큐, 선택적)
  ├── Topic: app-logs
  ├── Topic: access-logs
  └── Topic: error-logs
  → 트래픽 급증 시 버퍼 역할
  → Logstash 장애 시 데이터 보존
        │
        ▼
Logstash 클러스터 (3대 이상)
  ├── logstash-1 (Active)
  ├── logstash-2 (Active)
  └── logstash-3 (Standby)
  → 로드밸런서로 부하 분산
        │
        ▼
Elasticsearch 클러스터 (최소 3대)
  ├── Master Node (3대) : 클러스터 상태 관리
  ├── Data Node (N대)   : 실제 데이터 저장·검색
  ├── Coordinating Node : 쿼리 라우팅·집계
  └── ML Node (선택적)  : 머신러닝 작업
        │
        ▼
     Kibana (2대 + 로드밸런서)
  → 단일 장애점(SPOF) 제거
  → 세션 공유를 위한 Elasticsearch 세션 저장

ELK Stack 도입 체크리스트

[ELK Stack 실무 도입 전 체크리스트]

아키텍처 설계:
□ 로그 볼륨 예측 (일일 GB 단위) → Elasticsearch 용량 계획?
□ 데이터 보존 기간 정의 → ILM 정책 수립?
□ 고가용성 요구사항 → 클러스터 구성 대수 결정?
□ 메시지 큐(Kafka) 도입 여부 → 트래픽 버스트 대응?

보안:
□ Elasticsearch 인증(X-Pack) 활성화?
□ Beats → Logstash 구간 TLS 암호화?
□ 개인정보·민감 정보 마스킹 필터 적용?
□ Kibana 역할 기반 접근 제어(RBAC) 설정?

성능:
□ JVM 힙 메모리 적정 설정? (물리 메모리의 50%, 최대 32GB)
□ 인덱스 샤드 수 최적화? (샤드당 10~50GB 권장)
□ 인덱스 수명 관리(ILM) 정책 수립?
□ 멀티라인 로그(스택트레이스) 처리 설정?

운영:
□ Logstash 파이프라인 파싱 실패 로그 별도 저장?
□ Filebeat 레지스트리 경로 설정 (재시작 복구)?
□ 알림 규칙 설정 (ERROR 급증, 디스크 사용량 등)?
□ Elasticsearch 클러스터 헬스 모니터링?

결론

ELK Stack 아키텍처는 Filebeat(수집)→Logstash(처리)→Elasticsearch(저장·검색)→Kibana(시각화)의 네 단계 파이프라인으로 수백만 건의 로그를 실시간으로 처리하는 오픈소스 로그 분석 플랫폼입니다. Elasticsearch의 역색인이 수억 건 데이터도 수 밀리초 내에 검색하게 해주고, Grok 패턴이 구조 없는 로그를 필드별로 파싱해 분석 가능한 형태로 만들며, Kibana 대시보드가 장애 패턴을 시각화해 수십 분 걸리던 원인 파악을 수 분으로 단축합니다. 처음 도입한다면 Docker Compose로 로컬에서 시작하고, 서비스가 성장하면 Kafka를 버퍼로 추가하고 Elasticsearch 클러스터를 확장하는 방향으로 단계적으로 성장시키는 것이 현실적인 전략입니다.

지금 바로 Docker Compose 파일로 로컬 ELK Stack을 구동하고, 애플리케이션 로그를 Filebeat로 수집해 Kibana에서 첫 번째 대시보드를 만들어 보세요.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다