Kibana 대시보드를 임베드하는 방법

저와 같은 프런트엔드 엔지니어가 자주 받는 요청 중 하나는 Kibana®와 같은 소스의 기존 대시보드를 JavaScript 웹 애플리케이션에 임베드하는 것입니다 사용자 생성 보기를 빠르게 배포하거나 사용자가 특정 보기를 제어할 수 있도록 하기 위해 저도 여러 차례 이 작업을 수행해야 했습니다. 훌륭한 개발자 커뮤니티에서 정기적으로 받는 질문을 보면, 저만 그런 것은 아닌 듯합니다.
Kibana 대시보드와 같은 데이터 시각화 도구를 사용하면 설계나 기술에 익숙하지 않은 사용자도 Elasticsearch® 데이터를 기반으로 보기를 빠르고 쉽게 생성하고 보기의 프로토타입을 만들 수 있습니다. 실제로 이 때문에 기존 웹 애플리케이션에 대시보드를 임베드하는 일이 가장 어려운 부분이 됩니다. 특히 사용자에게 일관된 스타일과 경험을 제공하기 위해 데이터 보기를 제어하는 사용자 정의 웹 컨트롤을 통합하려는 경우 더욱 그렇습니다.
여기에서는 HTML iframe을 사용하여 웹 앱에 Kibana 대시보드를 임베드하는 방법을 코드 예시를 통해 설명하겠습니다. 또한 이러한 보기에 대한 Kibana 인증과 JavaScript를 사용하여 사용자 정의 컨트롤을 임베드된 보기에 연결하는 방법도 다룹니다.
iframe이란 무엇인가요?
이 글에서 다루는 두 가지 예시에서는 모두 iframe을 사용하여 대시보드를 임베드합니다. <iframe> HTML 태그로 표시되는 iframe을 사용하면 현재 문서에 다른 웹 페이지를 임베드할 수 있습니다. 구체적으로는 Sample flight data 샘플 데이터 세트에서 로드되는 Global Flight Dashboard를 페이지 내 자체 Elastic® 배포 환경에 포함합니다.
애플리케이션에 다른 소스를 임베드할 때는 사용자가 액세스해야 하는 신뢰할 수 있는 데이터 소스인지 확인하는 것이 중요합니다. 적절한 콘텐츠 보안 정책, sandbox 속성을 통한 사용 제한 및 권한을 사용하여 임베드된 콘텐츠의 작업을 제한해야 합니다. iframe에 sandbox 속성을 지정하지 않으면 기본적으로 모든 제한을 적용합니다.
애플리케이션에 타사 콘텐츠를 포함할 때는 성능도 고려해야 합니다. iframe은 다른 리소스보다 더 많은 대역폭을 소비할 수 있으므로 단일 애플리케이션에서 iframe을 많이 사용하면 전체 애플리케이션 속도가 느려질 수 있습니다. 애플리케이션에 여러 Kibana 대시보드를 임베드하려는 경우 포함하는 대시보드 수를 가능한 한 제한하고 애플리케이션 성능 테스트를 수행하세요. 구성 요소와 대시보드는 쉽게 추가할 수 있지만, 개발자는 사용자가 원하는 모든 화려한 컨트롤이 아니라 필요한 데이터를 제공해야 합니다. 따라서 대시보드와 시각화 중에서 선택할 때는 사용자와 협업하여 실제로 필요한 것이 무엇인지 파악하세요.
HTML iframe을 사용한 기본 임베딩
이 기본 예시에서 설명하는 Global Flight Dashboard를 웹 애플리케이션에 포함하는 코드는 Kibana의 공유 옵션에서 쉽게 생성할 수 있습니다.

선택한 관련 옵션 및 현재 필터가 포함된 iframe 스니펫이 생성되며, 이를 HTML에 붙여 넣을 수 있습니다.
<iframe src="https://my-deployment:9243/app/dashboards#/view/7adfa750-4c81-11e8-b3d7-01146121b73d?embed=true&_g=(refreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-1y%2Fd%2Cto%3Anow))&show-top-menu=true&show-query-input=true&show-time-filter=true" height="600" width="800"></iframe>생성된 스니펫에서는 iframe의 너비와 높이에 픽셀 측정값을 사용합니다. iframe 크기가 내부 콘텐츠를 반영하도록 설정하는 일은 일반적으로 어려운 과제였습니다. 권장 방법은 현대적인 반응형 디자인의 일부로 vw 및 vh 뷰포트 크기 지정 속성을 사용해 iframe 크기를 뷰포트에 상대적으로 지정하거나, 다양한 기기 크기를 처리하기 위해 미디어 쿼리를 사용하는 것입니다.
사용 가능한 설정이 많기 때문에 필요한 항목을 파악하기 어려울 수 있습니다. 이러한 옵션을 사용하면 대시보드의 상태와 iframe 내에 표시되는 컨트롤을 구성할 수 있습니다.
생성할 URL 유형은 다음 두 가지 옵션 중 하나입니다.
- 스냅샷: 대시보드의 현재 전체 상태를 인코딩하는 URL입니다. 즉, 대시보드에 적용한 변경 사항은 임베드된 버전에 반영되지 않습니다.
- 저장된 객체: 대시보드의 저장된 객체 ID를 참조하는 URL을 사용합니다. 즉, URL을 생성한 후 대시보드에 적용한 모든 변경 사항이 JavaScript 애플리케이션 사용자에게 표시됩니다.
작성자의 경험상 이러한 대시보드는 변경될 수 있습니다. 따라서 URL 생성 후 대시보드에 적용된 변경 사항이 표시되도록 하려면 임베딩에 저장된 객체 옵션을 사용하는 것이 가장 적절합니다.
포함 설정은 임베드된 대시보드 상단에 포함할 추가 컨트롤을 나타냅니다.

- 상단 메뉴: 편집 및 전체 화면과 같은 대시보드 기능이 포함된 설정입니다. Kibana URL에 show-top-menu=true를 포함하여 제어합니다.
- 쿼리: KQL 쿼리 표시줄을 사용하면 대시보드에 표시되는 데이터를 필터링할 수 있습니다. 이는 show-query-input=true URL 매개변수로 표시됩니다.
- 시간 필터: 대시보드 데이터의 날짜 범위를 선택하는 날짜 선택기입니다. URL 내에서 show-time-filter=true를 사용하여 활성화합니다.
- 필터 표시줄: 데이터 필터링을 추가하는 설정을 숨기려면 hide-filter-bar URL 매개변수를 true로 설정해야 합니다.
공개 URL을 사용하지 않으면 대시보드에 액세스하기 위해 로그인하라는 메시지가 표시됩니다. 이 시점에서는 원활한 경험을 제공하지 못하지만, 로그인 자격 증명이 있는 사용자는 대시보드에 액세스할 수 있습니다.

자동 로그인
대시보드가 자동으로 표시되도록 하려면, 사용자가 JavaScript 애플리케이션과 대시보드 모두에 자격 증명을 입력할 필요를 없애기 위해 Kibana의 대시보드에 인증을 통합해야 합니다. 이는 원활한 경험을 제공합니다. 이 작업은 다음 두 가지 방법 중 하나로 수행할 수 있습니다.
인증 토큰을 추출할 수 없는 모든 수신 요청에 기본 자격 증명과 권한을 부여하도록 익명 인증을 활성화합니다(무료 티어에서 사용 가능).
인증되지 않은 사용자를 SSO 포털로 리디렉션하고 인증된 사용자는 대시보드로 바로 통과시키도록 SAML 싱글 사인온(SSO) 제공자에 대한 지원을 추가합니다. 이 기능은 라이선스가 필요합니다.
여기에서는 익명 옵션을 다룹니다. 먼저 kibana.yml에 익명 인증 제공자 anonymous1을 추가해야 합니다.
xpack.security.authc.providers:
anonymous.anonymous1:
order: 0
credentials:
username: "my_anonymous_user"
password: "password"iframe URL도 다시 생성하여 auth_provider_hint 매개변수를 지정해야 합니다. 이렇게 하면 제공자 anonymous1에 대해 구성한 자격 증명이 임베드된 콘텐츠에 연결됩니다.
<iframe src="https://my-deployment-9f9945.kb.eu-west-2.aws.cloud.es.io:9243/app/dashboards?auth_provider_hint=anonymous1#/view/7adfa750-4c81-11e8-b3d7-01146121b73d?embed=true&_g=(refreshInterval%3A(pause%3A!f%2Cvalue%3A120000)%2Ctime%3A(from%3Anow-1y%2Cto%3Anow))&show-time-filter=true" height="600" width="800"></iframe>auth_provider_hint=anonymous1 매개변수를 포함하지 않으면 게스트로서 대시보드를 계속 이용할 수 없습니다. 마찬가지로 올바른 사용자 이름과 비밀번호로 Kibana에 등록된 해당 사용자 역할이 없으면 인증 오류가 발생합니다.

이 문제를 해결하려면 kibana.yml의 제공자 구성에 설정된 비밀번호와 일치하는 올바른 비밀번호를 사용하여 사용자가 등록되어 있는지 확인하세요. 인증되지 않은 사용자에게 액세스 권한이 부여되므로, 이 계정의 권한은 필요한 최소 수준으로 제한하는 것이 좋습니다.

이 시점에서는 모든 설정이 완료되었다고 생각할 수 있습니다. 하지만 대시보드에 연결하면 이상한 새로 고침 이벤트가 반복해서 발생하는 것을 볼 수 있습니다.

이 문제는 브라우저가 Kibana 대시보드를 차단하기 때문에 발생합니다. 최신 웹 브라우저는 임베드된 콘텐츠를 제한하기 위해 동일 출처 정책을 적용합니다. 두 URL은 프로토콜, 포트 및 호스트가 같을 때 동일한 출처를 공유합니다. 쉽게 말해 다른 출처에서 오는 콘텐츠는 콘텐츠 정책에서 허용하지 않는 한 기본적으로 차단됩니다.
Elastic v8.x부터 기본적으로 활성화되는 보안 기능이 적용된 ELK 스택에서 브라우저가 Kibana 서버로 세션 쿠키를 전송하도록 허용하려면 kibana.yml에서 sameSiteCookies 옵션을 구성해야 합니다.
xpack.security.sameSiteCookies: "None"이 마지막 단계를 마치면 JavaScript 애플리케이션에 임베드된 Kibana 대시보드를 볼 수 있습니다.

사용자 정의 컨트롤 사용
이 대시보드에서는 데이터를 필터링하기 위해 컨트롤을 사용한다는 점을 확인했을 수 있습니다. 사용자가 데이터를 조사하고 선택 범위를 좁혀 흥미로운 인사이트를 찾을 수 있도록 하는 것이 중요합니다.
특정 상황에서는 대시보드 내 컨트롤을 사용하는 것이 적절하지 않을 수 있습니다. 기존 애플리케이션 내에서 디자인 일관성을 유지하기 위해 자체 사용자 정의 컨트롤을 사용하고 싶을 수 있습니다. 또는 대시보드를 추가 데이터 소스 및 시각화와 나란히 배치하고, 이들을 모두 필터링하여 일관된 경험을 만들고 싶을 수도 있습니다.
이 고급 예시에서는 날짜 선택기와 드롭다운 선택에서 날짜 범위 설정을 대시보드에 전달하여 대시보드 업데이트를 강제하는 방법을 보여 줍니다.

사용자 정의 컨트롤을 사용하려면 대시보드 URL의 구성을 이해해야 합니다. 다음 예시를 살펴보겠습니다.
https://elastic-deployment-9f9945.kb.eu-west-2.aws.cloud.es.io:9243/app/dashboards?auth_provider_hint=anonymous1#/view/7adfa750-4c81-11e8-b3d7-01146121b73d?embed=true&_g=(filters:!(),refreshInterval:(pause:!f,value:0),time:(from:'${selectedStartDate}',to:'${selectedEndDate}'))&_a=(query:(language:kuery,query:'${carrierQuery}'))&hide-time-filter=true기본 예시에서 설명한 매개변수 외에도 필터를 조작해야 합니다. 앞서 커뮤니티에서 논의했듯이 Kibana에는 두 가지 수준의 필터가 있습니다.
_g 매개변수로 표시되는 전역 상태는 개별 Kibana 애플리케이션 간에 이동하는 상태를 나타냅니다. 대표적인 예로 선택한 시작일 및 종료일을 포함하는 고정된 필터가 있습니다.
현재 대시보드와 같은 개별 애플리케이션으로 제한된 상태입니다. 이는 _a URL 매개변수로 표시됩니다.
날짜 선택기에서 날짜 범위를 전달하려면 새 날짜 범위가 컨트롤에 적용될 때 선택한 시작일과 종료일로 iframe URL을 업데이트해야 합니다. 처음에는 이러한 값을 지난 1년의 상대 날짜 범위로 설정합니다. easepick을 예로 들면, 설정 시 등록된 select 이벤트에서 새 날짜를 캡처하고 필요한 ISO 날짜 형식으로 변환한 후 새 URL로 iframe의 src 속성을 업데이트합니다.
let selectedStartDate = 'now-1y';
let selectedEndDate = 'now';
const picker = new easepick.create({
element: '#datepicker',
css: [
'https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css'
],
zIndex: 10,
firstDay: 0,
autoApply: false,
format: 'MMM DD, YYYY @ HH:MM:00',
plugins: [
'RangePlugin',
'TimePlugin'
],
setup(picker) {
picker.on('select', (e) => {
const dateFormat = 'YYYY-MM-DDTHH:MM:00.000Z';
selectedStartDate = picker.getStartDate().format(dateFormat);
selectedEndDate = picker.getEndDate().format(dateFormat);
dashboardUri=getDashboardUri();
iframe.setAttribute('src', dashboardUri);
});
}
});URL 자체에서 전역 필터 매개변수 _g는 getDashboardUri() 도우미 메서드에서 볼 수 있듯이 선택한 범위로 업데이트됩니다.
function getDashboardUri() {
return `https://my-deployment-9f9945.kb.eu-west-2.aws.cloud.es.io:9243/app/dashboards?auth_provider_hint=anonymous1#/view/7adfa750-4c81-11e8-b3d7-01146121b73d?embed=true&_g=(filters:!(),refreshInterval:(pause:!f,value:0),time:(from:'${selectedStartDate}',to:'${selectedEndDate}'))&hide-time-filter=true`;
}드롭다운과 같은 컨트롤에서 필터링하려는 데이터 필드의 경우, _a 매개변수의 query 옵션을 통해 해당 값을 전달해야 합니다. 다음 HTML select 컨트롤을 예로 들어 보겠습니다.
<div class="carrier-select-container">
<label for="carrier-select">Carrier</label>
<select name="carrier-select" id="carrier-select" onchange="updateWithCarrier()">
<option value="ES-Air">ES-Air</option>
<option value="JetBeats">JetBeats</option>
<option value="Kibana Airlines">Kibana Airlines</option>
<option value="Logstash Airways">Logstash Airways</option>
</select>
</div>onchange 이벤트에 연결된 updateWithCarrier() 메서드에서 변경된 선택값을 추출할 수 있습니다. 이벤트 핸들러에서 select 컨트롤의 이벤트를 가져옵니다.
function updateWithCarrier() {
const carrierSelect = document.getElementById('carrier-select');
selectedCarrier = carrierSelect.value || '';
dashboardUri=getDashboardUri();
iframe.setAttribute('src', dashboardUri);
}여전히 getDashboardUri() 도우미를 사용하고 있다는 점에 유의하세요. 이 도우미는 애플리케이션 필터의 query 옵션을 통해 대시보드 URL에 전달할 KQL 쿼리를 생성하도록 업데이트해야 합니다.
function getDashboardUri() {
const carrierQuery = rison.encode_object({Carrier : encodeURIComponent(selectedCarrier)});
return `https://my-deployment-9f9945.kb.eu-west-2.aws.cloud.es.io:9243/app/dashboards?auth_provider_hint=anonymous1#/view/7adfa750-4c81-11e8-b3d7-01146121b73d?embed=true&_g=(filters:!(),refreshInterval:(pause:!f,value:0),time:(from:'${selectedStartDate}',to:'${selectedEndDate}'))&_a=(query:(language:kuery,query:'${carrierQuery}'))&hide-time-filter=true`;
}Kibana는 Rison 및 URI 인코딩을 사용하므로, 쿼리에 포함하기 전에 이를 적용해야 합니다. 위의 carrierQuery 정의에서 일반적인 encodeURIComponent 메서드를 사용하여 선택값을 이스케이프 처리하는 것과 함께 rison.js를 사용하는 부분에서 이를 확인할 수 있습니다.
연결을 완료하면 새 항목을 선택할 때마다 대시보드가 새로 고쳐지는 것을 볼 수 있습니다. 다만 디버깅하기 어려울 수 있는, 포럼에서 보고된 오류와 같은 잘못된 형식의 Rison을 나타내는 오류에 주의하세요.
URL은 언제든 변경될 수 있으므로, 임베드하기로 선택한 서드파티 도구의 새 버전에서 기능이 작동하지 않을 위험이 있습니다. 각 Kibana 릴리즈의 주요 변경 사항을 반드시 확인하고 애플리케이션에 대해 회귀 테스트를 신중하게 수행하세요.
Kibana 대시보드 더 활용하기
여기에서는 임베드된 Kibana 대시보드의 세계를 살펴보았습니다. 단일 HTML iframe을 사용하는 간단한 예시와 자체 JavaScript 구성 요소를 사용해 대시보드에 매개변수를 전달하는 복잡한 예시를 다뤘습니다. 모든 코드는 이 GitHub 리포지토리에서 확인할 수 있으며, 즐겨 사용하는 웹 기술이나 JavaScript 프레임워크 또는 TypeScript에서 사용하도록 쉽게 조정할 수 있습니다.
대시보드를 임베드하면서 마주친 질문이나 문제를 커뮤니티 포럼에서 공유해 주세요. 언제든 기꺼이 도와드리겠습니다. 즐거운 대시보드 작업 되세요!