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의 공유 옵션에서 쉽게 생성할 수 있습니다.

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 유형은 다음 두 가지 옵션 중 하나입니다.

  1. 스냅샷: 대시보드의 현재 전체 상태를 인코딩하는 URL입니다. 즉, 대시보드에 적용한 변경 사항은 임베드된 버전에 반영되지 않습니다. 
  2. 저장된 객체: 대시보드의 저장된 객체 ID를 참조하는 URL을 사용합니다. 즉, URL을 생성한 후 대시보드에 적용한 모든 변경 사항이 JavaScript 애플리케이션 사용자에게 표시됩니다.

작성자의 경험상 이러한 대시보드는 변경될 수 있습니다. 따라서 URL 생성 후 대시보드에 적용된 변경 사항이 표시되도록 하려면 임베딩에 저장된 객체 옵션을 사용하는 것이 가장 적절합니다.

포함 설정은 임베드된 대시보드 상단에 포함할 추가 컨트롤을 나타냅니다.

Kibana 대시보드 요소
  1. 상단 메뉴: 편집 및 전체 화면과 같은 대시보드 기능이 포함된 설정입니다. Kibana URL에 show-top-menu=true를 포함하여 제어합니다. 
  2. 쿼리: KQL 쿼리 표시줄을 사용하면 대시보드에 표시되는 데이터를 필터링할 수 있습니다. 이는 show-query-input=true URL 매개변수로 표시됩니다.
  3. 시간 필터: 대시보드 데이터의 날짜 범위를 선택하는 날짜 선택기입니다. URL 내에서 show-time-filter=true를 사용하여 활성화합니다.
  4. 필터 표시줄: 데이터 필터링을 추가하는 설정을 숨기려면 hide-filter-bar URL 매개변수를 true로 설정해야 합니다.

공개 URL을 사용하지 않으면 대시보드에 액세스하기 위해 로그인하라는 메시지가 표시됩니다. 이 시점에서는 원활한 경험을 제공하지 못하지만, 로그인 자격 증명이 있는 사용자는 대시보드에 액세스할 수 있습니다.

익명 인증이 없는 임베드된 대시보드

자동 로그인

대시보드가 자동으로 표시되도록 하려면, 사용자가 JavaScript 애플리케이션과 대시보드 모두에 자격 증명을 입력할 필요를 없애기 위해 Kibana의 대시보드에 인증을 통합해야 합니다. 이는 원활한 경험을 제공합니다. 이 작업은 다음 두 가지 방법 중 하나로 수행할 수 있습니다.

  1. 인증 토큰을 추출할 수 없는 모든 수신 요청에 기본 자격 증명과 권한을 부여하도록 익명 인증을 활성화합니다(무료 티어에서 사용 가능).

  2. 인증되지 않은 사용자를 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 인증 사용자 생성

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

임베드된 대시보드 콘텐츠 정책 차단

이 문제는 브라우저가 Kibana 대시보드를 차단하기 때문에 발생합니다. 최신 웹 브라우저는 임베드된 콘텐츠를 제한하기 위해 동일 출처 정책을 적용합니다. 두 URL은 프로토콜, 포트 및 호스트가 같을 때 동일한 출처를 공유합니다. 쉽게 말해 다른 출처에서 오는 콘텐츠는 콘텐츠 정책에서 허용하지 않는 한 기본적으로 차단됩니다.

Elastic v8.x부터 기본적으로 활성화되는 보안 기능이 적용된 ELK 스택에서 브라우저가 Kibana 서버로 세션 쿠키를 전송하도록 허용하려면 kibana.yml에서 sameSiteCookies 옵션을 구성해야 합니다.

xpack.security.sameSiteCookies: "None"

이 마지막 단계를 마치면 JavaScript 애플리케이션에 임베드된 Kibana 대시보드를 볼 수 있습니다.

기본 임베드 Kibana 대시보드

사용자 정의 컨트롤 사용

이 대시보드에서는 데이터를 필터링하기 위해 컨트롤을 사용한다는 점을 확인했을 수 있습니다. 사용자가 데이터를 조사하고 선택 범위를 좁혀 흥미로운 인사이트를 찾을 수 있도록 하는 것이 중요합니다.

특정 상황에서는 대시보드 내 컨트롤을 사용하는 것이 적절하지 않을 수 있습니다. 기존 애플리케이션 내에서 디자인 일관성을 유지하기 위해 자체 사용자 정의 컨트롤을 사용하고 싶을 수 있습니다. 또는 대시보드를 추가 데이터 소스 및 시각화와 나란히 배치하고, 이들을 모두 필터링하여 일관된 경험을 만들고 싶을 수도 있습니다.

고급 예시에서는 날짜 선택기와 드롭다운 선택에서 날짜 범위 설정을 대시보드에 전달하여 대시보드 업데이트를 강제하는 방법을 보여 줍니다.

고급 임베드 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에는 두 가지 수준의 필터가 있습니다.

  1. _g 매개변수로 표시되는 전역 상태는 개별 Kibana 애플리케이션 간에 이동하는 상태를 나타냅니다. 대표적인 예로 선택한 시작일 및 종료일을 포함하는 고정된 필터가 있습니다.

  2. 현재 대시보드와 같은 개별 애플리케이션으로 제한된 상태입니다. 이는 _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 자체에서 전역 필터 매개변수 _ggetDashboardUri() 도우미 메서드에서 볼 수 있듯이 선택한 범위로 업데이트됩니다.

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에서 사용하도록 쉽게 조정할 수 있습니다.

대시보드를 임베드하면서 마주친 질문이나 문제를 커뮤니티 포럼에서 공유해 주세요. 언제든 기꺼이 도와드리겠습니다. 즐거운 대시보드 작업 되세요!