포스트

Docker로 로컬 ELK 스택 구축 및 Kibana 데이터 연결하기

Docker로 로컬 ELK 스택 구축 및 Kibana 데이터 연결하기

실습 : ELK 스택을 활용한 실시간 주차장 및 상권 데이터 분석

데이터셋 설명

서울시에서 제공하는 실시간 공공데이터 API를 이용해, ELK 스택을 활용해 데이터 분석 및 시각화를 해보려고 한다.

  • 상권 정보: 서울시의 실시간 상권별 유동인구, 소비 현황 등을 포함한 실시간 도시데이터 API
  • 주차장 정보: 서울시가 운영하는 시영주차장의 실시간 주차 가능 대수 API

원래는 CSV 파일을 Logstash로 처리하는 구조를 생각했지만,
실시간 API 기반 데이터의 갱신성을 살리기 위해 구조를 변경했다.
현재는 Python 스크립트로 API를 직접 호출하고,
그 결과를 Elasticsearch에 바로 전송하여 Logstash 없이도 실시간 분석 파이프라인을 구성했다.

이처럼 ELK 스택은 원래 로그 데이터 분석에 많이 쓰이지만,
아래와 같은 이유로 실시간 도시데이터를 선택했다:

  • 로그 데이터는 직접 생성하거나 실습용으로 구하기 어려운 반면,
  • 서울시의 실시간 도시데이터는 누구나 접근 가능하고,
  • 시간 기반의 업데이트, 다양한 시설 및 상황 정보를 포함하고 있어
  • ELK 스택의 시계열 필터링, 대시보드 기능을 실습하기에 적합하다.

실제로 이 데이터는 로그와 비슷하게:

  • 5~10분 단위로 갱신되는 timestamp를 기반으로 시간 축 필터링이 가능하고,
  • 업종, 상권, 주차장 등 다양한 카테고리로 분석할 수 있다.

데이터 출처:

진행 흐름

전반적인 흐름은 다음과 같다.

1
2
3
4
5
6
7
8
서울시 실시간 도시데이터 API (상권 소비 + 시영주차장 정보)
  ↓ (Python 스크립트로 주기적 수집 + 좌표 변환)
Kakao API (주소 → 위도/경도 변환)
  ↓ (Geo 좌표 포함하여 데이터 구조화)
Elasticsearch (실시간 데이터 저장 및 색인)
  ↓ (검색/집계 결과 요청)
Kibana (Data View 생성, Discover에서 실시간 데이터 조회 및 시간 필터 적용)
  → 대시보드/시각화로 확장

Docker로 ELK 스택 로컬 설치

ELK 스택을 설치하기 위해 Docker를 사용했다. Docker를 쓰면, 로컬 환경에서도 복잡한 설치 없이 간단하게 ELK를 실행할 수 있다.

ELK 스택은 이미 GitHub에 유명한 docker-compose.yml 저장소들이 많이 올라와 있다. git clone만 해도 바로 실행 가능한 코드와 설정들이 준비되어 빠르고 간편하다.

그런데 나는 이 방식 대신 설정을 직접 해보기로 했다. 시간이 더 오래 걸리긴 하지만 배우는 과정이라 동작 원리를 좀 파악하고 싶었달까.

Docker Compose 설정

아래는 ELK 스택을 구성하기 위한 docker-compose.yml 파일이다. 실제 데이터 수집 및 적재는 Python으로 수행했기 때문에 Logstash는 활용하지 않았지만,
ELK 스택의 전체 구조를 이해하고자 Logstash도 함께 설치해보았다.

⭐ 여기서 중요한 점은:

  • Elasticsearch와 Kibana는 Docker 이미지에 기본 설정이 포함되어 있어 별도 설정 없이 실행 가능하다.

  • 하지만 Logstash는 반드시 logstash.conf 파이프라인 설정 파일이 필요하다!

  • Logstash는 /usr/share/logstash/pipeline 폴더 안의 파일을 읽어 실행되므로, 반드시 logstash/pipeline 폴더 안에 logstash.conf를 넣어주어야 한다!

  • 만약 이 파일이 없으면, Logstash 컨테이너는 실행되자마자 꺼진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
version: '3.8'

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.8.0
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false  
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    ports:
      - "9200:9200"
    volumes:
      - es_data:/usr/share/elasticsearch/data

  kibana:
    image: docker.elastic.co/kibana/kibana:8.8.0
    container_name: kibana
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch

  logstash:
    image: docker.elastic.co/logstash/logstash:8.8.0
    container_name: logstash
    ports:
      - "5044:5044"
      - "9600:9600"
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline
    depends_on:
      - elasticsearch

volumes:
  es_data:
    driver: local

ELK Stack 실행해보기

설정 파일이 준비되면, 아래 명령어로 ELK Stack을 실행할 수 있다.

1
docker compose up -d

컨테이너 실행 확인

실행 중인 컨테이너 목록을 확인하려면 다음 명령어를 사용한다.

1
docker ps

정상적으로 실행되면 아래와 비슷한 결과가 나온다:

1
2
3
CONTAINER ID   IMAGE                                                 COMMAND                  CREATED         STATUS         PORTS                                            NAMES
1ef627c64eaf   docker.elastic.co/kibana/kibana:8.8.0                 "/bin/tini -- /usr/l…"   4 seconds ago   Up 2 seconds   0.0.0.0:5601->5601/tcp                           kibana
851c8cf222e4   docker.elastic.co/elasticsearch/elasticsearch:8.8.0   "/bin/tini -- /usr/l…"   4 seconds ago   Up 3 seconds   0.0.0.0:9200->9200/tcp, 9300/tcp                 elasticsearch

elasticsearch, kibana 컨테이너가 모두 실행 중인 것을 확인할 수 있다.

🚨 버전 호환 이슈: Elasticsearch 8.x vs Python 클라이언트 9.x

Docker로 설치한 Elasticsearch는 기본적으로 8.8.0 버전인데,
pip install elasticsearch로 설치한 Python 클라이언트는 9.x가 설치되어 호환 오류가 발생했다.

1
2
3
4
raise HTTP_EXCEPTIONS.get(meta.status, ApiError)(
elasticsearch.BadRequestError: BadRequestError(400, 'media_type_header_exception', 'Invalid media-type value on headers [Content-Type, Accept]',
Accept version must be either version 8 or 7, but 
found 9. Accept=application/vnd.elasticsearch+json; compatible-with=9)

이 오류는 Python 클라이언트가 Elasticsearch 서버와 맞지 않는 버전으로 요청을 보내기 때문에 생긴다. 이는 클라이언트가 Elasticsearch 서버에 버전 9로 요청한다고 했지만, 서버는 8까지밖에 못 받는다고 응답하면서 생긴 오류다.

💡 해결법 : 클라이언트 다운그레이드

서버와 클라이언트의 버전을 맞춰주면 해결된다.

1
2
pip uninstall elasticsearch
pip install "elasticsearch>=8,<9"  # 8.x 버전으로 재설치

Elasticsearch 인덱스 생성

데이터 수집 및 전처리는 별도 Jupyter Notebook(eda.ipynb)에서 EDA와 folium을 활용한 지도 시각화를 통해
데이터 구조나 이상치 등을 먼저 확인한 뒤,
Python 스크립트를 통해 Elasticsearch에 총 3개의 인덱스를 생성하고 데이터를 업로드했다.

인덱스 이름설명
seoul_parking시영주차장 실시간 데이터. 위치 정보(geo_point), 가용률, 혼잡도 상태, 요일 정보 포함
seoul_commercial상권 중심 위치 및 반경 300m 내 주차장 수, 평균 주차 가용률 등 공간 기반 분석 데이터
seoul_commercial_categories업종/카테고리 기반의 상권 소비 정보 요약 데이터

각 인덱스는 Elasticsearch API를 통해 Python 코드에서 직접 생성하였으며, 데이터는 helpers.bulk()를 사용하여 대량 업로드하였고,
문서의 _id는 search_keyword_수집시간 형태로 생성해 중복 삽입을 방지했다.

Kibana에서 인덱스 패턴 (Data View) 생성

ELK 스택에 데이터가 잘 들어갔다면, Kibana에서 데이터를 확인하기 위해 인덱스 패턴(Data View) 을 생성해야 한다.

http://localhost:5601 로 Kibana에 접속한 뒤 왼쪽 사이드바 메뉴에서 “Discover” → “Create data view” 를 클릭한다.

Data View 이름은 실제 Elasticsearch 인덱스 이름과 동일하게
예: seoul_parking, seoul_commercial, seoul_commercial_categories 등으로 지정한다. 이렇게 하면 각 인덱스와 연결된 데이터를 Kibana에서 시각적으로 탐색할 수 있다.

image

🚨 Kibana에서 데이터가 보이지 않았던 문제

처음 timestamp 컬럼을 생성할 때 단순히 datetime.now()만 사용했는데, Kibana에서 Discover 탭에 데이터가 아무것도 보이지 않는 현상이 발생했다.

하지만 Elasticsearch에는 분명히 데이터가 잘 들어가 있었기 때문에, 문제는 시간 필터링 기준이 잘못 설정된 것임을 알 수 있었다.

Kibana는 기본적으로 UTC(세계 표준시) 기준으로 시간을 해석한다. 반면 Python에서 datetime.now()를 사용할 경우 시스템 로컬 시간이 적용되는데, 내 환경은 KST(한국 표준시)이므로 Kibana에서 데이터가 UTC 기준으로 9시간 밀려 해석된 것이다. 결과적으로 시간 필터 범위 밖의 값으로 간주되어 데이터가 표시되지 않는 것이다.

💡 해결 방법: timestamp에 KST(한국 시간) 명시하기

이 문제를 해결하기 위해 Python에서 timestamp 생성 방식을 다음과 같이 변경했다:

1
2
3
4
5
from datetime import datetime
import pytz

tz = pytz.timezone("Asia/Seoul")
now_kst = datetime.now(tz)

이렇게 하면 timestamp 값에 +09:00(KST) 시간대 정보가 포함되어 Kibana의 시간 필터와 정확히 일치하게 되어 데이터가 잘 표시된다.

⭐ Time field 설정 (timestamp)

이제 Kibana에서 Data View를 생성할 때, Time field로 반드시 timestamp 컬럼을 선택해줘야 한다.

이 필드는 Kibana의 시간 기반 필터링, 정렬, 시각화 기준이 되며, 정확한 시간 기반 탐색을 위해 꼭 지정해주어야 한다.

image

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.