From bb6e78ebbf8304c7be34345ed215674dc509632a Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Tue, 8 Oct 2024 19:13:04 +0100 Subject: [PATCH] [Observability] Unified Kubernetes Observability with OpenTelemetry (#194169) --- .../observability_onboarding_flow.tsx | 4 + .../public/application/pages/index.ts | 1 + .../application/pages/otel_kubernetes.tsx | 36 ++ .../kubernetes/use_kubernetes_flow.ts | 18 +- .../otel_kubernetes/index.tsx | 8 + .../otel_kubernetes/otel_kubernetes_panel.tsx | 331 ++++++++++++++++++ .../quickstart_flows/shared/empty_prompt.tsx | 2 +- .../shared/get_started_panel.tsx | 55 +-- .../server/routes/kubernetes/route.ts | 12 +- 9 files changed, 434 insertions(+), 33 deletions(-) create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/otel_kubernetes.tsx create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/index.tsx create mode 100644 x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/otel_kubernetes_panel.tsx diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx index 95b31aab24966..348b3c65f9371 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx @@ -16,6 +16,7 @@ import { KubernetesPage, LandingPage, OtelLogsPage, + OtelKubernetesPage, SystemLogsPage, FirehosePage, } from './pages'; @@ -50,6 +51,9 @@ export function ObservabilityOnboardingFlow() { + + + diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/index.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/index.ts index cd4b821f3cc0d..7e5606205b607 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/index.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/index.ts @@ -8,6 +8,7 @@ export { AutoDetectPage } from './auto_detect'; export { CustomLogsPage } from './custom_logs'; export { KubernetesPage } from './kubernetes'; +export { OtelKubernetesPage } from './otel_kubernetes'; export { LandingPage } from './landing'; export { OtelLogsPage } from './otel_logs'; export { SystemLogsPage } from './system_logs'; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/otel_kubernetes.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/otel_kubernetes.tsx new file mode 100644 index 0000000000000..c4fba1fd8ff0e --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/otel_kubernetes.tsx @@ -0,0 +1,36 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React from 'react'; +import { OtelKubernetesPanel } from '../quickstart_flows/otel_kubernetes/otel_kubernetes_panel'; +import { PageTemplate } from './template'; +import { CustomHeader } from '../header'; + +export const OtelKubernetesPage = () => ( + + } + > + + +); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/kubernetes/use_kubernetes_flow.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/kubernetes/use_kubernetes_flow.ts index e0a8c7722290f..4e8a54ccd77e7 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/kubernetes/use_kubernetes_flow.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/kubernetes/use_kubernetes_flow.ts @@ -11,7 +11,9 @@ import { OBSERVABILITY_ONBOARDING_FLOW_PROGRESS_TELEMETRY_EVENT } from '../../.. import { ObservabilityOnboardingAppServices } from '../../..'; import { useFetcher } from '../../../hooks/use_fetcher'; -export function useKubernetesFlow() { +export function useKubernetesFlow( + onboardingFlowType: 'kubernetes_otel' | 'kubernetes' = 'kubernetes' +) { const { services: { analytics, @@ -20,21 +22,27 @@ export function useKubernetesFlow() { } = useKibana(); const { data, status, error, refetch } = useFetcher( (callApi) => { - return callApi('POST /internal/observability_onboarding/kubernetes/flow'); + return callApi('POST /internal/observability_onboarding/kubernetes/flow', { + params: { + body: { + pkgName: onboardingFlowType, + }, + }, + }); }, - [], + [onboardingFlowType], { showToastOnError: false } ); useEffect(() => { if (data?.onboardingId !== undefined) { analytics?.reportEvent(OBSERVABILITY_ONBOARDING_FLOW_PROGRESS_TELEMETRY_EVENT.eventType, { - onboardingFlowType: 'kubernetes', + onboardingFlowType, onboardingId: data?.onboardingId, step: 'in_progress', }); } - }, [analytics, cloudServiceProvider, data?.onboardingId]); + }, [onboardingFlowType, analytics, cloudServiceProvider, data?.onboardingId]); return { data, status, error, refetch } as const; } diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/index.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/index.tsx new file mode 100644 index 0000000000000..596a035d4b4d1 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/index.tsx @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { OtelKubernetesPanel } from './otel_kubernetes_panel'; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/otel_kubernetes_panel.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/otel_kubernetes_panel.tsx new file mode 100644 index 0000000000000..c745793c47b3a --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_kubernetes/otel_kubernetes_panel.tsx @@ -0,0 +1,331 @@ +/* + * 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, { useState } from 'react'; +import { + EuiPanel, + EuiSkeletonText, + EuiSpacer, + EuiSteps, + EuiButtonGroup, + EuiIconTip, + EuiCodeBlock, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics'; +import { EmptyPrompt } from '../shared/empty_prompt'; +import { GetStartedPanel } from '../shared/get_started_panel'; +import { FeedbackButtons } from '../shared/feedback_buttons'; +import { CopyToClipboardButton } from '../shared/copy_to_clipboard_button'; +import { ObservabilityOnboardingContextValue } from '../../../plugin'; +import { useKubernetesFlow } from '../kubernetes/use_kubernetes_flow'; + +const CLUSTER_OVERVIEW_DASHBOARD_ID = 'kubernetes_otel-cluster-overview'; + +export const OtelKubernetesPanel: React.FC = () => { + const { data, error, refetch } = useKubernetesFlow('kubernetes_otel'); + const [idSelected, setIdSelected] = useState('nodejs'); + const { + services: { share }, + } = useKibana(); + const apmLocator = share.url.locators.get('APM_LOCATOR'); + const dashboardLocator = share.url.locators.get(DASHBOARD_APP_LOCATOR); + + if (error) { + return ( + + ); + } + + const namespace = 'opentelemetry-operator-system'; + const valuesFile = + 'https://raw.githubusercontent.com/elastic/opentelemetry/refs/heads/main/resources/kubernetes/operator/helm/values.yaml'; + + const addRepoCommand = `helm repo add open-telemetry 'https://open-telemetry.github.io/opentelemetry-helm-charts' --force-update`; + const installStackCommand = data + ? `kubectl create namespace ${namespace} +kubectl create secret generic elastic-secret-otel \\ + --namespace ${namespace} \\ + --from-literal=elastic_endpoint='${data.elasticsearchUrl}' \\ + --from-literal=elastic_api_key='${data.apiKeyEncoded}' +helm install opentelemetry-kube-stack open-telemetry/opentelemetry-kube-stack \\ + --namespace ${namespace} \\ + --create-namespace \\ + --values '${valuesFile}'` + : undefined; + + return ( + + + + {addRepoCommand} + + + + + ), + }, + { + title: i18n.translate( + 'xpack.observability_onboarding.otelKubernetesPanel.installStackStepTitle', + { + defaultMessage: 'Install the OpenTelemetry Operator', + } + ), + children: installStackCommand ? ( + <> +

+ + {i18n.translate( + 'xpack.observability_onboarding.otelKubernetesPanel.certmanagerLinkLabel', + { defaultMessage: 'cert-manager' } + )} + + ), + }} + />{' '} + +

+ + + {installStackCommand} + + + + + + + + + {i18n.translate( + 'xpack.observability_onboarding.otelKubernetesPanel.downloadValuesFileButtonEmptyLabel', + { defaultMessage: 'Download values file' } + )} + + + + + ) : ( + + ), + }, + { + title: i18n.translate( + 'xpack.observability_onboarding.otelKubernetesPanel.instrumentApplicationStepTitle', + { + defaultMessage: 'Instrument your application (optional)', + } + ), + children: ( + <> +

+ {i18n.translate( + 'xpack.observability_onboarding.otelKubernetesPanel.theOperatorAutomatesTheLabel', + { + defaultMessage: + 'The Operator automates the injection of auto-instrumentation libraries into the annotated pods for some languages.', + } + )} +

+ + setIdSelected(optionId)} + options={[ + { + id: 'nodejs', + label: 'Node.js', + }, + { + id: 'java', + label: 'Java', + }, + { + id: 'python', + label: 'Python', + }, + { + id: 'dotnet', + label: '.NET', + }, + { + id: 'go', + label: 'Go', + }, + ]} + /> + + + {`apiVersion: v1 +kind: Pod +metadata: + name: my-app + annotations: + instrumentation.opentelemetry.io/inject-${idSelected}: "true" +spec: + containers: + - name: my-app + image: my-app:latest`} + + + + +

+ + {i18n.translate( + 'xpack.observability_onboarding.otelKubernetesPanel.referToTheDocumentationLinkLabel', + { defaultMessage: 'refer to the documentation' } + )} + + ), + }} + /> +

+ + ), + }, + { + title: i18n.translate( + 'xpack.observability_onboarding.otelKubernetesPanel.monitorStepTitle', + { + defaultMessage: 'Visualize your data', + } + ), + children: data ? ( + <> +

+ {i18n.translate( + 'xpack.observability_onboarding.otelKubernetesPanel.onceYourKubernetesInfrastructureLabel', + { + defaultMessage: + 'Analyse your Kubernetes cluster’s health and monitor your container workloads.', + } + )} +

+ + + + ) : ( + + ), + }, + ]} + /> + +
+ ); +}; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/empty_prompt.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/empty_prompt.tsx index b5b427a718d67..771948f062fcf 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/empty_prompt.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/empty_prompt.tsx @@ -35,7 +35,7 @@ export const EmptyPrompt: FunctionComponent = ({ useEffect(() => { analytics?.reportEvent(OBSERVABILITY_ONBOARDING_FLOW_ERROR_TELEMETRY_EVENT.eventType, { onboardingFlowType, - error, + error: error.body?.message ?? error.message, context: telemetryEventContext, }); }, [analytics, error, onboardingFlowType, telemetryEventContext]); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/get_started_panel.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/get_started_panel.tsx index 98fb6c74a37de..e529a0782e395 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/get_started_panel.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/get_started_panel.tsx @@ -37,7 +37,7 @@ export function GetStartedPanel({ }: { onboardingFlowType: string; dataset: string; - integration: string; + integration?: string; newTab: boolean; actionLinks: Array<{ id: string; @@ -88,7 +88,7 @@ export function GetStartedPanel({ - + {actionLinks.map(({ id, title, label, href }) => ( @@ -110,30 +110,33 @@ export function GetStartedPanel({ - - - - - {i18n.translate( - 'xpack.observability_onboarding.dataIngestStatus.viewAllAssetsLinkText', - { - defaultMessage: 'View all assets', - } - )} - - ), - }} - /> - + {integration && ( + <> + + + + {i18n.translate( + 'xpack.observability_onboarding.dataIngestStatus.viewAllAssetsLinkText', + { + defaultMessage: 'View all assets', + } + )} + + ), + }} + /> + + + )} ); } diff --git a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/kubernetes/route.ts b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/kubernetes/route.ts index 7f63ec61249f5..33a501bd184b9 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/kubernetes/route.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/kubernetes/route.ts @@ -29,10 +29,14 @@ export interface HasKubernetesDataRouteResponse { const createKubernetesOnboardingFlowRoute = createObservabilityOnboardingServerRoute({ endpoint: 'POST /internal/observability_onboarding/kubernetes/flow', + params: t.type({ + body: t.type({ pkgName: t.union([t.literal('kubernetes'), t.literal('kubernetes_otel')]) }), + }), options: { tags: [] }, async handler({ context, request, + params, plugins, services, kibanaVersion, @@ -55,8 +59,14 @@ const createKubernetesOnboardingFlowRoute = createObservabilityOnboardingServerR const [{ encoded: apiKeyEncoded }, elasticAgentVersion] = await Promise.all([ createShipperApiKey(client.asCurrentUser, 'kubernetes_onboarding'), getAgentVersion(fleetPluginStart, kibanaVersion), - packageClient.ensureInstalledPackage({ pkgName: 'kubernetes' }), + // System package is always required packageClient.ensureInstalledPackage({ pkgName: 'system' }), + // Kubernetes package is required for both classic kubernetes and otel + packageClient.ensureInstalledPackage({ pkgName: 'kubernetes' }), + // Kubernetes otel package is required only for otel + params.body.pkgName === 'kubernetes_otel' + ? packageClient.ensureInstalledPackage({ pkgName: 'kubernetes_otel' }) + : undefined, ]); const elasticsearchUrlList = plugins.cloud?.setup?.elasticsearchUrl