Skip to content

Commit

Permalink
[Onboarding] k8s quickstart flow (elastic#186380)
Browse files Browse the repository at this point in the history
Depends on: elastic/elastic-agent#4754
Depends on: elastic#186106
Closes: elastic#182407

## Summary

Adds a Kubernetes onboarding quick start flow using `kubectl kustomize`
command.
![CleanShot 2024-06-18 at 15 10
27@2x](https://github.com/elastic/kibana/assets/793851/522d2481-6a0e-43d3-b9ef-d09ee9953b3c)

## How to test
1. Run Kibana and ES locally (make sure to expose ES on 0.0.0.0 so
elastic agent can reach it from within a container, I use this command
`yarn es snapshot --license trial -E
xpack.security.authc.api_key.enabled=true -E http.host=0.0.0.0`)
2. Setup a test cluster with
[minikube](https://minikube.sigs.k8s.io/docs/start/?arch=%2Fmacos%2Fx86-64%2Fstable%2Fbinary+download)
3. Open Kibana and navigate to the Onboarding screen
4. Make sure Kubernetes quick start card is visible under the
infrastructure category and click on it
5. Copy the command snippet
6. Paste the command into a terminal, but don't run it yet
7. Replace `localhost` in the command with you local IP `ipconfig
getifaddr en0`
8. In case elastic/elastic-agent#4754 was not
merged yet, you'd need to also clone the elastic-agent repo and replace
the template URL with a local path to the
`elastic-agent-kustomize/default/elastic-agent-standalone` folder.
9. Run the command and make sure all resources were created
10. Go back to Kibana, after ~1 minute UI should identify that the data
was ingested
11. Click on the cluster overview link and make sure it works

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
(cherry picked from commit 141e619)
  • Loading branch information
mykolaharmash committed Jul 10, 2024
1 parent b3e591b commit b74f3cb
Show file tree
Hide file tree
Showing 19 changed files with 525 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { SystemLogsPanel } from './quickstart_flows/system_logs';
import { CustomLogsPanel } from './quickstart_flows/custom_logs';
import { OtelLogsPanel } from './quickstart_flows/otel_logs';
import { AutoDetectPanel } from './quickstart_flows/auto_detect';
import { KubernetesPanel } from './quickstart_flows/kubernetes';
import { BackButton } from './shared/back_button';

const queryClient = new QueryClient();
Expand Down Expand Up @@ -66,6 +67,10 @@ export function ObservabilityOnboardingFlow() {
<BackButton />
<CustomLogsPanel />
</Route>
<Route path="/kubernetes">
<BackButton />
<KubernetesPanel />
</Route>
<Route path="/otel-logs">
<BackButton />
<OtelLogsPanel />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function useCustomCardsForCategory(
const { href: systemLogsUrl } = reactRouterNavigate(history, `/systemLogs/${location.search}`);
const { href: customLogsUrl } = reactRouterNavigate(history, `/customLogs/${location.search}`);
const { href: otelLogsUrl } = reactRouterNavigate(history, `/otel-logs/${location.search}`);
const { href: kubernetesUrl } = reactRouterNavigate(history, `/kubernetes/${location.search}`);

const otelCard: VirtualCard = {
id: 'otel-logs',
Expand Down Expand Up @@ -112,7 +113,23 @@ export function useCustomCardsForCategory(
];
case 'infra':
return [
toFeaturedCard('kubernetes'),
{
id: 'kubernetes-quick-start',
type: 'virtual',
title: 'Kubernetes',
description: 'Collect logs and metrics from Kubernetes using minimal configuration',
name: 'kubernetes-quick-start',
categories: ['observability'],
icons: [
{
type: 'svg',
src: http?.staticAssets.getPluginAssetHref('kubernetes.svg') ?? '',
},
],
url: kubernetesUrl,
version: '',
integration: '',
},
toFeaturedCard('docker'),
isServerless ? toFeaturedCard('prometheus') : otelCard,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { IntegrationCardItem } from '@kbn/fleet-plugin/public';

export const QUICKSTART_FLOWS = ['system-logs-virtual'];
export const QUICKSTART_FLOWS = ['system-logs-virtual', 'kubernetes-quick-start'];

export const toCustomCard = (card: IntegrationCardItem) => ({
...card,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

interface Params {
encodedApiKey: string;
onboardingId: string;
elasticsearchUrl: string;
elasticAgentVersion: string;
}

const KUSTOMIZE_TEMPLATE_URL =
'https://github.com/elastic/elastic-agent/deploy/kubernetes/elastic-agent-kustomize/default/elastic-agent-standalone';

export function buildKubectlCommand({
encodedApiKey,
onboardingId,
elasticsearchUrl,
elasticAgentVersion,
}: Params) {
const escapedElasticsearchUrl = elasticsearchUrl.replace(/\//g, '\\/');

return `
kubectl kustomize ${KUSTOMIZE_TEMPLATE_URL}\\?ref\\=v${elasticAgentVersion}
| sed -e 's/JUFQSV9LRVkl/${encodedApiKey}/g'
-e "s/%ES_HOST%/${escapedElasticsearchUrl}/g"
-e "s/%ONBOARDING_ID%/${onboardingId}/g"
-e "s/\\(docker.elastic.co\\/beats\\/elastic-agent\:\\).*$/\\1${elasticAgentVersion}/g"
-e "/{CA_TRUSTED}/c\\ "
| kubectl apply -f-
`
.trim()
.replace(/\n/g, ' ')
.replace(/\s\s+/g, ' ');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { EuiCodeBlock, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { buildKubectlCommand } from './build_kubectl_command';
import { CopyToClipboardButton } from '../shared/copy_to_clipboard_button';

interface Props {
encodedApiKey: string;
onboardingId: string;
elasticsearchUrl: string;
elasticAgentVersion: string;
}

export function CommandSnippet({
encodedApiKey,
onboardingId,
elasticsearchUrl,
elasticAgentVersion,
}: Props) {
const command = buildKubectlCommand({
encodedApiKey,
onboardingId,
elasticsearchUrl,
elasticAgentVersion,
});

return (
<>
<EuiText>
<p>
<FormattedMessage
id="xpack.observability_onboarding.kubernetesPanel.installElasticAgentDescription"
defaultMessage="Copy and run the install command. Note that the following manifest contains resource limits that may not be appropriate for a production environment, review our guide on {scalingLink} before deploying this manifest."
values={{
scalingLink: (
<EuiLink
data-test-subj="observabilityOnboardingKubernetesPanelScalingElasticAgentOnKubernetesLink"
href="https://www.elastic.co/guide/en/fleet/current/scaling-on-kubernetes.html"
external
target="_blank"
>
{i18n.translate(
'xpack.observability_onboarding.kubernetesPanel.scalingElasticAgentOnLinkLabel',
{ defaultMessage: 'Scaling Elastic Agent on Kubernetes' }
)}
</EuiLink>
),
}}
/>
</p>
</EuiText>

<EuiSpacer />

<EuiCodeBlock language="text" paddingSize="m" fontSize="m">
{command}
</EuiCodeBlock>

<EuiSpacer />

<CopyToClipboardButton textToCopy={command} fill />
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useEffect, useState } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiImage, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
import { FormattedMessage } from '@kbn/i18n-react';
import {
StepsProgress,
useFlowProgressTelemetry,
} from '../../../hooks/use_flow_progress_telemetry';
import { ObservabilityOnboardingContextValue } from '../../../plugin';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
import { ProgressIndicator } from '../shared/progress_indicator';

interface Props {
onboardingId: string;
}

const FETCH_INTERVAL = 2000;
const SHOW_TROUBLESHOOTING_DELAY = 120000; // 2 minutes
const CLUSTER_OVERVIEW_DASHBOARD_ID = 'kubernetes-f4dc26db-1b53-4ea2-a78b-1bfab8ea267c';

export function DataIngestStatus({ onboardingId }: Props) {
const {
services: { share, http },
} = useKibana<ObservabilityOnboardingContextValue>();
const [progress, setProgress] = useState<StepsProgress | undefined>(undefined);
const [checkDataStartTime] = useState(Date.now());

const dashboardLocator = share.url.locators.get(DASHBOARD_APP_LOCATOR);

const { data, status, refetch } = useFetcher(
(callApi) => {
return callApi('GET /internal/observability_onboarding/kubernetes/{onboardingId}/has-data', {
params: { path: { onboardingId } },
});
},
[onboardingId]
);

useEffect(() => {
const pendingStatusList = [FETCH_STATUS.LOADING, FETCH_STATUS.NOT_INITIATED];

if (pendingStatusList.includes(status) || data?.hasData === true) {
return;
}

const timeout = setTimeout(() => {
refetch();
}, FETCH_INTERVAL);

return () => clearTimeout(timeout);
}, [data?.hasData, refetch, status]);

useEffect(() => {
if (data?.hasData === true) {
setProgress({ 'logs-ingest': { status: 'complete' } });
}
}, [data?.hasData]);

useFlowProgressTelemetry(progress, onboardingId);

const isTroubleshootingVisible =
data?.hasData === false && Date.now() - checkDataStartTime > SHOW_TROUBLESHOOTING_DELAY;

return (
<>
<ProgressIndicator
title={data?.hasData ? 'We are monitoring your cluster' : 'Waiting for data to be shipped'}
iconType="checkInCircleFilled"
isLoading={!data?.hasData}
css={css`
max-width: 40%;
`}
/>

{isTroubleshootingVisible && (
<>
<EuiSpacer />
<EuiText color="subdued" size="s">
<FormattedMessage
id="xpack.observability_onboarding.dataIngestStatus.troubleshootingTextLabel"
defaultMessage="Find more details and troubleshooting solutions in our documentation. {troubleshootingLink}"
values={{
troubleshootingLink: (
<EuiLink
data-test-subj="observabilityOnboardingDataIngestStatusTroubleshootingLink"
href="https://www.elastic.co/guide/en/fleet/current/fleet-troubleshooting.html#agent-oom-k8s"
external
target="_blank"
>
{i18n.translate(
'xpack.observability_onboarding.dataIngestStatus.troubleshootingLinkText',
{
defaultMessage: 'Open documentation',
}
)}
</EuiLink>
),
}}
/>
</EuiText>
</>
)}

{data?.hasData === true && (
<>
<EuiSpacer />

<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiImage
src={http.staticAssets.getPluginAssetHref('waterfall_screen.svg')}
width={162}
height={117}
alt=""
hasShadow
/>
</EuiFlexItem>

<EuiFlexItem>
<EuiText>
<p>
{i18n.translate(
'xpack.observability_onboarding.kubernetesPanel.monitoringCluster',
{
defaultMessage:
'Overview your Kubernetes cluster with this pre-made dashboard',
}
)}
</p>
</EuiText>
<EuiSpacer size="xs" />
<EuiLink
data-test-subj="observabilityOnboardingDataIngestStatusViewDashboardLink"
href={dashboardLocator?.getRedirectUrl({
dashboardId: CLUSTER_OVERVIEW_DASHBOARD_ID,
})}
>
{i18n.translate('xpack.observability_onboarding.kubernetesPanel.exploreDashboard', {
defaultMessage: 'Explore Kubernetes cluster',
})}
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>

<EuiSpacer />

<EuiText color="subdued" size="s">
<FormattedMessage
id="xpack.observability_onboarding.dataIngestStatus.findAllPremadeAssetsTextLabel"
defaultMessage="Find all pre-made assets ready to use {viewAllAssetsLink}"
values={{
viewAllAssetsLink: (
<EuiLink
data-test-subj="observabilityOnboardingDataIngestStatusViewAllAssetsLink"
href={`${http.basePath.get()}/app/integrations/detail/kubernetes/assets`}
>
{i18n.translate(
'xpack.observability_onboarding.dataIngestStatus.viewAllAssetsLinkText',
{
defaultMessage: 'View all assets',
}
)}
</EuiLink>
),
}}
/>
</EuiText>
</>
)}
</>
);
}
Loading

0 comments on commit b74f3cb

Please sign in to comment.