Overview
ELK Stack 을 구축하는 방법에 대해서 알아본다.
- ElasitSearch
- Logstash
- Kibana
- Filebeat
ELK Stack 이란?
- Elasticsearch + Logstash + Kibana의 세 가지 구성 요소를 결합하여 ELK 스택이라 부른다.
- 최근에는 더 가벼운 EFK(Fluentd) 스택이 자주 사용된다.
- LogFlow만 이해하면 구성 방식은 크게 다르지 않다.
ELK Stack 아키텍처
- Log Pipeline:
- Filebeat(log shipper) -> Logstash(data processor) -> Elasticsearch(storage) <- Kibana(visualization)
- Metric Pipeline:
- Metricbeat(metric collector) -> Elasticsearch(storage) <- Kibana(visualization)
- APM Pipeline:
- Applications(APM agents) -> APM Server -> Elasticsearch(storage) <- Kibana(visualization)
ELK Stack 컴포넌트
- Elasticsearch: 로그 데이터를 저장하고 검색하는 검색 엔진
- Logstash: 로그를 저장하고 변환하는 데이터 처리 파이프라인(=Fluentd)
- Kibana: 로그 데이터 분석을 위한 시각화 대시보드
- Filebeat: 로그 파일 수집 및 전달(=Fluent-bit)
- Apmserver: 애플리케이션 추적 수집 및 처리를 위한 애플리케이션 성능 모니터링 서버
- Metricbeat: 시스템 및 서비스 메트릭을 위한 메트릭 전달
EFK Stack 컴포넌트
나머지 컴포넌트는 유사하다.
- Fluentd : 로그를 저장하고 변환하는 데이터 처리 파이프라인(=Logstash)
- Fluent-bit : 로그 파일 수집 및 전달(=Filebeat)
ELK Stack 구축
2023년 5월 16일에 archived 된 Helm Chart를 사용하여 설치하였다. 그리고 내부 개발 서버용이라서 Single Mode로 설치하였다.
구축방법은 크게 어렵지 않다. 아래의 Github를 참고하길 바란다.
최신버전으로 설치해보고 싶다면 아래의 operator chart를 사용하면 된다.
- https://github.com/elastic/cloud-on-k8s/tree/main/deploy/eck-operator
- https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-install-helm.html
elasticsearch , kibana, logstash, filebeat 순서로 설치해주면 된다.
filebeat와 logstash에는 내가 원하는 tag와 field를 추출하기 위한 parser를 설정할 수 있다.
`filebeat-values.yaml` 참고(github repo에는 없습니다.)
...
filebeatConfig:
filebeat.yml: |
filebeat.inputs:
- type: log
paths:
- /usr/share/filebeat/app/somaz/dev/app/logs/* # 마운트된 경로의 로그 파일
- /usr/share/filebeat/app/somaz/dev/app/logs/**/*
fields:
log_source: "dev-somaz-app" # 이 값이 Elasticsearch 인덱스 이름이 됨
environment: "dev"
app: "somaz"
component: "app"
fields_under_root: true
json.keys_under_root: true # JSON 필드를 루트 레벨로 승격
json.add_error_key: true # JSON 파싱 에러 시 에러 필드 추가
json.expand_keys: true # 추가: 중첩된 JSON 문자열을 객체로 확장
processors:
- decode_json_fields:
fields: ["data"]
process_array: true
max_depth: 2
target: ""
overwrite_keys: true
- script:
lang: javascript
source: |
function process(event) {
if (event.data && event.data.requestBody) {
try {
event.data.requestBody = JSON.parse(event.data.requestBody);
} catch (e) {}
}
if (event.data && event.data.responseBody) {
try {
event.data.responseBody = JSON.parse(event.data.responseBody);
} catch (e) {}
}
if (event.stack && Array.isArray(event.stack)) {
try {
event.stack = event.stack.map(function(str) {
return str.replace(/\\n/g, '\n');
});
} catch (e) {}
}
return event;
}
`logstash-values.yaml` 참고(github repo에는 없습니다.)
logstashPipeline:
logstash.conf: | # uptime.conf를 logstash.conf로 변경
input {
beats { # beats input만 사용
port => 5044
}
}
filter {
if [log_source] { # [fields][log_source]에서 변경
# log_source 값을 index_name 필드로 저장
mutate {
add_field => { "index_name" => "%{log_source}" } # [fields][log_source]에서 변경
}
} else {
# 기본값 설정
mutate {
add_field => { "index_name" => "default" }
}
}
# requestBody, responseBody, stack 문자열 처리
ruby {
code => '
# stack 처리
if event.get("stack").is_a?(Array)
event.set("stack", event.get("stack").map { |str|
str.gsub(/\\n/, "\n").gsub(/\\"/, "\"")
})
end
# requestBody와 responseBody를 문자열로 저장
if event.get("data")
if event.get("data")["requestBody"]
event.set("[data][requestBody]", event.get("data")["requestBody"].to_json)
end
if event.get("data")["responseBody"]
event.set("[data][responseBody]", event.get("data")["responseBody"].to_json)
end
end
# 루트 레벨 필드 제거
event.remove("requestBody")
event.remove("responseBody")
'
}
date {
match => [ "[timestamp]", "ISO8601" ]
target => "@timestamp"
remove_field => [ "timestamp" ]
}
}
output {
elasticsearch {
hosts => ["https://elasticsearch-master:9200"]
user => "${ELASTICSEARCH_USERNAME}"
password => "${ELASTICSEARCH_PASSWORD}"
ssl_certificate_verification => true
cacert => '/usr/share/logstash/config/certs/ca.crt'
# 동적 인덱스 이름 설정
index => "%{index_name}"
# 인덱스 설정을 직접 지정
manage_template => true
template => "/usr/share/logstash/config/template.json"
template_overwrite => true
}
# # 디버깅을 위한 stdout 출력
# stdout { codec => rubydebug }
# # S3 output 추가
# s3 {
# aws_access_key_id => "${AWS_ACCESS_KEY_ID}"
# aws_secret_access_key => "${AWS_SECRET_ACCESS_KEY}"
# region => "ap-northeast-2" # AWS 리전
# bucket => "your-bucket-name"
# prefix => "logstash-backup/%{+YYYY}/%{+MM}/%{+dd}"
# rotation_strategy => "size_and_time"
# size_file => 5242880 # 5MB
# time_file => 15 # 15분
# encoding => "gzip" # 압축 적용
# codec => json_lines
# }
}
parser 부분은 kibana에서 보고싶은 구조로 구성해주면 된다. (gpt, claude 둘다 잘짬)
구축을 완료하고 나면 curl로 인덱스를 조회해 볼 수 있다.
curl -k -u "elastic:somaz123!" "http://elasticsearch.somaz.link/_cat/indices?v"
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open .kibana-event-log-8.5.1-000002 0YwfADLHQryK5bYlBpBm5Q 1 1 0 0 225b 225b
yellow open dev-somaz-app 9zip7n0sdfasdfsadfsdfsdaf 1 1 24161 0 6.5mb 6.5mb
그리고 Kibana에서 해당 index에 맞는 discover를 생성해주면 아래와 같이 확인가능하다.
주의할점
1. elasitsearch health check 에는 yellow와 green이 있다. single node로 사용해도, green을 유지할 수는 있지만 정신건강에 좋지않다. yellow로 유지해도 무방하다.
# https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html#request-params wait_for_status
# multiple node cluster will be green
clusterHealthCheckParams: "wait_for_status=green&timeout=1s"
# # single node cluster will be yellow
clusterHealthCheckParams: "wait_for_status=yellow&timeout=1s"
2. kibana와 elasitcsearch는 한몸이라고 보면 된다. elasticsearch에 문제가 생기면, kibana 웹은 접속조차 되지 않는다.
3. 생각보다 filebeat로 모든 로그를 가져오게 되면 부하가 심하다. 필자는 특정 pod의 로그만 수집하여 logstash 그리고 elasticsearch로 전달하였다.
4. operator를 사용해서 설치해보고 싶다면 eck-operator-crds를 잘 분석해보길 바란다.
Reference
https://github.com/elastic/helm-charts
https://github.com/somaz94/helm-chart-template/tree/main/k8s-service/monitoring/elk-stack
'Monitoring' 카테고리의 다른 글
Loki와 Promtail 설치 (5) | 2024.10.02 |
---|---|
Loki란? (4) | 2024.09.26 |
Prometheus 와 Thanos 설치 및 구성 (0) | 2024.09.19 |
Prometheus와 Thanos란? (4) | 2024.09.12 |
Fluent Bit (With Loki) (4) | 2024.03.08 |