ELK 스택으로 모니터링 구축
로그 중앙집중화를 위해서 ELK를 설치하고 elastic으로 데이터를 볼 수 있는 절차를 정리했습니다.
내부 로그 수집(단일 노드) 예제이며, 운영 환경에서는 멀티노드/보안/TLS/계정 권한을 별도로 설계해야합니다.
1 설치 및 설정
- 경로: /home1
- docker-compose.yml
services:
elasticsearch:
image: docker.elastic.co/elasticsearch:8.12.2
container_name: es
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- xpack.security.enrollment.enabled=false
- ES_JAVA_OPTS=-Xms1g -Xmx1g # 2core/4G면 1g 권장
volumes:
- /opt/elk/esdata:/usr/share/elasticsearch/data
ports:
- "9200:9200"
kibana:
image: docker.elastic.co/kibana:8.12.2
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
depends_on:
- elasticsearch
ports:
- "5601:5601"
logstash:
image: docker.elastic.co/logstash:8.12.2
container_name: logstash
depends_on:
- elasticsearch
volumes:
- /opt/elk/logstash/pipeline:/usr/share/logstash/pipeline:ro
ports:
- "5514:5514/tcp"
- "5514:5514/udp"
- Logstash syslog 파이프라인:
/opt/elk/logstash/pipeline/syslog.conf
input {
tcp { port => 5514 ecs_compatibility => v8 }
udp { port => 5514 ecs_compatibility => v8 }
}
filter {
grok {
match => {
"message" =>
"<%{POSINT:[log][syslog][priority]:int}>%{SYSLOGTIMESTAMP:syslog_timestamp} %{HOSTNAME:host_name} %{DATA:program}(?:\\[%{POSINT:pid:int}\\])?: %{GREEDYDATA:syslog_message}"
}
tag_on_failure => ["_grok_syslog_fail"]
}
date {
match => ["syslog_timestamp", "MMM d HH:mm:ss", "MMM dd HH:mm:ss"]
timezone => "Asia/Seoul"
target => "@timestamp"
}
mutate {
rename => { "host_name" => "[host][name]" }
rename => { "program" => "[process][name]" }
rename => { "syslog_message" => "message" }
}
}
output {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "syslog-%{+YYYY.MM.dd}"
}
}
1.2 용량/보존(데이터가 너무 많이 쌓일 때)
ELK는 설정을 안 하면 인덱스가 계속 쌓여서 디스크가 꽉 찰 수 있음 → 보존 기간(예: 7/14/30일)과 삭제 정책을 정해두는 게 안전함.
현재 용량/인덱스 확인
# 인덱스별 크기/문서수
curl "http://localhost:9200/_cat/indices/syslog-*?v&s=index"
# 노드 디스크 사용량(단일노드라도 확인)
curl "http://localhost:9200/_cat/allocation?v"
# 호스트에서 ES 데이터 디렉토리 크기(대략)
du -sh /opt/elk/esdata
급할 때(수동 삭제)
# 예: 특정 일자 인덱스 삭제
curl -X DELETE "http://localhost:9200/syslog-YYYY.MM.DD"
권장(자동 삭제): ILM로 오래된 인덱스 자동 정리
현재처럼 syslog-YYYY.MM.dd(일별 인덱스)로 쌓는 구조면 “롤오버”까지는 필요 없고, Delete phase(보존기간 지난 인덱스 삭제) 만 걸어도 충분함.
- ILM 정책 생성(예: 30일 보관 후 삭제)
curl -X PUT "http://localhost:9200/_ilm/policy/syslog-delete-30d" \
-H 'Content-Type: application/json' \
-d '{
"policy": {
"phases": {
"delete": {
"min_age": "30d",
"actions": { "delete": {} }
}
}
}
}'
syslog-*에 정책 적용(인덱스 템플릿)
curl -X PUT "http://localhost:9200/_index_template/syslog-template" \
-H 'Content-Type: application/json' \
-d '{
"index_patterns": ["syslog-*"],
"template": {
"settings": {
"index.lifecycle.name": "syslog-delete-30d"
}
}
}'
- 이미 생성된 기존 인덱스에도 적용(필요 시)
curl -X PUT "http://localhost:9200/syslog-*/_settings" \
-H 'Content-Type: application/json' \
-d '{ "index.lifecycle.name": "syslog-delete-30d" }'
디스크 꽉 차면(읽기 전용 걸리는 케이스)
Elasticsearch는 디스크가 임계치에 가까워지면 인덱스를 read_only_allow_delete로 잠글 수 있음.
- 먼저 오래된 인덱스 삭제 등으로 디스크 공간 확보
- 잠금 해제
curl -X PUT "http://localhost:9200/syslog-*/_settings" \
-H 'Content-Type: application/json' \
-d '{ "index.blocks.read_only_allow_delete": null }'
2. 데이터보는 방법
2.1 빠른 확인(권장)
- ELK 기동 확인
- ES 확인:
curl http://localhost:9200(원격이면 `curl http://<ELK_HOST>:9200) - Kibana 접속:
http://<KIBANA_HOST>:5601(환경에 맞게)
- ES 확인:
- Syslog 전송(테스트 데이터 적재)
- TCP:
logger -n <ELK_HOST> -P 5514 -T -t cli-test "tcp check $(date)" - UDP:
logger -n <ELK_HOST> -P 5514 -t cli-test "udp check $(date)"
- TCP:
- Elasticsearch에 인덱스/문서가 생겼는지 확인
- 인덱스 목록:
curl "http://localhost:9200/_cat/indices/syslog-*?v" - 샘플 조회:
curl "http://localhost:9200/syslog-*/_search?size=1&sort=@timestamp:desc"
- 인덱스 목록:
2.2 Kibana에서 보기(Discover)
- 1~4 설정을 이미했다면 5번부터
- Kibana →
Stack Management→Data Views→Create data view - Name: 적당히 (예:
syslog) - Index pattern:
syslog*(logstash output이syslog-%{+YYYY.MM.dd}이므로) - Time field:
@timestamp선택 →Create data view - Kibana →
Analytics→Discover- Data view에서 방금 만든
syslog-*선택 - 우측 상단 Time range를 방금 보낸 시간으로 맞춤(예:
Last 15 minutes) - 필드 추가해서 확인(예:
host.name,host.ip,source.ip,process.name,message)
- Data view에서 방금 만든
2.3 “데이터가 안 보여요” 체크리스트
- Time range가 너무 짧거나 미래/과거로 잡혀있지 않은지 확인
- Data view 인덱스 패턴이
syslog-*로 맞는지 확인 - Logstash가 살아있는지/파이프라인 에러 없는지 확인(
docker compose logs -f logstash)
이슈
1. elastic search 안켜짐
- 로그
failed to obtain node locks, tried [/usr/share/elasticsearch/data]
AccessDeniedException: /usr/share/elasticsearch/data/node.lock
- 원인: ES 데이터 디렉토리(
/opt/elk/esdata) 권한 문제. - Elasticsearch 컨테이너는 UID 1000으로 실행 - 해결
docker-compose down
sudo chown -R 1000:0 /opt/elk/esdata
sudo chmod -R 775 /opt/elk/esdata
# 개발용이면 초기화
sudo rm -rf /opt/elk/esdata/*
docker compose up -d
2. elastic search 메모리 부족 위험
- 환경:
2core / 4GB RAM - 조치:
ES_JAVA_OPTS=-Xms1g -Xmx1g.docker-compose.yml에서 설정. - 이유: 시스템 메모리의 50%이상 넘지않고 정수로 할당해줌.
1.5G로도 해도되긴한데 터질 수도 있음.
3. Syslog를 TCP로 전송하면 host.ip가 elastic에서 안보임
- 증상:
udp로 보내면host.ip가 보이는데,tcp로 보내면host.ip가 비어있음. - 원인: 현재 설정(
ecs_compatibility => v8)에서udpinput은 송신자 IP를 이벤트 필드([host][ip])로 넣어주지만,tcpinput은 송신자 IP를@metadata(예:[@metadata][input][tcp][source][ip])에만 넣음.@metadata는 기본적으로 Elasticsearch로 출력되지 않아서host.ip가 안 보임. - 해결: filter에서
@metadata값을 이벤트 필드로 복사(권장:source.ip, 필요하면host.ip도 같이 세팅).
예시(syslog.conf의 filter에 추가):
filter {
if [@metadata][input][tcp][source][ip] and ![source][ip] {
mutate { add_field => { "[source][ip]" => "%{[@metadata][input][tcp][source][ip]}" } }
}
# 기존에 host.ip를 보고 있다면(UDP처럼) 같이 넣어줌
if [@metadata][input][tcp][source][ip] and ![host][ip] {
mutate { add_field => { "[host][ip]" => "%{[@metadata][input][tcp][source][ip]}" } }
}
# ... (기존 grok/date/mutate)
}
4. Logstash가 설정 읽다가 죽음 (non-ascii characters but are not UTF-8 encoded)
- 증상:
Could not fetch all the sources ... syslog.conf ... not UTF-8 encoded로그가 뜨면서 Logstash가 종료됨. - 원인:
/opt/elk/logstash/pipeline/syslog.conf파일 인코딩이 UTF-8이 아님(윈도우에서 편집 후 CP949/UTF-16 등으로 저장해서 생기는 경우가 많음). Logstash는 파이프라인 설정 파일을 UTF-8로 읽어야 함. - 해결:
syslog.conf를 UTF-8(권장: BOM 없이) 로 변환 후 컨테이너 재시작. 및 conf 파일에 한글 주석 삭제
예시(호스트에서 변환):
# 인코딩 확인(가능하면)
file -bi /opt/elk/logstash/pipeline/syslog.conf
# (자주 겪는 케이스) UTF-16LE -> UTF-8
iconv -f utf-16le -t utf-8 /opt/elk/logstash/pipeline/syslog.conf > /tmp/syslog.conf \
&& mv /tmp/syslog.conf /opt/elk/logstash/pipeline/syslog.conf
# 컨테이너 재시작
docker compose restart logstash
용어 정리(ELK/Elastic)
- Elastic Stack(ELK): Elasticsearch(저장/검색) + Logstash(수집/가공) + Kibana(시각화/조회) 묶음(요즘은 Beats/Agent까지 포함해서 “Elastic Stack”이라고도 함).
- Elasticsearch(ES): 문서를 JSON 형태로 저장하고 검색/집계하는 엔진(REST API로 조회/관리).
- Kibana: ES 데이터를 조회(Discover), 시각화(Dashboard), 관리(인덱스/템플릿/ILM 등)하는 UI.
- Logstash: 입력(Input) → 필터(Filter) → 출력(Output) 파이프라인으로 로그/이벤트를 수집하고 가공해서 ES로 보냄.
- Event(이벤트): Logstash/Beats가 처리하는 “한 건의 로그 레코드”(JSON 필드들의 묶음).
- Document(문서): ES에 저장되는 한 건(대부분 이벤트 1개 = 문서 1개).
- Field(필드): 문서 안의 키(예:
@timestamp,host.name,message). @timestamp: Elastic에서 시간 기반 조회/시각화에 쓰는 표준 시간 필드(Discover의 time field로 자주 선택).- Index(인덱스): 문서들이 저장되는 논리적 단위(예:
syslog-2025.12.18). 보통 시간 단위(일/주)로 쪼갬. - Data view(Kibana): Kibana에서 여러 인덱스를 한 번에 보기 위한 “조회용 이름/패턴”(예:
syslog-*). - Index pattern(인덱스 패턴): 여러 인덱스를 매칭하는 와일드카드 표현(예:
syslog-*). - Mapping(매핑): 필드 타입 정의(예:
keyword,text,ip,date). 잘못 잡히면 검색/집계가 불편해짐. - 적재: 인덱스에 문서를 저장하는 행위.
- Reindex(재인덱싱): 기존 인덱스의 문서를 “새 인덱스”로 다시 써서(복사해서) 매핑/설정 변경이나 필드 정리를 적용하는 작업.
- Alias swap(알리아스 컷오버): 재인덱싱 후 “조회/쓰기 대상” 알리아스를 새 인덱스로 바꿔 무중단에 가깝게 전환하는 방식.
- Shard(샤드): 인덱스를 쪼갠 물리 단위.
primary shard(원본) +replica shard(복제본)로 구성 가능. - Node(노드): ES가 실행되는 인스턴스(컨테이너/서버). 샤드가 노드에 배치됨.
- Cluster(클러스터): 여러 노드가 모인 ES 집합(단일 노드도 클러스터로 동작).
- Index template(인덱스 템플릿): 새 인덱스가 만들어질 때 적용되는 기본 설정/매핑/ILM 연결(패턴 기준).
- Alias(알리아스): 인덱스를 가리키는 별칭(여러 인덱스를 묶거나, “쓰기용” 대상을 바꾸는 데 사용).
- Data stream(데이터 스트림): 시간 기반 데이터에 권장되는 저장 방식(내부적으로 backing index들을 관리하며 롤오버/ILM와 잘 맞음).
- ILM(Index Lifecycle Management): 인덱스 생명주기 정책(핫/웜/콜드/삭제 등). 여기서는 주로 “N일 뒤 삭제”에 사용.
- Rollover(롤오버): 크기/문서수/기간 조건을 만족하면 “새 backing index”로 넘어가게 하는 동작(샤드가 너무 커지지 않게 관리).
- Watermark(디스크 워터마크): 디스크 사용량 임계치. 높아지면 샤드 할당이 제한되거나 인덱스가
read_only_allow_delete로 잠길 수 있음. read_only_allow_delete: 디스크 보호를 위해 ES가 인덱스를 읽기 전용으로 잠그는 설정(삭제는 허용).- ECS(Elastic Common Schema):
host.*,source.*같은 필드 네이밍 표준(서로 다른 로그도 같은 필드로 통일해서 검색/대시보드가 쉬움). - Grok: 로그 문자열을 정규식 패턴으로 파싱해서 필드를 뽑는 Logstash 필터.
@metadata: Logstash 내부용 임시 필드(기본적으로 ES로 출력되지 않음). TCP source IP가 여기에만 들어오는 케이스가 있어 필요 시 복사해야 함.