0. Background
전편에서는 안정적인 서비스 운영을 위해 꼭 필요한 APM(Application Performance Management)에 대해 이야기하였습니다. APM을 도입하면 운영 환경에서도 어느 곳이 성능 병목 구간인지, 어떤 에러가 발생하는지 쉽게 파악하고 추적할 수 있습니다. 하지만 이것만으로는 부족합니다. 예를 들어, 특정 사용자가 리멤버의 명함 조회 API를 호출했는데 어느 순간 갑자기 응답이 느려지기 시작합니다. APM을 통해 모니터링을 하면 이런 현상이 나타났다는 것을 쉽게 알 수 있지만 거기까지입니다. 더 정확한 원인을 추적하려면 해당 API 호출의 전후로 어떤 일들이 일어났는지 분석을 하는 것이 필요합니다. 따라서 이 때 필요한 것이 로그 관리 시스템입니다.
시스템 관점 vs. 비즈니스 관점
APM은 시스템 관점에서의 모니터링 성격이 강합니다. 트랜잭션별로, 메소드별로 응답 시간과 같은 데이터를 뽑아내고 이를 수집/가공/분석하고 예쁘게 포장하여 최종 사용자에게 보여줍니다. 하지만 어떤 트랜잭션이 업무적으로 중요하고, 각 트랜잭션들이 하나의 비즈니스 업무를 처리하기 위해 어떤 연관 관계를 가지는지는 알 수 없습니다(물론 NewRelic의 경우 Key Transaction으로 지정해 놓으면. 별도로 관리/모니터링이 가능합니다).
반면 로그 관리 시스템은 비즈니스 관점에서의 모니터링 성격을 지니고 있습니다. 로그는 개발자가 마음대로 추가할 수 있는 것이기 때문에 업무에 따라 필요한 데이터를 추출해낼 수 있습니다. 명함 조회 API 호출의 응답시간이 느려졌다면 그 전에 사용자가 어떤 명함을 업로드했는지, 해당 요청의 파라미터는 무엇이었는지, 요청의 처리결과는 어떠했는지 로그 관리 시스템을 통해 분석할 수 있습니다.
로그 관리 시스템은 이슈나 버그가 발생했을 때 추적 용도로 쓰일뿐 아니라, 일별 회원 가입수, DAU나 MAU 등 여러 가지 비즈니스적인 지표를 뽑아내는 데도 유용하게 사용될 수 있습니다.
사전 인지 vs. 사후 조치
APM이 장애가 발생하기 전에 징후를 포착하는 사전 인지의 성격이 강하다면, 로그 관리 시스템은 사건이 터지고 나서 그 원인을 분석하는데 더 많이 사용되는 것 같습니다(사실 딱 들어맞는 분류는 아니지만, 굳이 저렇게 한 번 나눠보았습니다. 로그 관리 시스템을 통해서도 얼마든지 장애를 사전 인지할 수 있고, 또 사후 원인 분석을 위해 APM도 당연히 보아야 합니다).
APM이 현재 시스템의 상태를 실시간으로 보여주는 것에 좀 더 초점을 맞추고 있다면 로그는 좀 더 거시적인 관점에서 분석하고 insight를 얻기 위해 쓰이는 경우가 많기 때문입니다.
1. Pain points
다른 많은 회사와 마찬가지로 저희도 크게 개발, 테스트, 운영 이렇게 3가지 환경을 갖추고 있습니다. 개발자는 개발 환경에서 소프트웨어를 개발하고 이를 테스트 환경에 배포하면 QA팀에서 검수를 시작합니다. 해당 배포본이 문제 없다고 판단되면 실제 운영 환경에 배포를 진행하게 됩니다.
그런데 테스트 환경에서 QA팀이 테스트를 하다보면 버그나 이슈가 많이 발생합니다. 이 때 개발자는 테스트 환경의 서버 로그를 열어서 원인 분석을 해야 합니다. 보통 테스트 서버는 로그 레벨이 debug로 설정되어 있기 때문에 로그의 양이 상당히 많고 여러 테스트 사용자의 로그가 섞여 있기 때문에 grep을 무한반복해가며 로그를 뒤져야 합니다. 먼저 문제가 된 API 호출을 찾고, 프로세스 ID 혹은 request id로 다시 grep을 해서 관련 내용만 뽑아낸 다음 한줄한줄 읽어가며 분석하는데 이게 여간 불편한 게 아닙니다.
개발에서의 불편함뿐 아니라 실제 운영을 할 때도 운영 서버에서 발생하는 로그를 한 곳에 수집해 놓지 않으면 무용지물입니다. 특정 시간에 API 호출 에러가 났는데 모든 서버를 일일이 돌아다니면서 grep을 해볼 수는 없는 노릇이니까요. 그래서 운영 환경의 경우 모든 서버/애플리케이션 로그를 하나의 저장소로 수집하고, 이를 검색에 용이한 형태로 가공하여 저장하고, 저장된 데이터를 토대로 정보를 조회하거나 그래프를 보여주는 등의 Visualization을 해주는 시스템이 필요합니다.
2. SaaS vs. Open source
많은 분들이 아시다시피 이런 요구를 충족시켜주는 대표적인 오픈 소스 솔루션이 ELK(ElasticSearch, Logstash, Kibana)입니다. 오픈 소스뿐만 아니라 loggly나 logentries 등 SaaS 형태의 서비스 벤더들도 굉장히 많습니다. 그래서 ELK로 갈 것이냐 SaaS 서비스를 돈내고 쓸 것이냐를 초기에 많이 고민했습니다. 저희가 가진 요구사항은 아래와 같았습니다.
- Rails 애플리케이션 로그를 수집/저장/검색하는 기본적인 기능을 제공해야 한다.(로그 기반으로 화려하게 Visualization해주거나 빡세게 분석해서 지표 데이터를 뽑아내거나 할 필요는 전혀 없다)
- 3개월 ~ 6개월 정도의 로그 데이터는 보관 가능해야 한다
- 최대한 저렴해야 한다(우린 스타트업이니까)
- 설치 혹은 연동이 간편해야 한다
- 로그 관리 시스템은 우리의 핵심 업무 영역이 아니므로 최대한 리소스를 덜 써야 한다
사실 Loggly라는 SaaS형 로그 관리 서비스를 주의깊게 조사했었고, 위에 열거한 거의 대부분의 요구사항을 만족시켰습니다. 하지만 딱 하나 만족하지 못한 게 있었는데 바로 비용이었습니다. 로그라는 것이 원체 양이 많다보니 이런 서비스 벤더에서도 보관하는데 부담이 많이 되나 봅니다. 그래서 보관 기간이 길어질수록 비용도 증가하는 가격 정책을 가지고 있었습니다. 그래서 결국 SaaS형은 포기하고 ELK 스택을 설치하여 직접 운영하기로 하였습니다.
3. ELK Stack
저희가 운영하는 ELK 구성은 다음 그림과 같습니다.
A. Logstasher
먼저 Logstasher 젬을 설치하여 Rails 애플리케이션에서 발생하는 로그들을 json형태로 만들어 logstash_{env}.log 파일에 쌓도록 하였습니다. json 형식은 Logstash에서 바로 파싱이 가능하기 때문에 별도의 grok 설정을 해줄 필요가 없어 편리합니다.
B. Logstash-forwarder
logstash_{env}.log 파일을 계속 polling하면서 쌓이는 로그를 Logstash 서버로 전달해 주는 데몬입니다. 예전에는 lumberjack이라는 이름으로 불렸었고, 지금은 Filebeat이라는 더 좋은 모듈이 나왔네요. 참고로 Filebeat은 내부적으로 logstash-forwarder의 프로토콜을 그대로 쓴다고 합니다(Weekly Beats: Preparing the first Filbeat release).
C. Logstash
모든 서버의 로그를 수집하여 이를 파싱하고 저장/검색하기 쉬운 형태로 가공해주는 역할을 담당합니다.
D. ElasticSearch
수집된 로그를 저장하고 검색을 위한 인덱스를 생성/관리하는 역할을 담당합니다.
E. Kibana
저장된 로그 데이터를 기반으로 예쁘게 Visualisation 해주는 도구입니다. 사용자가 검색 질의를 날리면 이를 ElasticSearch에 전달하고, 검색 결과를 받아 화면에 뿌려주는 자바 기반 웹 애플리케이션입니다.
저희는 아직 수집되는 로그의 양이 많지 않기도 하고, 운영에 드는 리소스를 최소로 하고자 ELK를 한 대의 서버로 몰아넣었습니다. 물론 시스템이 점점 커지고 로그의 양도 많아지면 별도의 서버로 분리하고 이것저것 할게 많겠지만, 아직까지는 한 대의 서버로 잘 버티고 있습니다.
ELK Stack에 대해서는 인터넷에 돌아다니는 자료도 워낙 많고, 문서도 잘 되어 있어서 별도로 더 언급하지는 않겠습니다(아래 References 섹션에 ELK에 대해 잘 정리된 웹페이지 링크들을 걸어두었습니다).
대신 ELK 도입 후 운영을 해보면서 느꼈던 점과 성능을 위해 튜닝했던 포인트들을 간략하게 이야기해보도록 하겠습니다.
4. ELK Tuning
처음에는 ELK에 어느 정도의 리소스가 필요한지 몰라서 AWS의 t2.medium급 인스턴스(CPU 2개, 메모리 4GB)에 설치하고 시범적으로 테스트 환경에 도입해 보았습니다. 한 달 정도 모니터링 후 문제가 없다고 판단되어 운영 환경에 적용하였습니다. 그런데 2주도 지나지 않아 금방 문제가 생겼습니다. 성능이 심각하게 저하되어 더 이상 Kibana에서 조회가 안 되는 상황이 된 겁니다.
A. 메모리 증설
첫 번째로 취한 조치는 당연히 서버 사양을 업그레이드하는 것이었습니다. ElasticSearch도 일종의 DB이고 최대한 많은 인덱스를 메모리에 올려놓는 것이 성능에 엄청난 차이를 가져오기 때문에 메모리를 최대한 많이 잡으라고(최대 31GB) 권고하고 있습니다.
하지만 서버 사양 말고도 튜닝할 포인트들이 많았기 때문에 일단 m4.xlarge급 인스턴스(CPU 4개, 메모리 16GB)로 유지해도 충분하다고 판단하였습니다. 현재 각 애플리케이션이 차지하고 있는 메모리 사용량은 아래와 같습니다.
B. Index Template 최적화
서버 메모리를 증설했지만 문제는 완전히 해결되지 않았습니다. “lumberjack input the pipeline is blocked temporarily refusing new connection”과 같은 에러가 나면서 logstash-forawrder에서 logstash로 맺은 커넥션이 자꾸 끊어지는 현상이 나타났습니다. 열심히 구글링을 해보니 Logstash에서 ElasticSearch로 데이터를 보내지 못하는 경우 큐에 보관을 하게 되는데 이 큐가 꽉차면 client(Logstash 입장에서는 logstash-forwarder)의 연결을 강제로 끊어버린다는 내용이 있었습니다. 추측컨대 ElasticSearch의 indexing 속도가 Logstash에서 로그를 밀어넣는 속도를 따라가지 못해서 그런 것 같았습니다. 그래서 ElasticSearch의 indexing 속도를 최적화하기로 하였습니다.
가장 먼저 눈에 띈 것은 ElasticSearch가 기본적으로 로그의 모든 속성을 다 인덱싱하고 있다는 것이었습니다. 이는 ElasticSearch를 설치하고 나면 기본적으로 Dynamic Mapping 기능이 활성화되어 있기 때문이었습니다. 따라서 검색에 꼭 필요한 필드들만 index를 생성하도록 인덱스 템플릿을 조정해 줄 필요가 있습니다.
위의 템플릿 설정에서 “_default_” 의 “dynamic” 속성을 false로 지정하여 지정된 필드 이외에는 인덱싱하지 않도록 설정하였습니다.
또 꼭 필요한 경우가 아니라면 analyzed index를 사용하지 않도록 하였습니다. analyzed index를 사용하면 해당 필드를 기반으로 분석 그래프를 그리는 등 Visualization을 용이하게 할 수 있지만, 그만큼 인덱스의 크기가 커지고 성능에 영향을 줍니다. 위에 기술한 요구사항대로 저희는 Visualization에 대한 많은 기능을 필요로 하지 않기 때문에 대부분의 필드들은 not analyzed index를 사용하고 있습니다.
C. Index Shard 최적화
Logstash는 하루에 한 개씩 인덱스가 생깁니다. 그리고 이를 지정된 샤드 개수만큼 분산하여 저장하게 되는데, ElasticSearch의 기본 설정은 인덱스 1개당 5개의 샤드를 생성하도록 되어 있습니다. 이를 그림으로 표현하면 다음과 같습니다.
ElasticSearch의 클러스터 노드가 2개라고 가정하고, number of replicas는 1, number of shards는 5로 지정되어 있으면 위와 같이 각 Logstash 일자별 인덱스가 5개의 primary shard로 분산되고 각 shard마다 복제본이 하나씩 생겨 총 10개의 shard가 생성됩니다. 그리고 클러스터의 2개 노드에 골고루 분산됩니다.
하지만 샤드 개수가 많아지면 많아질수록 검색 성능은 점점 떨어집니다. 하나의 질의를 처리하기 위해 건드려야 할 샤드가 그만큼 많아지기 때문입니다. 저희는 로그의 용량이 그리 크지 않기 때문에 샤드를 많이 만들 필요가 없었고, 따라서 인덱스당 1개만 생성하도록 지정했습니다.
D. Index 보관 주기 설정
위에서 언급한 대로 인덱스와 샤드의 수가 많으면 많아질수록 검색의 성능은 떨어지기 때문에 일정 시간이 지나 더 이상 검색할 일이 없는 인덱스들은 아카이빙을 하고 ElasticSearch에서는 삭제해주어야 합니다.
ElasticSearch의 인덱스 아카이빙은 보통 S3를 많이 이용하시는 듯 합니다. 저희도 추후 인덱스 아카이빙을 S3에 할 계획입니다.
E. System Configuration
위에서 이야기한대로 메모리를 늘려 잡았다고 하더라도, 메모리 Swap이 자주 일어난다면 ElasticSearch의 성능은 떨어질 수 밖에 없습니다. ElasticSearch는 메모리 Swap이 일어나지 않도록 강제로 메모리를 점유하게 하는 설정이 있는데 바로 bootstrap.mlockall 이란 속성입니다.
ES_HEAP_SIZE 환경 변수에 ElasticSearch에 할당할 메모리 크기를 지정해주고 elastic search.yml 환경 설정 파일에 위 속성을 true로 지정해주면 메모리 swap을 허용하지 않게 됩니다.
현재 ELK 서버는 메모리가 16GB이므로 ES_HEAP_SIZE는 그 반인 8GB로 설정하였습니다.
5. 향후 고려 사항
위의 튜닝 과정을 거쳐 현재는 한 대의 서버로 잘 운영되고 있지만 아직 개선해야 할 점들이 많이 있습니다. 앞으로 고려해야 할 사항들을 다음과 같이 정리해 보았습니다.
A. 서버 사양 Upgrade
리멤버 회원 수도 계속 증가하고, 이용률도 점점 많아진다면 수집되는 로그의 양도 그만큼 많을 수 밖에 없습니다. 따라서 향후 서버 사양을 몇 번 더 업그레이드하는 것은 피할 수 없는 일일 것입니다.
B. 꾸준한 Patch Update
ELK Stack은 Elastic.co라는 회사에서 꾸준히 관리하고 유지보수하기 때문에 오픈 소스임에도 패치 업데이트가 꽤 빠른 것 같습니다. 따라서 그 때 그 때 잘 업데이트를 해준다면 좀 더 성능도 좋아지고 버그도 줄어들지 않을까 생각합니다.
C. Index Template 관리
로그를 수집하는 서비스가 점점 늘어나면 Index Template도 그에 맞게 꾸준히 관리해주어야 합니다. 그렇지 않으면 불필요한 인덱스가 우후죽순처럼 생겨나게 되고 결국은 ELK 서버의 성능을 떨어뜨립니다. 또 나중에 이를 정리하는데에도 만만치않은 노력을 들여야 할 것입니다.
D. ElasticSearch 클러스터 노드 증설
아무래도 하나의 노드에서 많은 샤드를 관리할수록 인덱스나 질의 성능은 떨어질 것입니다. 또, 데이터 유실이 일어나면 안 되는 로그 정보들이라면 복제도 해야 합니다. 따라서 나중에는 클러스터의 노드를 점점 늘려가면서 이러한 요구사항을 다룰 필요가 있습니다.
6. Conclusion
지금까지 어떻게 해서 ELK를 도입하여 운영하게 되었는지 간략하게 이야기해보았습니다. 아시다시피, 오픈 소스는 공짜가 아닙니다. 도입 후 운영하는 비용까지 고려해야 하기 때문입니다. ELK 도입 후 간간히 신경을 많이 써주어야 하는 부분이 있지만, 그래도 지금까지는 상당히 만족하면서 잘 지내오고 있습니다.
앞으로 개발자도 더 늘어나고, 또 로그 데이터 분석에 대한 필요가 점점 늘어나게 되면 ELK를 본격적으로 운영/관리해야 할 것입니다. 그러려면 앞으로 ELK에 대해 더 많이 공부하고 노하우를 쌓아야 합니다. ELK는 다른 분들도 많이 도입해서 사용하시는 만큼 서로 지식과 노하우를 공유하면서 함께 발전해갔으면 하는 바램입니다. 저희도 ELK에 대해 아직 모르는 부분이 훨씬 더 많으니 좋은 정보와 가르침 부탁 드립니다.
7. References
- Centralized Logging Architecture
- The 7 Log Management Tools You Need To Know
- How To Install Elasticsearch, Logstash, and Kibana 4 on CentOS 7
- Tips for centralized logging infrastructure with logstash
- A New Beginning: ELK and Elasticsearch
- Big data in minutes with the ELK Stack
- 11 Tips to Optimize Elasticsearch · Hsu Han’s Software Engineering Blog
- Elasticsearch Indexing Performance Cheatsheet
- 9 Tips on ElasticSearch Configuration for High Performance
- ElasticSearch Configuration
- Create a Custom Elasticsearch Template
- Elasticsearch snapshot backup/restore to S3
좋은 글 잘 읽었어요. 좋은 경험 공유 감사합니다.
안녕하세요.
포스팅 잘 보고있습니다.
한 가지 궁금한게 있는데요,
logstash에 아래와 같이 복수개의 설정을 등록하고 AAAA.json에 데이터를 넣으면 AAAA 인덱스와 BBBB 인덱스 모두에게 AAAA.json 데이터가 쌓입니다.
왜 이런걸까요?
제가 설정을 뭔가 빠뜨린걸까요..?
[pipelines.yml]
“`
– pipeline.id: AAAA
path.config: “/etc/logstash/conf.d/AAAA.conf”
queue.type: persisted
– pipeline.id: BBBB
path.config: “/etc/logstash/conf.d/BBBB.conf”
queue.type: persisted
“`
[AAAA.conf]
“`
input {
stdin {
codec => json
}
file {
codec => json
path => “AAAA.json”
start_position => “beginning”
sincedb_path => “NUL”
}
}
output {
elasticsearch {
hosts => “localhost”
index => “AAAA”
}
stdout {}
}
“`
[BBBB.conf]
“`
input {
stdin {
codec => json
}
file {
codec => json
path => “BBBB.json”
start_position => “beginning”
sincedb_path => “NUL”
}
}
output {
elasticsearch {
hosts => “localhost”
index => “BBBB”
}
stdout {}
}
“`