Skip to main content

2 posts tagged with "elk"

View All Tags

· 8 min read

ELK stack을 사용하는 것. 괜찮다고 생각하지만 여전히 쉽지는 않다. 난이도를 떠나서 번거로운 요소들이 있다고 생각되는데 개인적으로 가능한 logstash를 활용하려고 하는게 더 번거롭게 만드는게 아닌가 싶다. 사족이지만.

어찌되었든 Docker container의 로그를 ELK에서 수집하기 위해서는 몇가지 방법을 생각해 볼 수 있을 것 같다.

1. Autodiscovery 기능을 활용

비교적 최근에 만들어진 기능으로 보이는데 내용을 훑어봤을 때 감은 오지만 로그수집 보다는 메트릭 수집의 목적이 강한 것 아닌가 싶다. 결론적으로 잘 되지 않아서 다른 방법을 알아보기로 했다.

https://www.elastic.co/kr/webinars/elasticsearch-log-collection-with-kubernetes-docker-and-containers

2. Syslog log driver를 사용하는 방법

Docker에서 제공하는 log driver 중 syslog가 있는데 syslog를 log driver로 설정한 호스트에서는 container 로그들이 모두 syslog에 쌓이기 때문에 filebeat로 syslog만 수집하면 된다. 하지만 syslog의 다른 로그들과 혼재되면 그걸 골라내는 것도 일일 것 같다.

3. Fluentd를 통해 elasticsearch로 로그를 전송하는 방법

역시 docker의 log driver로 fluentd를 지정해서 모든 container의 로그를 fluentd로 모은 후에 plugin을 이용해 elasticsearch에서 인덱싱하는 방법이 있을 수 있다. Container만 본다면 괜찮은 방법이라고 생각하는데 나의 경우엔 filebeat도 사용해야하는 상황이었기에 기존의 로그수집 방식을 fluentd로 변경해서 통합하거나 두가지를 혼용해야 할 것 같다.

4. Volume mount를 이용한 방법

Docker container 내부에서 로그를 파일로 쌓는 경우 해당 경로를 호스트의 특정 경로와 마운트하고 filebeat가 마운트 된 경로의 로그파일들을 수집하는 방식인데 application을 개발할 때 로그를 파일로 쌓도록 강제해야 하고 로그 경로가 공유되어야 한다는 문제가 있다. 개발 측면에서도 번거롭고 배포 과정에서도 volume 지정을 빼먹으면 안되니 비효율적이라고 생각한다.

5. Filebeat에서 add_docker_metadata processor를 사용하는 방법

마지막으로 검토해서 적용한 내용인데 filebeat에서 간단한 설정만으로 기존의 container나 시스템에 변경을 가하지 않고 로그를 수집할 수 있다. 문서(Enriching logs with Docker metadata using Filebeat)에 의하면 6.0.0 beta1 부터 새로 추가된 기능인 것 같다. Docker container의 stdout 로그들도 실제로는 호스트에 파일로 쌓이고 있기 때문에 해당 파일들을 전송하는 것 같은데 재미있는건 로그외의 container name, image 정보 등도 함께 날라가서 단순히 로그의 내용만 확인하는 데에는 큰 문제가 없다.

add_docker_metadata processor를 사용해보니

위에 링크된 문서를 참고해서 기존에 동작중인 filebeat의 prospector에 몇 줄 추가하고 restart를 하니 언급한대로 로그수집은 간단하게 된다.

# Sample prospectors
filebeat.prospectors:

- input_type: log
paths:
- /var/log/syslog
fields:
document_type: syslog

- type: log
paths:
- '/var/lib/docker/containers/*/*.log'
json.message_key: log
json.keys_under_root: true
processors:
- add_docker_metadata: ~
fields:
document_type: docker

로그 구분을 위해 사용하던 document_type이 deprecated 된 field라 custom field에 넣어준 것을 제외하고는 위의 문서의 내용과 동일하다. 로그는 아래와 유사한 형태로 kibana에서 확인할 수 있다.

Container의 이름도 표시되니 container 이름이나 image로 조회해서 살펴볼 수 있을 것이다. log field에 넘어오는 전체 로그 메세지는 경우에 따라 다시 별도의 field 들로 분류할 필요가 생기겠지만 간단하다.

time field에서 발생한 conflict

위의 이미지는 이미 문제를 해결하고 난 이후라 문제가 없어보인다. 원래 기본적으로는 time field가 넘어오게 되는데 나의 경우에는 다른 곳에서 filebeat가 보내고 있는 로그가 동일한 인덱스로 생성되고 있는 상황이었고 다른 로그의 time field와 type이 달라서 conflict이 발생하는 걸 볼 수 있었다.

(기존의 인덱스에 생성되어 있는 time field는 text, docker container 로부터 넘어오는 time field는 date) 이미 만들어진 index의 field type을 변경하는 것은 index 복제, field 변경, re-index 작업 등을 해야하니 무리라고 생각해서 container 로부터 넘어오는 field에만 조작을 가하기로 했다. (이런 경우에는 logstash가 있는게 불편하지만 편하다!?)

filter {
if [fields][document_type] == "docker" {
mutate {
convert => { "time" => "string" }
add_field => [ "received_at", "%{@timestamp}" ]
}
date {
match => [ "time", "ISO8601" ]
remove_field => "time"
}
}
}

위와 같이 logstash에 filter를 하나 추가해줬는데 이미 custom field로 document_type이 날라오고 있으니 구분해서 처리하고 time field를 string으로 변환해봤다. 처음엔 변환만 했는데도 동일한 문제가 생겨서 string으로 변환 후 timestamp로 찍히도록 date filter를 추가하고 time field는 날려버렸다. 차라리 time field를 다른 이름으로 rename 하는게 낫지 않을까도 생각해봤는데 timestamp가 있으니 의미는 없을 것 같다. Convert를 했는데 왜 동일한 문제가 생기는지는 좀 의문.

· 9 min read

서비스를 구성하면서 살펴보다 보면 어떤 서비스, 어떤 페이지에 request가 많은지 그 요청들은 어느 곳에서 많이 발생하는지 궁금할 때가 있다. 글로벌을 타겟으로 하는 서비스인 경우 특히 그럴 것이고 혹은 의도치 않은 곳에서 이상한 형태의 요청이 빈번할 경우 차단할지 판단하기 위해 필요할 수도 있다. Google Analytics 같은 서비스의 경우 이러한 요구사항을 간편한 방법으로 어느정도 해소할 수 있지만 어차피 여러가지 분석 용도로 ELK를 사용하고 있으니 packetbeat를 이용해보기로 했다.

Packetbeat?

ELK stack에서 로그 수집용으로 filebeat를 많이 사용하는데 filebeat는 특정 로그파일을 수집해서 logstash나 elasticsearch 등으로 전송하는 역할을 수행한다. Packetbeat는 로그파일 대신 network packet 데이터를 수집해서 전송할 수 있으며, 각종 프로토콜 별로 데이터를 분류해서 수집할 수 있게 되어있다.

테스트용 ELK 구성

우선 packetbeat를 사용해서 어느 지역에서 요청이 들어오는지를 보고 싶은 게 1차 목표였는데 실제 운영중인 ELK를 건드리고 싶지는 않아서 새로운 VM에 Ansible로 다시 구성했다. 미리 만들어둔 5개의 role을 clone해서 아래와 비슷한 형태로 playbook을 만들고 실행하면 된다. ELK가 동시에 여러 종류의 beat에서 전달되는 데이터를 받아야하니 다른 문제가 있는지를 보기 위해 filebeat도 같이 구성했다.

1. Roles

https://github.com/blurblah/ansible-role-elasticsearch

https://github.com/blurblah/ansible-role-logstash

https://github.com/blurblah/ansible-role-kibana

https://github.com/blurblah/ansible-role-docker (Dependency)

https://github.com/blurblah/ansible-role-filebeat

2. Playbook

- hosts: elk_test
become: yes
roles:
- role: elasticsearch
cluster:
name: test-es
- role: logstash
logstash_loglevel: debug
- role: kibana
server_name: test-kibana

- hosts: elk_test
become: yes
roles:
- role: filebeat
logstash_host: '{{ ansible_default_ipv4.address }}'
logstash_port: 5044
enable_kibana_dashboard: true
kibana_host: '{{ ansible_default_ipv4.address }}'
kibana_port: 5601
prospectors:
- { type: syslog, files: ['/var/log/auth.log'] }

Packetbeat 구성

Packetbeat도 역시 Ansible role로 만들어서 ELK 구성과 비슷한 형태로 간단히 완료. Role은 github에서 가져오면 되고 요청을 분석하고 싶은 서버를 대상으로 아래와 같은 형태로 playbook을 만들어서 실행했다.

- hosts: test_proxy
become: yes
roles:
- role: packetbeat
packetbeat_name: test_packetbeat
packetbeat_protocols:
- { type: tls, ports: [443] }
packetbeat_loglevel: debug
out_logstash:
host: '{{ hostvars["elk_test"].ansible_host }}'
port: 5044

내가 분석하고자 했던 서버는 nginx로 되어있는 reverse proxy 였는데 인증서가 적용되어 있었기 때문에 type을 tls로 port는 443 하나만 넣어서 지정했다.

Geoip filter

Packetbeat 까지 구성해서 데이터가 수집되는 것을 확인해보면 (Kibana에서 index pattern 생성까지 한 경우) 요청 ip, url, protocol 등은 표시되지만 위치 정보에 대한 내용은 없다. Logstash의 geoip filter는 수집된 정보 중 ip와 내부 DB에 매핑된 정보를 가지고 geoip라는 field를 생성해주는 역할을 수행한다고 해서 아래처럼 logstash pipeline 경로에 filter를 설정해주었다.

filter {
if [@metadata][beat] == "packetbeat" and [client_ip] and [client_ip] !~ /^10\./ {
geoip {
source => "client_ip"
}
}
}

Filebeat 등 다른 beat들은 geoip filter를 적용하지 않을 것이기 때문에 metadata를 이용해 거르게 했다. Private ip의 경우 위치 정보에 대해서 매핑된 정보가 없기 (알 수도 없고) 때문에 내 환경에서 요청이 발생하는 10.0.0.0/16 대역에 대해서도 적용하지 않기로 했다. Private ip에 대해서 처리를 하게 되면 아래와 같은 오류가 발생한다.

[2018-04-18T04:52:51,124][DEBUG][logstash.filters.geoip   ] IP 10.x.x.x was not found in the database {:event=>#<LogStash::Event:0x65726e3c>}

Filter 설정은 좀 더 살펴보고 더 효율적으로 수정할 필요는 있을 것 같다.

Geoip field 인식 문제

Filter 설정 후 logstash를 restart 하고 나서 kibana 쪽에서 살펴보니 없던 geoip field 들이 새로 만들어지는건 확인이 되는데 아래 그림처럼 warning이 발생하는 걸 볼 수 있었다.

이전에 데이터 들어오는걸 보겠다고 index pattern을 생성했는데 그 시점의 index에는 없던 field 들이기 때문에 warning이 발생하는 것이라서 index pattern을 제거하고 다시 생성해주는 걸로 문제를 해결

geo_point type

Warning이 없어졌으니 kibana의 visualize 메뉴에 가서 map을 추가해 그려볼까 하고 설정을 해보니 아래와 같은 오류가 뜨면서 그려지질 않는다.

무엇이 문제인가 싶어서 index pattern에서 field들의 type을 살펴보니 geo_point type으로 된 field가 보이질 않는다. 이런 부분은 좀 개선이 필요해 보이는데 field 들이 filter에 의해 자동으로 생성되는 것이라 알아서 처리되면 편할 것 같다.

이런 경우 elasticsearch에서 생성하는 index에 대한 변경이 필요한데 이미 존재하는 index를 일괄 변경하는 방법도 있을 것 같고 새로 생성되는 index에 대해서 적용할 template을 추가하는 방법도 있을 수 있다. 나의 경우엔 이미 만들어진 index와 데이터가 거의 없는 상태이기 때문에 존재하는 index는 모두 제거하고 template을 추가해서 이후 생성되는 index에 대해서만 field type이 변경되도록 했다. Template을 적용하기 위해서는 아래와 같은 json 파일을 생성해서 elasticsearch에 밀어넣어야 한다. (이전에 index, index pattern 제거하고 진행함)

{
"template": "packetbeat-*",
"mappings": {
"_default_": {
"properties": {
"geoip": {
"properties": {
"location": {"type": "geo_point"}
}
}
}
}
}
}

Json 생성 후 elasticsearch에게 보내주자.

curl -XPUT -H "Content-Type: application/json" localhost:9200/_template/packetbeat_geoip -d@packetbeat_geoip.json

다시 packetbeat 로부터 데이터가 들어오기 시작하면 template이 적용된 index가 생성되고 kibana에서 index pattern을 다시 생성해 확인해보면 아래처럼 geoip.location field가 geo_point type으로 되어있는 것을 확인할 수 있다.

이제 지도를 그리자

오류가 발생하던 부분을 해결했으니 다시 visualize 메뉴에서 지도를 추가해 설정하면 아래처럼 요청 횟수에 따라 색깔별로 표시되는 걸 볼 수 있다.

VPN에 연결해서 다른 나라로부터의 요청도 제대로 표시되는지 확인해보니 별 문제없이 된다.

 

Packetbeat로부터 들어오는 데이터들을 간단히 살펴보니 사실 지역에 대한 정보 이외에도 쓸만한 내용들이 많다. 요청 url 별로 분석할 때도 유용할 것 같고 protocol type (service) 별로 구분해서 확인하는 것도 쉽게 가능할 것 같다. 그러나 이런 도구들로 분석할만한 서비스를 만들어내는 건 어려운 문제 :)