엔지니어링

Kibana 스크립트 필드에서 Painless 사용

Kibana는 Elasticsearch에 저장된 데이터를 검색하고 시각화할 수 있는 강력한 방법을 제공합니다. 시각화를 위해, Kibana는 Elasticsearch 매핑에서 정의된 필드를 찾아 차트를 작성하는 사용자에게 옵션으로 제시합니다. 그러나 중요한 값을 스키마에서 별도 필드로 정의하는 것을 잊으면 어떻게 될까요? 아니면 두 필드를 결합하여 하나로 처리하고 싶다면 어떨까요? 여기서 Kibana 스크립트 필드가 중요한 역할을 하기 시작합니다.

스크립트 필드는 실제로 Kibana 4의 초기부터 존재했습니다. 스크립트 필드가 도입되었을 때, 스크립트 필드를 정의하는 유일한 방법은 숫자 값만을 다루는 Elasticsearch의 스크립팅 언어인 Lucene Expressions 에 의존했습니다. 따라서 스크립트 필드의 기능은 사용 사례의 하위 집합으로 제한되었습니다. 5.0에서는, Elasticsearch에 다양한 데이터 유형에서 작동할 수 있는 안전하고 강력한 스크립트 언어인 Painless를 도입했으며, 그 결과 Kibana 5.0의 스크립트 필드가 훨씬 더 강력해졌습니다.

이 블로그의 나머지 부분에서는, 일반적인 사용 사례를 위한 스크립트 필드를 만드는 방법에 대해 설명합니다. Kibana 시작하기 튜토리얼 의 데이터 세트를 기반으로 설명해 드리고, 무료로 스핀업하실 수 있는 Elastic Cloud에서 실행되는 Elasticsearch와 Kibana 인스턴스를 사용해 보겠습니다.

다음 동영상에서는 Elastic Cloud에서 개인 Elasticsearch와 Kibana 인스턴스를 스핀업하고 샘플 데이터 세트 를 로드하는 방법에 대해 설명합니다. 

스크립트 필드의 작동 방식

Elasticsearch를 통해 모든 요청에 대한 스크립트 필드를 지정할 수 있습니다. Kibana는 관리 섹션에서 스크립트 필드를 한 번 정의할 수 있도록 함으로써 이를 개선하며, 따라서 향후에 UI의 여러 위치에서 이를 사용할 수 있습니다. Kibana는 스크립트 필드를 다른 구성과 함께 .kibana 인덱스에 저장하지만, 이 구성은 Kibana별이며 Kibana 스크립트 필드는 Elasticsearch의 API 사용자에게 노출되지 않습니다.

Kibana에서 스크립트 필드를 정의하려면, 스크립트 언어를 선택하게 되며, Elasticsearch 노드에 설치된 동적 스크립트가 활성화된 모든 언어 중에서 선택할 수 있습니다. 기본값은 5.0에서는 "표현식" 및 "Painless"이고 2.x에서는 "표현식"입니다. 다른 스크립팅 언어를 설치하고 이 언어에 대해 동적 스크립팅을 활성화할 수는 있지만, 충분히 샌드박스화 할 수 없고 사용이 중단되었기 때문에 권장되지 않습니다.

스크립트 필드는 한 번에 하나의 Elasticsearch 문서에서 작동하지만, 해당 문서의 여러 필드를 참조할 수 있습니다. 따라서 스크립트 필드를 사용하여 단일 문서 내에서 필드를 결합하거나 변환하는 것은 적절하지만, 여러 문서를 기반으로 한 계산(예: 시계열 수학)은 수행하지 않도록 합니다. Painless 및 Lucene 표현식 양쪽 모두 doc_values에 저장된 필드에서 작동합니다. 따라서 스트링 데이터의 경우, 데이터 유형 키워드에 저장할 스트링이 있어야 합니다. Painless 기반의 스크립트 필드도 _source에서 직접 작동할 수 없습니다.

스크립트 필드가 "관리"에서 정의되면, 사용자는 Kibana의 다른 필드와 동일한 방식으로 필드와 상호 작용할 수 있습니다. 스크립트 필드는 검색(Discover) 필드 목록에 자동으로 표시되며 시각화 생성 목적으로 시각화(Visualize)에서 제공됩니다. Kibana는 평가를 위해 쿼리 시 스크립트 필드 정의를 Elasticsearch에 전달합니다. 결과 데이터 세트는 Elasticsearch에서 반환되는 다른 결과와 결합되어 사용자에게 표 또는 차트로 표시됩니다.

이 블로그 작성 시점을 기준으로, 스크립트 필드로 작업할 때 몇 가지 알려진 제한 사항이 있습니다. Kibana 비주얼 빌더에서 사용할 수 있는 대부분의 Elasticsearch 집계를 스크립트 필드에 적용할 수 있습니다. 단, 가장 눈에 띄는 예외는 중요 용어 집계입니다. 아래와 같이, 잘 정의된 값을 반환하는 적절한 스크립트를 작성하려면 주의해야 하지만 검색, 시각화 및 대시보드의 필터 표시줄을 통해 스크립트 필드를 필터링할 수도 있습니다. 스크립트 필드를 사용할 때는 아래의 "모범 사례" 섹션을 참조하여 환경을 불안정하게 만들지 않도록 하는 것도 중요합니다.

다음 동영상에서는 Kibana를 사용하여 스크립트 필드를 만드는 방법을 보여 드립니다.

스크립트 필드 예제

이 섹션에서는 일반적인 시나리오에서 Kibana의 Lucene 표현식 및 Painless 스크립트 필드의 몇 가지 예제를 보여 드립니다. 위에서 언급한 바와 같이, 이러한 예제는 Kibana 시작하기 튜토리얼 의 데이터 세트를 바탕으로 개발되었으며 이전 버전의 특정 유형의 스크립트 필드 필터링 및 정렬과 관련된 몇 가지 알려진 문제가 있으므로 Elasticsearch 및 Kibana 5.1.1을 사용하고 있다고 가정합니다.

Elasticsearch 5.0에서는 Lucene 표현식 및 Painless를 기본적으로 사용할 수 있으므로 스크립트 필드는 대부분 즉시 사용할 수 있습니다. 필드의 정규식 기반 구문 분석이 필요한 스크립트만 예외입니다. 이 경우, elasticsearch.yml에서 다음 설정을 설정하여 Painless: script.painless.regex.enabled: true에 대한 정규식 일치를 켜야 합니다

단일 필드에 대해 계산 수행

  • 예제: 바이트에서 킬로바이트 계산
  • 언어: 표현식
  • 반환 유형: 숫자
 doc['bytes'].value / 1024

참고: Kibana 스크립트 필드는 한 번에 하나의 문서에서만 작동하므로 스크립트 필드에서 시계열 수학을 수행할 수 없습니다.

날짜를 숫자로 반환

  • 예제: 날짜를 시간으로 구문 분석
  • 언어: 표현식
  • 반환 유형: 숫자

Lucene 표현식은 즉시 사용할 수 있는 전체 호스트의 날짜 조작 함수 를 제공합니다. 그러나 Lucene 표현식은 숫자 값만 반환하므로, 스트링 기반 요일을 반환하려면 Painless를 사용해야 합니다(아래).

 doc['@timestamp'].date.hourOfDay

참고: 위 스크립트는 1-24를 반환합니다

doc['@timestamp'].date.dayOfWeek

참고: 위 스크립트는 1-7을 반환합니다

두 스트링 값 결합

  • 예제: 소스와 대상 또는 이름과 성을 결합
  • 언어: painless
  • 반환 유형: 스트링
 doc['geo.dest.keyword'].value + ':' + doc['geo.src.keyword'].value

참고: 스크립트 필드는 doc_values의 필드에서 작동해야 하므로 위 스트링의 .keyword 버전을 사용하고 있습니다.

논리 도입

  • 예제: 바이트 수가 10000을 초과하는 문서에 대한 "빅 다운로드" 레이블 반환
  • 언어: painless
  • 반환 유형: 스트링
 if (doc['bytes'].value > 10000) { 
return "big download";
}
return "";

참고: 논리를 도입할 때는, 모든 실행 경로에 잘 정의된 반환 문과 잘 정의된 반환 값(null이 아님)이 있어야 합니다. 예를 들어, 위의 스크립트 필드는 마지막에 반환 문이 없는  Kibana 필터에서 사용되거나 문이 null을 반환하는 경우 컴파일 오류와 함께 실패합니다. 또한 Kibana 스크립트 필드 내에서는 논리를 함수로 분할할 수 없습니다. 

하위 스트링 반환

  • 예제: URL의 마지막 슬래시 뒤에 오는 부분 반환
  • 언어: painless
  • 반환 유형: 스트링
 def path = doc['url.keyword'].value;
if (path != null) {
int lastSlashIndex = path.lastIndexOf('/');
if (lastSlashIndex > 0) {
return path.substring(lastSlashIndex+1);
}
}
return "";

참고: indexOf() 작업은 리소스 집약적이지 않고 오류 발생 가능성이 낮으므로 가능한 한 정규식을 사용하여 하위 스트링을 추출하지 마세요. 

정규식을 사용하여 스트링 일치 및 일치 작업 수행

  • 예제: 하위 스트링 "error"가 "referer" 필드에 있으면 "error" 스트링을 반환하고, 그렇지 않으면 "no error" 스트링을 반환합니다.
  • 언어: painless
  • 반환 유형: 스트링
if (doc['referer.keyword'].value =~ /kr/error/) { 
return "error"
} else {
return "no error"
}

참고: 단순화된 정규식 구문은 정규식 일치를 기반으로 하는 조건에 유용합니다. 

스트링 일치 및 일치하는 스트링 반환

  • 예제: "host" 필드의 마지막 점 뒤에 오는 스트링인 도메인 반환
  • 언어: painless
  • 반환 유형: 스트링
def m = /^.*\.([a-z]+)$/.matcher(doc['host.keyword'].value);
if ( m.matches() ) {
return m.group(1)
} else {
return "no match"
}

참고: regex matcher() 함수를 통해 개체를 정의하면 정규식과 일치하는 문자 그룹을 추출하여 반환할 수 있습니다. 

숫자 일치 및 일치하는 숫자 반환

  • 예제: IP 주소의 첫 번째 옥텟을 반환하고(스트링으로 저장) 이를 숫자로 처리
  • 언어: painless
  • 반환 유형: 숫자
 def m = /^([0-9]+)\..*$/.matcher(doc['clientip.keyword'].value);
if ( m.matches() ) {
return Integer.parseInt(m.group(1))
} else {
return 0
}

참고: 스크립트에서 올바른 데이터 유형을 반환하는 것이 중요합니다. 정규식 일치는 숫자가 일치하더라도 스트링을 반환하므로, 반환 시 이를 정수로 명시적으로 변환해야 합니다. 

날짜를 스트링으로 반환

  • 예제: 날짜를 요일 스트링으로 구문 분석
  • 언어: painless
  • 반환 유형: 스트링
LocalDateTime.ofInstant(Instant.ofEpochMilli(doc['@timestamp'].value), ZoneId.of('Z')).getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())

참고: Painless는 Java의 모든 네이티브 유형을 지원하므로, LocalDateTime()과 같은 해당 유형의 네이티브 함수에 대한 액세스를 제공합니다. 따라서 더 복잡한 날짜 계산을 수행하는 데 유용합니다.

모범 사례

보시는 것과 같이, Painless 스크립트 언어는 Kibana 스크립트 필드를 통해 Elasticsearch에 저장된 임의 필드에서 유용한 정보를 추출하는 강력한 방법을 제공합니다. 하지만, 큰 힘에는 큰 책임이 따릅니다. 

아래에서는 Kibana 스크립트 필드 사용과 관련된 몇 가지 모범 사례를 개략적으로 설명합니다.

  • 스크립트 필드를 실험하려면 항상 개발 환경을 사용하세요. 스크립트 필드는 Kibana의 관리 섹션에 저장한 후 즉시 활성화되므로(예: 모든 사용자의 해당 인덱스 패턴에 대한 검색 화면에 표시됨), 프로덕션에서 직접 스크립트 필드를 개발해서는 안 됩니다. 먼저 개발 환경에서 구문을 시도하고, 스크립트 필드가 스테이징의 실제 데이터 세트 및 데이터 볼륨에 미치는 영향을 평가한 후 프로덕션으로 승격하는 것이 좋습니다. 
  • 스크립트 필드가 사용자에게 가치를 제공한다는 확신을 갖게 되면, 새 데이터 색인 시에 필드를 추출하도록 수집을 수정합니다. 이렇게 하면 쿼리 시에 Elasticsearch 처리가 절약되고 Kibana 사용자를 위한 응답 시간이 단축됩니다. Elasticsearch에서 _reindex API를 사용하여 기존 데이터를 다시 색인할 수도 있습니다.