[ELK] 3. ELK와 Kafka로 Log 관제하기

아직 ELK에 대한 이해가 부족하여 많이 헷갈린다.

우선 서비스들이 Kafka로 로그를 전송하면 ELK로 Kafka의 로그를 수집하는 시나리오로 동작 시켜 보자.

ELK를 사용해보도록 하자.

1. 도커로 만들어진 ELK 를 사용한다.

$ git clone https://github.com/deviantony/docker-elk.git

2. Elasticsearch 설정 변경

x-pack은 보안,알림, 모니터링, 보고, 그래프 기능을 편리한 단일 패키지로 제공하는 유료 서비스라고 하니 지우도록 한다.

# elasticsearch/config/elasticsearch.yml
cluster.name: "docker-cluster"
network.host: 0.0.0.0

3. Kibana 설정 변경

elasticsearch에서 제공하는 rest api를 사용하기 위해 서버 주소가 필요한데, 같은 도커 네트워크에서는 컨테이너 명으로 dns가 설정되니 변경하고, 유료 서비스같은거 안 쓸거니까 elasticsearch 패스워드는 지우도록 한다.

# kibana/config/kibana.yml
server.name: kibana
server.host: 0.0.0.0
elasticsearch.hosts: [ "http://elk_elasticsearch_1:9200" ]
monitoring.ui.container.elasticsearch.enabled: true

4. Logstash 설정 변경

elasticsearch의 호스트 정보를 변경한다.

# logstash/config/logstash.yml
http.host: "0.0.0.0"

5. Logstash 파이프라인 변경

카프카로부터 로그 정보를 수집할거기 때문에 input에 kafka, output은 elasticsearch로 한다.

Kibana에서 데이터를 시각화하기 위해서 Elasticsearch의 index에 저장이 되어야 하므로 index를 설정해준다. Kibana에서 index pattern을 wildcard로 처리가 가능하므로 index정보를 일별로 생성 할 수 있도록 한다.

# logstash/pipeline/logstash.conf
input {
	kafka {
		bootstrap_servers => "kafka-container:9092"
		group_id => "logstash"
		topics => ["logs"]
		consumer_threads => 1
	}
}

## Add your filters / logstash plugins configuration here

output {
	elasticsearch {
		hosts => "elk-kafka_elasticsearch_1:9200"
        index => "log-%{+YYYY.MM.dd}"
	}
}

6. 실행

미리 Kafka에 Topic을 생성해두고 ELK를 실행해보자.

$ docker-compose build && docker-compose up

뭐지????

docker가 빌드되고 컨테이너가 실행되면서 로그가 빠르게 출력되다 logstash가 예외를 뱉으며 종료된다.

LogStash::Error: Don't know how to handle `Java::JavaLang::IllegalStateException` for `PipelineAction::Create<main>`
          create at org/logstash/execution/ConvergeResultExt.java:129
             add at org/logstash/execution/ConvergeResultExt.java:57
  converge_state at /usr/share/logstash/logstash-core/lib/logstash/agent.rb:378
[ERROR][logstash.agent           ] An exception happened when converging configuration {:exception=>LogStash::Error, :message=>"Don't know how to handle `Java::JavaLang::IllegalStateException` for `PipelineAction::Create<main>`"}
[FATAL][logstash.runner          ] An unexpected error occurred! {:error=>#<LogStash::Error: Don't know how to handle `Java::JavaLang::IllegalStateException` for `PipelineAction::Create<main>`>, :backtrace=>["org/logstash/execution/ConvergeResultExt.java:129:in `create'", "org/logstash/execution/ConvergeResultExt.java:57:in `add'", "/usr/share/logstash/logstash-core/lib/logstash/agent.rb:378:in `block in converge_state'"]}
[ERROR][org.logstash.Logstash    ] java.lang.IllegalStateException: Logstash stopped processing because of an error: (SystemExit) exit
 warning: thread "Api Webserver" terminated with exception (report_on_exception is true):
 NameError: uninitialized constant Rack::Builder
     const_missing at org/jruby/RubyModule.java:3760
              app at /usr/share/logstash/logstash-core/lib/logstash/api/rack_app.rb:97
     start_webserver at /usr/share/logstash/logstash-core/lib/logstash/webserver.rb:99
              run at /usr/share/logstash/logstash-core/lib/logstash/webserver.rb:60
              each at org/jruby/RubyRange.java:526
     each_with_index at org/jruby/RubyEnumerable.java:1258
              run at /usr/share/logstash/logstash-core/lib/logstash/webserver.rb:55
     start_webserver at /usr/share/logstash/logstash-core/lib/logstash/agent.rb:424
Exception in thread "Api Webserver" java.lang.NullPointerException
     at org.jruby.internal.runtime.ThreadService.getMainThread(ThreadService.java:233)
     at org.jruby.RubyThread.exceptionRaised(RubyThread.java:1822)
     at org.jruby.internal.runtime.RubyRunnable.run(RubyRunnable.java:112)
     at java.base/java.lang.Thread.run(Thread.java:834)

왜 그런지 모르겠으나 host주소를 container의 이름으로 pipeline 내부에서 파싱할 수 없나보다.

elasticsearch의 docker network 정보를 확인하여 파이프 라인의 elasticsearch 주소를 변경한다.

# logstash/pipeline/logstash.conf
....
output {
	elasticsearch {
		hosts => "172.20.0.4:9200"
		index => "log-%{+YYYY.MM.dd}"
	}
}

다시 실행

elk에서 예외가 발생하면서 종료되진 않는다. 그런데 지속적으로 출력되는 예외 메시지가 눈에 보인다.

Error: Failed to construct kafka consumer
Exception: Java::OrgApacheKafkaCommon::KafkaException
Stack: org.apache.kafka.clients.consumer.KafkaConsumer.<init>(org/apache/kafka/clients/consumer/KafkaConsumer.java:820)

곰곰히 생각을 하다 깨달은게 있다.

kafka와 elk는 현재 다른 도커 네트워크에 있기 때문에 컨테이너명으로 host를 쓸 수 없는 것이다.

이를 해결하기 위해 network를 하나 생성 후 kafka와 elk의 docker-compose에 각각 network 설정해준다.

# docker-compose.yml
...
networks:
  default:
    external:
      name: my-network
...

이번엔 무슨 오류?

실행을 하니 이번엔 또 새로운 오류가 보인다.

[WARN ][org.apache.kafka.clients.NetworkClient][main][15a7e73f9380c35108d8b47ea292cf9dc52e5a47679aab2cd6f22b3ba33a37f7] [Consumer clientId=logstash-0, groupId=logstash] Connection to node -1 (/172.20.0.3:9092) could not be established. Broker may not be available.
[WARN ][org.apache.kafka.clients.NetworkClient][main][15a7e73f9380c35108d8b47ea292cf9dc52e5a47679aab2cd6f22b3ba33a37f7] [Consumer clientId=logstash-0, groupId=logstash] Bootstrap broker 172.20.0.3:9092 (id: -1 rack: null) disconnected

ELK는 정상적으로 실행이 되고 지속적으로 Kafka에 접속 시도하는데 마치 Kafka에 차단이 되어있는 느낌이다.

Kafka설정을 확인을 해보았다.

# kafka의 docker-compose.yml
....
  kafka:
    container_name: kafka-container
    image: 'bitnami/kafka:latest'
    depends_on:
      - zookeeper
    ports:
    - '9092:9092'
    environment:
    - KAFKA_ADVERTISED_HOST_NAME=127.0.0.1
    - KAFKA_ZOOKEEPER_CONNECT=zookeeper-container:2181
    - ALLOW_PLAINTEXT_LISTENER=yes
    - KAFKA_LISTENERS=PLAINTEXT://:9092
    - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092

Kafka에서 Listener 설정이 localhost로 되어있어 Logstash에서 정상적으로 접근이 불가능 했던 것이다.

Listener 설정을 localhost에서 kafka 주소로 변경해준다.

# kafka의 docker-compose.yml
....
    - KAFKA_LISTENERS=PLAINTEXT://kafka-container:9092
    - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka-container:9092

위 과정을 모두 마치고 실행 하면 정상 동작을 확인 할 수 있다.

Kafka Producer로 메시지를 발행하고 Kibana에 접속하여 log의 Discuss를 확인한다. elk-kafka

아직 의문

Logstash는 로그를 수집하기 위해서 꼭 Kafka와 같은 네트워크에 위치해야 하는가?

이를 위해 나온게 Beats인가?