기존에 사용하던 ELK 6.3을 7.1.1 버전으로 업그레이드하며 그 사이 발생했던 문제들을 정리해 보았습니다. 참고로 모두 수동 설치되어 있는 상태입니다.

1. 설치 및 사용 환경 
2. 신규 장비 세팅하기
3. 신규 장비에 ES 설치하기 (ver 6.3)
4. Rolling 업그레이드 시작 - 6.3에서 6.8로 
5. Rolling 업그레이드 시작 - 6.8에서 7.1.1로
6. Kibana, Logstash 업그레이드


[1. 설치 및 사용 환경 ]
  

 현재 ELK를 사내 시스템에 설치하여 사용중에 있습니다. 돈이 많으면 그냥 편하게 Cloud 서비스를 사용하고 싶은 마음은 간절했으나 사정상 외부에서 서비스하는 시스템들이 보내는 raw data를 모두 가져와 사내의 ES에 밀어 넣고 있습니다. 운영비용도 절감할 수 있지만, 사내에 두고 쓰기 때문에 접근 속도, 운영 편의성 등 여러가지 장점도 있습니다. 어쨌든 좋은 솔루션을 제공해 준 elastic.co 과 오픈소스 개발자 여러분들께 무한 감사 드립니다. 

 

사양은 아래와 같습니다. 

 

OS  CentOS 7.5.x 
H/W

Intel i5, 32GB RAM, 1TB SCSI HDD x2

Intel i7, 32GB RAM, 1TB SSD x2

( 예. 맞아요.   ...    PC에 설치해서 사용중입니다. T-T )

 

총 4대를 모두 master, data mode로 설정하여 사용중에 있습니다. i7 장비 두 대는 이번 업그레이드할 때 신규로 추가했습니다.

 


[2. 신규 장비 세팅하기]

 

 다른 글에서도 정리한 적 있지만, 이번 기회에 다시 한번 깔끔하게 정리를 다시 해봤습니다. 

 1) CentOS 최소 버전 설치(USB로...)
  CentOS 설치용 USB 제작하는 방법은 이 곳을 참고해 주세요. 
 
 2) 설치 시 minimal version으로 설치합니다. 필요한 별도의 패키지는 없으며, 필요하면 추가로 설치하면 되니까요. ES는 root 계정으로 실행하지 않으니 사용할 유저를 미리 추가해 주세요. (이 글에서는 esuser라고 명명합니다.)

 3) Network 설정하기
  /etc/sysconfig/network-scripts/ifcfg-enpxxx 파일을 열어 아래와 같이 내용을 편집/추가 합니다. 

. 
. 
ONBOOT=yes 
IPADDR=172.xx.xx.xx 
GATEWAY=172.xx.xx.x 
DNS1=xx.xx.xx.xx 
DNS2=xxx.xxx.xxx.xxx 
ZONE=public 
. 
. 

   이후 systemctl restart network 한 후 외부와의 통신이 잘 되는지 확인합니다.


 4) hostname을 변경해 두세요. 

 그냥 놔두면 어느 시스템으로 원격 접속을 해도 localhost로 보일 수 있습니다.  /etc/hostname에서 원하는 이름으로 수정할 수 있습니다.

 5) sshd로 원격 접속도 하고, 파일도 이동시켜야 하니 설치를 해둡니다.
  > yum install openssh-server openssh-clients openssh-askpass

 

   22번 포트를 그대로 사용하기 보다는 다른 포트로 바꿔봅니다.
  > vi /etc/ssh/sshd_config

 

  수정된 포트를 방화벽에 추가합니다. 
  > firewall-cmd --permanent --zone=public --add-port=XXX/tcp

  > firewall-cmd --reload

 

  아래와 같이 원격 접속, 파일 전송을 테스트 해보세요.
   원격 접속 시 : > ssh 'root'@'172.31.xx.xx' -p ssh_port_number
   파일 이동 시 : > scp -r -P ssh_port_number /path_you_want_to_copy root@172.31.xx.xx:/path_to_copy

 6) Java 설치 - 7.0부터는 OpenJDK가 내장되어 별도로 설치하실 필요 없습니다.. 7.0 이상 버전을 설치하시려면 이 과정을 건너띄세요.
 > java -version  // 버전을 확인합니다.
 > yum remove java-1.7.0-openjdk.x86_64 -y // 기존 버전이 있을 경우 삭제합니다.
 > yum install java-1.8.0-openjdk-devel.x86_64 -y // 신규 버전을 설치합니다.

 7) ES는 많은 자원을 사용합니다. 아래 내용을 참고하여 시스템 자원 사용량의 제한을 수정하세요.
  > vi /etc/security/limits.conf 에 아래 내용을 추가하세요.  (https://www.elastic.co/guide/en/elasticsearch/reference/current/file-descriptors.html)

esuser hard memlock unlimited >> 하드 세팅으로 메모리 락 제한 없도록 설정 
esuser soft memlock unlimited >> 소프트 세팅으로 메모리 락 제한 없도록 설정 
esuser hard nofile 65536 >> 하드 세팅으로 65536번의 파일을 열어 볼 수 있게 설정 
esuser soft nofile 65536 >> 소프트 세팅으로 65536번의 파일을 열어 볼 수 있게 설정 
esuser hard nproc 65536 >> 하드 세팅으로 65536번의 프로시저를 실행 할 수 있게 설정 
esuser soft nproc 65536 >> 소프트 세팅으로 65536번의 프로시저를 실행 할 수 있게 설정 

* esuser가 아닌 모든 유저의 리소스 자원 한계를 수정하려면 esuser 대신 *을 사용할 수 있습니다.
* ES에서 해당 설정이 잘 되어 있는지는 아래의 방법으로 확인할 수 있습니다. 

GET _nodes/stats/process?filter_path=**.max_file_descriptors 

  > vi /etc/sysctl.conf 에 아래 내용을 추가하세요. 

 vm.max_map_count=262144 


[3. 신규 장비에 ES 설치하기 (ver 6.3)]


 1. 파일을 /app/(혹은 선정된 다른 폴더) 폴더에 복사하고 압축을 풀어둡니다.
 2. /home/esuser/esdata 폴더를 생성합니다. 
 3. 1/2번 과정을 root 계정으로 실행했다면 chown esuser:esuser /path_to_yours 로 권한을 꼭 설정하세요.
 4. /app/elasticsearch-6.xx/config의 jvm.options을 수정합니다.

-Xms15g  // 통상 메인 메모리의 절반 
-Xmx15g 

 5. /app/elasticsearch-6.xx/config의 elasticsearch.yml을 수정합니다.

cluster.name: cluster-name 
node.name: node-mdata-X.XX (ip끝번으로 이름 지정하면 구분하기 쉽습니다.) 
node.master: true 
node.data: true 

path.data: /home/esuser/esdata/data 
path.logs: /home/esuser/esdata/logs 
path.repo: /home/esuser/esdata/repo 

bootstrap.memory_lock: true 

network.bind_host: 0.0.0.0          
network.publish_host: 172.xx.xx.xx      // 설치된 시스템의 IP. 외부 통신용 주소 
network.host: 172.xx.xx.xx              // 내부 통신용 주소  

transport.tcp.compress: true        // node간 통신하는 데이터의 압축 여부를 설정. 
transport.tcp.port: 9300              // node간 통신에 사용하는 포트 

http.port: 9200                         // http를 통한 elasticsearch API 지원 노드의 port를 설정. master에게만 필요함. 

discovery.zen.minimum_master_nodes: 1   // 실제 서비스 환경에서는 최소 3대 이상의 마스터를 운영 필수.해당 옵션은 7.0부터 사용되지 않음 
discovery.zen.ping.unicast.hosts: ["172.xx.xx.xx:9300"] // node 간 연결을 위해 unicast로 master 노드를 지정. 마스터로 등록된 시스템의 모든 ip를 기록. 기본 포트인 9300을 사용한다면 굳이 기록할 필요는 없고, 포트를 변경한 경우만 이와 같이 기록하여 처리. 해당 옵션은 7.0부터 사용되지 않음 

 

 

(* 아래 업그레이드 내용 중 시스템 A/B는 (Intel i5, 32GB RAM, 1TB 자기 기록방식 HDD x2, 기존 사용 중인 시스템)로 구성된 시스템, C/D는 (Intel i7, 32GB RAM, 1TB SSD x2, 신규 시스템)로 구성된 시스템입니다. )
(* 업그레이드 전 두 시스템(A, B)에 400GB의 데이터, 1080개의 인덱스, 8500개의 샤드를 운영중에 있습니다. )


[4. Rolling 업그레이드 시작 - 6.3에서 6.8로]

 

 우선 C, D 시스템에 ES 6.3으로 순차적으로 가동했습니다. 6.8 혹은 7.1로 바로 갈 수 있지만 테스트를 위해 네 대 모두 6.3에서 시작되도록 준비했습니다. ( 호기심에 Kibana 6.8을 연결해 보았지만 실패!! Kibana는 ES와 버전을 맞춰야 하네요. )


 이제 C, D 시스템을 순차적으로 6.8로 업그레이드하여 재시작했습니다. 이까지는 아주 순조로왔습니다. 다만, Kibana 모니터링 화면에서는 시스템을 재가동 할 때 elected master가 바뀌는 것을 확인할 수 있었고, 6.8로 업그레이드 된 C와 D는 Node수에서는 변하지 않았지만, 모니터링 화면에서는 보이지 않는 것을 확인할 수 있었습니다.


 A를 업그레이드 하기 전, A에 연결된 Logstash를 잠시 종료한 후 (ps -ef | grep logstash로 확인 후 kill -SIGTERM ... , rpm으로 설치되어 있지 않은 상태입니다.), C로 연결했습니다. Kibana도 A에서 C로 연결되도록 설정ㅇ르 변경했습니다. 이 때, Kibana 모니터링 화면에서 6.8로 업그레이드 된 모든 서버가 보였습니다. 아마도 kibana 혹은 elected master의 버전과 같은 ES 서버만 보이게 되는 듯 합니다.


 바로 이어 B도 6.8 버전으로 재시작했습니다. 6.8로의 업그레이드는 전반적으로 순조롭게 마무리 되었습니다.
 
[5. Rolling 업그레이드 시작 - 6.8에서 7.1.1로]


 7.1.1에서 ES 설정 중 "discovery.zen.minimum_master_nodes", "discovery.zen.ping.unicast.hosts" 옵션이 "discovery.seed_hosts", "cluster.initial_master_nodes" 옵션으로 대체되었습니다.

    - "discovery.seed_hosts"는 "discovery.zen.ping.unicast.hosts"를 그대로 대체하면 됩니다.
    - "cluster.initial_master_nodes"는 "node.name"에 기록된 이름을 입력해야 합니다. 

 두 옵션에 대해서는 공식 문서에서 보다 상세 내용을 확인할 수 있습니다.

 

 A, B, C 를 7.1.1로 순차적으로 업그레이드 완료하였고, 당시 elected master는 D ES 6.3인 상태였습니다. 이어서 D를 업그레이드 하기 위해 종료했는데, 이때부터 문제가 발생하기 시작했습니다. 이상하게 다른 때와 달리 Kibana에서 elected master가 D인 상태로 유지되었으며, D를 종료한 상태인데도 모니터링 화면에서는 계속 live 되어 있다고 표기가 되었습니다. 시간이 지나도 바뀌는건 없었습니다. 일단 D를 재시작하면 괜찮겠지 하는 안일한 마음에 D를 7.1.1 버전으로 다시 시작했지만, "waiting for elected master node.."라는 메세지가 계속 나오면서 시작되지 않았습니다. 급한 마음에 A를 재시작 해보았지만 역시 "waiting for elected master node.."라는 메세지가 나오면서 시작되지 않았습니다. 이때 서두르지 않고 GET _cluster/health로 상태를 체크하면 시간을 더 두고 어떤 문제가 발생하는지 로그를 정확히 봤어야 하는데, 사정상 그러지 못한 것이 너무 아쉽더군요.


 결국 모든 서버를 내린 후 전체를 재시작하기로 결정을 했습니다. 하지만 계속 같은 경고 메세지를 출력하며, 정상적으로 진행되지 않았습니다. 그래서 공식 포럼에 질문을 올려보았습니다. 이하 내용은 "https://discuss.elastic.co/t/rolling-upgrade-problem-from-6-8-to-7-1-1/186571"에 담당자분과 확인하며 진행한 내용을 정리했습니다.

 

 ES 클러스터일 경우 한 대만 실행했을 때는 elected master를 선출할 수 없는 상태이므로 최소 2대 이상 실행해야 합니다. "cluster.initial_master_nodes"에 설정하면 한 대일 경우도 정상기동이 되는줄 알았는데 제가 잘못 이해한 듯 했습니다. 하지만 2대(B, C)를 실행했을 때도 실행되지 않았습니다. 이때는 아래와 같은 에러가 발생했습니다. 

[REDACTED] failed to join REDACTED... 
org.elasticsearch.transport.RemoteTransportException: 
. 
. 
Caused by: org.elasticsearch.ElasticsearchException: publication cancelled before committing: timed out after 30s 
. 
. 

 담당자는 timeout이 발생한 원인을 좀 더 상세히 알기 위해 elasticsearch.yml에 아래 옵션을 추가 후 로그를 보내달라고 제게 요청했죠.

logger.org.elasticsearch.cluster.service:TRACE
logger.org.elasticsearch.gateway.MetaStateService:TRACE

담당자는 로그를 살핀 후 현재 클러스터에 1000개가 넘는 인덱스가 있으며, elected master는 선출과정에서 각각의 인덱스를 위해 작은 metadata를 써야만 하는데, 이는 인덱스당 평균 60ms가 걸린다고 합니다. 추척된 로그에는 아래와 같은 내용이 기록되어 있었습니다. 

4  [2019-06-20T17:20:50,030][TRACE][o.e.g.MetaStateService   ] [REDACTED-NODE-NAME] [[REDACTED-INDEX-NAME/SHRkSR17SkKb9YAd6q-fEA]] state written ... 
1008  [2019-06-20T17:21:51,765][TRACE][o.e.g.MetaStateService   ] [REDACTED-NODE-NAME] [[REDACTED-INDEX-NAME/jkTAbDtIShmorVWmk-nxUQ]] state written 


   담당자는 현재 시스템 구성에서 1000개 이상의 인덱스는 너무 많으며, 이 때문에 기동 시 제한 시간인 60s를 넘어가면서 timeout이 발생했다고 했습니다. 또한 기동된 B서버는 자기 기록방식의 디스크라 속도가 느려지는 원인 중 하나임을 알려주었습니다. 그래서 인덱스를 줄여야 하지만, 당장 지울 수 없으니 cluster.publich.timeout : 90s 옵션을 elasticsearch.yml에 추가하는게 좋겠다고 이야기 했습니다.

   이 설정을 추가 후 B, C로 다시 시작했지만 "waiting for elected master node..." 경고가 계속 올라왔습니다. 여전히 B 서버의 느린 읽기 속도의 문제 인 것 같아  C, D 서버로 재시작을 시도했더니, 시스템이 정상적으로 가동되기 시작했습니다. 이후 A 서버를 실행하니 정상적으로 가동되었습니다.

 (이 시점에 모니터링을 위해 Kibana를 기동에 문제 발생. 아래 챕터에서 확인하실 수 있습니다.)

 다음으로 남은 B 서버를 가동했는데, 에러가 화면에 나타나지는 않았지만 status 가 계속 red인 상태에서 변하지 않는 문제가 발생했습니다. curl http://172.xx.xx.xx:9200/_cluster/state?pretty=true >> /app/status.txt 로 상태 출력해서 :/UNASSINGED 로 할당 안되는 것 확인하니.. 파일이 너무 많이 열려 있는 문제가 확인되었습니다. 분명 ulimit 설정은 65535까지 설정했는데... ;;  curl http://172.xx.xx.xx:9200/_nodes/stats/process?filter_path=**.max_file_descriptors 로 확인해봐도 65535로 설정된 것을 확인할 수 있었습니다. 다시 > ulimit -a 로 시스템 사용 자원을 확인해 봤더니 open files의 값이 65535였습니다. 할 수 없이 Kibana에서 red로 되어 있는 인덱스와 1000개 넘는 인덱스 중 테스트로 올라간 후 사용하지 않는 인덱스를 삭제했습니다. 이후 인덱스 총 개수는 900여개로 줄었고, B 서버도 정상 가동 되었습니다.

 이로서 이틀간의 롤링 업그레이드를 겨우 마칠 수 있었습니다. 하지만...  ;;;

[6. Kibana, Logstash 업그레이드]
 업그레이드 중간에 키바나도 7.1.1 로 업그레이드 하여 재시작을 해봤습니다. 하지만, A, C, D 서버가 정상 가동 된 후 1000개가 넘는 인덱스 정리를 위해 Kibana를 재시작했지만 정상 가동이 되지 않고 프로세스가 종료되는 것을 확인했습니다. 로그를 확인하니 max shard count의 값이 1000인데 현재 node 당 2000개가 넘어가고 있었습니다. (당시 에러 로그를 찾을 수 없어 정확한 메세지는 알 수 없네요.) ES는 정상 기동했는데 Kibana에서 이 에러가 나는 이유를 에러를 기록해두지 않아 정확히 알 수 없는 상태입니다. 어쨌든 아래와 같이 각 노드 별 최대 shard 개수를 조정했습니다. 

 curl -XPUT -H 'Content-Type: application/json' '172.xx.xx.xx:9200/_cluster/settings' -d '{ "persistent" : {"cluster.max_shards_per_node" : 3000}}'

 이후 Kibana가 정상 가동 되었으며, 마지막 가동되지 않던 B 서버도 정상화 될 수 있었습니다. 하지만 Kibana의 Discover화면으로 들어가서 검색을 시도하면 계속 에러가 발생했습니다. 로그를 보니 painless script 에서 오류가 나는 것을 확인할 수 있었습니다. 검색하는 data에 painless script에서 사용하는 키워드가 없기 때문인데, 이전 버전에서는 문제가 없었던 것 같은데, 7.0이상에서는 에러가 나더군요. 해당 script에 특정 컬럼이 존재하는지를 검사하는 코드를 모두 추가 했습니다. 

if (!doc.containsKey('your_column') || doc['your_column'].empty) return 'NULL'; else 

.

.

 이제 logstash(6.3 버전)를 재가동 했습니다. 아래 에러가 발생했네요. logstash를 재가동할 때는 이미 새로운 에러가 발생할 것이라는 기대를 하고 있었습니다. 부끄럽군요. :(

 Could not index event to Elasticsearch. 
 {
 	:status=>400, 
    :action=>
 	[
    	"index", 
        	{
            	:_id=>nil, 
                :_index=>"logstash-2019.06.21", 
                :_type=>"_doc", 
                :_routing=>nil
            }, 
    #], 
    :response=>{
    	"index"=>{
        	"_index"=>"logstash-2019.06.21", 
            "_type"=>"_doc", 
            "_id"=>nil, 
            "status"=>400, 
            "error"=>{
            	"type"=>"illegal_argument_exception", 
                "reason"=>"The [default] mapping cannot be updated on index [logstash-2019.06.21]: 
                defaults mappings are not useful anymore now that indices can have at most one type."
            }
        }
    }
}

아마도... 7.0에 수정된 type 관련 문제인 듯 했습니다. logstash도 이제 7.1.1로 업그레이드 후 변경된 config를 우선 적용했습니다.
   
<pipelines.yml file>

- pipeline.id : your_pipeline_name 
  path.config : "/app/logstash-7.1.1/config/your_pipeline_file.config" 

<logstash.yml file>

pipeline.batch.delay : 10 
config.reload.automatic: true 
config.reload.interval: 10s 

자! 이제 끝이길 기대하며 logstash를 다시 재시작했습니다. 조용!!! 오.. !!  끝인가..!! 했는데 신규 데이터가 들어오질 않더군요. 원인은 기존의 "your_pipeline_file.config"에서 type값을 _doc가 아닌 값으로 기존에 지정해뒀기 때문이었습니다.

재시작!! 또 에러!!! logstash 7.0 에서(인지 6.3 다음의 다른 버전이었는지 모르지만.. ) 다중 파이프라인 설정은 하나의 config파일안에 설정하지 않고 pipelines.yml에 추가 id와 config를 추가 설정하여 사용하도록 개선되었습니다. (문서 정독이 꼭 필요하네요!)

 드디어!! ELK의 모든 업그레이드가 종료되었습니다. 6.8 업그레이드와 네 대 중 세 대의 7.1.1 업그레이드가 순조로와 금방 끝날 것으로 기대했던 작업이 무려 하루하고 반일이 걸려버렸습니다. 끝까지 도와주신 David Turner씨에게 다시 한번 감사드리며, 다음부터 업그레이드 전 문서 정독 잘 하고 공식 포럼은 담당자들의 작업 시간을 알아둬야 겠다는 교훈을 얻고 마무리를 할 수 있었습니다.

 이제 index Forcemerge( https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html ) 해봐야 겠네요. 무슨 일이 일어날지 두렵긴 하지만... 또 삽집을 하게 되면 공유해 보겠습니다. ^^a

 

글이 다 정리되고 나니 7.2 버전이 발표되었네요. 하하하!!


안녕 ~~!! 

며칠 전 데이타 손실이 발생하여 내용을 공유하기 위해 기록합니다. 


일단 구축한 시스템은 사내에 ES로 시스템을 구축해두고, 각 컨텐츠 별로 REST API 서버  + Redis로 중간 저장소를 만들어 둔 상태입니다. 그리고 사내의 세팅된 Logstash에서 방화벽이 열리는 시간에 컨텐츠 별 중간 저장소로 접속해 그동안 쌓여있는 로그를 가져오도록 되어 있습니다. 


이렇게 세팅된 이유는 Node 별 초기/유지 비용을 줄이고 (사내에서 PC로 시스템을 구축해 둠. 서버와 비슷한 성능의 시스템을 저렴하게 구축할 수 있으며, IDC의 상면비 등을 절감할 수 있기 때문입니다. 문제 발생 시 접근성도 용이합니다.), 운영자들의 접근 속도도 높이기 위함입니다. ES외에도 리포트 사이트를 구축하여 Kibana로는 결과를 만들 수 없는 리포트를 원하는 대로 만들 수 있도록 구축해 두었습니다. 


문제는 주말이 지난 후에 발생했습니다. 


월요일 오전이면 주말 동안 가져오지 않고 쌓여 있던 데이타들을 한번에 가져오게 됩니다. 평소에 별 이상이 없었는데, 이날은 Master Node 하나가 다운되면서 발생했습니다. 가끔 다운되는 경우가 있었기에 (과도한 양의 쿼리를 하는 등으로 메모리 초과되면서 다운되는 경우가 종종 있었습니다.), 이날도 그대로 node를 기동해 두었죠. 


그런데 운영자들로부터 몇몇 데이타들이 검색되지 않는다고 보고를 받게 됩니다. 어차피 지표를 보기 위한 데이타이기에 손실이 일부 있다고 큰 문제는 되지 않지만, 이 시스템을 통해 매출 리포트를 자동화 시켜두어, 수동 매출 리포트와 결과가 틀리다는걸 바로 듣게 되었습니다. 


살펴본 결과, 실제 documents의 수가 평소 주말보다 30~50% 정도 적은 것을 확인했습니다. Facebook elasticsearch user group에 문의한 결과 아래와 같은 원인을 알게 되었습니다. 


- node를 재가동하면 shard initialize를 진행하는데, GET _cat/indices?v 로 인덱스들의 상태를 살펴보면 green이 아닌 red로 표기되는 것들이 있습니다. 혹은 ElasticHQ로 보면 unassigned Shards, Initializing Shards에 표기된 숫자들을 볼 수 있습니다. 


- 초기화가 진행되는 동안 데이터가 들어오면 bulk rejected가 되는데, 이 때 bulk queue에 보관하고 있다 작업이 완료되면 처리되도록 되어 있습니다.


- 그런데, 입력이 과다할 경우 누락이 발생되기도 한다고 합니다. 


제가 겪었던 월요일 오전의 그 사건이 딱 이런 경우였습니다. 


그래서 이를 방지하기 위해 조치를 하고, 혹시 몰라 밀려오는 데이터들을 일시적으로 redis에 expire 1주일 설정을 해두고서 마무리를 해 두었습니다. 


끝!!!

Invalid aggregation order path [something]. Buckets can only be sorted on a sub-aggregator path that is built out of zero or more single-bucket aggregations within the path and a final single-bucket or a metrics aggregation at the path end.



아래와 같은 샘플을 만들었었다.



"query" : {

   ... 검색 조건...

}.

"aggs" : {

"term_key" : {

"terms": {

"field" : "field_first",

"order" : {

"something" : "desc"

}

}

"aggs": {

"something" : {

"terms":{

"field":"field_second"

},

"aggs" : {

"sumofgold" : {

"sum": {

"field":"gold"

}

}

}

}

}

}

}


something과 sumofgold 둘 다 정렬을 하고 싶었는데 그건 안된다. path의 마지막에 오는 metrics aggregation 혹은 단일 버킷으로만 정렬 가능하다.


결과 받아서 코드로 정렬 시켜 해결함.


다음과 같은 순서로 작성되어 있습니다.


1. 개요

2. 준비

3. 쿼리 알아보기

4. nodejs와 통합 

5. chart로 표현하기


1. 개요


최근 회사에 Elasticsearch(이하 ES)로 통계 시스템을 구축했습니다. Mysql MyISAM 엔진으로 구축하던 것과 비교해보면 엄청나게 편리해졌네요.


Kibana로 쿼리하고 결과를 손쉽게 출력하여 담당자가 아닌 컨텐츠 개발자도 자신이 보고 싶은 결과를 바로 추가하여 볼 수 있을 정도니, 작은 개발사에는 이보다 더 좋은 솔루션이 있을까 싶네요.


그런데 ES에서 나온 다른 두 결과의 비교가 필요한 경우 불가능한 경우가 있어서 조금 아쉽더군요. 그래서 이를 직접 만들어 보기로 했습니다.


시간을 기준으로 데이터를 비교하는 경우는 timelion을 사용하면 되나, 시간 기준이 아니거나 두 결과의 상세 비교, 예를 들어 지난 주 매출과 이번 주 매출을 비교하여 등락을 정확히 표기하고 싶은 경우에는 CSV로 데이터를 export하여 엑셀 등에서 별도 그래프 작업이 필요했습니다.


그래서 ES에 쿼리를 날려 결과를 가져 온 후 회사 자체에서 사용하는 홈페이지에 있는 리포트 기능과 연동하면 좋겠다라는 생각을 하게 되었습니다. nodejs로 Elasticsearch에서 쿼리를 통해 결과를 얻어와 어딘가 저장하고, 이 결과를 기존의 결과와 비교하여 내가 원하는 그래프, 표로 출력하도록 하는 것이었죠.


만들면서 다른 것들은 큰 문제가 없었지만, elasticsearch에서 사용하는 쿼리 포멧에 대한 정보를 찾기가 어려워 조금 애를 먹었습니다. 이를 기록하여 메뉴얼로 만들어 두고, 공유하면 좋겠다는 생각이 들어 정리를 해봤습니다.


사용된 상세 솔루션, 기술 스택은 아래와 같습니다.


Elasticsearch( Saving raw data )

aws-cognito( Authentication )

aws-s3( Saving search results )

nginx/javascript/jquery/chart.js  ( Front-end interface )

Node.js/Express ( Rest API )


아래 글에서는 ELK를 이용한 시스템이 운영중인 것을 기준으로 Node.js로 Elasticsearch에 어떻게 쿼리를 하는지에 대해서 기술하였습니다


시스템은 아래와 같이 구축되었습니다.




(1) 리포트 사이트에서 정보를 요청한다. (필요하다면 cognito의 사용자 풀을 이용하여, 인증을 처리하고, 사용자별 권한을 나눌 수 있습니다.)

(2) 우선 S3에 저장된 요청 정보가 있는지 확인한다. (한번 읽어온 정보는 최대 100MB 공간을 마련해서 S3에서 다시 읽을 필요없도록 저장하고 있는다. 최대 용량 초과 시 참조하지 않는 결과부터 삭제). 원하는 결과가 있을 경우 바로 (5)번으로.

(3) S3에 저장된 결과가 없다면 ES에 직접 쿼리한다. 

(4) (3)에서 얻어온 결과를 S3에 저장하고, 메모리에도 저장해둔다.

(5) 검색된 결과를 돌려준다.



이 글에서는 (3)번에 대한 내용을 집중적으로 다루도록 하겠습니다. 다른 내용에 대해 필요한 분은 댓글로 남겨주세요.


브라우저에서 직접 쿼리를 실행할 수도 있겠지만, Caching, Security 등의 이슈가 있어 실제 쿼리를 담당하는 부분은 REST API 형태로 제작하였습니다.


2. 준비


동적 웹 페이지 작성 : jquery(https://jquery.com/)

결과물 출력 : chart.js(http://www.chartjs.org/)

AWS (인증 및 저장소) : https://aws.amazon.com/ko/sdk-for-node-js/ , https://aws.amazon.com/ko/sdk-for-browser/ (참고 사항으로 이 글에서는 다루지 않습니다.)

elasticsearch의 공식 nodejs client library는 아래 사이트들에서 확인이 가능합니다. 

github : https://github.com/elastic/elasticsearch-js

npm site: https://github.com/elastic/elasticsearch-js

공식 문서: https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html



위 라이브러리를 사용하여 아래와 같이 elasticsearch에 연결할 수 있습니다. (npm site 예제 인용)

var elasticsearch = require('elasticsearch');
var client = new elasticsearch.Client({
  host: 'localhost:9200',
  log: 'trace'
});

ping 함수를 사용하여 연결 여부를 테스트 할 수 있습니다. (npm site 예제 인용)

client.ping({
  // ping usually has a 3000ms timeout
  requestTimeout: 1000
}, function (error) {
  if (error) {
    console.trace('elasticsearch cluster is down!');
  } else {
    console.log('All is well');
  }
});



(아래 내용은 elasticsearch 6.3을 기준으로 작성되어 있습니다.)
3. 쿼리 알아보기


이제 이 라이브러리를 이용해서 우리가 원하는 결과를 어떻게 쿼리할 수 있는지 알아보겠습니다.


주로 다룰 함수는 search입니다. 다른 기능들은 https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/api-reference.html 를 참고하세요.


search 함수에 대한 상세 내용은 아래 링크에서 확인할 수 있습니다.

https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/api-reference.html#api-search 


쿼리를 요청하는 방법은 두 가지가 있습니다. parameter에 검색어를 넣어 요청하거나 Elasticsearch가 제공하는 Json 형태의 Query DSL(Domain Specific Language)를 사용하는 방법입니다. 여기서는 Lucene Query 방법을 간단히 확인 후 Query DSL을 이용하여 원하는 결과를 얻어오는 방법을 다뤄보겠습니다.


만약 raw data의 종류를 log_index라는 키를 이용해 구분한다고 가정하고, 1000번인 데이터만 검색하고 싶다고 가정해 보겠습니다.


nodejs에서는 search 함수를 다음과 같이 사용합니다. 


[Lucene query 문을 사용한 방법]

const response = await client.search({
  index: 'myindex',
  q: 'title:test'
});


virtualize와 Timelion 등 전반적으로 Kibana에서는 Lucene query를 사용합니다. 하지만, 상세한 필터 적용, 조건 처리를 위해서는 Query DSL이 더 유용합니다.


[Query DSL을 사용한 방법]

client.search({
index: logstash-2018.10.01,
body: {
"query": {
"match" : { "log_index" : "1000"}
}
}
});

이를 nodejs에 적용하기 전에 body의 내용을 Kibana 의 Dev Tool에서 확인할 수 있습니다.


POST logstash-2018.10.01/_search?size=0
{
index: logstash-2018.10.01,
body: {
"query": {
"match" : { "log_index" : 1000}
}
}
});

size는 출력할 결과의 수이며 default는 10입니다. 0으로 지정하면 결과의 정보만 볼 수 있습니다. 아래와 같은 결과를 확인할 수 있습니다.


{ "took": 253,           //    ms 단위로 결과를 수행한 시간 "timed_out": false,        "_shards": { "total": 1000,       //    조건에 맞는 검색된 총 개수 "successful": 1000, "skipped": 0, "failed": 0 }, "hits": { "total": 100000000,    //    검색한 총 개수 "max_score": 0, "hits": [] } }

size를 원하는 만큼 지정하면 hits에 배열 형태로 포함되어 출력됩니다.


테스트 결과를 확인하는 방법을 알아보았으니, 이제 필요한 Query DSL에 대해서 좀 더 살펴보겠습니다.


아래 document에서 좀 더 상세한 내용을 확인할 수 있습니다.


https://www.elastic.co/guide/en/elasticsearch/reference/6.3/search-request-body.html

https://www.elastic.co/guide/en/elasticsearch/reference/6.3/query-dsl.html


이 중 몇 가지 샘플을 통해서 사용법을 알아보겠습니다.


예제) 특정 시간대의 데이터만 검색하기


query:{

"bool": {

"must" : [

{"match" : {"log_index" : 1000}}

],

"must_not": [

{"match" : {"item_type" : 100}},

{"query_string": {"fields":["name"],"query" : "tester OR manager"}}

],

"filter": {

"range" : {

"@timestamp" : {

"gte" : "2018-09-01",

"lte" : "2018-09-30"

}

},

}

}

}

"9월 한달 간 로그 종류가 1000번 이고, 아이템 종류가 100번이 아니고, name 필드에 tester나 manager가 포함되지 않은 데이터를 검색"하는 예제입니다.


위 쿼리에서 볼드체로 처리된 키워드들을 살펴보겠습니다. 


bool : 다른 쿼리들에 공통적으로 일치하는 document를 검색합니다. Lucene의 BooleanQuery와 같은 역할을 합니다.

must, must_not : 일치하거나 배치되는 조건을 설정합니다.

match : 정확히 일치하는 조건을 설정합니다. 이와 유사한 키워드는 한 단어를 검색할 때 사용하는 term이 있는데, 이는 주어진 조건의 단어가 포함된 것 전체를 검색하게 됩니다.

query_string : query_string의 query는 조건 검색을 지원합니다. (링크)

filter : must와 비슷하지만, score[각주:1]를 고려하지 않습니다.

range : 검색 범위를 저장할 수 있습니다.



예제) 기간 별 count 확인하기


query:{

"bool" : {

"must" : [

{"match" : {"log_index" : 2000}}

],

"filter": {

"range" : {

"@timestamp" : {

"gte" : "2018-09-01",

"lte" : "2018-09-30"

}

},

}

},

"aggs" : {

"days" : {

"date_histogram":{

"field" : "@timestamp",

"interval" : "day

},

"aggs" : {

"type_count":{

"cardinality" : {

"field": "login"

}

}

}

}

}

}


"9월 한 달간 일별 login한 횟수를 집계"하는 예제입니다. Aggregations에 대해서는 이 곳에서 상세한 정보를 확인할 수 있습니다. 


aggs : 집계에 대한 쿼리를 정의합니다. "days"는 집계 정보에 대한 사용자 지정 이름으로, 원하는 이름을 지정할 수 있습니다.

date_histogram : 시간을 기록한 @timestamp를 기준으로 일별 지표를 집계합니다. 집계된 정보는 "aggregations"하위에 buckets에 날짜별로 doc_count에 기록되어 결과를 얻을 수 있습니다.

cardinality : 공식 문서에 'approximate count' 라는 부연 설명이 있는데, 정확한 결과를 얻기에는 리소스가 많이 필요해 HyperLogLog++ algorithm에 기반하여 결과를 도출한다고 되어 있습니다. 보다 상세한 내용은 이 곳 을 참고 바랍니다.



4. nodejs에 통합


Elasticsearch로부터 원하는 결과를 얻었다면 이제 이 결과를 분석해서 웹 사이트에 표로 출력을 해보겠습니다. 


위에서 보았던 elasticsearch 공식 nodejs client library를 초기화하는 코드입니다.


var elasticsearch = require('elasticsearch');
var client = new elasticsearch.Client({
  host: 'localhost:9200',
  log: 'trace'
});

ping 함수를 사용하거나 테스트 쿼리를 날려 잘 접속되는지 확인해 보세요. 


이제 위에서 언급했던 "특정 시간대의 데이터만 검색하기" 결과를 받아 그래프를 그려보겠습니다. 


아래 코드는 Elasticsearch에 쿼리하여 원하는 데이터를 검색하고 결과를 가공하는 REST API에서 호출될 예제 함수들입니다. 


.
.
app.route('/test/query').post(function(req, res, next){
req.accepts('application/json'); testQuery(req.body.sday, req.body.eday).then((result) => { res.json('{"result":"'+JSON.stringify(result)+'"}'); // 결과를 클라이언트에 돌려줍니다.  }).catch((err) => { res.json('{"err":"'+ err + '"}'); }); })

.
.

function testQuery(startDate, endDate){
return new Promise(resolve => {
var listRawData = {};
var count =0;
.
.
(미리 caching했던 결과물이 있다면 search전체 읽어 결과를 바로 돌려줍니다. )
.
.
client.search({
index : 'logstash-2018.10.01',
scroll: '2s',
body : {

query:{

"bool": {

"must" : [

{"match" : {"log_index" : 1000}}

],

"must_not": [

{"match" : {"item_type" : 100}},

{"query_string": {"fields":["name"],"query" : "tester OR manager"}}

],

"filter": {

"range" : {

"@timestamp" : {

"gte" : startDate,

"lte" : endDate

}

}

}

}

}

}
}, function getMoreUntilDone(err, res){

// listRawData res.hits.hits의 내용을 적절히 쌓아둡니다.

count += res.hits.hits.length; // listRawData 에는 중복된 데이터가 발생할 수 있으니 카운트를 별도로 처리
if(count != res.hits.total){
client.scroll({    //    아직 total count에 도달하지 않았다면 다음 데이터를 받아옵니다.
scrollId: res._scroll_id,
scroll:'2s'
}, getMoreUntilDone);
}else{
var result=[];

//    listRawData 의 결과를 가공하여 result에 넣어두세요.
//    이 예제에서는 day와 value, value2 형태로 데이터를 가공하여 저장했다고 가정합니다.
//    가공된 결과를 s3, 별도의 저장소 혹은 cache에 저장하여 활용할 수 있습니다.
resolve(result); // 가공된 데이터를 돌려줍니다.
}
});
});
}


5. Chart로 출력하기


이제 클라이언트측에서 받은 결과를 chart로 출력하는 과정을 살펴보겠습니다.


function drawResult(mainDiv){ // 그래프를 그릴 div를 전달받습니다.
$.post("http://localhost/test/query", {'sday':'2018-09-01', 'eday':'2018-09-30'}, function(res){
res = JSON.parse( res );
var labels = []; // 그래프에 표기할 label을 넣습니다. 이 예제에서는 날짜 데이터가 들어갑니다.
var values =[]; // 그래프에 표기할 값을 넣습니다.
var values2 =[];

res.result.forEach(function(data){
labels.push( data.day );
values.push( Number( data.value));
values2.push( Number( data.value2));
});

var div = document.createElement('div'); // 추후 삭제를 위해 sub div 하나를 추가합니다.
div.classList.add('chart-container');
var chart = document.createElement('canvas');
div.appendChild(chart);
mainDiv.appendChild(div);

//    이하는 chart.js를 이용해 그래프를 설정하는 샘플입니다.
var ctx = chart.getContext('2d');
var lineChart = new Chart(ctx, {
type: 'line',
data : {
labels : labels,
datasets : [{
label : "Sample Graph",
borderColor: 'rgb(255,99,132)',
data : values
},{
label : "Sample Graph 2",
borderColor: 'rgb(99,255,132)',
data : values2
}]
},
options: {
responsive : true
}
});
});
}

출력된 샘플 결과는 아래와 같습니다.





지금까지 Elasticsearch를 nodejs에 통합하여 결과를 우리가 원하는 곳에 출력하는 전반적인 과정을 살펴 보았습니다.


다음에 요청이 있거나 다른 기회가 있다면, 각 단계의 상세 과정을 다시 정리할 수 있는 기회가 있으면 좋겠습니다.


짧지 않은 글, 읽어 주셔서 감사합니다. 





  1. 얼마나 일치하는지를 나타내는 수치이다. 예를들어 우리가 "tester"라는 단어를 검색했다면 "tester"라고 된 document의 score 는 1일 것이고, "tester 1"이라고 된 document는 0.9 정도가 될 것이다. [본문으로]

간혹 테스트를 하거나 잘못 보내진 문서만 골라서 지워야 하는 경우가 있다.


kibana dev tools에서 _delete_by_query를 사용하여 손쉽게 처리할 수 있다. 


(앞으로도 간혹 생각나는 것들이 있거나 기록하고 싶으면 아래 내용을 추가할 예정!!)


>> 특정 범위의 로그를 지울 경우

POST logstash-rs1_lnk_kr-*/_delete_by_query

{

  "query": {

    "range" : {

      "item_name_you_want_to_delete": {

        "lt" : "value_you_find_out"  // lt= less than, gt=greater than, eq=equal

      }

    }

  }

}



쉽다길래 별 생각 안했는데.. 설정할게 많네 -_-



ERROR. max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]

ERROR. memory locking requested for elasticsearch process but memory is not locked


위 두 에러가 발생하면 아래 내용을 참고하여 수정하면 된다.


클러스터를 구성하기 위해서는 리소스 사용에 대한 제한을 풀어줘야 한다. 


아래 command로 현재 리소스 제한 사항을 볼 수 있으며, 

ulimit -Sa


vi /etc/security/limits.conf


로 들어가서 아래와 같이 설정한다.


es-user hard memlock unlimited          >> 하드 세팅으로 메모리 락 제한 없도록 설정

es-user soft memlock unlimited           >> 소프트 세팅으로 메모리 락 제한 없도록 설정

es-user hard nofile 65536                   >> 하드 세팅으로 65536번의 파일을 열어 볼 수 있게 설정

es-user soft nofile 65536                    >> 소프트 세팅으로 65536번의 파일을 열어 볼 수 있게 설정

es-user hard nproc 65536                   >> 하드 세팅으로 65536번의 프로시저를 실행 할 수 있게 설정

es-user soft nproc 65536                    >> 소프트 세팅으로 65536번의 프로시저를 실행 할 수 있게 설정

(es-user는 Elasticsearch를 실행할 유저를 말한다. 사용중인 계정으로 수정하여 설정할 것!)

모든 설정이 끝나면 다시 시스템을 리붓한다. 



ERROR. max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]


하나의 프로세스가 가질 수 있는 메모리 맵 영역의 최대 수를 말하는데 아래와 같이 수정이 가능함.


sysctl -w vm.max_map_count=262144

(root 권한으로 실행)

재실행 없이 바로 적용됨. /etc/sysctl.conf에 추가하면 재부팅시 적용됨.


ERROR. failed to send join request to master ... 


network.bind_host, network.publish_host, network.host 세 가지 설정을 적절히 했는지 잘 살펴 볼 것!



config 파일에서 수정해야 할 내용들


config/jvm.options


메모리 관련된 설정인데 역할에 따라 다르다. 찾아보면 다양한 정보가 나오는데 아직 경험하지 못해서 이렇다 저렇다 적기가 뭐 하네. 일단 아래 두 가지를 적절히 수정. 해당 서버의 리소스에 맞춰 수정이 필요. 


-Xms 와 -Xmx 를 master는 2g , data는 4g로 수정해 봄.


-------------------------------------------------------------------------------------------------------------------


config/elasticsearch.yml


cluster.name: es-cluster             << 적절한 이름으로 설정. 클러스터마다 고유해야 함.


node.name: node-master           << 적절한 이름으로 설정. 노드마다 고유해야 함.

node.master : true                    << 마스터 노드인 경우 true 아니면 false

node.data: false                       << 데이터 전용 노드인 경우 true 아니면 false


bootstrap.memory_lock: true      <<  bootstrap 검사. 5버전부터 추가 된 듯. 주요 설정들이 잘못된 경우 상용 모드에서는 서버가 실행되지 않고, 개발 모드에서는 경고 메세지 출력 처리. memory_lock은 jvm 메모리의 swapping lock 여부를 확인한다. 


network.bind_host: 0.0.0.0         
network.publish_host: 1.1.1.1      << 설치된 서버의 IP
network.host: 1.1.1.1                

transport.tcp.compress: true       << node간 통신하는 데이터의 압축 여부를 설정.
transport.tcp.port: 9300             << node간 통신에 사용하는 포트

http.port: 9200                        << http를 통한 elasticsearch API 지원 노드의 port를 설정한다.

(각 포트는 별도 설정하지 않았는데 기본값을 사용한다고 가정한다. 필요하다면 http.port , transport.tcp.port 옵션을 수정하여 사용할 것!)
discovery.zen.minimum_master_nodes: 1  << 실제 서비스 환경에서는 최소 2대 이상의 마스터를 운영해야 한다.
discovery.zen.ping.unicast.hosts: ["1.1.1.1:9300"]     << node 간 연결을 위해 unicast로 master 노드를 지정하자. 마스터로 등록된 서버의 모든 ip를 기록해야 한다. 기본 포트인 9300을 사용한다면 굳이 기록할 필요는 없고, 포트를 변경한 경우만 이와 같이 기록하며 된다.

-------------------------------------------------------------------------------------------------------------------

이상 Elasticsearch Cluster 를 구성할 때 발생하는 에러 유형과 해결책, 그리고 config 설정 정보를 추가로 정리해 보았다.


  1. SSSS 2019.08.31 20:02

    감사합니다정말로

ES에서 제공하는 결과물은 대부분 멋지지만 가끔 odbc의 기능들이 필요한 경우가 있다. (X-pack을 사용하면 sql like query가 가능하지만 비용이 ..)


kibana 등에서는 결과를 실시간 쿼리를 통해 보기 때문에 반응성이 항상 아쉽다.


이런 저런 이유로 리포트용 솔루션을 별도 구축하게 되었다. 


만드는 김에, 일부 sql query가 필요한 경우를 위해, 필요한 로그들을 ES로 보냈다가 가져오기는 추가적인 절차가 필요해서,


logstash에서 ES로 보내면서 필요한 일부분만 바로 mysql에 직접 보내기로 했다. 


플러그인을 만들어야 하나 찾아보니... 


https://www.elastic.co/guide/en/logstash/current/input-plugins.html  >> input에는 


https://www.elastic.co/guide/en/logstash/current/output-plugins.html >> output에는 없네??


구글링 해보니 git에 있다.


https://github.com/theangryangel/logstash-output-jdbc/tree/master


하지만 2018/07/23 기준으로 아직 6.x 버전 대에서는 문제가 있을 수 있다네.


"See master branch for logstash v5 & v6 warning This is untested under Logstash 6.3 at this time, and there has been 1 unverified report of an issue. Please use at your own risk until I can find the time to evaluate and test 6.3."


대량 삽입에 대한 이슈가 한 개 올라와 있는데, 회피하거나, 어차피 오픈소스니 문제 생기면 고쳐서 리포트도 해줄 겸 해보자고 시작함. 



[ 설  치 ]


일단 jdbc 다운로드 해서 vendor 폴더 안에 jar/jdbc 폴더 만들고 삽입.


http://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-8.0.11.tar.gz  >> 오늘 기준 최신 파일. https://dev.mysql.com/downloads/connector/j/에서 최신 버전을 확인 후 버전 번호를 대체하면 다운 받을 수 있음.


압축 풀어서 vendor/jar/jdbc 폴더에 삽입. logstash가 설치된 곳마다 별도 설치해야 함.


이후 logstash 폴더에서 bin/logstash-plugin install logstash-output-jdbc를 실행. 


[ 설  정 ]


사용하는 config파일 열어서 아래 내용 추가.


output 내용 중 필요한 조건에 따라 아래 내용을 추가하면 됨.


if[조건]==값{ jdbc { driver_class => "com.mysql.cj.jdbc.Driver" connection_string => "jdbc:mysql://your_mysql_addr:port/your_db?characterEncoding=UTF-8&serverTimezone=UTC&user=input_your_id&password=input_your_pwd" statement => [ "INSERT INTO table_name (column1, column2) VALUES(?,?)", "raw_data_name1", "raw_data_name_2" ] } }


보다 상세한 옵션에 대해서는 https://github.com/theangryangel/logstash-output-jdbc/tree/master#configuration-options 를 참고할 것.


이상. 





'logstash-' 날짜 형태로 인덱스를 자동 생성하는데, 여러 타이틀 혹은 용도 별로 분리하려면 수정이 필수적임. 



만약 새롭게 생성하는 상황이라면, 원하는 곳에 pipelines에 적용할 config 파일을 하나 만들자. 


( https://www.elastic.co/guide/en/logstash/current/advanced-pipeline.html 참고)



만약 이미 만들어 두었다면 config/pipelines.yml을 열어 pipeline.id의 path.config를 확인해보자.


이제 기본적인 pipeline config를 구성했다면 원하는 조건과 이름을 활용해 인덱스를 수정해보자.


만약 logstash에서 elasticsearch로 바로 보낸다면 output 부분이 아래와 같을 것이다. 


output { elasticsearch { hosts => ["IP Address:port"] } }



hosts아래에 인덱스 이름 조건을 아래와 같이 삽입하면 elasticsearch에서 인덱스 명이 수정된 것을 확인할 수 있다.



output { elasticsearch { hosts => ["IP Address:port"]

index => 'test-%{+YYYY.MM.dd}' } }



만약 다중 pipelines을 사용한다면 input의 type별로 인덱스를 다르게 할 수 있다.



input {

redis {

.

.

type => "game_name"

}

.

.

}

.

.



output {

if[type]=="game_name"{ elasticsearch { hosts => ["IP Address:port"]

index => 'test-%{+YYYY.MM.dd}' }

}

.

.

}



* 조건은 아래와 같이 다중으로 처리 할 수 있다. 


if[type]=="game_name" and [playtime]>0{




보다 상세한 내용은 https://www.elastic.co/guide/en/logstash/current/configuration.html 문서에서 찾을 수 있다.



------------------------------------------------------------------------------------------------------------------------------


커스텀 인덱스 사용에 대한 주의 사항이 있어 내용을 추가한다.


유저 ip로 유저들의 위치 등을 찾아 낼 수 있는 플러그인을 기본적으로 제공하는데 한 가지 제약이 있다.


커스텀 인덱스를 사용하면 geoip의 정보를 찾아주지면 location이 아래처럼 일반 데이터 형태로 처리된다. 



그래서 Visualize/Map으로 가면 아래와 같은 에러를 볼 수 있다. 




원인은 logstash-로 시작되는 인덱스가 아닌 경우 geoip 를 제대로 처리하지 않기 때문이다. (https://www.elastic.co/blog/geoip-in-the-elastic-stack 참고)


elasticsearch로 output 하는 기본 Template 파일을 보면 "template":"logstash-*"로 시작되는걸 볼 수 있다. 


이를 해결하기 위한 방법은 두 가지가 있는데, 첫 번째는 별도의 template 파일을 만들어서 적용하는 것이다. 


elasticsearch {
      manage_template => true
      template => "/etc/logstash/templates/custom_template.json"
}

(https://stackoverflow.com/questions/29344547/kibana4-geo-map-error-not-showing-the-client-ip-field 참고)

하지만 이는 커스텀 인덱스 별로 모두 별도로 만들어야 하는 문제가 있다. 이를 해결하기 위한 두 번째 방법을 권고하는데, 그 방법은 커스텀 인덱스 이름 앞에 "logstash-"를 붙이는 방법이다. 개인적으로도 이 방법을 추천한다. 

index => 'logstash-test-%{+YYYY.MM.dd}'

잘 처리되면 아래와 같은 결과를 볼 수 있다. 

이상. 또 다른 이슈 안생기길...

tar 파일 가져와서 설치했더니 데몬으로 가동이 안되는 상태.


rpm으로 다시 설치할까 그냥 systemd에 등록함. 


elasticsearch , kibana 모두 6.3 버전 사용.


/etc/systemd/system/kibana.service 파일을 만들어 아래 내용을 삽입.


[Unit]

Description=Kibana 6.3


[Service]

Type=simple

User=root

Environment=CONFIG_PATH=/your kibana path/config/kibana.yml

Environment=NODE_ENV=production

ExecStart=/your kibana path/node/bin/node /your kibana path/src/cli


[Install]

WantedBy=multi-user.target



systemctl daemon-reload 

systemctl start kibana

systemd로 start


systemctl status kibana 로 상태를 확인해보면 아래와 같이 나온다.


● kibana.service - Kibana 6.3

   Loaded: loaded (/etc/systemd/system/kibana.service; enabled; vendor preset: disabled)

   Active: active (running) since 화 2018-07-10 09:57:33 KST; 24h ago


CentOS에 Elasticsearch를 설치해 보았다. 정리할 생각이 없었는데, 하다보니 중간에 막히는 것들이 처리하면서 정리 한번 해둬야겠단 생각이 들었다. 


일단 처음 설치할 때 주의할 점은 Elasticsearch는 root 계정으로 실행할 수 없다. (실행 가능하게 하는 옵션이 있던데, 최신 버전에서는 안되는 듯. 그리고 보안의 측면에서도 당연히 좋지 않아 추천하지 않는다.) 그러므로 전용 계정을 설정해서 설치, 실행하자.


설치 환경 및 각종 버전은 아래와 같다. 

CentOS  7.5.x

Elasticsearch 6.3.0 (https://www.elastic.co/downloads/elasticsearch)

plugin, Elastic-HQ (https://github.com/ElasticHQ/elasticsearch-HQ)



wget으로 다운로드 받아 설치한다. 별도 인스톨 과정은 없다. bin/elasticsearch 로 실행 가능.


wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.0.tar.gz

tar xvzf elasticsearch-6.3.0.tar.gz


실행전에 한 가지 수정하고 진행하자.

config/elasticsearch.yml에서 http.host를 자신이 사용하고자 하는 ip로 수정한다. 예를들어 내 ip가 172.150.x.x 이며 config를 수정하지 않고 실행할 경우 로컬에서 localhost로 접속은 되지만, 다른 PC에서 172.150.x.x로 접속되지는 않는다.


처음이니 데몬 옵션 주지말고 elasticsearch로 실행해보라. -d로 실행하며 혹시 모를 에러가 보이지 않으니 주의가 필요하다. 잘 실행되었는지 일단 확인해보자.


root 권한으로 실행되지 않으니 주의!


cluster 설정을 한 후 root로 실행하면 root권한으로 실행되지 않는다는 메세지와 함께, logs 폴더 아래 몇몇 로그 파일이 생긴다. 이를 지우지 않고 다른 계정으로 실행하면 log 파일 접근 실패 에러가 뜨면서 실행되지 않는다. 해당 로그 파일들을 삭제 후 다시 시작할 것!


curl 172.150.x.x:9200 ( 혹은 config를 수정하지 않았다면 localhost:9200)  

아래와 같은 간단한 health 정보가 출력되면 정상이다. 





혹시 그래도 접속이 안된다면 iptables에 9200번 ACCEPT 처리를 해주자.


 firewall-cmd --zone=public --permanent --add-port=9200/tcp


이제 플러그인 Elastic-HQ를 설치해보자. 이를 설치하기 위해 python 3.4 이상의 버전이 필요하다. CentOS 7.5 버전에는 기본적으로 2.x 버전이 설치되어 있다. 3.4 이상의 버전을 추가로 설치한 후 아래로 진행하자. 


( http://docs.elastichq.org/installation.html 참고)


git clone https://github.com/ElasticHQ/elasticsearch-HQ.git


설치에 필요한 추가 라이브러리를 설치한다. elasticsearch-HQ 폴더로 가서 아래를 실행.

(pip가 없다면 "yum install python-pip"로 설치.)


pip install -r requirements.txt

아래와 같은 에러 발생.

python-socketio 2.0.0 has requirement python-engineio>=2.2.0, but you'll have python-engineio 2.0.2 which is incompatible.

바로 업그레이드 시킴
pip3.6 install --upgrade python-engineio
(pip도 link설정 수정 안했다면 pip버전 지정해서 설정해야 함.)

이제 아래와 같이 실행 가능해졌다.

python3 application.py &
위에서 따로 언급 안했지만 python 3.x 버전을 추가로 설치 후 python 링크를 python3.6으로 대체했다면 시스템에 여러 문제가 발생할 수 있다. 

리눅스 시스템, 특히 CentOS는 python2를 많이 사용하고 있기 때문이다. 그래서 가능하면 python3.6.x 버전을 python3으로만 단축해서 사용해야 한다. 

HQ의 접속 포트는 5000번이다. iptables에 추가 후 브라우져로 접속해보면 아래와 같은 화면을 볼 수 있다.




주소를 지정하면 다음 화면으로 넘어간다.

HQ는 백그라운드로 실행했으므로 종료 시킬 때는 kill로... 


공식 문서에 python manage.py runserver 이걸로 실행하라는 이야기 있는데 에러가 나고, 해결책은 관심없어서 안 찾아봤음. 게다가 root 로 실행해야 함. -0-a 해결 방법 아시는 분 계시면 댓글 부탁드립니다. (공손..)


이까지.. 


다음은 Kibana 설치와 샘플 올려서 그래프 보는 방법에 문제가 있으면 이어서 계속, 별 문제 없으면 스킵.  :)


큰 건 아니고 작은 허들이 있어서 기록해 둠.

다운로드는 아래와 같이. (kibana는 elasticsearch와 동일한 버전을 다운받아 설정해야 한다. 안그럼 시작 시 경고 등장함.)

wget https://artifacts.elastic.co/downloads/kibana/kibana-6.3.0-linux-x86_64.tar.gz
압축을 풀고 config 일부를 수정하자.

config/kibana.yml에서  아래 사항을 설정한다. 


server.host: "yourkibanaip"

elasticsearch.url: "http://yourip:9200"

xpack.security.enabled: false   <<- 이에 대한 경고가 뜬다. 일단 테스트 과정이니 넘어감.


elasticsearch.url을 현재까지는 멀티로 지정할 수 없다. 커뮤니티에서도 다들 stack 솔루션 중 kibana만 안된다고 성토 중. 
( 관련 링크 : https://github.com/elastic/kibana/issues/214)

host 주소를 정확히 입력해두고, elasticsearch 주소 설정, 시작할 때 관리자 비번 안물어보게 설정.


이제 실행!!


bin/kibana


그럼 아래와 같은 화면을 볼 수 있다.




이제 logstash와 실제 데이터를 연동해서 결과를 만들어 볼 차례.


그 과정에 또 허들이 생기면 이젠 다른 글에 이어 정리할 예정. 바이~~~!


[아래 문서를 같이 봐야 함.]


About max file descriptors



+ Recent posts