ELK를 사용한 로그 시스템 구축하기

ELK 로그 환경

요즈음 로그 시스템을 만들때 아마도 가장 많이 사용되는 기술은 ELK일 것입니다.
ELK에 대해서는 너무 잘 알려져있기 때문에 설명은 생략하겠습니다. (https://www.elastic.co/kr/what-is/elk-stack)

이번 포스팅에서는 Kafka + ELK 를 통해서 로그 시스템을 구축하는 예제를 정리하려 합니다.
예제에 사용될 서비스들은 모두 Docker를 통해 Local 환경에 스택들을 구성하여 사용할 예정입니다.

전체적인 서비스 환경은 아래와 같습니다.

1


Kafka 환경 구축하기

먼저, 로그 메세지를 통신하기 위한 Kafka를 띄워보겠습니다.

우선, kafka가 잘 구성되어있는 Docker image를 사용하기 위해 아래 git 을 통해 파일을 clone 합니다.

$ git clone https://github.com/wurstmeister/kafka-docker && cd kafka-docker

다음, docker-compose 파일을 수정합니다.

해당 예제 환경에서는 kafka single-node 만 사용할 예정으로 'docker-compose-single-broker.yml' 파일의 설정을 변경 합니다.

// docker-compose-single-broker.yml
 
// 서버 host name 설정
KAFKA_ADVERTISED_HOST_NAME: 10.x.x.x
// 사용할 topic 설정 (토픽은 Kafka 구성 이후 생성할수도 있음)
KAFKA_CREATE_TOPICS: "log-topic:1:1"

다음, docker-compose를 위에서 설정한'docker-compose-single-broker.yml' 파일 으로 실행 합니다.

$ docker-compose -f docker-compose-single-broker.yml up -d

kafka컨테이너가 정상 수행 되었는지 확인 합니다.

$ docker ps

2


ELK 환경 구축하기

이번에는 ELK stack을 마찬가지로 docker를 이용해 띄워보겠습니다.

ELK가 잘 구성되어있는 Docker image를 사용하기 위해 아래 git 을 통해 파일을 clone 합니다.

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

다음, Elasticsearch 파일을 수정합니다.

xpack 설정파일 주석처리

// elasticsearch/config/elasticsearch.yml
---
## Default Elasticsearch configuration from Elasticsearch base image.
## https://github.com/elastic/elasticsearch/blob/master/distribution/docker/src/docker/config/elasticsearch.yml
#
cluster.name: "docker-cluster"
network.host: 0.0.0.0
 
 
## X-Pack settings
## see https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-xpack.html
#
#xpack.license.self_generated.type: trial
#xpack.security.enabled: true
#xpack.monitoring.collection.enabled: true

nori 분석기 추가

// elasticsearch/Dockerfile
 
ARG ELK_VERSION
   
# https://www.docker.elastic.co/
FROM docker.elastic.co/elasticsearch/elasticsearch:${ELK_VERSION}
 
# Add your elasticsearch plugins setup here
# Example: RUN elasticsearch-plugin install analysis-icu
RUN elasticsearch-plugin install analysis-nori

다음, Logstash 파일을 수정합니다.

xpack 설정파일 주석처리


// logstash/config/logstash.yml
---
## Default Logstash configuration from Logstash base image.
## https://github.com/elastic/logstash/blob/master/docker/data/logstash/config/logstash-full.yml
#
http.host: "0.0.0.0"
xpack.monitoring.elasticsearch.hosts: [ "http://elasticsearch:9200" ]
 
 
## X-Pack security credentials
#
#xpack.monitoring.enabled: true
#xpack.monitoring.elasticsearch.username: elastic
#xpack.monitoring.elasticsearch.password: changeme

pipeline 설정 내용 세팅

// logstash/pipeline/logstash.config
 
input {
        kafka {
                bootstrap_servers =>  "10.x.x.x:9092" // kafka ip
                group_id => "logstash"
                topics => ["log-topic"]
                consumer_threads => 1
        }
        tcp {
                port => 5000
        }
}
 
## Add your filters / logstash plugins configuration here
output {
        elasticsearch {
                hosts => "elasticsearch:9200"
                index => "logs-%{+YYYY.MM.dd}"
                user => "elastic"
                password => "password"
        }
}

다음, Kibana 파일을 수정합니다.

xpack 설정파일 주석처리

// kibana/config/kibana.yml
 
 
---
## Default Kibana configuration from Kibana base image.
## https://github.com/elastic/kibana/blob/master/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts
#
server.name: kibana
server.host: 0.0.0.0
elasticsearch.hosts: [ "http://elasticsearch:9200" ]
monitoring.ui.container.elasticsearch.enabled: true
 
 
## X-Pack security credentials
#
#elasticsearch.username: elastic
#elasticsearch.password: changeme

이제 ELK 각각의 구성설정을 마쳤으니 docker-compose 파일을 수정해줍니다.

elastic password 수정

// docker-compose
...
      ELASTIC_PASSWORD: password
...


// docker-stack
...
      ELASTIC_PASSWORD: password
...

다음, docker-compose를 실행 합니다.

$ docker-compose up -d

ELK 컨테이너가 정상 수행 되었는지 확인 합니다.

$ docker ps

3


Log 적재하기

이제 Kafka + ELK 환경이 갖추어졌으니 로그 시스템이 다 갖춰졌습니다.

다음과 같은 로직으로 위 시스템을 검증해보려 합니다.

  • Kafka 에서 message 생성
  • ES Index 생성 확인
  • ES Document 생성 확인
  • Kibana 데이터 조회

1. Kafka Message 생성

Kafka container bash 접속

// docker ps 를 통해 container id를 조회 후 bash를 실행
 
$ sudo docker exec -it 0c0af88851f0 /bin/bash

Kafka console producer 실행

$ kafka-console-producer.sh --broker-list 10.200.8.5:9092 --topic log-topic

log message 발행

> {"reg_dt":"2020-01-21 10:00:00","ldept_nm":"개발본부","mdept_nm":"개발실2","sdept_nm":"개발팀","reg_id":"test_id_1","reg_nm":"테스터1","emp_id":"202101010001","product_id":1000001,"product_name":"테스트 상품1","task_dtl":"상품 생성"}
> {"reg_dt":"2020-01-21 10:05:03","ldept_nm":"개발본부","mdept_nm":"개발실2","sdept_nm":"개발팀","reg_id":"test_id_1","reg_nm":"테스터1","emp_id":"202101010001","product_id":1000001,"product_name":"테스트 상품1","task_dtl":"상품 수정"}
> {"reg_dt":"2020-01-21 10:05:03","ldept_nm":"개발본부","mdept_nm":"개발실2","sdept_nm":"개발팀","reg_id":"test_id_1","reg_nm":"테스터1","emp_id":"202101010001","product_id":1000002,"product_name":"테스트 상품2","task_dtl":"상품 생성"}
> {"reg_dt":"2020-01-21 10:05:13","ldept_nm":"개발본부","mdept_nm":"개발실2","sdept_nm":"개발팀","reg_id":"test_id_1","reg_nm":"테스터1","emp_id":"202101010001","product_id":1000003,"product_name":"테스트 상품3","task_dtl":"상품 생성"}
> {"reg_dt":"2020-01-21 10:17:03","ldept_nm":"개발본부","mdept_nm":"개발실2","sdept_nm":"개발팀","reg_id":"test_id_1","reg_nm":"테스터1","emp_id":"202101010001","product_id":1000002,"product_name":"테스트 상품2","task_dtl":"상품 수정"}

2. Elasticsearch Index 생성 확인

Elasticsearch index 조회

$ curl localhost:9200/_cat/indices?v

결과

health status index                           uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   logs-2021.02.05            w4Oy85xWThaXT9nat6yFTg   1   1          5            0     15.8kb         15.8kb
green  open   .apm-custom-link                vvCTctk7R--fI17vdW-QQg   1   0          0            0       208b           208b
green  open   .kibana_task_manager_1          2I5nX90PQlKWg2ExJIPd_Q   1   0          5          537    112.3kb        112.3kb
green  open   .apm-agent-configuration        Hjmli6SFRtaRW8CyVWV1DA   1   0          0            0       208b           208b
green  open   .kibana_1                       bUDjQkl7Skm7EL14oSa1sg   1   0          9            0      2.1mb          2.1mb
green  open   .kibana-event-log-7.10.2-000001 LPNt7Q0zSqS53vv4vURPVQ   1   0          1            0      5.6kb          5.6kb

로그스태시에 설정한대로 logs-2021.02.05 인덱스가 생성된것을 확인 할 수 있습니다.

3. ES Document 생성 확인

이제 index 내부에 Document가 실제로 생성되었는지 확인할 차례입니다.

$ curl localhost:9200/logs-2021.02.05/_search?pretty

결과

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "logs-2021.02.05",
        "_type" : "_doc",
        "_id" : "vyCRcHcBRkK_WRzTT99_",
        "_score" : 1.0,
        "_source" : {
          "@version" : "1",
          "message" : "{"reg_dt":"2020-01-21 10:00:00","ldept_nm":"개발본부","mdept_nm":"개발실2","sdept_nm":"개발팀","reg_id":"test_id_1","reg_nm":"테스터1","emp_id":"202101010001","product_id":1000001,"product_name":"테스트 상품1","task_dtl":"상품 생성"}",
          "@timestamp" : "2021-02-05T05:01:46.492Z"
        }
      },
      {
        "_index" : "ogs-2021.02.05",
        "_type" : "_doc",
        "_id" : "xiCUcHcBRkK_WRzTqN8L",
        "_score" : 1.0,
        "_source" : {
          "@version" : "1",
          "message" : "{"reg_dt":"2020-01-21 10:05:03","ldept_nm":"개발본부","mdept_nm":"개발실2","sdept_nm":"개발팀","reg_id":"test_id_1","reg_nm":"테스터1","emp_id":"202101010001","product_id":1000001,"product_name":"테스트 상품1","task_dtl":"상품 수정"}",
          "@timestamp" : "2021-02-05T05:05:26.431Z"
        }
      },
      {
        "_index" : "logs-2021.02.05",
        "_type" : "_doc",
        "_id" : "xyCUcHcBRkK_WRzTt9_3",
        "_score" : 1.0,
        "_source" : {
          "@version" : "1",
          "message" : "{"reg_dt":"2020-01-21 10:05:03","ldept_nm":"개발본부","mdept_nm":"개발실2","sdept_nm":"개발팀","reg_id":"test_id_1","reg_nm":"테스터1","emp_id":"202101010001","product_id":1000002,"product_name":"테스트 상품2","task_dtl":"상품 생성"}",
          "@timestamp" : "2021-02-05T05:05:30.510Z"
        }
      },
      {
        "_index" : "logs-2021.02.05",
        "_type" : "_doc",
        "_id" : "yCCVcHcBRkK_WRzTxN8f",
        "_score" : 1.0,
        "_source" : {
          "@version" : "1",
          "message" : "{"reg_dt":"2020-01-21 10:05:13","ldept_nm":"개발본부","mdept_nm":"개발실2","sdept_nm":"개발팀","reg_id":"test_id_1","reg_nm":"테스터1","emp_id":"202101010001","product_id":1000003,"product_name":"테스트 상품3","task_dtl":"상품 생성"}",
          "@timestamp" : "2021-02-05T05:06:39.157Z"
        }
      },
      {
        "_index" : "logs-2021.02.05",
        "_type" : "_doc",
        "_id" : "1SC6cHcBRkK_WRzTk9-G",
        "_score" : 1.0,
        "_source" : {
          "@version" : "1",
          "message" : "{"reg_dt":"2020-01-21 10:17:03","ldept_nm":"개발본부","mdept_nm":"개발실2","sdept_nm":"개발팀","reg_id":"test_id_1","reg_nm":"테스터1","emp_id":"202101010001","product_id":1000002,"product_name":"테스트 상품2","task_dtl":"상품 수정"}",
          "@timestamp" : "2021-02-05T05:46:51.547Z"
        }
      }
    ]
  }
}

위 검증로직을 통해 ES에 정상적으로 로그 메세지가 적재되는것을 확인하였습니다.


검색 가능한 ES 적재

위 상태에서는 넘겨준 message가 ES 에서 단순히 message 에 적재되고 있습니다. 검색이 가능하도록 필드를 구성하여 적재시켜보도록 하겠습니다.

현재 log 메세지가 Json 형태로 넘어오고 있기 때문에 단순히 logstash 에서 json 을 파싱해주는 filter만 추가해주면 됩니다.

// logstash/pipeline/logstash.config
 
 
input {
        kafka {
                bootstrap_servers =>  "10.200.8.5:9092"
                group_id => "logstash"
                topics => ["log-topic"]
                consumer_threads => 1
        }
 
        tcp {
                port => 5000
        }
}
 
 
## Add your filters / logstash plugins configuration here
filter {
      json {
        source => "message"
      }
}
 
output {
        elasticsearch {
                hosts => "elasticsearch:9200"
                index => "logs-%{+YYYY.MM.dd}"
                user => "elastic"
                password => "password"
        }
}

이후 메세지를 다시 생성해 ES 를 조회해보면 다음과같은 결과를 확인 할 수 있습니다.

{
  "took" : 385,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "logs-2021.02.05",
        "_type" : "_doc",
        "_id" : "mXlUcXcBQOlug89tq04l",
        "_score" : 1.0,
        "_source" : {
          "product_name" : "테스트 상품1",
          "reg_id" : "test_id_1",
          "message" : "{\"reg_dt\":\"2020-01-21 10:00:00\",\"ldept_nm\":\"개발본부\",\"mdept_nm\":\"개발실2\",\"sdept_nm\":\"개발팀\",\"reg_id\":\"test_id_1\",\"reg_nm\":\"테스터1\",\"emp_id\":\"202101010001\",\"product_id\":1000001,\"product_name\":\"테스트 상품1\",\"task_dtl\":\"상품 생성\"}",
          "@timestamp" : "2021-02-05T08:35:09.744Z",
          "ldept_nm" : "개발본부",
          "product_id" : 1000001,
          "emp_id" : "202101010001",
          "task_dtl" : "상품 생성",
          "@version" : "1",
          "mdept_nm" : "개발실2",
          "reg_nm" : "테스터1",
          "reg_dt" : "2020-01-21 10:00:00",
          "sdept_nm" : "개발팀"
        }
      },
      {
        "_index" : "logs-2021.02.05",
        "_type" : "_doc",
        "_id" : "mnlVcXcBQOlug89tWk7l",
        "_score" : 1.0,
        "_source" : {
          "product_name" : "테스트 상품1",
          "reg_id" : "test_id_1",
          "message" : "{\"reg_dt\":\"2020-01-21 10:05:03\",\"ldept_nm\":\"개발본부\",\"mdept_nm\":\"개발실2\",\"sdept_nm\":\"개발팀\",\"reg_id\":\"test_id_1\",\"reg_nm\":\"테스터1\",\"emp_id\":\"202101010001\",\"product_id\":1000001,\"product_name\":\"테스트 상품1\",\"task_dtl\":\"상품 수정\"}",
          "@timestamp" : "2021-02-05T08:35:55.128Z",
          "ldept_nm" : "개발본부",
          "product_id" : 1000001,
          "emp_id" : "202101010001",
          "task_dtl" : "상품 수정",
          "@version" : "1",
          "mdept_nm" : "개발실2",
          "reg_nm" : "테스터1",
          "reg_dt" : "2020-01-21 10:05:03",
          "sdept_nm" : "개발팀"
        }
      },
      {
        "_index" : "-logs-2021.02.05",
        "_type" : "_doc",
        "_id" : "m3lVcXcBQOlug89tc07y",
        "_score" : 1.0,
        "_source" : {
          "product_name" : "테스트 상품2",
          "reg_id" : "test_id_1",
          "message" : "{\"reg_dt\":\"2020-01-21 10:05:03\",\"ldept_nm\":\"개발본부\",\"mdept_nm\":\"개발실2\",\"sdept_nm\":\"개발팀\",\"reg_id\":\"test_id_1\",\"reg_nm\":\"테스터1\",\"emp_id\":\"202101010001\",\"product_id\":1000002,\"product_name\":\"테스트 상품2\",\"task_dtl\":\"상품 생성\"}",
          "@timestamp" : "2021-02-05T08:36:01.545Z",
          "ldept_nm" : "개발본부",
          "product_id" : 1000002,
          "emp_id" : "202101010001",
          "task_dtl" : "상품 생성",
          "@version" : "1",
          "mdept_nm" : "개발실2",
          "reg_nm" : "테스터1",
          "reg_dt" : "2020-01-21 10:05:03",
          "sdept_nm" : "개발팀"
        }
      },
      {
        "_index" : "logs-2021.02.05",
        "_type" : "_doc",
        "_id" : "nHlVcXcBQOlug89tp04A",
        "_score" : 1.0,
        "_source" : {
          "product_name" : "테스트 상품3",
          "reg_id" : "test_id_1",
          "message" : "{\"reg_dt\":\"2020-01-21 10:05:13\",\"ldept_nm\":\"개발본부\",\"mdept_nm\":\"개발실2\",\"sdept_nm\":\"개발팀\",\"reg_id\":\"test_id_1\",\"reg_nm\":\"테스터1\",\"emp_id\":\"202101010001\",\"product_id\":1000003,\"product_name\":\"테스트 상품3\",\"task_dtl\":\"상품 생성\"}",
          "@timestamp" : "2021-02-05T08:36:14.613Z",
          "ldept_nm" : "개발본부",
          "product_id" : 1000003,
          "emp_id" : "202101010001",
          "task_dtl" : "상품 생성",
          "@version" : "1",
          "mdept_nm" : "개발실2",
          "reg_nm" : "테스터1",
          "reg_dt" : "2020-01-21 10:05:13",
          "sdept_nm" : "개발팀"
        }
      },
      {
        "_index" : "logs-2021.02.05",
        "_type" : "_doc",
        "_id" : "nXlVcXcBQOlug89twk4g",
        "_score" : 1.0,
        "_source" : {
          "product_name" : "테스트 상품2",
          "reg_id" : "test_id_1",
          "message" : "{\"reg_dt\":\"2020-01-21 10:17:03\",\"ldept_nm\":\"개발본부\",\"mdept_nm\":\"개발실2\",\"sdept_nm\":\"개발팀\",\"reg_id\":\"test_id_1\",\"reg_nm\":\"테스터1\",\"emp_id\":\"202101010001\",\"product_id\":1000002,\"product_name\":\"테스트 상품2\",\"task_dtl\":\"상품 수정\"}",
          "@timestamp" : "2021-02-05T08:36:21.559Z",
          "ldept_nm" : "개발본부",
          "product_id" : 1000002,
          "emp_id" : "202101010001",
          "task_dtl" : "상품 수정",
          "@version" : "1",
          "mdept_nm" : "개발실2",
          "reg_nm" : "테스터1",
          "reg_dt" : "2020-01-21 10:17:03",
          "sdept_nm" : "개발팀"
        }
      }
    ]
  }
}

원하던대로 필드가 구성되어 ES에 적재된것을 확인 할 수 있습니다.
이제 Elasticsearch에 원하는 검색 조건을 추가하여 조회 할 수 있습니다.

$ curl localhost:9200/logs-2021.02.05/_search?q=product_id:1000001
{
   "took":1,
   "timed_out":false,
   "_shards":{
      "total":1,
      "successful":1,
      "skipped":0,
      "failed":0
   },
   "hits":{
      "total":{
         "value":2,
         "relation":"eq"
      },
      "max_score":1.0,
      "hits":[
         {
            "_index":"logs-2021.02.05",
            "_type":"_doc",
            "_id":"mXlUcXcBQOlug89tq04l",
            "_score":1.0,
            "_source":{
               "product_name":"테스트 상품1",
               "reg_id":"test_id_1",
               "message":"{\"reg_dt\":\"2020-01-21 10:00:00\",\"ldept_nm\":\"개발본부\",\"mdept_nm\":\"개발실2\",\"sdept_nm\":\"개발팀\",\"reg_id\":\"test_id_1\",\"reg_nm\":\"테스터1\",\"emp_id\":\"202101010001\",\"product_id\":1000001,\"product_name\":\"테스트 상품1\",\"task_dtl\":\"상품 생성\"}",
               "@timestamp":"2021-02-05T08:35:09.744Z",
               "ldept_nm":"개발본부",
               "product_id":1000001,
               "emp_id":"202101010001",
               "task_dtl":"상품 생성",
               "@version":"1",
               "mdept_nm":"개발실2",
               "reg_nm":"테스터1",
               "reg_dt":"2020-01-21 10:00:00",
               "sdept_nm":"개발팀"
            }
         },
         {
            "_index":"logs-2021.02.05",
            "_type":"_doc",
            "_id":"mnlVcXcBQOlug89tWk7l",
            "_score":1.0,
            "_source":{
               "product_name":"테스트 상품1",
               "reg_id":"test_id_1",
               "message":"{\"reg_dt\":\"2020-01-21 10:05:03\",\"ldept_nm\":\"개발본부\",\"mdept_nm\":\"개발실2\",\"sdept_nm\":\"개발팀\",\"reg_id\":\"test_id_1\",\"reg_nm\":\"테스터1\",\"emp_id\":\"202101010001\",\"product_id\":1000001,\"product_name\":\"테스트 상품1\",\"task_dtl\":\"상품 수정\"}",
               "@timestamp":"2021-02-05T08:35:55.128Z",
               "ldept_nm":"개발본부",
               "product_id":1000001,
               "emp_id":"202101010001",
               "task_dtl":"상품 수정",
               "@version":"1",
               "mdept_nm":"개발실2",
               "reg_nm":"테스터1",
               "reg_dt":"2020-01-21 10:05:03",
               "sdept_nm":"개발팀"
            }
         }
      ]
   }
}