Skip to content

Commit

Permalink
feat: added functionality for emitting results into the k8sgpt-operat…
Browse files Browse the repository at this point in the history
…or log (#589)

Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
  • Loading branch information
AlexsJones authored Jan 27, 2025
1 parent d2b341b commit 9826bff
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 24 deletions.
98 changes: 98 additions & 0 deletions config/samples/exhaustive_sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#metadata: Specifies the name and namespace of the custom resource.
#spec.ai: Defines AI-related configurations, including backend, models, and optional parameters like anonymized data and retries.
#spec.customAnalyzers: Configures additional custom analyzers, with URLs and ports for connections.
#spec.extraOptions: Includes extra configurations like enabling Backstage integration.
#spec.filters: Sets up filters for resource analysis.
#spec.imagePullSecrets: References secrets for pulling images from private registries.
#spec.integrations: Configures integrations such as Trivy.
#spec.kubeconfig: Specifies a custom kubeconfig secret, if needed.
#spec.noCache: Indicates whether caching is disabled.
#spec.nodeSelector: Allows pod scheduling constraints based on node labels.
#spec.remoteCache: Configures remote caching options like Azure, GCS, or S3.
#spec.repository: Specifies the container image repository.
#spec.sink: Configures notification sinks, e.g., Slack, with webhook and authentication details.
#spec.targetNamespace: Target namespace for the resource.
#spec.version: Version of K8sGPT to use.
#status: Placeholder for status, typically managed by the operator.
apiVersion: core.k8sgpt.ai/v1alpha1
kind: K8sGPT
metadata:
name: example-k8sgpt
namespace: default
spec:
ai:
anonymized: false
backOff:
enabled: true
maxRetries: 10
backend: openai
baseUrl: "https://api.openai.com"
enabled: true
engine: "davinci"
language: "english"
maxTokens: "4096"
model: "gpt-4"
providerId: "provider-123"
proxyEndpoint: "http://proxy.example.com"
region: "us-east-1"
secret:
name: openai-secret
key: api-key
topk: "100"
customAnalyzers:
- name: "custom-analyzer-1"
connection:
url: "http://analyzer-1.example.com"
port: 8080
- name: "custom-analyzer-2"
connection:
url: "http://analyzer-2.example.com"
port: 9090
extraOptions:
backstage:
enabled: true
filters:
- "PodNotReady"
- "MemoryPressure"
imagePullSecrets:
- name: my-image-pull-secret
integrations:
trivy:
enabled: true
namespace: "trivy-namespace"
skipInstall: false
kubeconfig:
name: kubeconfig-secret
key: config
noCache: true
nodeSelector:
disktype: ssd
env: production
remoteCache:
azure:
containerName: "azure-container"
storageAccount: "azure-storage-account"
credentials:
name: "azure-credentials"
gcs:
bucketName: "gcs-bucket"
projectId: "gcs-project-id"
region: "us-central1"
interplex:
endpoint: "http://interplex.example.com"
s3:
bucketName: "s3-bucket"
region: "us-west-2"
repository: ghcr.io/k8sgpt-ai/k8sgpt
sink:
type: slack
channel: "#alerts"
username: "k8sgpt-bot"
icon_url: "https://example.com/icon.png"
webhook: "https://hooks.slack.com/services/..."
secret:
name: slack-webhook-secret
key: webhook-url
targetNamespace: "default"
version: "latest"
status: {}
2 changes: 1 addition & 1 deletion config/samples/valid_k8sgpt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ spec:
# language: english
noCache: false
repository: ghcr.io/k8sgpt-ai/k8sgpt
version: v0.3.8
version: v0.3.48
35 changes: 32 additions & 3 deletions controllers/analysis_step.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ limitations under the License.
package controllers

import (
"encoding/json"
"fmt"

"github.com/go-logr/logr"
corev1alpha1 "github.com/k8sgpt-ai/k8sgpt-operator/api/v1alpha1"
"github.com/k8sgpt-ai/k8sgpt-operator/pkg/resources"
ctrl "sigs.k8s.io/controller-runtime"
Expand All @@ -31,7 +32,15 @@ var (
)

type AnalysisStep struct {
next K8sGPT
next K8sGPT
enableResultLogging bool
logger logr.Logger
}

type AnalysisLogStatement struct {
Name string
Error string
Details string
}

func (step *AnalysisStep) execute(instance *K8sGPTInstance) (ctrl.Result, error) {
Expand Down Expand Up @@ -149,10 +158,30 @@ func (step *AnalysisStep) processRawResults(rawResults map[string]corev1alpha1.R
numberOfResultsByType.Reset()
}
for _, result := range rawResults {
err := resources.CreateOrUpdateResult(instance.ctx, instance.r.Client, result)
result, err := resources.CreateOrUpdateResult(instance.ctx, instance.r.Client, result)
if err != nil {
return err
}
// Rather than using the raw corev1alpha.Result from the RPC, we log on the v1alpha.Result from KubeBuilder
if step.enableResultLogging {

// check if result.spec.error is nil
var errorString = ""
if len(result.Spec.Error) > 0 {
errorString = fmt.Sprintf("Error %s", result.Spec.Error)
}
logStatement := AnalysisLogStatement{
Name: result.Spec.Name,
Details: result.Spec.Details,
Error: errorString,
}
// to json
jsonBytes, err := json.Marshal(logStatement)
if err != nil {
step.logger.Error(err, "Error marshalling logStatement")
}
step.logger.Info(string(jsonBytes))
}
numberOfResultsByType.WithLabelValues(result.Spec.Kind, result.Spec.Name, instance.k8sgptConfig.Name).Inc()
}

Expand Down
16 changes: 10 additions & 6 deletions controllers/k8sgpt_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ var (
// K8sGPTReconciler reconciles a K8sGPT object
type K8sGPTReconciler struct {
client.Client
Scheme *runtime.Scheme
Integrations *integrations.Integrations
SinkClient *sinks.Client
K8sGPTClient *kclient.Client
MetricsBuilder *metricspkg.MetricBuilder
Scheme *runtime.Scheme
Integrations *integrations.Integrations
SinkClient *sinks.Client
K8sGPTClient *kclient.Client
MetricsBuilder *metricspkg.MetricBuilder
EnableResultLogging bool
}

type K8sGPTInstance struct {
Expand Down Expand Up @@ -90,7 +91,10 @@ func (r *K8sGPTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
finalizerStep := FinalizerStep{}
configureStep := ConfigureStep{}
preAnalysisStep := PreAnalysisStep{}
analysisStep := AnalysisStep{}
analysisStep := AnalysisStep{
enableResultLogging: r.EnableResultLogging,
logger: instance.logger.WithName("analysis"),
}
resultStatusStep := ResultStatusStep{}

initStep.setNext(&finalizerStep)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ require (
github.com/prometheus/client_golang v1.20.5
google.golang.org/grpc v1.69.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.29.3
k8s.io/apimachinery v0.29.3
k8s.io/cli-runtime v0.29.3
Expand Down Expand Up @@ -99,6 +98,7 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.27.2 // indirect
k8s.io/component-base v0.29.3 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
Expand Down
13 changes: 8 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var enableResultLogging bool
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableResultLogging, "enable-result-logging", false, "Whether to enable results logging")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
Expand Down Expand Up @@ -121,11 +123,12 @@ func main() {
metricsBuilder := metrics.InitializeMetrics()

if err = (&controllers.K8sGPTReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Integrations: integration,
SinkClient: sinkClient,
MetricsBuilder: metricsBuilder,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Integrations: integration,
SinkClient: sinkClient,
MetricsBuilder: metricsBuilder,
EnableResultLogging: enableResultLogging,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "K8sGPT")
os.Exit(1)
Expand Down
16 changes: 8 additions & 8 deletions pkg/resources/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,33 +62,33 @@ func GetResult(resultSpec v1alpha1.ResultSpec, name, namespace, backend string,
},
}
}
func CreateOrUpdateResult(ctx context.Context, c client.Client, res v1alpha1.Result) error {
func CreateOrUpdateResult(ctx context.Context, c client.Client, res v1alpha1.Result) (*v1alpha1.Result, error) {
var existing v1alpha1.Result
if err := c.Get(ctx, client.ObjectKey{Namespace: res.Namespace, Name: res.Name}, &existing); err != nil {
if !errors.IsNotFound(err) {
return err
return nil, err
}
if err := c.Create(ctx, &res); err != nil {
return err
return nil, err
}
fmt.Printf("Created result %s\n", res.Name)
return nil
return &existing, nil
}
if len(existing.Spec.Error) == len(res.Spec.Error) && reflect.DeepEqual(res.Labels, existing.Labels) {
existing.Status.LifeCycle = string(NoOpResult)
err := c.Status().Update(ctx, &existing)
return err
return &existing, err
}

existing.Spec = res.Spec
existing.Labels = res.Labels
if err := c.Update(ctx, &existing); err != nil {
return err
return nil, err
}
existing.Status.LifeCycle = string(UpdatedResult)
if err := c.Status().Update(ctx, &existing); err != nil {
return err
return nil, err
}
fmt.Printf("Updated result %s\n", res.Name)
return nil
return &existing, nil
}

0 comments on commit 9826bff

Please sign in to comment.