본문 바로가기
Programming/검색엔진

ELK 구성 노트

by ★용호★ 2017. 3. 18.

ElasticSearch



외부에서 접속이 안될 때

  • config/elasticsearch.yml 파일에 바인딩 설정
network.bind_host: 0.0.0.0

vm.max_map_count 부족 오류

  • 로그에 찍힌 오류메세지
max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
  • 설정되어 있는 vm.max_map_count 값 확인
$ sysctl vm.max_map_count
  • vm.max_map_count 값 설정
$ sysctl -w vm.max_map_count=262144
  • conf 파일로 설정
$ vi /etc/sysctl.conf
$ sysctl -p




logstash



logstash로의 input/output

input은 filebeats로 부터, 출력은 콘솔 창에.

input {
  beats {
    port => 5044
  }
}

output {
  stdout {
  }
}
  • 위와 같이 설정 후 logstash를 구동 시키면 filebeats가 동작중 일 경우 데이터를 전달받아 지정된 output인 stdout(콘솔)으로 출력된다.
  • 출력되는 형식을 보기 좋게 하기 위해 output 설정을 변경한다.
output {
  stdout {
    codec => rubydebug
  }
}
  • 출력 결과
{
    "@timestamp" => 2017-01-31T05:13:44.504Z,
        "offset" => 354,
      "@version" => "1",
    "input_type" => "log",
          "beat" => {
        "hostname" => "hiveDev2",
            "name" => "hiveDev2",
         "version" => "5.1.2"
    },
          "host" => "hiveDev2",
        "source" => "/home/hive/RnD/elastic-demo/data/test.log",
       "message" => "{\"time_slot\":\"2014-08-10T14:00:00.000Z\",\"line_num\":\"4호선\",\"line_num_en\":\"Line 4\",\"station_name\":\"남태령\",\"station_name_kr\":\"남태령\",\"station_name_en\":\"Namtaeryeong\",\"station_name_chc\":\"南泰嶺\",\"station_name_ch\":\"南泰岭\",\"station_name_jp\":\"ナムテリョン\",\"station_geo\":{\"lat\":37.463873,\"lon\":126.989134},\"people_in\":2,\"people_out\":22}",
          "type" => "log",
          "tags" => [
        [0] "beats_input_codec_plain_applied"
    ]
}
  • 하지만 입력받은 data가 json 스트링으로 보여지기 때문에 이를 key-value로 보기 위해서는 input 형식이 json 타입이라는 것을 명시해주어야 한다.
input {
  beats {
    codec => json
    port => 5044
  }
}
  • 출력 결과
{
        "station_name" => "서울역",
     "station_name_kr" => "서울역",
              "offset" => 349,
            "line_num" => "1호선",
     "station_name_en" => "Seoul Station",
          "input_type" => "log",
         "station_geo" => {
        "lon" => 126.972559,
        "lat" => 37.554648
    },
              "source" => "/home/hive/RnD/elastic-demo/data/test.log",
                "type" => "log",
           "people_in" => 2870,
         "line_num_en" => "Line 1",
                "tags" => [
        [0] "beats_input_codec_json_applied"
    ],
          "@timestamp" => 2017-01-31T05:26:16.438Z,
            "@version" => "1",
                "beat" => {
        "hostname" => "hiveDev2",
            "name" => "hiveDev2",
         "version" => "5.1.2"
    },
                "host" => "hiveDev2",
    "station_name_chc" => "-",
     "station_name_ch" => "首尔站",
     "station_name_jp" => "ソウルヨク",
          "people_out" => 1798,
           "time_slot" => "2014-09-19T14:00:00.000Z"
}
  • key-value 내용들을 보면 @version과 같은 사용하지 않는 불필요한 값들이 존재한다. 설정을 통해 이러한 값들을 제외시킬 수 있다.
filter{
  mutate {
    remove_field => [ "@version", "@timestamp", "host", "path" ]
  }
}
  • 결과
{
        "station_name" => "동작",
     "station_name_kr" => "동작(현충원)",
              "offset" => 1095,
            "line_num" => "4호선",
     "station_name_en" => "Dongjak (Seoul National Cemetery)",
          "input_type" => "log",
         "station_geo" => {
        "lon" => 126.979306,
        "lat" => 37.502971
    },
              "source" => "/home/hive/RnD/elastic-demo/data/test.log",
                "type" => "log",
           "people_in" => 3,
         "line_num_en" => "Line 4",
                "tags" => [
        [0] "beats_input_codec_json_applied"
    ],
                "beat" => {
        "hostname" => "hiveDev2",
            "name" => "hiveDev2",
         "version" => "5.1.2"
    },
    "station_name_chc" => "銅雀(顯忠院)",
     "station_name_ch" => "铜雀(显忠院)",
     "station_name_jp" => "トンジャク",
          "people_out" => 41,
           "time_slot" => "2014-10-22T15:00:00.000Z"
}
  • 이제 ouput을 콘솔이 아닌 elasticsearch로 전달하여 데이터를 저장할 수 있도록 변경한다.
output {
  elasticsearch {
    hosts => ["127.0.0.1:9200"]
    index => "seoul-metro-2014"
    document_type => "seoul-metro"
  }
}

logback을 사용하여 logstash로 로그 전송

  • build.gradle 의존성 추가
compile 'net.logstash.logback:logstash-logback-encoder:4.8'
  • logback.xml에 appender와 logger 추가
<appender name="LogstashAppender" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
    <remoteHost>127.0.0.1</remoteHost>
    <port>5000</port>
</appender>

<root level="debug">
    <appender-ref ref="STDOUT"/>
    <appender-ref ref="DEBUG"/>
    <appender-ref ref="ERROR"/>
    <appender-ref ref="LogstashAppender"/>
</root>
  • logstash.conf 설정
input {  # 서버와 클라이언트에서 각각 로그를 보낼 수 있도록 2개의 port로 리스닝한다.
  tcp {
    port => 5000
    codec => "json"
    mode => "server"
  }
  tcp {
    port => 5001
    codec => "json"
    mode => "server"
  }
}

filter {
  # logback을 통해 전달되는 로그를 파싱하게 되면 message 부분에 실제 로그가 담기게 되는데 
  # 각 의미별로 key-value 형태로 추출한다.
  # grok 사용법은 https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html 참조
  grok {
    match => { "message" => "%{NUMBER:uid} %{WORD:type} %{WORD:packet} %{NUMBER:elapsedTime} %{GREEDYDATA:data}" }
  }
  # grok으로 추출한 data 부분은 json 형태로 파싱
  json {
    source => "data"
  }
  # 불필요한 필드 제외
  mutate {
    remove_field => [ "data", "message", "HOSTNAME", "port", "thread_name", "@version", "logger_name" ]
  }
}

output {  # 테스트를 위해 콘솔 출력
  stdout{
    codec => rubydebug
  }
}

grok의 패턴은 서버에서 로그를 남기는 방식과 동일하게 맞춘 것이다. 즉, 서버에서는 공백을 구분자로 하여 uid, type packet, .. 순으로 로그를 남겼다. grok 패턴에서 사용할 수 있는 패턴 타입은 여기를 참조.

  • 출력 결과
{
          "level" => "DEBUG",
           "type" => "R",
         "packet" => "BuyShopItem",
         "result" => "SUCCESS",
            "uid" => "1234",
     "@timestamp" => 2017-02-01T08:14:53.365Z,
    "level_value" => 10000,
           "host" => "192.168.0.29",
    "elapsedTime" => "4"
}

Grok

grok의 패턴 문법은 %{SYNTAX : SEMANTIC}이다.

  • SYNTAX : 텍스트와 일치하는 패턴의 이름.
    • 예를 들면, 3.44는 NUMBER 패턴과 일치하고 55.3.244.1은 IP 패턴과 일치한다.
  • SEMANTIC : 일치시킬 텍스트에 사용자가 부여한 식별자.
    • 예를 들면, 3.44는 일정 기간이 될 수 있으므로 간단하게 기간으로 지정할 수 있고, 문자열 55.3.244.1은 요청하는 클라이언트를 식별하도록 할 수 있다.

위에서 설명한 예의 gork 필터는 아래와 같다.

%{NUMBER:duration} %{IP:client}

예제 : 가상의 http 요청 로그와 같은 샘플 로그에서 유용한 필드를 추출할 수 있다.

55.3.244.1 GET /index.html 15824 0.043

패턴은 다음과 같다.

%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}

조금 더 실질적인 예제를 위해 아래와 같이 파일로부터 로그를 읽고 결과를 elasticsearch로 전송하도록 설정한다.

input {
  tcp {
    port => 5000
    codec => "json"
    mode => "server"
  }
  tcp {
    port => 5001
    codec => "json"
    mode => "server"
  }
}

filter {
  grok {
    match => { "message" => "%{NUMBER:uid} %{WORD:packetType} %{WORD:packet} %{NUMBER:elapsedTime} %{GREEDYDATA:data}" }
  }
  json {
    source => "data"
  }
  mutate {
    remove_field => [ "data", "message", "HOSTNAME", "port", "thread_name", "@version", "logger_name" ]
  }
}

output {
  stdout {
    codec => rubydebug
  }
  elasticsearch {
    hosts => ["192.168.0.201:9200"]
    index => "hive"
    document_type => "%{packet}"
  }
}

grok 필터를 수행하고 나면 아래와 같이 필드가 추출된다.

  • client: 55.3.244.1
  • method: GET
  • request: /index.html
  • bytes: 15824
  • duration: 0.043

input 타입에 따라 output 분기

하나의 logstash 서버를 사용하여 server와 client의 로그를 모두 받을 경우 elasticsearch에 index를 달리하여 저장을 해야한다. 이를 위해 input으로 들어오는 tcp 연결에 따라 다른 index를 사용하게끔 해야 하는데 이를 위해 input tcp의 type 필드를 사용하여 분기하였다. 그리고 기존에 grok에서 파싱했던 type 필드가 tcp의 type필드와 겹쳐서 network_type으로 변경하였다.

input {                                                                                                                   
  tcp {                                                                                                                   
    port => 5000                                                                                                          
    codec => "json"                                                                                                       
    mode => "server"                                                                                                      
    type => "server"                                                                                                      
  }                                                                                                                       
  tcp {                                                                                                                   
    port => 5001                                                                                                          
    codec => "json"                                                                                                       
    mode => "server"                                                                                                      
    type => "client"                                                                                                      
  }                                                                                                                       
}                                                                                                                         
                                                                                                                          
filter {                                                                                                                  
  ... 생략 ...
}                                                                                                                         
                                                                                                                          
output {                                                                                                                  
  stdout{                                                                                                               
    codec => rubydebug                                                                                                  
  }
  if [type] == "server" {                                                                                                 
    elasticsearch {                                                                                                       
      hosts => ["192.168.10.73:9200"]                                                                                     
      index => "hive_server"                                                                                              
      document_type => "%{packet}"                                                                                        
    }                                                                                                                     
  } else if [type] == "client" {                                                                                          
     elasticsearch {                                                                                                      
       hosts => ["192.168.10.73:9200"]                                                                                    
       index => "hive_client"                                                                                             
       document_type => "%{packet}"                                                                                       
     }                                                                                                                    
  }                                                                                                                       
}                                                                                                                         

매핑 설정

long 타입의 숫자 값을 자동으로 매핑할 경우 String으로 인식하여 변환이 제대로 되지 않는 문제가 발생하여 해당 타입만 매핑을 설정하여 long 타입으로 명시.
PUT hive
{
  "mappings": {
    "*": {
      "properties": {
        "uid": {
          "type": "long"
        }
      }
    }
  }
}

logback-logstash library 사용 시 오류

  • logback.xml 파일에 appender 지정 후 root에 지정하지 않을 경우 종료 시 에러 발생
09-Mar-2017 14:31:46.425 INFO [main] org.apache.catalina.core.StandardServer.await A valid shutdown command was received via the shutdown port. Stopping the Server instance.
09-Mar-2017 14:31:46.425 INFO [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
09-Mar-2017 14:31:46.475 INFO [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["ajp-nio-8009"]
09-Mar-2017 14:31:46.526 INFO [main] org.apache.catalina.core.StandardService.stopInternal Stopping service Catalina
09-Mar-2017 14:31:46.540 WARNING [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [Hive] appears to have started a thread named [logback-appender-LogstashAppender-192.168.10.73:5000-2] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 sun.misc.Unsafe.park(Native Method)
 java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
 java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedNanos(AbstractQueuedSynchronizer.java:1037)
 java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireSharedNanos(AbstractQueuedSynchronizer.java:1328)
 java.util.concurrent.CountDownLatch.await(CountDownLatch.java:277)
 net.logstash.logback.appender.AbstractLogstashTcpSocketAppender$TcpSendingEventHandler.openSocket(AbstractLogstashTcpSocketAppender.java:599)
 net.logstash.logback.appender.AbstractLogstashTcpSocketAppender$TcpSendingEventHandler.onStart(AbstractLogstashTcpSocketAppender.java:488)
 net.logstash.logback.appender.AsyncDisruptorAppender$EventClearingEventHandler.onStart(AsyncDisruptorAppender.java:332)
 net.logstash.logback.encoder.com.lmax.disruptor.BatchEventProcessor.notifyStart(BatchEventProcessor.java:185)
 net.logstash.logback.encoder.com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:114)
 java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
 java.util.concurrent.FutureTask.run(FutureTask.java:266)
 java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
 java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 java.lang.Thread.run(Thread.java:745)
09-Mar-2017 14:31:46.545 INFO [main] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-nio-8080"]
09-Mar-2017 14:31:46.546 INFO [main] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["ajp-nio-8009"]
09-Mar-2017 14:31:46.546 INFO [main] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["http-nio-8080"]
09-Mar-2017 14:31:46.546 INFO [main] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["ajp-nio-8009"]
  • logback-logstash 라이브러리에서 사용하는 logback 버전과 호스트에서 사용 중인 버전이 다를 경우 구동 시 에러 발생
14:32:23,983 |-ERROR in ch.qos.logback.core.joran.action.NestedBasicPropertyIA - Unexpected aggregationType AS_BASIC_PROPERTY_COLLECTION


Kibana



외부에서 접속 방법

  • kibana의 bind 기본 설정은 localhost이므로 외부에서 접속이 불가능하다. 아래와 같이 config/kibana.yml 파일을 수정한다.
server.host = "0.0.0.0"

kibana kill 방법

  • ps 명령을 수행하면 kibana라는 이름의 프로세스가 존재하지 않는다. kibana 프로세스를 확인하려면 아래 명령 수행
$ ps -ef | grep '.*node/bin/node.*src/cli'

인덱스 패턴 설정 중 time-field name 드롭다운 메뉴가 비어있는 경우

  1. Settings → Advanced.
  2. metaFields 에서 수정 버튼을 눌러 "_timestamp"를 추가한 후 저장.
  3. 다시 인덱스 패턴 설정 페이즈로 이동해보면 time-field 드롭다운 메뉴에 _timestamp가 추가되어있다.


댓글