timeout |
string |
@@ -3145,40 +3138,6 @@ or 'm' (minutes).
-### UIPlugin.spec.troubleshootingPanel.korrel8r
-[↩ Parent](#uipluginspectroubleshootingpanel)
-
-
-
-korrel8r defines the Korrel8r instance that the troubleshooting panel plugin will connect to
-
-
-
-
- Name |
- Type |
- Description |
- Required |
-
-
-
- name |
- string |
-
- Name of the korrel8r instance
- |
- false |
-
- namespace |
- string |
-
- Namespace of the korrel8r instance
- |
- false |
-
-
-
-
### UIPlugin.status
[↩ Parent](#uiplugin)
diff --git a/docs/user-guides/observability-ui-plugins.md b/docs/user-guides/observability-ui-plugins.md
index 02719fc40..78edc233a 100644
--- a/docs/user-guides/observability-ui-plugins.md
+++ b/docs/user-guides/observability-ui-plugins.md
@@ -25,7 +25,7 @@ spec:
### Troubleshooting Panel
-The plugin will connect to a Korrel8r instance named `korrel8r` in the `korrel8r` namespace. A "Troubleshooting Panel" button is added to the alerts page, which will convert the current alert into a Korrel8r query, then retrieve related neighbors and display them in a topology view.
+The plugin will connect to a Korrel8r service named `korrel8r` in the namespace where the observability operator(and Korrel8r) is deployed. A "Troubleshooting Panel" button is added to the alerts page, which will convert the current alert into a Korrel8r query, then retrieve related neighbors and display them in a topology view.
To enable the troubleshooting panel console plugin, create a `UIPlugin` CR. The following example shows how to create a CR to enable the troubleshooting panel console plugin:
diff --git a/pkg/apis/uiplugin/v1alpha1/types.go b/pkg/apis/uiplugin/v1alpha1/types.go
index 15220d954..9736fdf8f 100644
--- a/pkg/apis/uiplugin/v1alpha1/types.go
+++ b/pkg/apis/uiplugin/v1alpha1/types.go
@@ -75,25 +75,6 @@ type TroubleshootingPanelConfig struct {
// +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="OCP Console Query Timeout",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:ocpConsoleTimeout"}
// +kubebuilder:validation:Pattern:="^([0-9]+)([sm]{1})$"
Timeout string `json:"timeout,omitempty"`
- // korrel8r defines the Korrel8r instance that the troubleshooting panel plugin will connect to
- //
- // +optional
- // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Korrel8r Instance"
- Korrel8r TroubleshootingPanelKorrel8rConfig `json:"korrel8r,omitempty"`
-}
-
-type TroubleshootingPanelKorrel8rConfig struct {
- // Name of the korrel8r instance
- //
- // +optional
- // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Korrel8r Instance Name"
- Name string `json:"name,omitempty"`
-
- // Namespace of the korrel8r instance
- //
- // +optional
- // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Korrel8r Instance Namespace"
- Namespace string `json:"namespace,omitempty"`
}
// DistributedTracingConfig contains options for configuring the Distributed Tracing plugin
diff --git a/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go
index 9ca85326d..c72a7c3dc 100644
--- a/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go
@@ -119,7 +119,6 @@ func (in *LokiStackReference) DeepCopy() *LokiStackReference {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TroubleshootingPanelConfig) DeepCopyInto(out *TroubleshootingPanelConfig) {
*out = *in
- out.Korrel8r = in.Korrel8r
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TroubleshootingPanelConfig.
@@ -132,21 +131,6 @@ func (in *TroubleshootingPanelConfig) DeepCopy() *TroubleshootingPanelConfig {
return out
}
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *TroubleshootingPanelKorrel8rConfig) DeepCopyInto(out *TroubleshootingPanelKorrel8rConfig) {
- *out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TroubleshootingPanelKorrel8rConfig.
-func (in *TroubleshootingPanelKorrel8rConfig) DeepCopy() *TroubleshootingPanelKorrel8rConfig {
- if in == nil {
- return nil
- }
- out := new(TroubleshootingPanelKorrel8rConfig)
- in.DeepCopyInto(out)
- return out
-}
-
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UIPlugin) DeepCopyInto(out *UIPlugin) {
*out = *in
diff --git a/pkg/controllers/uiplugin/components.go b/pkg/controllers/uiplugin/components.go
index 1285acf64..8e8d0c75b 100644
--- a/pkg/controllers/uiplugin/components.go
+++ b/pkg/controllers/uiplugin/components.go
@@ -1,9 +1,13 @@
package uiplugin
import (
+ "bytes"
+ "embed"
"fmt"
"hash/fnv"
+ "io"
"sort"
+ "text/template"
osv1alpha1 "github.com/openshift/api/console/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
@@ -18,9 +22,13 @@ import (
)
const (
- port = 9443
- serviceAccountSuffix = "-sa"
- servingCertVolumeName = "serving-cert"
+ port = 9443
+ serviceAccountSuffix = "-sa"
+ servingCertVolumeName = "serving-cert"
+ Korrel8rConfigFileName = "korrel8r.yaml"
+ Korrel8rConfigMountDir = "/config/"
+ OpenshiftLoggingNs = "openshift-logging"
+ OpenshiftNetobservNs = "netobserv"
annotationPrefix = "observability.openshift.io/ui-plugin-"
)
@@ -31,6 +39,9 @@ var (
}
hashSeparator = []byte("\n")
+
+ //go:embed config/korrel8r.yaml
+ korrel8rConfigYAMLTmplFile embed.FS
)
func pluginComponentReconcilers(plugin *uiv1alpha1.UIPlugin, pluginInfo UIPluginInfo) []reconciler.Reconciler {
@@ -67,6 +78,16 @@ func pluginComponentReconcilers(plugin *uiv1alpha1.UIPlugin, pluginInfo UIPlugin
}
}
+ if pluginInfo.Korrel8rImage != "" {
+ kname := "korrel8r"
+ components = append(components, reconciler.NewUpdater(newKorrel8rService(kname, namespace), plugin))
+ korrel8rCm, err := newKorrel8rConfigMap(kname, namespace, pluginInfo)
+ if err == nil && korrel8rCm != nil {
+ components = append(components, reconciler.NewUpdater(korrel8rCm, plugin))
+ components = append(components, reconciler.NewUpdater(newKorrel8rDeployment(kname, namespace, pluginInfo), plugin))
+ }
+ }
+
return components
}
@@ -295,6 +316,165 @@ func newService(info UIPluginInfo, namespace string) *corev1.Service {
}
}
+func newKorrel8rDeployment(name string, namespace string, info UIPluginInfo) *appsv1.Deployment {
+ volumes := []corev1.Volume{
+ {
+ Name: servingCertVolumeName,
+ VolumeSource: corev1.VolumeSource{
+ Secret: &corev1.SecretVolumeSource{
+ SecretName: name,
+ DefaultMode: ptr.To(int32(420)),
+ },
+ },
+ },
+ }
+ volumeMounts := []corev1.VolumeMount{
+ {
+ Name: servingCertVolumeName,
+ ReadOnly: true,
+ MountPath: "/secrets/",
+ },
+ }
+
+ volumes = append(volumes, corev1.Volume{
+ Name: "korrel8r-config",
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: name,
+ },
+ },
+ },
+ })
+ volumeMounts = append(volumeMounts, corev1.VolumeMount{
+ Name: "korrel8r-config",
+ ReadOnly: true,
+ MountPath: Korrel8rConfigMountDir,
+ })
+
+ deploy := &appsv1.Deployment{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: appsv1.SchemeGroupVersion.String(),
+ Kind: "Deployment",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ Labels: componentLabels(name),
+ },
+ Spec: appsv1.DeploymentSpec{
+ Selector: &metav1.LabelSelector{
+ MatchLabels: componentLabels(name),
+ },
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ Labels: componentLabels(name),
+ },
+ Spec: corev1.PodSpec{
+ ServiceAccountName: info.Name + serviceAccountSuffix,
+ Containers: []corev1.Container{
+ {
+ Name: name,
+ Image: info.Korrel8rImage,
+ Command: []string{"korrel8r", "web", "--https=:8443", "--cert=/secrets/tls.crt", "--key=/secrets/tls.key", "--config=/config/korrel8r.yaml"},
+ Ports: []corev1.ContainerPort{
+ {
+ ContainerPort: 8443,
+ Protocol: corev1.ProtocolTCP,
+ },
+ },
+ SecurityContext: &corev1.SecurityContext{
+ RunAsNonRoot: ptr.To(true),
+ AllowPrivilegeEscalation: ptr.To(false),
+ Capabilities: &corev1.Capabilities{
+ Drop: []corev1.Capability{
+ "ALL",
+ },
+ },
+ SeccompProfile: &corev1.SeccompProfile{
+ Type: corev1.SeccompProfileTypeRuntimeDefault,
+ },
+ },
+ VolumeMounts: volumeMounts,
+ },
+ },
+ Volumes: volumes,
+ },
+ },
+ },
+ }
+ return deploy
+}
+
+func newKorrel8rService(name string, namespace string) *corev1.Service {
+ annotations := map[string]string{
+ "service.beta.openshift.io/serving-cert-secret-name": name,
+ }
+
+ return &corev1.Service{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: corev1.SchemeGroupVersion.String(),
+ Kind: "Service",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ Labels: componentLabels(name),
+ Annotations: annotations,
+ },
+ Spec: corev1.ServiceSpec{
+ Ports: []corev1.ServicePort{
+ {
+ Port: port,
+ Name: "web",
+ Protocol: corev1.ProtocolTCP,
+ TargetPort: intstr.FromInt32(port),
+ },
+ },
+ Selector: componentLabels(name),
+ Type: corev1.ServiceTypeClusterIP,
+ },
+ }
+}
+
+func newKorrel8rConfigMap(name string, namespace string, info UIPluginInfo) (*corev1.ConfigMap, error) {
+
+ korrel8rData := map[string]string{"Metric": "thanos-querier", "MetricAlert": "alertmanager-main", "Log": "logging-loki-gateway-http", "Netflow": "loki-gateway-http", "MonitoringNs": "openshift-monitoring", "LoggingNs": OpenshiftLoggingNs, "NetobservNs": OpenshiftNetobservNs}
+
+ if info.LokiServiceNames[OpenshiftLoggingNs] != "" {
+ korrel8rData["LoggingNs"] = info.LokiServiceNames[OpenshiftLoggingNs]
+ }
+ if info.LokiServiceNames[OpenshiftNetobservNs] != "" {
+ korrel8rData["NetobservNs"] = info.LokiServiceNames[OpenshiftNetobservNs]
+ }
+
+ var korrel8rConfigYAMLTmpl = template.Must(template.ParseFS(korrel8rConfigYAMLTmplFile, "config/korrel8r.yaml"))
+ w := bytes.NewBuffer(nil)
+ err := korrel8rConfigYAMLTmpl.Execute(w, korrel8rData)
+ if err != nil {
+ return nil, err
+ }
+
+ cfg, _ := io.ReadAll(w)
+
+ return &corev1.ConfigMap{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ConfigMap",
+ APIVersion: corev1.SchemeGroupVersion.String(),
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ Labels: componentLabels(name),
+ },
+ Data: map[string]string{
+ Korrel8rConfigFileName: string(cfg),
+ },
+ }, nil
+}
+
func componentLabels(pluginName string) map[string]string {
return map[string]string{
"app.kubernetes.io/instance": pluginName,
diff --git a/pkg/controllers/uiplugin/config/korrel8r.yaml b/pkg/controllers/uiplugin/config/korrel8r.yaml
new file mode 100644
index 000000000..d6b971e4d
--- /dev/null
+++ b/pkg/controllers/uiplugin/config/korrel8r.yaml
@@ -0,0 +1,20 @@
+# Default configuration for deploying Korrel8r as a service in an OpenShift cluster.
+# Store service URLs assume that stores are installed in their default locations.
+stores:
+ - domain: k8s
+ - domain: alert
+ metrics: https://{{ .Metric }}.{{ .MonitoringNs }}.svc:9091
+ alertmanager: https://{{ .MetricAlert }}.{{ .MonitoringNs }}.svc:9094
+ certificateAuthority: ./run/secrets/kubernetes.io/serviceaccount/service-ca.crt
+ - domain: log
+ lokiStack: https://{{ .Log }}.{{ .LoggingNs }}.svc:8080
+ certificateAuthority: ./run/secrets/kubernetes.io/serviceaccount/service-ca.crt
+ - domain: metric
+ metric: https://{{ .Metric }}.{{ .MonitoringNs }}.svc:9091
+ certificateAuthority: ./run/secrets/kubernetes.io/serviceaccount/service-ca.crt
+ - domain: netflow
+ lokiStack: https://{{ .Netflow }}.{{ .NetobservNs }}.svc:8080
+ certificateAuthority: ./run/secrets/kubernetes.io/serviceaccount/service-ca.crt
+
+include:
+ - /etc/korrel8r/rules/all.yaml
\ No newline at end of file
diff --git a/pkg/controllers/uiplugin/controller.go b/pkg/controllers/uiplugin/controller.go
index 99366adac..229cc94d1 100644
--- a/pkg/controllers/uiplugin/controller.go
+++ b/pkg/controllers/uiplugin/controller.go
@@ -154,7 +154,7 @@ func (rm resourceManager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
return ctrl.Result{}, nil
}
- pluginInfo, err := PluginInfoBuilder(plugin, rm.pluginConf, rm.clusterVersion)
+ pluginInfo, err := PluginInfoBuilder(ctx, rm.k8sClient, plugin, rm.pluginConf, rm.clusterVersion)
if err != nil {
logger.Error(err, "failed to reconcile plugin")
diff --git a/pkg/controllers/uiplugin/plugin_info_builder.go b/pkg/controllers/uiplugin/plugin_info_builder.go
index 564e6479a..76cfaccc3 100644
--- a/pkg/controllers/uiplugin/plugin_info_builder.go
+++ b/pkg/controllers/uiplugin/plugin_info_builder.go
@@ -1,18 +1,22 @@
package uiplugin
import (
+ "context"
"fmt"
osv1alpha1 "github.com/openshift/api/console/v1alpha1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "sigs.k8s.io/controller-runtime/pkg/client"
uiv1alpha1 "github.com/rhobs/observability-operator/pkg/apis/uiplugin/v1alpha1"
)
type UIPluginInfo struct {
Image string
+ Korrel8rImage string
+ LokiServiceNames map[string]string
Name string
ConsoleName string
DisplayName string
@@ -26,7 +30,7 @@ type UIPluginInfo struct {
ResourceNamespace string
}
-func PluginInfoBuilder(plugin *uiv1alpha1.UIPlugin, pluginConf UIPluginsConfiguration, clusterVersion string) (*UIPluginInfo, error) {
+func PluginInfoBuilder(ctx context.Context, k client.Client, plugin *uiv1alpha1.UIPlugin, pluginConf UIPluginsConfiguration, clusterVersion string) (*UIPluginInfo, error) {
compatibilityInfo, err := lookupImageAndFeatures(plugin.Spec.Type, clusterVersion)
if err != nil {
return nil, err
@@ -108,7 +112,17 @@ func PluginInfoBuilder(plugin *uiv1alpha1.UIPlugin, pluginConf UIPluginsConfigur
return pluginInfo, nil
}
case uiv1alpha1.TypeTroubleshootingPanel:
- return createTroubleshootingPanelPluginInfo(plugin, namespace, plugin.Name, image, []string{})
+ {
+ pluginInfo, err := createTroubleshootingPanelPluginInfo(plugin, namespace, plugin.Name, image, []string{})
+ if err == nil {
+ pluginInfo.Korrel8rImage = pluginConf.Images["korrel8r"]
+ pluginInfo.LokiServiceNames[OpenshiftLoggingNs], err = getLokiServiceName(ctx, k, OpenshiftLoggingNs)
+ }
+ if err == nil {
+ pluginInfo.LokiServiceNames[OpenshiftNetobservNs], err = getLokiServiceName(ctx, k, OpenshiftNetobservNs)
+ }
+ return pluginInfo, err
+ }
case uiv1alpha1.TypeDistributedTracing:
return createDistributedTracingPluginInfo(plugin, namespace, plugin.Name, image, []string{})
case uiv1alpha1.TypeLogging:
diff --git a/pkg/controllers/uiplugin/troubleshooting_panel.go b/pkg/controllers/uiplugin/troubleshooting_panel.go
index 5a27e5504..28931026b 100644
--- a/pkg/controllers/uiplugin/troubleshooting_panel.go
+++ b/pkg/controllers/uiplugin/troubleshooting_panel.go
@@ -2,6 +2,7 @@ package uiplugin
import (
"bytes"
+ "context"
"fmt"
"strings"
@@ -9,12 +10,14 @@ import (
"gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "sigs.k8s.io/controller-runtime/pkg/client"
uiv1alpha1 "github.com/rhobs/observability-operator/pkg/apis/uiplugin/v1alpha1"
)
func createTroubleshootingPanelPluginInfo(plugin *uiv1alpha1.UIPlugin, namespace, name, image string, features []string) (*UIPluginInfo, error) {
troubleshootingPanelConfig := plugin.Spec.TroubleshootingPanel
+ korrel8rSvcName := "korrel8r"
configYaml, err := marshalTroubleshootingPanelPluginConfig(troubleshootingPanelConfig)
if err != nil {
@@ -29,23 +32,13 @@ func createTroubleshootingPanelPluginInfo(plugin *uiv1alpha1.UIPlugin, namespace
extraArgs = append(extraArgs, fmt.Sprintf("-features=%s", strings.Join(features, ",")))
}
- proxyName, proxyNamespace := "korrel8r", "korrel8r"
-
- if plugin.Spec.TroubleshootingPanel != nil {
- if plugin.Spec.TroubleshootingPanel.Korrel8r.Name != "" {
- proxyName = plugin.Spec.TroubleshootingPanel.Korrel8r.Name
- }
- if plugin.Spec.TroubleshootingPanel.Korrel8r.Namespace != "" {
- proxyNamespace = plugin.Spec.TroubleshootingPanel.Korrel8r.Namespace
- }
- }
-
pluginInfo := &UIPluginInfo{
Image: image,
Name: plugin.Name,
ConsoleName: "troubleshooting-panel-console-plugin",
DisplayName: "Troubleshooting Panel Console Plugin",
ResourceNamespace: namespace,
+ LokiServiceNames: make(map[string]string),
ExtraArgs: extraArgs,
Proxies: []osv1alpha1.ConsolePluginProxy{
{
@@ -53,9 +46,9 @@ func createTroubleshootingPanelPluginInfo(plugin *uiv1alpha1.UIPlugin, namespace
Alias: "korrel8r",
Authorize: false,
Service: osv1alpha1.ConsolePluginProxyServiceConfig{
- Name: proxyName,
- Namespace: proxyNamespace,
- Port: 8443,
+ Name: korrel8rSvcName,
+ Namespace: namespace,
+ Port: 9443,
},
},
},
@@ -99,3 +92,23 @@ func marshalTroubleshootingPanelPluginConfig(cfg *uiv1alpha1.TroubleshootingPane
return buf.String(), nil
}
+
+func getLokiServiceName(ctx context.Context, k client.Client, ns string) (string, error) {
+
+ serviceList := &corev1.ServiceList{}
+ if err := k.List(ctx, serviceList, client.InNamespace(ns)); err != nil {
+ return "", err
+ }
+
+ // Accumulate services that contain "gateway" in their names
+ var gatewayServices []corev1.Service
+ for _, service := range serviceList.Items {
+ if strings.Contains(service.Name, "gateway") {
+ gatewayServices = append(gatewayServices, service)
+ }
+ }
+ if len(gatewayServices) > 0 {
+ return gatewayServices[0].Name, nil
+ }
+ return "", nil
+}
|