[Elasticsearch] 로그 데이터 수집용 클러스터 및 인덱스 전략

SH·2024년 1월 29일
0

인프라

목록 보기
5/6

회사에서 일할 당시 로그 데이터 관리 용도로 efk 스택을 만들어야 했었다. 업무 초반 elasticsearch의 클러스터와 인덱스를 어떻게 구성할 것인지 정했어야 했고, elasticsearch 측면에서 아래와 같은 사항을 반영해야 한다고 생각했다.


  1. efk를 사용하는 클라이언트는 개발자이다. 개발자들은 로그 데이터를 efk 스택에서 볼 때 날짜 및 시간별로 검색하는 경우가 많다.
    (ex) 에러 발생 시 로그를 확인하려고 할 때 날짜나 시간별로 확인)

  2. 프로덕트 단위로, 프로덕트 안에서도 msa 단위로 로그 데이터를 나눠어야 보기 편하다. 프로덕트 별로(더 세밀하게는 msa 별로) 개발자가 남기는 로그 형태가 다를 수 있고, 에러 로그를 찾을 때도 msa 단위로 찾는 게 편하기 때문이다.

  3. 또한 전체 프로덕트를 통합한 단위로, 나아가 회사 전체 단위로 통계를 내야하는 요구사항이 발생할 수 있다.
    (ex) 월별 에러 발생률 등)

  4. 로그 데이터는 주기적으로 삭제되어야 한다.


당시 efk에 로그 데이터를 인덱싱해야하는 프로덕트는 두 개였고, 프로덕트 별로 각각 msa 서버가 4개, 6개로 돌아가던 상황이었다. (product는 productA, pruductB, msa는 msaA1, msaB1로 지칭)


1차 전략

  1. 클러스터 2대
    프로덕트로 클러스터를 나눠서 msaA용 클러스터 1개, msaB용 클러스터 1개로 총 2대를 둔다.

  2. 인덱스는 날짜별 + 프로젝트 msa별로 생성

    사유:

  • 프로젝트 간 로그가 따로 관리하는게 관리나 조회 면에서 편하기 때문에 프로덕트별로 구분하였다. 예를 들어 A 프로덕트에서 남겨야 하는 로그와 B 프로덕트에서 남겨야 하는 로그가 다를 수 있다. 이 때 인덱스 매핑 전략이 달라지기 때문에 프로덕트 별로 따로 관리

  • msa도 마찬가지. msa 별로도 남기는 로그가 다를 수 있음

  • 로그와 같은 시계열 데이터는 가장 최근 데이터를 조회하고 가장 오래된 데이터는 삭제하는 등의 시간 단위로 관리됨.

위 전략으로라면 아래와 같이 인덱스가 생성된다.

2024.01.01 일자에 생성되는 인덱스 목록
productA_msaA1_20240101
productA_msaA2_20240101
...

productB_msaB1_20240101
productB_msaB1_20240101
...

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

2024.01.02 일자에 생성되는 인덱스 목록
productA_msaA1_20240102
productA_msaA1_20240102
...

productB_msaB1_20240102
productB_msaB1_20240102
...

문제점

다만 이렇게 할 경우 아래와 같이 문제가 발생한다.

  1. 클러스터를 구분하면 그만큼 필요한 노드 수가 2배가 된다.
    프로덕트만 다를 뿐이지 동일한 로그 관리라는 목적을 갖기 때문에 두 데이터가 같은 클러스터에 있어도 문제될 부분이 없다. 노드 수가 증가함에 따른 서버 비용 + 클러스터 2개를 관리하는데 드는 비용을 감수하면서까지 굳이 구분할 필요가 없다.

  2. 인덱스 증가에 따른 샤드 개수 증가
    인덱스에는 무조건 프라이머리 샤드 1개와 레플리카 1개가 배정되어야 한다. 위 전략에 프라이머리 1개, 레플리카 1개로만 인덱스에 할당해도 아래와 같이 매우 많은 인덱스가 만들어진다.

dev, stage, production 3가지 환경별로 인덱스를 다시 구분한다고 할 때

하루에 인덱스 30개, 샤드 30개, 리플리카 30개
6개월 뒤에는 인덱스 5400개, 샤드 5400개, 리플리카 5400개가 클러스터에 존재. 

6개월 단위라는 삭제 주기를 고려했을 때, EFK를 구축하고 6개월 뒤 프라이머리+레플리카 도합 10800개가 하나의 클러스터 내에서 유지된다.
인덱스 증가는 샤드 개수 증가로 이어지고, 필요 이상의 샤드 수는 클러스터 내 오버헤드가 커지는 오버샤딩 문제를 야기할 수 있다.


2차 전략

1차 젼략이 나오고 나서 이후 회사에서 개발 환경이 dev, stg, prod 3가지 환경으로 구분하겠다는 요구사항이 추가되었다. 그에 따라 앞서 언급한 오버샤딩 문제가 우려되었다. 따라서 위에서 언급한 문제점을 보완하고 새로 추가된 요구사항을 반영하여 아래와 같은 인덱스 전략으로 수정하였다.


  1. 클러스터 한 대에 모든 로그 데이터 관리

  2. 인덱스 생성 - 프로덕트 msa+환경별로 구분하여 생성, 생성 주기는 ILM 세팅으로 월별로 자동으로 생성되도록 하여 인덱스가 너무 많아지는 현상 방지

    -> 날짜별로 인덱스를 구분할 때 로그를 보다 편하게 조회할 수 있다는 이점을 포기하는거 아닌가? 라고 생각했으나 어차피 키바나 UI에서 하나의 인덱스라도 시간 필터를 통해 날짜별/주별/시간별로 세분하여 조회 가능하기 때문에 상관 없다고 판단

  3. 인덱스 삭제 - 6개월 간 주기적으로 삭제

  4. 인덱스 별명을 사용하여 전체 프로덕트를 다 모아서 조회하거나 msa를 프로덕트 별로 묶어서 조회하는데 보다 편리하게 변경


+) 참고: ILM 전략
https://www.elastic.co/guide/en/elasticsearch/reference/current/index-rollover.html


위 전략대로라면 아래와 같이 인덱스 및 샤드, 리플리카가 생성된다.

dev, stage, production 3가지 환경별로 인덱스를 구분한다고 할 때

한달에 인덱스 30개, 샤드 30개, 리플리카 30개
6개월 뒤에는 인덱스 180개, 샤드 180개, 리플리카 180개가 클러스터에 존재 

추가로 인덱스 생성의 경우 한 달 이내에 샤드 크기가 20GB가 넘어가면 한달이 지나지 않아도 새로 인덱스를 생성하도록 지정했다.

다른 얘기지만 샤드 개수와 크기는 es에 인덱싱되는 로그 데이터가 클수록 es 성능에 직접적인 영향을 미친다. 매핑 패턴, 압축률, es에 인덱싱되는 데이터 크기 등을 고려하여 결정하여야 한다. 노드 개수를 얼마나 할지도 중요하다. 노드 1개당 가상 서버 1개가 필요하기 때문에 경제적으로 드는 비용이 비싸기 때문이다.

일단 노드 개수는 일단 마스터 후보 노드 3개, 데이터 노드 2개로 지정하고 샤드 크기는 20GB로 설정했다. 그리고 이후에 테스트를 진행하여 노드 개수와 샤드 크기를 조정하였다. es 테스트 관련된 부분은 이후 포스팅에 넣겠다.


기타 트러블슈팅

인덱스를 생성할 때 rollover 전략을 사용 시 인덱스 이름 뒤에 xxxx-000001 이런식으로 자동으로 넘버링이 된다. 당시 es에 들어오기 바로 전 단계에 fluentd가 있었는데, 이 fluentd에 인덱스 이름을 지정해서 es로 데이터를 전송하기 때문에 인덱스 이름이 변경되면 인덱스를 찾을 수 없어 전송이 불가하다는 문제가 있었다.

예를 들어 1월에는 인덱스 이름이 my_index-000001 로 되어있어서 fluentd에서 my_index-000001로 보냄
-> 2월에 인덱스가 rollover되고 새 인덱스 my_index-000002가 생성
-> fluentd는 여전히 my_index-000001로 도큐먼트를 보내려고 해서 문제 발생


fluentd와 es 인덱스 이름 간 의존성 문제는 인덱스 템플릿과 인덱스 ilm 별명을 사용하여 해결할 수 있었다.

  1. 인덱스 템플릿에서 패턴을 my_index*로 지정해 my_index로 시작하는 인덱스 색인 요청은 전부 해당 템플릿과 연결된 인덱스에 넣어지도록 설정
  2. 해당 인덱스 템플릿을 ILM 정책과 연결, index.lifecycle.rollover_aliasmy_index로 지정하여 새로운 인덱스가 롤오버될 때 my_index로 시작하는 인덱스 이름을 갖도록 설정

이렇게하면 fluentd는 my_index라는 이름의 인덱스로 요청을 보내도 2월달에 my_index-000002에 인덱싱이 될 수 있다.

// 템플릿 예시
{
  "index_patterns": [
    "my_index*"
  ],
  "template": {
    "settings": {
      "index.lifecycle.name": "my_index_ilm",
      "index.lifecycle.rollover_alias": "my_index",
      "number_of_shards": 1,
      "number_of_replicas": 1
    },
profile
블로그 정리안하는 J개발자

0개의 댓글