본 포스팅은 2022년 12월 01일을 기준으로 작성되었습니다.
엘라스틱서치를 대표하는 기능인 검색 기능을 웹사이트 상에서 구현하는 것이 주 목표였으며, 그 과정에서 엘라스틱, 키바나 내부의 기능들을 사용하고 파이썬, Django, Javascript 등으로 기타 기능을 구현했다.
검색 엔진 구현을 위한 데이터 수집은 네이버 뉴스 API를 이용했다.
네이버 개발자 센터에서 제공하는 API로,
네이버 뉴스의 검색 결과를 출력해주는 REST API이다.
title, link, description, pubDate 등을 제공한다.
1회 호출 때 마다 1000건의 뉴스를 불러올 수 있다.
파이썬을 통해 구축한 크롤러로 API를 호출, 데이터를 수집하며
수집된 뉴스들 중 최신 기사에 대한 정보는 별도 CSV 파일로 저장해
현재까지의 뉴스 수집 상황을 기록해둔다.
수집한 데이터는 파이썬 클라이언트를 통해 API로 불러오지 못한 정보를 추가하고, Ingest Pipeline에서 가공된다.
파이썬 클라이언트를 사용하기로 결정한 이유는 사용하는 크롤러 코드와 웹 프레임워크인 Django 모두 파이썬 언어로 개발되어있었기 때문이다.
크롤러에서 가져온 데이터를 바로 엘라스틱으로 전송할 수 있도록 크롤러 코드 끝부분에 엘라스틱서치 인덱싱 코드를 추가했다.
각 기사별 썸네일 이미지를 가져오는 것은 BeautifulSoup4 패키지를 사용해서 파이썬 크롤러에 기능을 추가했다.
뉴스 기사들의 html속 head의 메타데이터 이미지 링크를 가져와 API 데이터와 함께 인덱싱했다.
언론사의 이름을 가져오는 데에는 Enrich 프로세서를 사용했다.
네이버 뉴스 API에서 제공하는 link 데이터 중에서 도메인만을 가져오는 코드를 통해 info_press 라는 새로운 필드를 생성, 동시에 인덱싱했다.
별도로 press_data 라는 인덱스를 생성했다. press_data인덱스에는
각 언론사의 도메인과 언론사 명으로 info_press와 press_name이라는 이름의 필드를 생성했다.
Enrich 프로세서를 사용해 elastic_news와 press_name 인덱스에
존재하는 info_press 필드의 값이 같은 경우 press_name 인덱스의 데이터를 elastic_news 인덱스 안에 press라는 필드로 추가했다.
인덱싱 과정에서 모든 데이터들은 elastic_news_pipeline을 통해 처리가 되어서 들어온다.
Date, HTML strip, Lowercase 등의 프로세서들을 통해 앞서 이야기한 데이터들의 문제를 필터링했다.
앞서 이야기한 Enrich Processor를 통한 언론사 정보를 가져오는 과정도 동일한 파이프라인을 통해 진행한다.
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import csv
from elasticsearch import Elasticsearch, helpers
from urllib.parse import urlparse
import os.path
import logging
def get_naver_news(keyword, display, start):
df = pd.DataFrame()
url = 'https://openapi.naver.com/v1/search/news.json?query={}&display={}&start={}&sort=date'
headers = {
'X-Naver-Client-Id': 'id',
'X-Naver-Client-Secret': 'pw'
}
while True:
res = requests.get(url.format(keyword, display, start), headers=headers)
try:
datas = res.json()
searched_data = pd.DataFrame(datas['items'])
df = pd.concat([df, searched_data], ignore_index=True)
start = start + display
time.sleep(0.15)
except:
break
return df
def get_image(link):
try:
page = requests.get(
link,
headers={"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"},
timeout = 0.5
)
except:
logging.debug("error")
image = ""
pass
else:
soup = BeautifulSoup(page.text, 'html.parser')
if len(soup.select('meta[property="og:image"]')):
image = str(soup.select('meta[property="og:image"]')[0]['content'])
if len(image) > 10:
return image
else:
return ""
def elastic_news_crawler(keyword):
logging.info(keyword + " news crawling start")
display = 100
start = 1
df = get_naver_news(keyword, display, start)
for i in range(len(df)):
if not(df.iloc[i]["originallink"]):
df.iloc[i]["originallink"] = df.iloc[i]["link"]
del df["link"]
df.rename(columns={"originallink": "link"}, inplace=True)
df.rename(columns={"pubDate": "date"}, inplace=True)
links = df["link"].values.tolist()
info_press = []
for link in links:
press_name = urlparse(str(link))
info_press.append(press_name.netloc)
info_press = pd.DataFrame(info_press, columns=["info_press"])
df = pd.concat([df, info_press], axis=1)
if os.path.isfile("/home/ec2-user/elastic_django/search_app/csv/"+keyword+"_news.csv"):
csv_file = open("/home/ec2-user/elastic_django/search_app/csv/"+keyword+"_news.csv", 'r', encoding='utf-8')
else:
csv_file = open("/home/ec2-user/elastic_django/search_app/csv/"+keyword+"_news.csv", 'w', encoding='utf-8')
csv_file.write("empty")
csv_file.close()
csv_file = open("/home/ec2-user/elastic_django/search_app/csv/"+keyword+"_news.csv", 'r', encoding='utf-8')
rdr = csv.reader(csv_file)
csv_data = []
for row in rdr:
csv_data.append(row)
docs = []
doc = {}
image_url = []
for num in range(len(df)):
if csv_data[0] != csv_data[-1]:
if df.iloc[num]['date'] == csv_data[1][4] and df.iloc[num]['title'] == csv_data[1][1]:
logging.debug(f"no more {keyword} news")
logging.info(f"added news: {num}")
break
image = get_image(df.iloc[num]['link'])
try:
doc = {
"title": df.iloc[num]['title'],
"link": df.iloc[num]['link'],
"release_date": df.iloc[num]['date'],
"description": df.iloc[num]['description'],
"info_press": df.iloc[num]['info_press'],
"search_keyword": keyword,
"image_url": image,
}
docs.append({
'_index':'elastic_news',
'pipeline':'elastic_news_pipeline',
'_source':doc
})
except:
doc = {
"title": df.iloc[num]['title'],
"link": df.iloc[num]['link'],
"release_date": df.iloc[num]['date'],
"description": df.iloc[num]['description'],
"info_press": df.iloc[num]['info_press'],
"search_keyword": keyword,
}
docs.append({
'_index':'elastic_news',
'pipeline':'elastic_news_pipeline',
'_source':doc
})
logging.debug('adding '+keyword+' news | num: ' + str(num+1))
helpers.bulk(es, docs)
df.iloc[0:1].to_csv("/home/ec2-user/elastic_django/search_app/csv/"+keyword+"_news.csv", mode='w', encoding='utf-8-sig')
logging.info(keyword + " news crawling done")
date_today = time.strftime('%Y-%m-%d', time.localtime(time.time()))
logger = logging.getLogger()
logger.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s-%(levelname)s-%(message)s")
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
file_handler = logging.FileHandler("/home/ec2-user/elastic_django/search_app/crawler_log/elastic_news_crawler_"+date_today+".log")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logging.info("Crawling Start")
keyword_array = ["엘라스틱서치", "apm", "로그", "마이데이터", "보안", "모니터링", "클라우드", "게임", "MZ세대", "대학생", "직장인", "wips","호캉스", "공무원", "2022", "2023"]
es = Elasticsearch(
hosts=['host'],
http_auth=('id', 'pw'),
verify_certs=False,
ssl_show_warn=False
)
for keyword in keyword_array:
elastic_news_crawler(keyword)
logging.info("Crawling Done.")
es.close()
PUT elastic_news
{
"settings" : {
"index" : {
"analysis" : {
"filter" : {
"my_posfilter" : {
"type" : "nori_part_of_speech",
"stoptags" : [
"NNB","NNBC","NR","NP","VV","VA","VX","VCP","VCN","MM","MAG","MAJ","IC",
"JKS","JKC","JKG","JKO","JKB","JKV","JX","JC","EP","EF","EC","ETN","ETM",
"XPN","XSN","XSV","XSA","XR","SF","SE","SSO","SSC","SC","SH","SN"
]
},
"length_filter" : {
"type" : "length",
"min" : "2"
}
},
"analyzer" : {
"korean" : {
"filter" : [
"my_posfilter",
"length_filter",
"lowercase"
],
"char_filter" : [
"html_strip"
],
"type" : "custom",
"tokenizer" : "nori_user_dict"
},
"ngram_analyzer" : {
"tokenizer" : "ngram_tokenizer"
}
},
"tokenizer" : {
"nori_user_dict" : {
"type" : "nori_tokenizer",
"user_dictionary" : "userdict_ko.txt",
"decompound_mode" : "mixed"
},
"ngram_tokenizer" : {
"token_chars" : [
"letter",
"digit"
],
"min_gram" : "1",
"type" : "edge_ngram",
"max_gram" : "10"
}
}
}
}
},
"mappings" : {
"properties" : {
"description" : {
"type" : "text",
"fields" : {
"ngram" : {
"type" : "text",
"analyzer" : "ngram_analyzer",
"search_analyzer" : "standard"
}
},
"analyzer" : "korean",
"search_analyzer" : "standard",
"fielddata" : true
},
"image_url" : {
"type" : "keyword"
},
"info_press" : {
"type" : "keyword"
},
"link" : {
"type" : "keyword"
},
"release_date" : {
"type" : "date"
},
"search_keyword" : {
"type" : "keyword"
},
"title" : {
"type" : "text",
"fields" : {
"ngram" : {
"type" : "text",
"analyzer" : "ngram_analyzer",
"search_analyzer" : "standard"
}
},
"analyzer" : "korean",
"search_analyzer" : "standard",
"fielddata" : true
}
}
}
}
효율적인 검색을 위하여 다양한 세팅과 구체적인 매핑을 진행했다.
검색의 대상이 되는 필드인 title과 description 필드의 경우 한글 전문 검색과 ngram 방식의 문장 분석 두 가지 모두 필요했기 때문에
nori tokenizer와 edge n-gram tokenizer를 이용한 analyzer를 multifield 방식으로 적용시켰다.
nori analysis plugin은 엘라스틱서치에서 한글 전문을 analyze할 수 있도록 기능을 추가해주는 플러그인이다.
추가 설정 없이도 한글 구문을 높은 정확도로 분석해주지만, 더 나은 검색 결과를 도출해내기 위해 nori analyzer에는 다음과 같은 설정이 적용되었다.
nori analysis plugin에서 제공되는 nori_part_of_speech 필터를 사용했다.
한국어 품사별로 정의되어있는 코드를 stoptag에 등록하면 그 품사의 경우는 tokenize되지 않는 필터이다.
이번 검색 엔진의 경우는 명사를 제외한 나머지 품사(대명사, 수사 등)가
검색 결과에 나오게 되면 깔끔하지 않아 보여 명사를 제외한 대부분의 품사를 stoptag에 등록했다.
elasticsearch에서 제공하는 length token 필터를 사용했다.
앞서서 my_posfilter에서 품사별로 필터링을 거쳤으나 검색 결과에 무의미한 한 글자 단어들이 필터링되지 않아 (의존 명사, nori_part_of_speech 필터에서 명사로 판단해 잘못 필터링 된 다른 품사의 단어들)
length 필터로 2글자 이상의 단어들부터 tokenize 될 수 있도록 했다.
elasticsearch에서 제공하는 lowercase token 필터를 사용했다.
description이나 title 필드에 들어오는 영어 단어 중 대문자가 포함된 것들을 모두 lowercase 처리했다.
elasticsearch에서 제공하는 synonym token 필터를 사용했다.
한/영 단어, 하이픈 표시 등으로 표기는 다르지만
같은 뜻으로 사용되는 단어들을 synonym 필터를 사용해 동의어 처리했다.
elasticsearch에서 제공하는 stop 필터를 사용했다.
nori_tokenizer에서 처리되지 않는 영어 단어들 중 관사, 접속사 등
검색 결과에 방해되는 단어들을 불용어 처리했다.
기본적으로 제공하는 _english_ 언어팩을 사용했다.
nori analysis plugin에서 제공하는 nori_tokenizer를 사용했다.
user_dictionary를 사용해 기본적으로 analyzer에서 분석이 제대로 되지 않는 단어들을 (엘라스틱서치, 로그스태시, …) user_dictionary에 등록해 원하는 대로 분석이 될 수 있도록 했다.
- decompound_mode 설정은 mixed로 설정했다. 두 개 이상의 단어가 합쳐져 하나의 단어가 된 경우 tokenizer에서 어떤 방식으로 단어를 분석할지를 설정하는 부분인데, mixed로 설정할 경우 원래의 단어까지 모두 tokenize 한다.
- 예시: 엘라스틱서치 → 엘라스틱서치, 엘라스틱, 서치
검색 엔진의 편의성을 위해서 필요한 기능인 자동완성 기능을 구현했다.
검색창에 검색어를 입력하면 검색창 아래에 자동으로 현재 입력한 검색어와 유사한 검색어를 추천해주는 기능이다.
검색창에 검색어를 입력할 때 마다 엘라스틱서치에 query 요청을 보내 추천 검색어들을 계속 업데이트하는 방법으로 구현을 하려 하였으나 몇 가지의 문제가 있었다.
query를 통해 추천 검색어를 가져오기 위해서는 당연하게도 추천 검색어들이 필요하다. 엘라스틱에는 앞서 이야기한 것처럼 다양한 토크나이저와 애널라이저들이 있다. 그렇기에 full text query를 할 때에 각 단어들/글자들이 분리된 채 역인덱스 구조로 정리되어 있어 검색에 용이함을 준다. 하지만 이렇게 분리된 단어/글자들을 query를 통해 불러올 수는 없다.
tokenize 되어있는 각 단어들을 빈도값을 기준으로 정렬될 수 있는 새로운 인덱스를 만들어 검색어 자동완성을 위한 새로운 인덱스를 생성할 필요가 있었다.
transform을 사용하면 기존의 인덱스를 변환하여 필요한 데이터 형식을 가진 새로운 인덱스를 만들 수 있다.
Group by : description의 terms
Aggregations: value_count
로 설정하여 description 필드 내 텍스트들의 각 단어들과, 그 단어들의 출현 빈도 수를 가진 새로운 인덱스를 생성한다.
Continuous mode를 true로 설정해 새로운 데이터가 들어올 때마다 계속 transform을 진행한다.
transform을 통해 만들어진 새로운 terms 인덱스는 이제 description 필드 내 텍스트에 포함되어 있는 각 단어들과 단어들의 빈도수를 담게 된다.
transform으로 만들어진 search_terms_transform 인덱스 만으로도 자동완성을 구현할 수는 있다.
그러나 현재 구현하고 있는 검색 엔진의 경우 주 검색 언어가 한국어이기 때문에 추가로 각 단어들의 자모를 분리해 검색 자동 완성 결과를 향상시켜야 할 필요가 있다.
조합형 문자
한글은 조합형 문자이기 때문에 타이핑을 할 때 마다 글자의 모양이 바뀌게 된다.
"엘라스틱" 이라는 키워드를 자동완성하려고 할 때
(ㅇ(OFF) → 에(OFF) → 엘 (ON) → 엘ㄹ(OFF) → 엘라(ON) →엘랏(OFF) → …) 과 같이 새로운 자모를 입력할 때 마다 글자의 모양이 달라져 자동 완성 기능이 자연스럽게 이어지지 않고 끊어지게 된다.
초성 검색
단순 키워드 검색을 할 경우 초성으로 검색을 진행하는 것이 더 편리할 때가 있다.
ㄷㅇㅌ -> 데이터, ㅇㄽㅌ -> 엘라스틱
초성 검색을 지원하기 위해서 초성의 데이터를 담은 새로운 필드가 필요했다.
한/영 오타
매우 빈번하게 발생하는 한/영 키를 누르지 않은 상태로 영어/한글 검색어를 입력하는 경우에 검색 엔진에서 자동으로 각 검색어의 한/영 오타를 탐지해 올바른 검색어로 검색될 수 있게 하는 기능을 구현할 수 있다.
dpffktmxlrtjcl -> 엘라스틱서치, ㅏㅑㅠ뭄 -> kibana
앞서 transform으로 만들어진 terms 인덱스를 pipeline을 통해 가공한다.
script processor를 이용해 직접 작성한 스크립트의 내용대로 데이터를 가공할 수 있다. painless script라는 elastic에서 제공하는 JAVA 문법을 차용한 언어를 이용해 각 한글 낱말들을 유니코드에 따라 분리해 초성, 중성, 종성의 값을 구하고, 각 한글 자모를 영어 쿼티 자판에 대응하는 값으로 저장한다. (ㄱ -> r, ㄴ -> s)
각각 계산된 값을 jamo, chosung이라는 새로운 필드로 저장한다.
파이썬에서 검색을 진행할 때 입력된 검색어를 가공한다. 파이썬의 jamo 라이브러리를 사용해 한글 자모들을 모두 분리했고, 그 후 한글/영어 자판이 key:value 형식으로 저장되어 있는 alphabet_dictionary를 통해 영어로 값을 수정하는 과정을 거쳤다.
검색 창에 검색어를 입력할 때마다 영어로 분리되어 search_terms_transfrom 인덱스의 terms, jamo 필드에 query를 보내고,
가장 score가 높은 값 10개가 결과로 반환되어 자동 완성 검색어로 하단 창에 나오게 된다.
모든 검색 입력 값을 영어로 된 데이터로 변환하여 query를 날리는 것이므로 한/영 오타가 발생하더라도 자동완성 창에는 정상적인 검색어가 노출되게 된다.
검색창 하단에 나오는 자동 완성에서는 한/영 오타를 해결할 수 있었다. 하지만 실제로 검색 버튼을 눌렀을 때는 오타가 난 그대로 query를 보내버리기 때문에 검색이 제대로 되지 않았다.
검색 버튼을 눌러 query를 보냈을 때, 입력한 값이 한/영 오타인 경우 정상적인 검색어로 자동으로 바뀌어 검색을 해줄 수 있도록 하기 위해 앞서 만들었던 search_terms_transform 인덱스를 사용했다.
검색창에 검색어를 입력해 페이지가 search.wnytech.co.kr/search로 넘어가게 되면 자동으로 search_terms_transform 인덱스에 query를 요청한다. 만약 요청받은 jamo 값이 입력받은 검색어와 같은 경우, 정상적인 검색어는 한글이지만 영어로 입력된 경우이다. 입력받은 검색어의 자모값이 요청받은 term 값과는 다르고, 요청받은 term값이 jamo 값과는 다를 경우, 정상적인 검색어는 영어이지만 한글로 입력된 경우이다.
엘라스틱 내부에서 지원하는 Suggest Search API를 사용했다.
앞서 다룬 한/영 오타의 경우 입력된 문자가 한글/영어인 차이가 있을 뿐 입력한 데이터의 키보드 배열이 같았기 때문에 모든 검색어들을 자모 분리후 변환하는 과정을 거치는 것으로 해결할 수 있었다.
검색어의 일부가 오타로 입력되어 검색이 되지 않는 경우는 Suggest Search API를 통해 수정할 수 있다.
Suggest API를 이용하면 입력한 검색어와 유사한 특정 필드 내에 존재하는 단어를 반환한다.
위와 같은 search template을 통해서 현재 입력된 검색어가 terms 인덱스 내에 존재하지 않는 검색어일 경우, 가장 유사한 검색어를 반환해, 그 검색어로 실제 검색을 진행한다.
위와 같은 흐름을 통해 오타가 발생한 검색어를 자동 수정 후 검색을 진행하게 된다.
앞서 설명한 기능들을 포함하고 있는 check_term 파이썬 함수를 만들었다.
입력받은 모든 검색어가 검색을 진행하기 전에 check_term 함수를 통해 가공된다.
def check_term(search_word, search_word_jamo):
check_term = es.search(
index=search_terms_index,
body={
"query":{
"multi_match":{
"query":search_word_jamo,
"fields":["terms", "jamo", "chosung"]
}
},
"size":1
}
)
search_word_original = search_word
search_term_jamo = ""
search_term = ""
if len(check_term['hits']['hits']):
search_term_jamo = check_term['hits']['hits'][0]['_source']['jamo']
search_term = check_term['hits']['hits'][0]['_source']['terms']
if search_word == "":
search_word = search_term
search_word_original = search_word
if search_term == search_word_jamo and search_term != search_word:
search_word_original = search_word
search_word = search_term
#return "한->영 오타", search_word_original
return search_word, search_word_original
elif search_word == search_term_jamo:
search_word_original = search_word
search_word = search_term
#return "영->한 오타", search_word_original
return search_word, search_word_original
else:
suggestion = ""
if search_term_jamo:
suggestion_jamo = term_suggestion(search_word_jamo, "jamo")
else:
suggestion_jamo = term_suggestion(search_word, "terms")
if suggestion_jamo:
suggestion_terms = es.search(
index=search_terms_index,
body={
"query":{
"multi_match":{
"query":suggestion_jamo,
"fields":["jamo", "terms"]
}
},
"size":1
}
)
suggestion = suggestion_terms['hits']['hits'][0]['_source']['terms']
return suggestion, search_word_original
#return check_term("", suggestion)
return search_word, search_word_original
최종적으로 return 받은 search_word로 elastic_news 인덱스에 검색을 실행한다.
자동으로 한/영 변환된 결과
자동으로 오타 수정된 결과 (엘라스틸 → 엘라스틱)
원본 검색 결과 보기를 눌렀을 때의 결과. URL에 original=true라는 태그가 추가되어 있다.
원본 검색 결과 보기를 눌렀을 때의 결과. URL에 original=true라는 태그가 추가되어 있다.
오타 수정 함수를 통해 검색이 진행된 경우 검색어가 자동 수정되었다는 내용의 알림이 검색 결과 화면 상단에 표시된다.
만약 자동으로 한/영 변환된 검색어가 아닌 원본의 검색어로의 검색이 필요할 경우를 위해 검색 결과 URL에 original=true 태그가 추가되어 있다면 원본 검색어로도 검색을 진행할 수 있도록 했다.
mapping 단계에서 description 필드에 fielddata라는 파라미터를 설정했다.
fielddata 파라미터를 true로 설정하면 전문의 tokenize된 단어들을 가져올 수 있다.
이 단어들을 가지고 tag cloud를 만들어 검색어에 따라 어떤 단어들이 많이 나타났는지 볼 수 있도록 했다.
키바나의 대시보드는 URL 뒤의 파라미터를 수정하여 변화를 줄 수 있다. 웹 사이트에 키바나 대시보드를 임포트하고, 현재 검색어 값을 description 필드의 필터값으로 적용시켜 검색어에 따라 대시보드에 나타나는 tag cloud에 변화를 줄 수 있게 했다.
프로젝트 초반에는 ELK stack과 Django 개발 환경이 모두 로컬이었고 ELK 스택도 7.x.x 버전대였기 때문에 별도의 설정 없이도 share embed code를 통해 iframe으로 대시보드를 웹사이트에 가져올 수 있었다.
Django 환경을 EC2로 옮기고 ELK 스택의 업그레이드 후부터는 iframe 입력만으로 대시보드를 가져올 수 없었다.
ELK 스택 8.x.x 버전부터 생긴 보안 업데이트로 인하여 iframe으로 키바나 대시보드를 가져오기 위해서는 키바나 창에 접속을 했었던 쿠키가 남아있어야 했다. 즉, 현재 접속 환경에서 최소 한번이라도 키바나 환경에 로그인을 해야 대시보드를 확인할 수 있었다.
nginx.conf
user UserName ;
worker_processes auto;
pid /var/run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf ;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 8001;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Authorization "Basic Base64Auth";
proxy_buffering off;
proxy_pass <Kibana Address>;
}
}
}
문제를 해결하기 위해 Nginx를 사용했다. 웹 페이지에 바로 키바나 대시보드를 불러내는 것이 아닌, 중간에 Nginx 웹서버를 두고 그 안에 로그인 정보를 포함해 불러오는 방식으로 대시보드를 가져온다.
proxy_set_header Authorization 부분에 ID:Password를 Base64 인코더를 사용해 인코딩한 문자열을 넣어 로그인 정보를 포함시켜준다.
기본적으로 위의 설정만 마치게 된다면 키바나 대시보드가 웹페이지에 불러와질 것이다. 하지만 간혹 위의 사진과 같은 오류가 발생하기도 한다. 키바나 대시보드를 불러오는 속도가 느려 생기는 일종의 Timeout 오류이다. proxy_buffering off; 를 설정에 포함시켜주면 timeout이 발생하지 않게 된다.
기본적으로 위의 설정만 마치게 된다면 키바나 대시보드가 웹페이지에 불러와질 것이다. 하지만 간혹 아래의 사진과 같은 오류가 발생하기도 한다. 키바나 대시보드를 불러오는 속도가 느려 생기는 일종의 Timeout 오류이다. proxy_buffering off; 를 설정에 포함시켜주면 timeout이 발생하지 않게 된다.
@이동훈(Donghoon Lee)