규도자 개발 블로그

macOS에서 ELK stack 구성하기 (elasticsearch) 본문

Database/elasticsearch

macOS에서 ELK stack 구성하기 (elasticsearch)

규도자 (gyudoza) 2022. 2. 27. 21:47

macOS에서 ELK stack 구성하기 (elasticsearch)

그냥 brew install로 무지성 설치를 하려니 안되더라. 해서 내가 삽질한 내용과 더불어 다른 사람들은 헤매지 않길 바라며 기록해둘 요량이다.

 

먼저 현재(2022년 2월 27일) brew search elasticsearch 하면 나오는 내용이다.

$ brew search elasticsearch
==> Formulae
elasticsearch                                                     elasticsearch@6
Shell

깔아보자.

$ elasticsearch -V
warning: no-jdk distributions that do not bundle a JDK are deprecated and will be removed in a future release
WARNING: A terminally deprecated method in java.lang.System has been called
WARNING: System::setSecurityManager has been called by org.elasticsearch.bootstrap.Elasticsearch (file:/usr/local/Cellar/elasticsearch/7.10.2/libexec/lib/elasticsearch-7.10.2-SNAPSHOT.jar)
WARNING: Please consider reporting this to the maintainers of org.elasticsearch.bootstrap.Elasticsearch
WARNING: System::setSecurityManager will be removed in a future release
Version: 7.10.2-SNAPSHOT, Build: oss/tar/unknown/2021-01-16T01:41:27.115673Z, JVM: 17.0.2
Shell

아무튼 7.10.2가 깔린 걸 알 수 있다. 마찬가지로 brew install명령어를 통해 kibana와 logstash를 깔면

$ logstash --version
Using JAVA_HOME defined java: /Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home
logstash 7.15.2
Shell
$ kibana --version
7.10.2
Shell

자, 이렇게 elasticsearch@7.10.2 kibana@7.10.2 logstash@7.15.2가 깔린 모습이다. 슬프지만 벌써 실패했다. 보통 ELK스택이라고 부르는 만큼 이 세 어플리케이션 모두 엘라스틱서치에서 제작하고 배포하는 것들인데 릴리즈가 전부 동시에 된다. 그리고 호환성도 동일버전에 맞춰져있다. 해당 부분은 https://www.elastic.co/kr/downloads/past-releases#elasticsearch 이곳 엘라스틱서치 과거 릴리즈 버전을 다운받는 곳에 가보면 알겠지만 동일날짜 동일버전으로 패키지들이 배포되는 걸 알 수 있다. 그리고 무엇보다 내가 이렇게 진행했을 때 logstash와의 연결부분에서 실패했으니 안심해도 된다.

 

 

다음 방법으로 위 사이트에서 같이 릴리즈된 버전의 tar.gz파일을 설치해서 진행해보려 했으나 service설정이나 바이너리파일을 위치하는 곳도 지정해야하는 등 갑자기 귀찮음이 밀려와서 다른 방법이 없나 싶었다. 역시나 있었다. brew tap을 이용한 설치방법이다.

일단 위에서 설치한 brew 패키지들을 전부 삭제하자.

$ brew uninstall elasticsearch kibana logstash
Shell

elasticsearch를 제공하는 저장소를 tap명령어를 통해 추가하자.

$ brew tap elastic/tap
==> Tapping elastic/tap
Cloning into '/usr/local/Homebrew/Library/Taps/elastic/homebrew-tap'...
remote: Enumerating objects: 1303, done.
remote: Counting objects: 100% (544/544), done.
remote: Compressing objects: 100% (305/305), done.
remote: Total 1303 (delta 423), reused 307 (delta 238), pack-reused 759
Receiving objects: 100% (1303/1303), 317.08 KiB | 2.22 MiB/s, done.
Resolving deltas: 100% (1009/1009), done.
Tapped 17 formulae (37 files, 428KB).
Shell

다시 brew search elastic명령어를 쳐보면 해당 탭 저장소에서 긁어오는 다른 패키지들도 보인다.

$ brew search elastic
==> Formulae
aws-elasticbeanstalk                    elastic/tap/filebeat-full               elastic/tap/metricbeat-full
elastic/tap/apm-server-full             elastic/tap/filebeat-oss                elastic/tap/metricbeat-oss
elastic/tap/apm-server-oss              elastic/tap/heartbeat-full              elastic/tap/packetbeat-full
elastic/tap/auditbeat-full              elastic/tap/heartbeat-oss               elastic/tap/packetbeat-oss
elastic/tap/auditbeat-oss               elastic/tap/kibana-full                 elasticsearch
elastic/tap/ecctl                       elastic/tap/logstash-full               elasticsearch@6
elastic/tap/elasticsearch-full          elastic/tap/logstash-oss

==> Casks
elasticwolf
Shell

여기서 설치하는 elastic package들은 version이 맞춰져있다.

 

아 그전에 elasticsearch는 jvm위에서 돌아가는 어플리케이션이므로 openjdk를 설치하고 환경변수를 설정하는 부분이 필요하다.

$ brew tap AdoptOpenJDK/openjdk
$ brew cask install adoptopenjdk11
Shell

현재 elastic tap에 등록돼있는 애들은 java 11을 사용하므로 이렇게 설치하면 설치가 된다. 그리고 본인의 bash_profile에 환경변수 설정을 해준다. 나 같은 경우엔 zsh가 기본이므로 .zprofile에 기록했다.

export ES_JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home
export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home
Shell

왜 이렇게 두개를 해주냐. 분명히 JAVA_HOME만 선언해주면 이건 곧 deprecated된다고 ES_JAVA_HOME으로 바꾸라고 하면서 오류가 나오는데 그렇다고 또 ES_JAVA_HOME만 선언해주면 또 오류가 난다. 이렇게 두개 다 설정해주니까 오류 없이 작동하더라. 이 환경변수는 본인 컴퓨터의 옵션이나 환경에 따라서 달라질 수 있다는 점 명심하자.

 

이번엔 드디어 elk를 설치할 차례이다. 위에서 봤던 패키지 중

elastic/tap/elasticsearch-full
elastic/tap/logstash-full
elastic/tap/kibana-full
Shell

이렇게 세 개의 패키지를 brew install 명령어로 차례대로 설치하자. 물론 차례대로 설치 안해도 된다. 참고로 oss버전과 full버전의 차이에 대해서는 http://kimjmin.net/2020/06/2020-06-elastic-devrel/ 이글을 읽어보면 많은 도움이 될 것이다. 짧게 요약하자면 비지니스용도로 쓰려면 OSS버전(오픈소스버전)을 써야한다는 내용이다.

아, 그리고 위 패키지들을 설치하는 과정에서 xcode를 업데이트해달라는 요청이 있을 수 있다. App Store에서 xcode를 업데이트해주면 되긴 하는데 본인의 경우는 xcode를 사용하지도 않고 버전이 상당히 낮았던 터라 굉장히 오랜 시간이 걸렸다.

 

아무튼 위 세개의 패키지를 설치하면 brew services 명령어를 쳤을 때 위 세개가 모두 서비스로 등록된 것을 확인할 수 있을 것이다.

elasticsearch-full none
kibana-full        none
logstash-full      none
Shell

차례대로 실행해가며 정상작동을 확인해보자. 아 그리고 글을 쓰는 시점에는 모두 7.17.0버전으로 설치되었다.

 

 

 

elasticsearch

brew services start elasticsearch-full명령어를 통해 service에 등록한다.

curl -X GET localhost:9200명령어를 통해 정상작동을 확인한다. 당연한 얘기지만 인터넷 브라우저를 키고 해당 포트로 접속해봐도 된다. elasticsearch가 실행되는 데에도 시간이 좀 걸리니 바로 안뜬다고 씅내지말고 조금 기다려보는 인내를 가져보자.

$ curl -X GET localhost:9200
{
  "name" : "gyuui-MacBookPro.local",
  "cluster_name" : "elasticsearch_gyu",
  "cluster_uuid" : "tNa4dMP6QhOMtIeQ6qfVBg",
  "version" : {
    "number" : "7.17.0",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "bee86328705acaa9a6daede7140defd4d9ec56bd",
    "build_date" : "2022-01-28T08:36:04.875279988Z",
    "build_snapshot" : false,
    "lucene_version" : "8.11.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}
Shell

이렇게 뜨면 성공한 것이다.

브라우저로 접속해봐도 확인할 수 있다. 일단 이렇게 뜨면 elasticsearch는 해결된 것이다.

 

 

 

kibana

brew services start kibana-full명령어를 통해서 kibana를 실행하자. 기본설정은 localhost:5601이다. 만약에 해당 주소에서 확인할 수 없다면 vim /usr/local/etc/kibana/kibana.yml명령어를 통해 kibana.yml파일을 수정해야 한다. server.port와 server.host가 주석처리돼있을 수도 있으니 그부분을 풀어주면 된다. 만약에 주석처리된 상태였다면 주석상태를 해제하고 service를 restart하자 (brew services restart kibana-full)

# /usr/local/etc/kibana/kibana.yml

path.data: /usr/local/var/lib/kibana/data
# Kibana is served by a back end server. This setting specifies the port to use.
server.port: 5601

# Specifies the address to which the Kibana server will bind. IP addresses and host names are both valid values.
# The default is 'localhost', which usually means remote machines will not be able to connect.
# To allow connections from remote users, set this parameter to a non-loopback address.
server.host: "localhost"

YAML

설치와 실행이 정상적으로 됐으면 이런화면을 만날 수 있을 것이다.

 

 

 

logstash

마찬가지로 brew services start logstash-full명령어를 통해 logstash도 서비스에 등록해보자. 하지만 기대와는 달리 logstash는 error상태가 뜨는 걸 알 수 있다.

logstash야 아프지마...

 

error는 당연한 현상이니 왜 error가 떴는지 알아보자. /usr/local/var/log/logstash.log에 보면 왜 에러가 출력되는지 확인해볼 수 있다.

ERROR: Pipelines YAML file is empty. Location: /usr/local/Cellar/logstash-full/7.17.0/libexec/config/pipelines.yml
usage:
  bin/logstash -f CONFIG_PATH [-t] [-r] [] [-w COUNT] [-l LOG]
  bin/logstash --modules MODULE_NAME [-M "MODULE_NAME.var.PLUGIN_TYPE.PLUGIN_NAME.VARIABLE_NAME=VALUE"] [-t] [-w COUNT] [-l LOG]
  bin/logstash -e CONFIG_STR [-t] [--log.level fatal|error|warn|info|debug|trace] [-w COUNT] [-l LOG]
  bin/logstash -i SHELL [--log.level fatal|error|warn|info|debug|trace]
  bin/logstash -V [--log.level fatal|error|warn|info|debug|trace]
  bin/logstash --help
[2022-02-27T18:15:48,771][FATAL][org.logstash.Logstash    ] Logstash stopped processing because of an error: (SystemExit) exit
Shell

아 pipeline.yml파일이 비었답니다. 해당 파일을 수정할 건데 위 오류가 발생한 원본이 아닌 user level에서 사용하는 설정파일을 건드릴 것이다. 해당 파일은 /usr/local/etc/logstash에 위치하고 있다.

여기에 보면 pipelines.yml이라는 파일이 있는데 이걸 열어서 새로운 파이프라인을 정의해줄 것이다.

# /usr/local/etc/logstash/pipelines.yml

# List of pipelines to be loaded by Logstash
#
# This document must be a list of dictionaries/hashes, where the keys/values are pipeline settings.
# Default values for omitted settings are read from the `logstash.yml` file.
# When declaring multiple pipelines, each MUST have its own `pipeline.id`.
#
# Example of two pipelines:
#
# - pipeline.id: test
#   pipeline.workers: 1
#   pipeline.batch.size: 1
#   config.string: "input { generator {} } filter { sleep { time => 1 } } output { stdout { codec => dots } }"
# - pipeline.id: another_test
#   queue.type: persisted
#   path.config: "/tmp/logstash/*.config"
 - pipeline.id: sample
   path.config: "/usr/local/etc/logstash/syslog.conf"
YAML

어지럽게 쓰여져있는 설정들은 그대로 놔두고 아래 sample이라는 새로운 파이프라인을 정의했다. 파이프라인을 정의했으니 이제 해당 conf파일을 써보도록 하자. 위에서 말한 디렉토리에 보면 logstash-sample.conf라는 파일을 확인할 수 있다. 해당 파일을 보면 대충 conf파일을 어떻게 선언하는 것인지 확인할 수 있는데

# /usr/local/etc/logstash/logstash-sample.conf


# Sample Logstash configuration for creating a simple
# Beats -> Logstash -> Elasticsearch pipeline.

input {
  beats {
    port => 5044
  }
}

output {
  elasticsearch {
    hosts => ["http://localhost:9200"]
    index => "%{[@][version]}-%{+YYYY.MM.dd}"
    #user => "elastic"
    #password => "changeme"
  }
}
nginx

이런모양으로 들어있다. 저기에 써있는 beats라는 건 elsasticsearch에서 지원하는 write application이다. 하지만 여기선 일단 패스... 아무튼 인풋을 어떻게 받아들여서 아웃풋을 통해 아까 우리가 확인했던 elasticsearch 주소로 보내는 걸 확인할 수 있다.

위 파이프라인설정에 syslog.conf라는 이름으로 conf file을 선언한다고 했으니 그 이름으로 conf file을 하나 써보자면

# /usr/local/etc/logstash/syslog.conf

input {
  file {
    path => [ "/var/log/*.log", "/var/log/messages", "/var/log/syslog" ]
    type => "syslog"
  }
}

filter {
  if [type] == "syslog" {
    grok {
      match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
      add_field => [ "received_at", "%{@timestamp}" ]
      add_field => [ "received_from", "%{host}" ]
    }
    syslog_pri { }
    date {
      match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
    }
  }
}

output {
  elasticsearch {
    hosts => ["127.0.0.1:9200"]
    index => "syslog-demo"
  }
  stdout { codec => rubydebug }
}
nginx

이런 예제가 있다. /var/log/syslog에 쌓이는 systemlog를 filter형태를 거쳐서 output인 elasticsearch로 보내겠다는 의미이다.

 

여기까지 마쳤으면 logstash파이너리파일을 통해 syslog.conf파일의 구성요소를 로드하자. -f option은 logstash config를 파일에서 불러오겠다는 의미이다.

/usr/local/bin/logstash -f /usr/local/etc/logstash/syslog.conf

로그가 주르르륵 올라가면서 정상작동하는 것을 확인했으면 이제 services로 등록해도 무리없이 작동할 것이다.

brew services restart logstash-full

명령어를 때리면 아까와는 달리 logstash도 error가 뜨지 않고 멀쩡히 started형태로 상태가 유지되는 걸 확인할 수 있다.

그리고 logstash도 특정 port에서의 service를 제공한다.

$ curl -X GET localhost:9600
{"host":"gyuui-MacBookPro.local","version":"7.17.0","http_address":"127.0.0.1:9600","id":"5223f2ab-ccd9-4499-b2a8-064c1f1d5a60","name":"gyuui-MacBookPro.local","ephemeral_id":"ed34dfee-8351-4415-b4c5-424eae0599c6","status":"green","snapshot":false,"pipeline":{"workers":16,"batch_size":125,"batch_delay":50},"build_date":"2022-01-28T08:37:12Z","build_sha":"455995c32aa353c4a719cca4f7fe8592cc160926","build_snapshot":false}
Shell

이렇게 응답이 오는 걸 확인할 수 있다.

기본 서비스포트를 정리하자면

applicationport
elasticsearch9200
logstash9600
kibana5601

위와 같다.

 

그럼 이렇게 수집하는 데이터를 kibana에선 어떻게 확인할까. 위 syslog.conf파일에서 볼 수 있다시피 syslog-demo라는 이름의 index로 해당 데이터가 수집되고 있는 걸 확인할 수 있다. 여기에서 index란 무엇이냐, 보통 RDBMS의 database에 해당하는 것이라 보면 된다. 용어를 비교해보자면

RDBMSelasticsearch
databaseindex
tabletype
rowdocument
columnfield

대충 이렇게 대응된다고 보면 된다. 데이터 하나가 document로 대응되는 건 mongoDB와 비슷한 느낌이라고도 볼 수 있겠다.

 

아무튼 이렇게 수집되고 있는 데이터는 왼쪽 메뉴에서 최 하단의 Stack management -> Index Patterns -> Create index pattern

을 클릭하면

이렇게 syslog-demo라는 index로 수집되고 있는 데이터들을 확인할 수 있는데 name에 syslog-demo라고 입력한 뒤에(자동완성되는 와일드카드를 이용해도 된다) Timestamp field를 우리가 conf파일에서 설정했던 @timestamp로 맞추고 index pattern생성을 마치면

mune -> analytics -> Discover에서 좌측 상단에 syslog-demo라는 새로운 선택지가 생긴 걸 확인할 수 있을 것이다.

클릭해보면 이렇게 데이터가 잘 수집되고 있는 걸 확인할 수 있다.

 

당장에 elk stack을 실무에서 적극적으로 사용하고 있진 않지만 여기저기에 파편화된 설치방법을 한곳에 모아두는 것에 의의를 두려 한다.

 

Comments