Beat는 데이터 수집기로 주로 로그 등의 데이터를 Logstash, Elasticsearch, 혹은 어딘가의 다른 곳으로 전달할 때 사용할 수 있다. logstash보다 상당히 경량이며 코드는 Go로 작성되어 있다. Beat는 공통 라이브러리인 libbeat기반으로 작성되는데, 다음과 같은 여러가지 종류의 어플리케이션이 제공되고 있다.

  • Filebeat: 로그 파일
  • Metricbeat: 메트릭
  • Packetbeat: 네트워크 데이터
  • Winlogbeat: 윈도우 이벤트 로그
  • Heartbeat: 가동 시간 모니터링

이 libbeat는 오픈소스로 공개되어 있으므로 여기 있는 제품 이외에도, 이를 기반으로 해서 많은 Beat들이 작성되어 있다.

그 중에서 여기에서는 로그를 전달하기 위한 목적으로 사용하는 Filebeat에 대해서 알아보자.

vs. Logstash

파일비트로 할 수 있는 것은 당연히 Logstash로도 가능하다. logstash쪽이 파일비트보다 커버하는 범위가 더 크기 때문이다. 하지만 그럼에도 파일비트를 알아본 이유가 있었는데, logstash를 사용하게 되면 회사에서 운영중인 API서버(t2.micro)의 크레딧을 미세하게 소모하기 때문이었다. 사실 생각해 보면 어플리케이션 서버의 자원은 온전히 서비스를 위해 쓰여져야 하는 것이 맞는데, 이를 로그를 파싱하고 Elasticsearch에 전달하는데 쓰여야 한다는 것이 조금 이상한 것 같기도 하다. 따라서 filebeat를 사용할 때에는 다음과 같은 구성을 그려볼 수 있다.

구성으로는 별 차이가 없어보이는데다 파일비트의 경우 파일을 파싱하거나 처리하는 mutate, grok등의 필터가 없기 때문에 후처리를 위해 반드시 로그를 Logstash로 보내야 해서 조금 더 불편한 구성일수도 있다. 하지만 전문적으로 이를 처리하는 Logstash 인스턴스를 두는 것이 더욱 효율적이라고 할 수 있다. 또한 파일비트에서는 output.logstash에서 여러 서버의 주소를 받을 수 있기 때문에 로드밸런싱이 가능하며, 이는 인프라의 효율적인 증감을 가능하게 한다. 그 외의 특징적인 기능으로는 다음과 같은 내용도 있다.

Filebeat는 Logstash나 Elasticsearch로 데이터를 전송할 때 backpressure-sensitive 프로토콜을 이용하여 더 많은 데이터 볼륨을 처리합니다. Logstash가 데이터 처리로 정체된 중에는 Filebeat가 읽기 속도를 낮추도록 합니다. 정체가 해결되면 Filebeat가 원래 속도를 되찾아 수집을 계속합니다.

물론 장점만 있는 것은 아니다. 가장 큰 단점으로는 input/output이 logstash에 비해 굉장히 그 수가 적다는 점이다. 위에서 적은 것처럼 많은 종류의 beat가 제품으로든 오픈소스로든 제공되고 있으나, logstash에서는 플러그인 기반으로 제공되고 있기 때문에 설치가 간편하고, conf파일에 이를 사용하도록 기록하면 되지만 filebeat는 이것이 불가능하다. 예를 들어 파일비트를 통해 elasticsearch, logstash, stdout이 아닌 어딘가의 커스텀 어플리케이션으로 보낸다면 따로 빌드해야 한다. 또한 이미 적었다시피 mutate/grok등의 필터 기능이 없으므로 로그를 입맛에 맞게 변경할 수는 없다.

따라서 파일비트는 매우 빠르고 가볍게 로그를 elasticsearch/logstash 등으로 전달할 목적으로 사용하는 것이 좋겠다.

간단 설치

파일비트는 다음 페이지에서 다운로드가 가능하다. 우분투의 경우는 deb 패키지를 받아 dpkg로 설치하면 된다.

환경 설정과 실행

파일비트는 환경 설정을 yaml 파일로 지정하는데, 이를 -path.config로 경로를 지정하여 사용한다. 예를 들어 파일비트 설정 파일 경로가 /root/filebeat에 위치한다면, -path.config는 /root/filebeat가 된다. -c옵션으로는 파일 이름을 지정하는데 별다른 지정이 없으면 자동으로 filebeat.yml을 읽는다.

그렇다면 파일비트 설정을 다음과 같이 작성해 보자.

filebeat.prospectors:
- input_type: log
  document_type: fake_apachelog
  paths:
    - /root/filebeat/*.log

output.logstash:
  hosts: ["192.168.16.254:5044"]
  index: test-log

logstash와 비슷한 방식으로, input/output으로 나뉘어 있다. 다음과 같은 순서로 이해하면 된다.

  1. filebeat.prospectors에서 파일을 읽는다.
    1. input_type: log이며
    2. 로그 파일들의 경로는 /root/filebeat에 있는 *.log
    3. 문서 타입은 fake_apachelog가 된다.
  2. output.logstash를 통해 읽어들인 내용은 logstash로 전달된다.
    1. logstash host는 192.168.16.254:5044이며,
    2. 해당 아웃풋의 index는 test-log가 된다.

설정 파일을 /root/filebeat/filebeat.yml에 저장했다면 다음과 같이 실행할 수 있다.

$ ./filebeat-linux-arm -path.config /root/filebeat

sincedb

로그를 전달할 때 중요한 점을 생각해 보자. 로그는 filebeat.yml에서 “*.log” 로 지정되어 있으니, 해당되는 파일 중에서 전달하지 못한 파일은 무엇인지, 어떤 파일을 어디까지 보냈었는지에 대한 기록이 필요하다. 그래야 다시 실행되었을 때 해당 파일을 중복없이 전달할 수 있을 것이다. Logstash에서는 이를 $LS_HOME의 .sincedb에 기록한다. 반면에 파일비트에서는 이를 data 디렉토리에서 json 형태의 데이터로 관리한다. 간단하게 tree로 보면 다음과 같다.

$ tree
/root/filebeat
|-- access_log_20170506-082029.log
|-- ...
|-- access_log_20170506-141518.log
|-- data
|   |-- meta.json
|   `-- registry
|-- filebeat-linux-arm
|-- filebeat.yml
`--  logs
    `--  filebeat

data 디렉토리의 registry 파일을 보면 이런 내용이 기록되어 있다.

[
   ...
   {
      "source":"/root/filebeat/access_log_20170506-130124.log",
      "offset":220307,
      "timestamp":"2017-05-06T13:04:42.387205873Z",
      "ttl":-2,
      "type":"log",
      "FileStateOS":{
         "inode":9960,
         "device":46
      }
   },
   {
      "source":"/root/filebeat/access_log_20170506-130304.log",
      "offset":22277,
      "timestamp":"2017-05-06T13:04:42.387208998Z",
      "ttl":-2,
      "type":"log",
      "FileStateOS":{
         "inode":9947,
         "device":46
      }
   },
   ...
]

이 파일을 지우면 로그를 처음부터 다시 읽을 것이다. 참고로 파일비트 실행시에 -path.data, -path.logs 등의 옵션으로 기본 경로를 변경할 수 있다.

테스트

실제로 테스트를 해 보았다. filebeat 설정은 위의 설정과 같으며, 가짜 아파치 로그는 다음의 코드를 통해 생성하였다. logstash 설정은 다음과 같으며, 최대한 심플하게 logstash stdout으로 찍기 위해 쓸모없는 내용은 mutate로 드롭시켰다.

input {
        beats {
                port => 5044
        }
}

filter {
        grok {
                match => { "message" => "%{COMBINEDAPACHELOG}" }
        }

        date {
                match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
                target => ["@timestamp"]
        }

        mutate {
                add_field => {
                        index => "%{[@metadata][beat]}-%{+YYYY.MM}"
                }
                remove_field => ["version", "tags", "message", "source", "timestamp", "beat"]
        }
}

output {
        stdout {
                codec => rubydebug
        }
}

logstash 출력은 다음과 같다.

...
{
        "request" => "/explore",
          "agent" => "\"Mozilla/5.0 (Windows NT 5.01) AppleWebKit/5310 (KHTML, like Gecko) Chrome/14.0.896.0 Safari/5310\"",
         "offset" => 410,
           "auth" => "-",
          "ident" => "-",
     "input_type" => "log",
           "verb" => "PUT",
          "index" => "test-log-2017.05",
           "type" => "fake_apachelog",
       "referrer" => "\"http://thomas.com/privacy.html\"",
     "@timestamp" => 2017-05-06T14:17:31.000Z,
       "response" => "200",
          "bytes" => "4987",
       "clientip" => "178.229.218.218",
       "@version" => "1",
           "host" => "b8255b19aa4e",
    "httpversion" => "1.0"
}
{
        "request" => "/list",
          "agent" => "\"Mozilla/5.0 (Windows NT 6.0; it-IT; rv:1.9.0.20) Gecko/2011-03-25 20:23:35 Firefox/3.8\"",
         "offset" => 606,
           "auth" => "-",
          "ident" => "-",
     "input_type" => "log",
           "verb" => "PUT",
          "index" => "test-log-2017.05",
           "type" => "fake_apachelog",
       "referrer" => "\"http://www.roth.com/search/\"",
     "@timestamp" => 2017-05-06T14:20:38.000Z,
       "response" => "200",
          "bytes" => "4886",
       "clientip" => "133.43.70.180",
       "@version" => "1",
           "host" => "b8255b19aa4e",
    "httpversion" => "1.0"
}
{
        "request" => "/app/main/posts",
          "agent" => "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_0 rv:4.0; en-US) AppleWebKit/532.41.6 (KHTML, like Gecko) Version/5.0.2 Safari/532.41.6\"",
         "offset" => 850,
           "auth" => "-",
          "ident" => "-",
     "input_type" => "log",
           "verb" => "PUT",
          "index" => "test-log-2017.05",
           "type" => "fake_apachelog",
       "referrer" => "\"http://www.brown.com/\"",
     "@timestamp" => 2017-05-06T14:24:34.000Z,
       "response" => "200",
          "bytes" => "5033",
       "clientip" => "74.61.238.99",
       "@version" => "1",
           "host" => "b8255b19aa4e",
    "httpversion" => "1.0"
}
{
        "request" => "/search/tag/list",
          "agent" => "\"Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_0) AppleWebKit/5330 (KHTML, like Gecko) Chrome/14.0.806.0 Safari/5330\"",
         "offset" => 1974,
           "auth" => "-",
          "ident" => "-",
     "input_type" => "log",
           "verb" => "GET",
          "index" => "test-log-2017.05",
           "type" => "fake_apachelog",
       "referrer" => "\"http://www.petersen.biz/privacy/\"",
     "@timestamp" => 2017-05-06T14:36:11.000Z,
       "response" => "200",
          "bytes" => "4890",
       "clientip" => "77.25.221.249",
       "@version" => "1",
           "host" => "b8255b19aa4e",
    "httpversion" => "1.0"
}
{
        "request" => "/list",
          "agent" => "\"Mozilla/5.0 (X11; Linux i686) AppleWebKit/5310 (KHTML, like Gecko) Chrome/13.0.876.0 Safari/5310\"",
         "offset" => 2171,
           "auth" => "-",
          "ident" => "-",
     "input_type" => "log",
           "verb" => "GET",
          "index" => "test-log-2017.05",
           "type" => "fake_apachelog",
       "referrer" => "\"http://miller.com/\"",
     "@timestamp" => 2017-05-06T14:37:43.000Z,
       "response" => "200",
          "bytes" => "5033",
       "clientip" => "159.26.241.37",
       "@version" => "1",
           "host" => "b8255b19aa4e",
    "httpversion" => "1.0"
}
...