logstash를 활용한 실시간 검출 시스템 구축

회사에서 DBA 분이 elastic 제품군을 가지고 나름 재미있는 기능을 개발하신 걸 공유받은 적이 있다.  그걸 보면서 WoW!!! 라는 감탄이 절로 나왔다.  주변의 오픈 소스 유틸리티들을 활용하면 쿨한 기능들을 설정만으로도 만들 수 있다라는 사실이 놀라웠다.

더욱 내가 반성했던 건 이 작품이 개발자가 아닌 DBA님의 도전이었다라는 점!  약간의 반성을 더 해보자면 뭔가를 집착적으로 코딩할 생각만 했다라는 생각이 훅~ 하고 머리를 때렸다.

언제가 기회가 된다면 elastic 제품을 함 써봐야겠다라는 다짐을 마음속에 뒀다.  근데 생각보다 일찍 기회가 찾아오는군. ㅋㅋ

뭘 해야하는가 하면…

뭔가 시스템을 만들어놓으면 씨잘데기없이 뭐 없나… 하면서 주변을 서성거리는 분들이 있다.  대부분의 일반 사용자를 위한 시스템들이 그렇지만 대용량 처리를 위해 아래와 같은 시스템 형상을 가진다.   시스템의 가장 앞단에는 방화벽이 존재해서 DDoS와 같은 공격을 우선 차단하고 정상적인 요청들만 실제 서비스에 흘려 보낸다.  방화벽을 통과한 요청 건들은 각 서비스 장비들로 분산된다.  전체에 비해 분산된 개별 장비에서 인지한 의심스러운 건수들은 미미하게 간주될 수 밖에 없다.

AsIsConfiguration

나뉘어진 실체를 파악하기 위해서는 이를 다시 모아야 한다. 데이터를 모아서 함께 평가하기 위한 좋은 툴이 바로 elastic 제품군이라는 걸 앞서 링크한 슬라이드를 통해 쏠쏠히 배웠다. 배웠다면 제대로 써먹어봐야지.

우선 각 서비스 장비들에는 처리한 요청의 요구자(소스)와 처리 결과를 로그 파일로 기록한다.  이건 당연히 개발자의 상식이다. 이걸 바탕으로 활용할 수 있는 제품들을 궁리해봤다.

  • filebeat – 로그 파일을 읽어들여서 이를 수집 시스템에 전달한다.  수집 시스템은 logstash 혹은 elastic search 혹은 bigdata 처리용 hive 등등을 지정할 수 있다.
  • logstash – 수집된 로그를 regular expression을 통해 특정 필드를 추출하거나 거르는 역할을 수행한다.  정제된 값들은 elastic search를 통해 전송하거나 hive 등으로 전송할 수 있다.
  • elastic search – 수집된 데이터를 다양한 검색 조건으로 빠르게 조회할 수 있는 기능을 제공한다.
  • watcher – elastic search에 존재하는 데이터 패턴을 조회하여 지정된 조건에 도달했을 때 이를 지정된 방식으로 알린다.  알림 방식은 email 혹은 http post 등을 사용할 수 있다.

근데 이렇게 하면 정말 되나?

이 정도의 제품 셋이면 원하는 나쁜 놈들을 찾을 수 있겠다 싶었다. 하지만 급하게 기술 검토를 하다보니 몇가지 문제점이 보였다.

  1. 만들려는 시스템의 궁극적인 목표는 비정상적인 요청자들을 찾아내 엉뚱한 짓을 못하도록 방화벽(Firewall)에 도움을 요청하는 것이다.  최근 N분 동안 비정상 요청들의 소스를 찾는 것이 기본 전제이다.  하지만 watcher의 동작은 배치 방식이다. 따라서 최근 N 분이라는 전제를 달성하기 어렵다.
  2. 물론 1분 정도 단위로 계속 돌리면 될 것 같기는 하다.  하지만 인위적인 Pull 방식으로 데이터를 elastic search를 통해 처리하면 전체적인 효율성의 문제가 나타날 수 있다. filebeat, logstash를 통해 들어오는 정보를 실시간으로 판단하면 되는데, 그걸 1분 단위로 Pulling하면 중간에 있는 elastic search가 부하를 발생시키고 이후에 시스템의 bottleneck이 될 수 있다.
  3. 이 시점에서 개발자의 역량이면 이 요구 사항을 대응할 수 있는 간단한 처리 기능을 직접 만드는 편이 오히려 효율적이다.  logstash를 통해 정제된 요청들을 받아서 각 소스 단위의 최근 N분 데이터를 평가하고 오류 발생시 이를 처리하는 기능이면 족하니까.

위 설명을 정리해보면 아래와 같은 구조가 개발자의 약간의 작업을 가미했을 때 가장 적합한 구조로 보였다.

System flows

이것저것 많이 쓰는 구조에서 간결해지긴 했다. 하지만 원래 의도했던 있는 것들을 잘 활용해보자에서는 약간 멀어진 느낌이다.  설명한 Architecture 구조는 내가 다루는 시스템의 상황에 맞는 구조이다. 상황은 각자에 따라 다르기 때문에 이것이 절대 정답이 될 수 없다. 사실 정답이란 없다.  하지만 한가지 중요한 이야기를 하나 더한다.  있는 것들에 너무 의존하지 말라는 것이다.  얽매이다보면 문제를 보는 바른 시각을 잃게 되고 제대로 보지 못한다.  되려 자신의 기술 수준에서 최선을 길을 찾으려고 노력해라.  설령 그 길이 잘못된 길이라도 시도함으로써 얻는 것들이 훨씬 많다.

정리가 됐으니 이제 만들어보자

시스템을 만드는 건 우선 elastic software를 설치하는 것부터 시작이다. 데이터를 제대로 받아올 수 있는지를 확인하는게 가장 먼저일테니까.

Elastic 제품군을 깔아보자

가장 먼저 할 일이 logstash를 설치하는 일이다. 설치 방법은 링크한 사이트에 충분히 자세히 설명이 나와있으니 그걸 참고하면 되겠다.  이미 들어 알고 있는 사람들은 다들 알겠지만 설정의 핵심은 filebeat을 통해 뿜어져 들어오는 로그에서 어떤 내용을 취할 건지를 설정하는 대목이다.  일반적인 Regular expression과 아주 약간 차이가 있기에 사용할 형식을 미리 테스트해보는게 좋다.

샘플 로그를 대상으로 데이터를 미리보기식으로 살펴볼 수 있는 웹앱이 있는데 쏠쏠하다.  포맷 오류 혹은 파싱 오류로 logstash를 몇번 내렸다올렸다하는 수고를 덜어줄 것 같다.  막무가내로 정의하는 것 말고도 logstash 설치 후 공통적으로 사용할 수 있는 로그 패턴들이 있으니까 다음 링크에서 참고하는 것도 좋다.

https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns

다음으로 filebeat을 설치한다. 설치 및 설정 방법 역시 친절한 설명이 사이트에 있다.  내려 받아서 그냥 설정을 잡으면 된다………………………  하지만 Segmentation fault를 내면서 죽어버네!  뭥미???

http://pds20.egloos.com/pds/201005/25/46/c0060146_4bfb18415c1f5.gif

확인해보니 filebeat이 GO 언어로 만들어졌군.  GO 플랫폼이 리눅스 커널 2.6.23 이상 버전부터 지원하기 땜시롱 안된다.  GO 플랫폼을 까는 건 벼룩잡다가 초가삼간 태우는 격이다.  ㅠㅠ

나는 개발자다

초가삼간을 태울 수는 없지만 역할을 되집어 생각해보면 로그 파일을 읽어다가 logstash 서버로 전송하면 된다.  다행이 logstash에서 TCP로 텍스트 전송하는 입력 타입을 지원한다.  그럼 내가 해야할 일은 tail -f 와 동일한 기능을 수행하는 모듈을 하나 만들면 된다.  와중에 JRE가 설치되어 있어서 자바 기반으로 하나 만들었다!!

https://github.com/tony-riot/logreader

사용법은 간단하다.  jar 파일을 만들거나 혹은 다운로드 받은 이후에 다음 명령으로 실행하면 된다.


java -jar logreader.jar /your/logs/absolute/path.log logstash-IP logstash-listening-port

각 실행 파라미터는 다음의 의미를 갖는다.

  • path.log – 읽어들일 파일의 경로를 지정한다. 가능하면 절대 경로를 지정한다.
  • logstash-IP – Logstash 서버의 IP 주소 혹은 도메인 이름을 입력한다.
  • logstash-listening-port – Logstash 서버에서 설정한 TCP 입력의 포트를 지정한다.

실행하면 아무런 Output도 출력하지 않는다. 출력되는게 없다고 놀라지 말자. 🙂

가장 메인이 되는 logstash 설정은 아래와 같다.

input {
    tcp {
        port => "5555"
        codec => line
    }
}

filter {
    grok {
        match => { "message" => "\AINFO\s{2}\|\s%{DATE_US:date} %{TIME:time}\s\|\s[a-zA-Z0-9._-]+\s\|\s[a-zA-Z0-9._\(\)]+ \|\stransaction_code=error\: (?[a-zA-Z\s]+),userName=%{USERNAME:username}\,ip=%{IP:sourceIp},source=%{USERNAME:source}" }
    }
    if "_grokparsefailure" in [tags] {
        drop { }
    }
}

output {
    http {
        url => "http://127.0.0.1:8080/api/v1/log-receiver"
        http_method => "post"
        format => "json"
        content_type => "application/json"
        mapping => ["date", "%{date}", "time", "%{time}", "cause", "%{cause}", "username", "%{username}", "sourceIp", "%{sourceIp}", "source", "%{source}" ]
    }
}

이런 설정으로 이제 수집에 관련된 부분은 마무리가 됐다. 

2017/08/01 첨언

logstash 5.5.1 버전으로 업하면서 이전과 설정이 변경된 사실을 완전 삽질 가운데 알았다.  이전 버전에서는 grok의 match 설정만으로도 매치되지 않는 다른 패턴의 경우에는 데이터가 output으로 전달되지 않았다.  근데 버전업을 해보니 매치가 되던 안되던 죄다 output으로 데이터를 전송해버려서 과도한 Stacktrace로 인해 시스템이 맛탱이가 가버렸다. ㅠㅠ

이런 현상을 막기 위해서는 성공 여부를 파악할 수 있는 _grokparsefailure 값을 tags에서 검색해서 존재하는 경우, 이를 drop filter를 사용해서 걸려내야한다. 버전업에 항상 좋은 일은 아니라는거… 덕분에 반나절 가까이를 날려버렸다.

 

자, 그럼 Analyzer라는 걸 이야기해볼까?

앞선 설정에서 볼 수 있는 것처럼 logstash에서 정제된 결과는 POST 방식으로 지정된 API 서버에 전달된다.  이때 POST의 Payload 값에는 match를 통해 추출된 값들이 JSON format을 통해 전달된다.

이제 Source 단위로 정리해서 추출하는 Business Logic을 구현하면 되겠다.  이건 뭐 누구나 할 수 있는 웹 어플리케이션 개발이다.  구질구질한 설명은 생략하겠다. 🙂

이렇게 만들어졌다.

지금까지의 설명을 한장의 그림으로 설명하면 이렇다.  원래는 많은 것들을 빌어다가 손쉽게 시스템을 만들 계획이었지만 생각만큼 녹록하지는 않았던 것 같다.  결국 1개 시스템만 활용하고 말았으니 말이다.

LogstashBasedSystem

의도한 전체 시스템을 검증된 오프 소스 제품들을 이용해서 구축해본 몇 가지 소감을 정리해본다.

  • 전체적인 시스템 구축 비용이 확실히 절감된다.  다른 개발자들을 통해 이미 검증되었고, 사용에 대한 다양한 레퍼런스들이 많아서 Troubleshooting이 쉽다.
  • logstash라는 중심축을 통해 필요한 부분만을 구현하기 때문에 개발에 낭비가 없다.  딱 그 부분 혹은 그 기능까지만 개발하면 된다.
  • 시스템적인 제약 사항이나 성능적인 부분은 쓰기 전에 충분히 검토해봐야 뒤늦은 후회를 안한다.
  • 이름에 현혹되지 말아야겠다. 명성이 자자하더라도 쓰고자 하는 현실에 맞질 않으면 폭망이다.

 

에필로그

이 작업을 하면서 30대의 젊음을 불사르면 만들었던 Open Manager 라는 제품 생각이 났다.  로그 처리 하나는 기막히게 했던 물건이었다.  과거의 추억이지만 간만에 로그 다루는 작업을 하다보니 예전 추억이 새록새록하다.

몇 날의 밤세움이후에도 지치지 않았던 건 그만한 열정이 가슴 안에 가득했기 때문이겠다.

아직은 그 열정의 불꽃을 꺼뜨리고 싶지 않다.

 

7 Comments

    • 수집 대상 파일이 logstash와 같은 장비에 있다면 그 방법도 괜찮은 방법인 것 같네요. *^^*
      근데 파일이 다른 장비에 쌓이는 경우에는 퍼와야 하기 땜시..
      logstash 자체도 그리 가볍다고는 볼 수 없는 놈이라 이걸 개별 장비에 설치하는 것보다는 작은 놈을 하나 두는 편이 좋을 것 같아서 글에서 언급한 방식으로 처리를 했죠~
      상황에 따라 다른 문제라 정답은 없고 각 상황에 따라 활용하시면 좋을 것 같습니다.

    • 글로만 읽어서는 제대로 체감이 안되더라구요.
      역시 만고의 진리는 직접 써보고 설정 잡아보고 코딩을 해봐야지 온전히 내것이 되는 것 같습니다.
      써보시고 후기 공유해주시면 좋을 것 같아요 ^^

Comments are closed.