✍🏻
**ELK **구성 방법 중에서도 Docker를 활용한 방법으로 진행.

📎 Reference.

📌 Contents.


Docker 활용 ELK 구성 테스트


git clone

git clone https://github.com/deviantony/docker-elk.git
cd docker-elk

.env 파일에 패스워드 설정부분 참고

나의 경우, Nginx에 대한 컨테이너를 추가로 구성. 기본 페이지 접속시 Access 로그를 Logstash를 통해 Elasticsearch로 전달하는 구성으로 커스텀해보았다.

docker-compose.yml 변경점

version: '3.7'

services:

  # The 'setup' service runs a one-off script which initializes users inside
  # Elasticsearch — such as 'logstash_internal' and 'kibana_system' — with the
  # values of the passwords defined in the '.env' file. It also creates the
  # roles required by some of these users.
  #
  # This task only needs to be performed once, during the *initial* startup of
  # the stack. Any subsequent run will reset the passwords of existing users to
  # the values defined inside the '.env' file, and the built-in roles to their
  # default permissions.
  #
  # By default, it is excluded from the services started by 'docker compose up'
  # due to the non-default profile it belongs to. To run it, either provide the
  # '--profile=setup' CLI flag to Compose commands, or "up" the service by name
  # such as 'docker compose up setup'.
  setup:
    profiles:
      - setup
    build:
      context: setup/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    init: true
    volumes:
      - ./setup/entrypoint.sh:/entrypoint.sh:ro,Z
      - ./setup/lib.sh:/lib.sh:ro,Z
      - ./setup/roles:/roles:ro,Z
    environment:
      ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:-}
      LOGSTASH_INTERNAL_PASSWORD: ${LOGSTASH_INTERNAL_PASSWORD:-}
      KIBANA_SYSTEM_PASSWORD: ${KIBANA_SYSTEM_PASSWORD:-}
      METRICBEAT_INTERNAL_PASSWORD: ${METRICBEAT_INTERNAL_PASSWORD:-}
      FILEBEAT_INTERNAL_PASSWORD: ${FILEBEAT_INTERNAL_PASSWORD:-}
      HEARTBEAT_INTERNAL_PASSWORD: ${HEARTBEAT_INTERNAL_PASSWORD:-}
      MONITORING_INTERNAL_PASSWORD: ${MONITORING_INTERNAL_PASSWORD:-}
      BEATS_SYSTEM_PASSWORD: ${BEATS_SYSTEM_PASSWORD:-}
    networks:
      - elk
    depends_on:
      - elasticsearch
      - kibana
      - logstash

  elasticsearch:
    build:
      context: elasticsearch/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    volumes:
      - ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro,Z
      - elasticsearch:/usr/share/elasticsearch/data:Z
    ports:
      - 9200:9200
      - 9300:9300
    environment:
      node.name: elasticsearch
      ES_JAVA_OPTS: -Xms512m -Xmx512m
      # Bootstrap password.
      # Used to initialize the keystore during the initial startup of
      # Elasticsearch. Ignored on subsequent runs.
      ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:-}
      # Use single node discovery in order to disable production mode and avoid bootstrap checks.
      # see: https://www.elastic.co/guide/en/elasticsearch/reference/current/bootstrap-checks.html
      discovery.type: single-node
    networks:
      - elk
    restart: unless-stopped

  web:
    image: nginx:latest
    volumes:
      - web-logs:/var/log/nginx:Z  # Nginx 로그 디렉토리 공유
    networks:
      - elk
    depends_on:
      - logstash
    restart: unless-stopped
    ports:
      - 8081:80

  logstash:
    build:
      context: logstash/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    volumes:
      - ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro,Z
      - ./logstash/pipeline:/usr/share/logstash/pipeline:ro,Z
      - ./logstash/input:/usr/share/logstash/input:rw,Z
      - web-logs:/var/log/nginx:Z   # 새로운 볼륨 추가
    ports:
      - 5044:5044
      - 50000:50000/tcp
      - 50000:50000/udp
      - 9600:9600
    environment:
      LS_JAVA_OPTS: -Xms256m -Xmx256m
      LOGSTASH_INTERNAL_PASSWORD: ${LOGSTASH_INTERNAL_PASSWORD:-}
    networks:
      - elk
    depends_on:
      - elasticsearch
    restart: unless-stopped

  kibana:
    build:
      context: kibana/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    volumes:
      - ./kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml:ro,Z
    ports:
      - 5601:5601
    environment:
      KIBANA_SYSTEM_PASSWORD: ${KIBANA_SYSTEM_PASSWORD:-}
    networks:
      - elk
    depends_on:
      - elasticsearch
    restart: unless-stopped

networks:
  elk:
    driver: bridge

volumes:
  elasticsearch:
  web-logs:

basic 라이센스 적용

xpack.license.self_generated.type: trial -> basic

elasticsearch.yml

수정된 logstash.conf


input {
    beats {
        port => 5044
    }
    tcp {
        port => 50000
    }
    file {
        path => "/usr/share/logstash/input/test-input.csv"
        start_position => "beginning"
        sincedb_path => "/usr/share/logstash/input/sincedb_file"
        mode => "tail"
    }
    file {
        path => "/var/log/nginx/host.access.log"  # 새로운 로그 파일 경로
        start_position => "beginning"
        sincedb_path => "NUL"
    }
}

output {
    elasticsearch {
        hosts => "elasticsearch:9200"
        user => "elastic"
        password => "${LOGSTASH_INTERNAL_PASSWORD}"
        index => "test-input"
    }
    stdout {
        codec => json_lines
    }
}
docker-compose up -d

원래는 nginx access 로그를 활성화해야하는 내용까지 적용해야하겠지만, 일단 다이렉트로 nginx 컨테이너 접속 후 access 로그 활성화

docker exec -it docker-elk-web-1 /bin/bash
vi /etc/nginx/conf.d/default.conf

`===============================================
#access_log /var/log/nginx/host.access.log main;
===============================================`
# 위 내용 주석처리('#') 제거 후 저장

service nginx restart

이 후 localhost:8081 접속하여 host.access.log 파일에 액세스 로그 정상 확인.

localhost:5601 (키바나) 접속 → Stack Management → Index management 접속

test-input 인덱스 → console → GET test-input/_search?pretty

→ 액세스 로그 확인

✅ 테스트 성공

이제 위 테스트 경험을 바탕으로 다른 애플리케이션 Docker Compose에 결합 테스트할 차례. (Nginx Proxy Manager 관련 애플리케이션)

ELK 통합 완료


통합 시점 수정된 파일목록 일부

결합 후 수정된 파일 목록

결합 후 수정된 파일 목록

주요 코드

▼ kibana.yml

server.name: kibana
server.host: 0.0.0.0
elasticsearch.hosts: [ http://elasticsearch:9200 ]
monitoring.ui.container.elasticsearch.enabled: true
monitoring.ui.container.logstash.enabled: true
elasticsearch.username: kibana_system
elasticsearch.password: ${KIBANA_SYSTEM_PASSWORD}

▼ logstash.conf

input {
  file {
    path => "/data/logs/modsec_audit.log"
    start_position => "end"
    sincedb_path => "/data/logs/.sincedb_modsec_audit"
    type => "modsecurity-audit"
  }
}

filter {
  if [type] == "modsecurity-audit" {
    grok {
      match => {
        "message" => "(?m)%{DATA:modsec_timestamp}.*%{DATA:transaction_id}.*%{GREEDYDATA:raw_data}"
      }
    }
    date {
      match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
    }
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "modsecurity-audit-%{+YYYY.MM.dd}"
    user => "elastic"
    password => "${LOGSTASH_INTERNAL_PASSWORD:-}"
    ssl => false
    ssl_certificate_verification => false
  }
}

▼ docker-compose.yml

# WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production.
services:

  setup:
    profiles:
      - setup
    build:
      context: ../elk/setup/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    init: true
    volumes:
      - ../elk/setup/entrypoint.sh:/entrypoint.sh:ro,Z
      - ../elk/setup/lib.sh:/lib.sh:ro,Z
      - ../elk/setup/roles:/roles:ro,Z
    environment:
      ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:-}
      LOGSTASH_INTERNAL_PASSWORD: ${LOGSTASH_INTERNAL_PASSWORD:-}
      KIBANA_SYSTEM_PASSWORD: ${KIBANA_SYSTEM_PASSWORD:-}
      METRICBEAT_INTERNAL_PASSWORD: ${METRICBEAT_INTERNAL_PASSWORD:-}
      FILEBEAT_INTERNAL_PASSWORD: ${FILEBEAT_INTERNAL_PASSWORD:-}
      HEARTBEAT_INTERNAL_PASSWORD: ${HEARTBEAT_INTERNAL_PASSWORD:-}
      MONITORING_INTERNAL_PASSWORD: ${MONITORING_INTERNAL_PASSWORD:-}
      BEATS_SYSTEM_PASSWORD: ${BEATS_SYSTEM_PASSWORD:-}
    networks:
      - nginx_proxy_manager
    depends_on:
      - elasticsearch
      - kibana
      - logstash

  npm:
    image: nginxproxymanager:dev
	  ..(생략)..
    volumes:
      - npm_data:/data
      - le_data:/etc/letsencrypt
      - ../backend:/app
      - ../frontend:/app/frontend
      - ../global:/app/global
    depends_on:
      - db
    working_dir: /app

  db:
	  ..(생략)..

  elasticsearch:
    build:
      context: ../elk/elasticsearch/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    init: true
    volumes:
      - ../elk/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro,Z
      - elasticsearch_data:/usr/share/elasticsearch/data:Z # !
    container_name: elasticsearch
    environment:
      node.name: elasticsearch
      ES_JAVA_OPTS: -Xms512m -Xmx512m
      ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:-}
      discovery.type: single-node
    ports:
      - 9200:9200
    networks:
      - nginx_proxy_manager

  logstash:
    build:
      context: ../elk/logstash/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    container_name: logstash
    volumes:
      - ../elk/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro,Z
      - ../elk/logstash/pipeline:/usr/share/logstash/pipeline:ro,Z
      - npm_data:/data:rw
    ports:
      - 5044:5044
    networks:
      - nginx_proxy_manager
    depends_on:
      - elasticsearch
    environment:
      LS_JAVA_OPTS: -Xms256m -Xmx256m
      LOGSTASH_INTERNAL_PASSWORD: ${LOGSTASH_INTERNAL_PASSWORD:-}

  kibana:
    build:
      context: ../elk/kibana/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    container_name: kibana
    volumes:
      - ../elk/kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml:ro,Z
    ports:
      - 5601:5601
    environment:
      KIBANA_SYSTEM_PASSWORD: ${KIBANA_SYSTEM_PASSWORD:-}
    networks:
      - nginx_proxy_manager
    depends_on:
      - elasticsearch

volumes:
  npm_data:
    name: npm_core_data
  le_data:
    name: npm_le_data
  db_data:
    name: npm_db_data
  elasticsearch_data:
    driver: local
    name: elk_es_data

networks:
  nginx_proxy_manager:
    name: npm_network
    driver: bridge

최종 결합 완료 및 실행

docker compose -f docker-compose.yml --profile setup up 명령 실행 결과:

(몇가지 시행착오가 있었지만 최종적으로 정상 실행 완료된 로그)

Nginx Proxy Manager 관리자 페이지:

http://localhost:3081 → ✅ 정상

Kibana 페이지:

http://localhost:5601 → ✅ 정상

→ ✅ logstash를 통해 modsec log 수집 성공

키바나에서 로그 확인을 위한 추가설정 가이드

모든 서비스가 정상적으로 실행 완료되면, localhost:3080 (추후 npm proxy 서버를 거쳐갈 트래픽 대상 서비스) 접속에 대한 modesecurity audit 로그는 elasticsearch에 저장되고있는 상황
  1. 키바나 접속(http://localhost:5601)초기 계정 정보
  • ID: elastic

  • PASSWORD: changeme

  1. 좌측 네비게이션 > Stack Management > Data view > Create Data View
  • 인덱스 명: (아무 이름)

  • 인덱스 패턴: modsecurity-audit-*

  • 시간 필드: @timestamp

→ ✅ 인덱스 생성 완료

Kibana 초기 화면 Discover 메뉴에서 http://localhost:3080 접속에 대한 로그 확인 및 Visualize 메뉴에서 차트 구성 가능

Comments


진행 간에 로그인 페이지 없는 ELK 구성은 빨리 구현할 수 있었다. 하지만 위와 같이 로그인 페이지를 붙이는 과정은 조금 달랐다.

“1️⃣Docker 컨테이너 활용 + 2️⃣ 다른 애플리케이션에 결합”하는 조건과 동일하게 진행된 레퍼런스를 찾지 못했고, 이 때문에 직접 이런저런 소스 분석하며 결합해보고 테스트 하느라 시간이 제법 소요된 작업이었다. ✅ 아무튼 성공.