data engineering

fluentd와 elasticsearch, kibana(EFK)를 이용한 haproxy 로그 시각화

qkqhxla1 2020. 2. 27. 15:17

현재 우리 프록시 시스템의 아키텍쳐 일부 그림이다.

일부 간단하게 몇개만 그리긴 했지만 haproxy로 들어오는 패킷이 엄청나게 많다. 패킷이 추가로 많이 들어올것 같아서, 이 패킷들이 어디에서 들어와서 어디로 나가는지 모니터링하기위한 시스템을 구축하고자 한다. 

 

시각화 툴은 https://qkqhxla1.tistory.com/1031?category=802922 에서 삽질하면서 구축했던 키바나를 쓰고자 한다. 

fluentd로 haproxy의 로그를 실시간으로 모아서, 엘라스틱서치로 쏴주고, 그걸 키바나로 시각화하고자 한다. 

만들어질 아키텍쳐 예상도는 아래와 같다.

위의 아키텍쳐 예상도를 보면 내가 추가해야할 부분은 fluentd만 오토 스케일링 그룹 내의 인스턴스에 하나씩 설치 후, 엘라스틱서치로 쏘게 하면 된다. 

인스턴스가 내려갔을때 다시 올라올것을 감안해, fluentd를 도커로 한번 감싸준 후 이 도커 이미지를 auto scaling group의 인스턴스가 올라올때 자동으로 실행되는 스크립트에서 실행되도록 한다. (이미지에 도커는 기본적으로 깔려 있음.)

말은 쉬운데 이런걸 해본적이 없어서 한참 삽질했다.

 

우선 fluentd가 뭔지 알아보기 위해서는 https://www.fluentd.org/architecture 를 살펴보자. 간단하게 정리하면 모든 로그를 한방에 정리해주는 오픈소스 로그 시스템이다.

그림 하나로 정말 잘 설명했다. 또다른 참고 : https://bcho.tistory.com/1115

 

fluentd는 이런거고.. 현재 위의 모든 아키텍쳐가 production이기 때문에 테스트를 위해서haproxy의 auto scaling group launch configuration을 하나 더 만들었다. 그리고 그거로 haproxy를 따로 하나 더 띄웠다. 이경우 동작은 동일하지만 production이 아니기 때문에 haproxy가 죽거나 해도 운영과는 상관없어 테스트를 하기에 용이하다.

 

일단 나는 fluentd를 하나도 모르기때문에 새로 띄운 haproxy에 직접 들어가서 fluentd를 테스트했다. 

https://docs.fluentd.org/v/0.12/container-deployment/install-by-docker 에 보면 도커 예시가 아주 잘 나와있다.

간단하게 설명을 좀 하자면 도커 이미지를 받아와서 fluentd.conf라는 설정파일을 만들고, 내가 만든 설정파일을 바라보도록 한다음 실행하는거다. 그리고 step 3에서 잘 날라가고 받는지 테스트해본다. 인데.

 

도커의(디스크의) 마운트개념이 뭔가 머리에 박혀들어오지 않아서 이해하는데 좀 걸렸다. 위의 공식 홈페이지에서

docker run -d \
-p 9880:9880 -v /tmp:/fluentd/etc -e FLUENTD_CONF=fluentd.conf \
fluent/fluentd

이렇게 실행시키는데, 여기서 -p 9880:9880은 9880 포트를 포워딩 해주겠다는 소리이고, -v x:y의 뜻은

호스트 머신의 위치 x를 도커 내부의 y의 위치로 마운트시켜주겠다이다. 예시에서 fluentd.conf의 위치는 /tmp/fluentd.conf인데, fluentd가 기본적으로 보는 위치는 /fluentd/etc/내부의 파일들이다.

-v /tmp:/fluentd/etc로 마운트하게 되면 도커 시스템 내부의 /fluentd/etc안에 내가 /tmp안에 만들었던 fluentd.conf가 생기는거다.

 

좀 애매하게 이해가 될 경우 도커로 직접 들어가서 확인할수도 있다. docker ps를 입력하면 현재 도커 프로세스들이 보이는데, 그중에 방금 올린 이미지의 CONTAINER ID를 찾자. 그리고 docker exec -it {CONTAINER ID} /bin/sh 로 접속이 가능하다.

ls했을때 fluentd 디렉터리가 보이는데 저기 들어가서 /fluentd/etc/fluentd.conf가 내가 입력했던게 맞는지 확인해보자.

 

이제는 도커로 기본적인 fluentd를 돌리는 법과 테스트하는법을 알았다. fluentd.conf를 어떻게 작성해야 할지 알아보자. 첫번째로 fluentd의 생명주기를 간단히 읽어보자 뭔가 <source>컴포넌트에서 시작되어서 <match>에서 끝난다는것 같다. 그다음엔 config file syntax을 읽어보자. 중간에 source가 뭔지, match가 뭔지, filter등이 뭔지가 나와있다. 예상한대로 source에서 데이터를 받아서 filter등에서 데이터를 처리하고, match에서 처리된 데이터를 쏜다.
이제 이것저것 만들면서 테스트를 해보자. 나같은경우 아래의 fluentd.conf가 나왔다.


디버깅을 위해 처음에는 파일로 출력하도록 만들자.

<system>
  log_level debug
</system>

<source>
  @type tail
  path /var/log/haproxy/haproxy.log
  pos_file /fluentd/log/fluentd.pos
  tag aws.haproxy
  format ~~~~
  time_key logtime
  time_format %b %d %H:%M:%S
</source>

<filter **>
  @type parser
  format json
  key_name data
</filter>

<match **>
  @type copy
# for test
   <store>
    @type file
    path /fluentd/log/
  </store>
</match>

기본적으로 fluentd의 문법같은거 설명하려면 엄청 내용이 많다.. 대충 이런식으로 만든다음에 파일로 출력하는걸 테스트해본다. source 부분이 내가 로그를 가지고 오려는 파일 부분이고 haproxy.log를 가져올거라는걸 알 수 있다. pos_file은 내가 어디까지 읽었는지 기록해두는 위치정보가 기록된 파일이다. format부분은 지웠다.

 

만들고 도커를 실행시켰다 프로세스 죽였다 하면서 테스트한다. 

conf를 만들고 docker run~~ 으로 실행시키고, docker ps를 입력해서 도커 프로세스가 잘 뜨는지 살펴본다. fluentd.conf가 이상할경우 도커 프로세스가 뜨자마자 죽거나 떠있는데도 잘 안 돌아가는 경우가 둘다 있다. 이경우 도커에서 실행되는 로그를 아래처럼 확인할수 있다. docker ps -all을 입력하면 떴다가 바로 죽은 도커 프로세스의 container id를 얻을 수 있고, docker logs {container id}로 도커가 왜 죽었는지 살펴볼 수 있다.

도커 프로세스를 죽일땐 docker kill {container id}로 죽일수 있다. 이런식으로 fluentd.conf변경 -> docker run -> docker ps ->(ps가 없으면) docker ps -all -> docker logs {container id}로 로그확인 -> 뭔가 이상하면 docker exec -it {container id} /bin/sh로 컨테이너 직접 들어가서 확인(/bin/bash가 없어서 /bin/sh로 들어감)

이 가능하다. 이런식으로 왔다갔다하면서 fluentd.conf를 완성시킨다.

 

haproxy의 로그가 match **의 path인 /fluentd/log에 잘 찍히면 이제 haproxy의 로그를 fluentd에서 제대로 파싱해야한다. 현재 haproxy의 로그 포맷은 아래와 같다.

log-format """{\"ci\":\"%ci\",\"cp\":%cp,\"t\":\"%t\",\"ft\":\"%ft\",\"b\":\"%b\",\"s\":\"%s\",\"Tl\":\"%Tl\",\"Tw\":%Tw,\"Tc\":%Tc,\"Tr\":%Tr,\"Tt\":%Tt,\"ST\":%ST,\"r\":%{+Q}r, \"HM\":\"%HM\", \"HP\":\"%HP\", \"B\":%B, \"HQ\":\"%HQ\", \"HU\":\"%HU\", \"HV\":\"%HV\"}"""

 

이게 json형태로 저장된다. 예시를 들면 아래와 같다.

Feb 26 05:17:05 localhost haproxy[22]: {"ci":"x.x.x.x","cp":43174,"t":"26/Feb/2020:05:17:05.438","ft":"inbound_policy","b":"default_9999","s":"test-test-test","Tl":"26/Feb/2020:05:17:05 +0000","Tw":0,"Tc":1,"Tr":5,"Tt":92,"ST":200,"r":"CONNECT x.x.x.x:443 HTTP/1.0", "HM":"CONNECT", "HP":"x.x.x.x:443"}

 

각 값이 어떤걸 의미하는지는 https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#8.2.4 여기 잘 나와있다. 그러면 이제 fluentd에서 haproxy의 로그를 가져올때 잘 파싱해야한다.

위에 내가 예시로 올려놓은 fluentd.conf에서 <source>내의 format부분을 수정해야 한다. fluentd가 루비 언어를 써서, 루비 정규식을 찾아가며 만들었다.(정규식이 아니어도 되었던것 같음.) https://rubular.com/ 에서 하나하나 넣어가며 만들어보자.

어떻게 파싱되었는지 잘 나온다. 이제 정규식 만들기에 성공했으면 이걸 format부분에 잘 넣어주고 다시 fluentd 도커를 올린다음에 테스트해보자. 

 

이제 나만의 도커 이미지를 만들어 사내 docker-hub에 올려야 한다. Dockerfile은 아주 간단하게 아래와 같이 구성했다. ./fluentd.conf에는 내가 만든 fluentd.conf가 있고, 아래처럼 COPY를 사용해 자동으로 기본 위치로 복사해준다.

Dockerfile은 https://hub.docker.com/r/fluent/fluentd/ 여기서 참조할수있다.

FROM fluent/fluentd:latest
MAINTAINER ~~~

COPY ./entrypoint.sh /

# fluentd.conf Configure
COPY ./fluentd.conf /fluentd/etc/fluentd.conf

도커파일을 build해주고 이미지가 사내 docker-hub에 잘 올라갔으면 haproxy서버에서 내 docker-hub의 이미지를 받아다가 실행시켜본다. 실행은 위에 있는

docker run -d \
-p 9880:9880 -v /tmp:/fluentd/etc -e FLUENTD_CONF=fluentd.conf \
fluent/fluentd

에서 본인이 커스터마이징한거로 변경해준다. 나같은경우 fluentd.conf를 미리 도커에서 복사해주기에 -v옵션이 변경되었고, 다른 옵션이 들어갔다.

 

본인이 만든 fluentd도커를 실행시키고, 파일에 haproxy의 로그들이 잘 파싱되어 나왔으면 이걸 엘라스틱서치에 뿌려주도록 하자.

#match부분만 올림.
<match **>
  @type copy
# for test
#  <store>
#    @type file
#    path /fluentd/log/
#  </store>
  <store>
    @type elasticsearch
    host 엘라스틱서치 주소
    port 80
    logstash_format true
    logstash_prefix ${tag}
    <buffer>
        @type file
        path /fluentd/log/
        chunk_limit_size 128MB
        total_limit_size 4GB
        flush_thread_count 4
        flush_interval 5s
        retry_wait 15s
    </buffer>
  </store>
</match>

이런식으로 대충 만들고 돌려보자. 근데 실행시켜보니 도커 로그에서 fluentd에 엘라스틱서치 플러그인이 없다고 나온다. 이경우 다시 https://hub.docker.com/r/fluent/fluentd/ 를 보면 중간에 엘라스틱서치 플러그인을 같이 설치해서 도커로 말아주는 방법이 나온다.

FROM fluent/fluentd:v1.7-1

# Use root account to use apk
USER root

# below RUN includes plugin as examples elasticsearch is not required
# you may customize including plugins as you wish
RUN apk add --no-cache --update --virtual .build-deps \
        sudo build-base ruby-dev \
 && sudo gem install fluent-plugin-elasticsearch \
 && sudo gem sources --clear-all \
 && apk del .build-deps \
 && rm -rf /tmp/* /var/tmp/* /usr/lib/ruby/gems/*/cache/*.gem

COPY fluent.conf /fluentd/etc/
COPY entrypoint.sh /bin/

USER fluent

여기서 RUN apk부분만 잘 가져와서 복사해주고 돌리면 엘라스틱서치로 fluentd가 데이터를 잘 보냄을 확인할 수 있다.

 

이제 kibana에서 받아서 알아서 시각화하면 된다. 아래는 대충 시각화한 모습.