서버 로그를 grep으로 뒤지는 건 한두 대일 때나 가능하다. 서비스가 늘어나면 로그를 한 곳에 모아서 검색하고 시각화할 수 있는 환경이 필요하다. ELK 스택(Elasticsearch + Logstash + Kibana)이 업계 표준인데, 작은 규모에서는 Logstash 대신 Filebeat만 써도 충분하다. Docker로 띄우면 설치 자체는 30분이면 끝난다.

Docker Compose 설정

version: "3.8"

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - es-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
    restart: unless-stopped
    healthcheck:
      test: curl -s http://localhost:9200 >/dev/null || exit 1
      interval: 30s
      timeout: 10s
      retries: 5

  kibana:
    image: docker.elastic.co/kibana/kibana:8.12.0
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"
    depends_on:
      elasticsearch:
        condition: service_healthy
    restart: unless-stopped

volumes:
  es-data:

xpack.security.enabled=false는 개발/내부용으로 쓸 때 편하려고 끈 거다. 외부에 노출한다면 반드시 켜야 한다.

docker compose up -d

Elasticsearch가 뜨는 데 1~2분 걸린다. curl http://localhost:9200으로 확인:

{
  "name": "abc123",
  "cluster_name": "docker-cluster",
  "version": {
    "number": "8.12.0"
  },
  "tagline": "You Know, for Search"
}

Kibana는 http://localhost:5601로 접속한다.

Filebeat로 로그 수집

Logstash는 무겁다. 단순히 로그 파일을 Elasticsearch에 보내는 용도라면 Filebeat가 가볍고 좋다. compose에 추가:

  filebeat:
    image: docker.elastic.co/beats/filebeat:8.12.0
    user: root
    volumes:
      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
    depends_on:
      - elasticsearch
    restart: unless-stopped

filebeat.yml 설정:

filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/syslog
      - /var/log/auth.log

  - type: container
    paths:
      - /var/lib/docker/containers/*/*.log

output.elasticsearch:
  hosts: ["elasticsearch:9200"]
  indices:
    - index: "syslog-%{+yyyy.MM.dd}"
      when.contains:
        log.file.path: "/var/log/"
    - index: "docker-%{+yyyy.MM.dd}"
      when.contains:
        log.file.path: "/var/lib/docker/"

setup.kibana:
  host: "kibana:5601"

이렇게 하면 시스템 로그와 Docker 컨테이너 로그를 자동으로 수집해서 Elasticsearch에 넣는다.

Kibana에서 인덱스 패턴 생성

Kibana에 접속해서:

  1. 왼쪽 메뉴 → Stack Management → Index Patterns
  2. Create index pattern 클릭
  3. syslog-* 입력 → 타임스탬프 필드 선택 → Create

이제 Discover 메뉴에서 로그를 검색할 수 있다.

유용한 검색 쿼리

Kibana Discover에서 KQL(Kibana Query Language)로 검색:

# SSH 접속 시도 찾기
message: "sshd" and message: "Failed password"

# 특정 컨테이너 로그
container.name: "nginx" and message: "error"

# 특정 시간대 에러
message: "ERROR" or message: "Exception"

대시보드 만들기

Kibana의 진짜 힘은 시각화다. Dashboard에서:

  1. Create visualization → Lens 선택
  2. 인덱스 패턴 선택
  3. X축에 @timestamp, Y축에 Count
  4. 필터 추가해서 에러 로그만 표시

시간대별 에러 발생 추이를 한눈에 볼 수 있어서, 배포 후에 문제가 생겼는지 바로 파악할 수 있다.

메모리 관리

Elasticsearch는 메모리를 많이 먹는다. ES_JAVA_OPTS로 힙 사이즈를 제한하지 않으면 서버 메모리를 다 잡아먹는다. 서버 RAM의 절반 이하로 설정하는 게 좋고, 최소 512MB는 줘야 제대로 돌아간다.

오래된 인덱스는 주기적으로 삭제해야 디스크가 안 찬다:

# 30일 지난 인덱스 삭제
curl -X DELETE "http://localhost:9200/syslog-$(date -d '30 days ago' +%Y.%m.%d)"

ILM(Index Lifecycle Management)을 설정하면 자동으로 관리할 수 있지만, 소규모에서는 cron으로 삭제 스크립트를 돌리는 게 더 간단하다.

처음에 세팅할 때 좀 귀찮지만, 한번 구축하면 로그 분석 시간이 확 줄어든다. grep으로 30분 걸리던 작업이 Kibana에서 10초면 끝난다.