diff --git a/README.md b/README.md index 34788c12..37e18472 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,47 @@ you will be able to see the Results objects of the analysis after some minutes ( "details": "The error message means that the service in Kubernetes doesn't have any associated endpoints, which should have been labeled with \"control-plane=controller-manager\". \n\nTo solve this issue, you need to add the \"control-plane=controller-manager\" label to the endpoint that matches the service. Once the endpoint is labeled correctly, Kubernetes can associate it with the service, and the error should be resolved.", ``` +## Remote Cache + +
+ +S3 + +1. Install the operator from the [Installation](#installation) section. + +2. Create secret: +```sh +kubectl create secret generic k8sgpt-sample-cache-secret --from-literal=aws_access_key_id= --from-literal=aws_secret_access_key= -n k8sgpt- +operator-system +``` + +3. Apply the K8sGPT configuration object: +``` +kubectl apply -f - << EOF +apiVersion: core.k8sgpt.ai/v1alpha1 +kind: K8sGPT +metadata: + name: k8sgpt-sample + namespace: k8sgpt-operator-system +spec: + model: gpt-3.5-turbo + backend: openai + noCache: false + version: v0.3.0 + enableAI: true + secret: + name: k8sgpt-sample-secret + key: openai-api-key + remoteCache: + credentials: + name: k8sgpt-sample-cache-secret + bucketName: foo + region: us-west-1 +EOF +``` + +
+ ## Other AI Backend Examples
diff --git a/api/v1alpha1/k8sgpt_types.go b/api/v1alpha1/k8sgpt_types.go index 57150b34..b720c357 100644 --- a/api/v1alpha1/k8sgpt_types.go +++ b/api/v1alpha1/k8sgpt_types.go @@ -36,6 +36,16 @@ type ExtraOptionsRef struct { Backstage *Backstage `json:"backstage,omitempty"` } +type CredentialsRef struct { + Name string `json:"name,omitempty"` +} + +type RemoteCacheRef struct { + Credentials *CredentialsRef `json:"credentials,omitempty"` + BucketName string `json:"bucketName,omitempty"` + Region string `json:"region,omitempty"` +} + type WebhookRef struct { // +kubebuilder:validation:Enum=slack Type string `json:"type,omitempty"` @@ -66,6 +76,7 @@ type K8sGPTSpec struct { ExtraOptions *ExtraOptionsRef `json:"extraOptions,omitempty"` Sink *WebhookRef `json:"sink,omitempty"` AI *AISpec `json:"ai,omitempty"` + RemoteCache *RemoteCacheRef `json:"remoteCache,omitempty"` } const ( diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 7b507896..c131490b 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -60,6 +60,21 @@ func (in *Backstage) DeepCopy() *Backstage { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CredentialsRef) DeepCopyInto(out *CredentialsRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialsRef. +func (in *CredentialsRef) DeepCopy() *CredentialsRef { + if in == nil { + return nil + } + out := new(CredentialsRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExtraOptionsRef) DeepCopyInto(out *ExtraOptionsRef) { *out = *in @@ -182,6 +197,11 @@ func (in *K8sGPTSpec) DeepCopyInto(out *K8sGPTSpec) { *out = new(AISpec) (*in).DeepCopyInto(*out) } + if in.RemoteCache != nil { + in, out := &in.RemoteCache, &out.RemoteCache + *out = new(RemoteCacheRef) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8sGPTSpec. @@ -209,6 +229,26 @@ func (in *K8sGPTStatus) DeepCopy() *K8sGPTStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RemoteCacheRef) DeepCopyInto(out *RemoteCacheRef) { + *out = *in + if in.Credentials != nil { + in, out := &in.Credentials, &out.Credentials + *out = new(CredentialsRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoteCacheRef. +func (in *RemoteCacheRef) DeepCopy() *RemoteCacheRef { + if in == nil { + return nil + } + out := new(RemoteCacheRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Result) DeepCopyInto(out *Result) { *out = *in diff --git a/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml b/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml index d8c67226..d071c423 100644 --- a/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml +++ b/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml @@ -83,6 +83,18 @@ spec: type: array noCache: type: boolean + remoteCache: + properties: + bucketName: + type: string + credentials: + properties: + name: + type: string + type: object + region: + type: string + type: object sink: properties: type: diff --git a/config/samples/core_v1alpha1_k8sgpt.yaml b/config/samples/core_v1alpha1_k8sgpt.yaml index 6d214b28..56687150 100644 --- a/config/samples/core_v1alpha1_k8sgpt.yaml +++ b/config/samples/core_v1alpha1_k8sgpt.yaml @@ -12,4 +12,9 @@ spec: name: k8sgpt-sample-secret key: openai-api-key noCache: false - version: v0.3.0 + version: v0.3.5 + remoteCache: + credentials: + name: k8sgpt-sample-cache-secret + bucketName: k8sgpt-debug-test + region: eu-west-2 \ No newline at end of file diff --git a/controllers/k8sgpt_controller.go b/controllers/k8sgpt_controller.go index 96e562f4..a939a428 100644 --- a/controllers/k8sgpt_controller.go +++ b/controllers/k8sgpt_controller.go @@ -170,6 +170,15 @@ func (r *K8sGPTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr defer k8sgptClient.Close() + // Configure the k8sgpt deployment if required + if k8sgptConfig.Spec.RemoteCache != nil { + err = k8sgptClient.AddConfig(k8sgptConfig) + if err != nil { + k8sgptReconcileErrorCount.Inc() + return r.finishReconcile(err, false) + } + } + response, err := k8sgptClient.ProcessAnalysis(deployment, k8sgptConfig) if err != nil { k8sgptReconcileErrorCount.Inc() diff --git a/go.mod b/go.mod index 0bbb812a..97411f6c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230620082254-6f80f9533908.1 - buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.30.0-20230620082254-6f80f9533908.1 + buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230620082254-6f80f9533908.1 github.com/onsi/ginkgo/v2 v2.12.0 github.com/onsi/gomega v1.27.10 github.com/prometheus/client_golang v1.16.0 @@ -91,7 +91,7 @@ require ( gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 32a08651..348a523f 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230620082254-6f80f9533908.1 h1:Z0zeGzAumjLyAb/24aiBNyAheT+SDBhlxPfcUy12nPI= buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230620082254-6f80f9533908.1/go.mod h1:ydXSuYyk0CN76EA+cjFemhpz87XtuU310GdmkmXUUY8= buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.28.1-20230620082254-6f80f9533908.4/go.mod h1:i/s4ALHwKvjA1oGNKpoHg0FpEOTbufoOm/NdTE6YQAE= -buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.30.0-20230620082254-6f80f9533908.1 h1:FNJYUdFjROTTKhIQ+VtJCzuWywQU430leJfnkStRRic= -buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.30.0-20230620082254-6f80f9533908.1/go.mod h1:karV92RruD5davytOQq20lDyAqBnai8ajNolo98nu94= +buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230620082254-6f80f9533908.1 h1:FCNAoc1SnZQ0nUgWgV75izPiLGl3Q1X6pxWjL/wB6cI= +buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.31.0-20230620082254-6f80f9533908.1/go.mod h1:gtnk2yAUexdY5nTuUg0SH5WCCGvpKzr7pd3Xbi7MWjE= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -1259,8 +1259,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/main.go b/main.go index fdae8141..fd839fe7 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,8 @@ package main import ( "context" "flag" + "fmt" + "math/rand" "os" "time" @@ -64,7 +66,15 @@ func main() { flag.Parse() ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - + if os.Getenv("LOCAL_MODE") != "" { + setupLog.Info("Running in local mode") + min := 7000 + max := 8000 + metricsAddr = fmt.Sprintf(":%d", rand.Intn(max-min+1)+min) + probeAddr = fmt.Sprintf(":%d", rand.Intn(max-min+1)+min) + setupLog.Info(fmt.Sprintf("Metrics address: %s", metricsAddr)) + setupLog.Info(fmt.Sprintf("Probe address: %s", probeAddr)) + } mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, diff --git a/pkg/client/analysis.go b/pkg/client/analysis.go new file mode 100644 index 00000000..1fbafae6 --- /dev/null +++ b/pkg/client/analysis.go @@ -0,0 +1,50 @@ +package client + +import ( + "context" + "encoding/json" + "fmt" + + rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc" + schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" + "github.com/k8sgpt-ai/k8sgpt-operator/api/v1alpha1" + "github.com/k8sgpt-ai/k8sgpt-operator/pkg/common" + v1 "k8s.io/api/apps/v1" +) + +func (c *Client) ProcessAnalysis(deployment v1.Deployment, config *v1alpha1.K8sGPT) (*common.K8sGPTReponse, error) { + + client := rpc.NewServerServiceClient(c.conn) + req := &schemav1.AnalyzeRequest{ + Explain: config.Spec.AI.Enabled, + Nocache: config.Spec.NoCache, + Backend: config.Spec.AI.Backend, + Filters: config.Spec.Filters, + Anonymize: config.Spec.AI.Anonymize, + Language: config.Spec.AI.Language, + } + + res, err := client.Analyze(context.Background(), req) + if err != nil { + return nil, fmt.Errorf("failed to call Analyze RPC: %v", err) + } + + var target []v1alpha1.ResultSpec + + jsonBytes, err := json.Marshal(res.Results) + if err != nil { + return nil, err + } + + err = json.Unmarshal(jsonBytes, &target) + if err != nil { + return nil, err + } + + response := &common.K8sGPTReponse{ + Status: res.Status, + Results: target, + Problems: int(res.Problems), + } + return response, nil +} diff --git a/pkg/client/client.go b/pkg/client/client.go index 6d3cb327..e4ff946e 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -15,18 +15,13 @@ package client import ( "context" - "encoding/json" "fmt" "net" "os" "time" - rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc" - schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" "github.com/k8sgpt-ai/k8sgpt-operator/api/v1alpha1" - "github.com/k8sgpt-ai/k8sgpt-operator/pkg/common" "google.golang.org/grpc" - v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -79,40 +74,3 @@ func GenerateAddress(ctx context.Context, cli client.Client, k8sgptConfig *v1alp return address, nil } - -func (c *Client) ProcessAnalysis(deployment v1.Deployment, config *v1alpha1.K8sGPT) (*common.K8sGPTReponse, error) { - - client := rpc.NewServerServiceClient(c.conn) - req := &schemav1.AnalyzeRequest{ - Explain: config.Spec.AI.Enabled, - Nocache: config.Spec.NoCache, - Backend: config.Spec.AI.Backend, - Filters: config.Spec.Filters, - Anonymize: config.Spec.AI.Anonymize, - Language: config.Spec.AI.Language, - } - - res, err := client.Analyze(context.Background(), req) - if err != nil { - return nil, fmt.Errorf("failed to call Analyze RPC: %v", err) - } - - var target []v1alpha1.ResultSpec - - jsonBytes, err := json.Marshal(res.Results) - if err != nil { - return nil, err - } - - err = json.Unmarshal(jsonBytes, &target) - if err != nil { - return nil, err - } - - response := &common.K8sGPTReponse{ - Status: res.Status, - Results: target, - Problems: int(res.Problems), - } - return response, nil -} diff --git a/pkg/client/config.go b/pkg/client/config.go new file mode 100644 index 00000000..7d49d006 --- /dev/null +++ b/pkg/client/config.go @@ -0,0 +1,46 @@ +package client + +import ( + "context" + "fmt" + + rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc" + schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" + "github.com/k8sgpt-ai/k8sgpt-operator/api/v1alpha1" +) + +func (c *Client) AddConfig(config *v1alpha1.K8sGPT) error { + client := rpc.NewServerServiceClient(c.conn) + + req := &schemav1.AddConfigRequest{ + Cache: &schemav1.Cache{ + BucketName: config.Spec.RemoteCache.BucketName, + Region: config.Spec.RemoteCache.Region, + }, + } + + _, err := client.AddConfig(context.Background(), req) + if err != nil { + return fmt.Errorf("failed to call AddConfig RPC: %v", err) + } + + return nil +} + +func (c *Client) RemoveConfig(config *v1alpha1.K8sGPT) error { + client := rpc.NewServerServiceClient(c.conn) + + req := &schemav1.RemoveConfigRequest{ + Cache: &schemav1.Cache{ + BucketName: config.Spec.RemoteCache.BucketName, + Region: config.Spec.RemoteCache.Region, + }, + } + + _, err := client.RemoveConfig(context.Background(), req) + if err != nil { + return fmt.Errorf("failed to call RemoveConfig RPC: %v", err) + } + + return nil +} diff --git a/pkg/resources/k8sgpt.go b/pkg/resources/k8sgpt.go index 7425bbe5..99c76157 100644 --- a/pkg/resources/k8sgpt.go +++ b/pkg/resources/k8sgpt.go @@ -22,6 +22,7 @@ import ( "github.com/k8sgpt-ai/k8sgpt-operator/pkg/utils" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" r1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" @@ -272,6 +273,29 @@ func GetDeployment(config v1alpha1.K8sGPT) (*appsv1.Deployment, error) { deployment.Spec.Template.Spec.Containers[0].Env, password, ) } + if config.Spec.RemoteCache != nil { + + // check to see if key/value exists + addRemoteCacheEnvVar := func(name, key string) { + envVar := v1.EnvVar{ + Name: name, + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: config.Spec.RemoteCache.Credentials.Name, + }, + Key: key, + }, + }, + } + deployment.Spec.Template.Spec.Containers[0].Env = append( + deployment.Spec.Template.Spec.Containers[0].Env, envVar, + ) + } + addRemoteCacheEnvVar("AWS_ACCESS_KEY_ID", "aws_access_key_id") + addRemoteCacheEnvVar("AWS_SECRET_ACCESS_KEY", "aws_secret_access_key") + + } if config.Spec.AI.BaseUrl != "" { baseUrl := corev1.EnvVar{ Name: "K8SGPT_BASEURL",