From 796f212a87cd03f147b82fb282fa5cbb6ac38d19 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 28 Mar 2024 07:37:48 +0000 Subject: [PATCH] feat: bedrock support (#389) * feat: updated the bedrock secret setting Signed-off-by: Alex Jones * chore: updated CONTRIBUTING.md Signed-off-by: Alex Jones * chore: updated chart Signed-off-by: Alex Jones --------- Signed-off-by: Alex Jones --- CONTRIBUTING.md | 12 +++++ README.md | 36 ++++++++++++- api/v1alpha1/k8sgpt_types.go | 3 ++ chart/operator/templates/k8sgpt-crd.yaml | 7 ++- config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml | 5 +- config/manager/kustomization.yaml | 4 +- go.mod | 8 +-- go.sum | 22 +++++--- pkg/resources/k8sgpt.go | 56 ++++++++++++++++++-- 9 files changed, 132 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d53a9b4f..11ec34d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,6 +26,18 @@ In the scenario where you are testing an uncomitted or unreleased K8sGPT with yo by running again `LOCAL_MODE=1 make run` and in your local K8sGPT clone you can run `k8sgpt serve` or simply ` go run main.go serve` Note: You should always deploy a K8sGPT Custom Resource so the operator's reconcilaition can be triggered and you can set arbitrary values for the k8sgpt's version since you will bypass them. +### Testing with GRPCurl + +It is possible to test the operator's GRPC API with the grpcurl tool. +This enables you to test the K8sGPT GRPC API without the need of the K8sGPT Operator. + +Example: +``` +grpcurl -plaintext -d '{ "backend": "amazonbedrock", "explain": true}' localhost:8080 schema.v1.ServerService/Analyze +``` + +For API details visit the spec on [buf](https://buf.build/k8sgpt-ai/k8sgpt) + ## Help Feel free to join our slack [channel](https://k8sgpt.slack.com) and open GH issues, so we can make the development experience better for all K8sGPT contributors diff --git a/README.md b/README.md index 80a0f33b..fc334e38 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,7 @@ EOF ## Other AI Backend Examples +
AzureOpenAI @@ -277,6 +278,39 @@ EOF
+Amazon Bedrock + +
+1. Install the operator from the [Installation](#installation) section. + +2. Create secret: +```sh +kubectl create secret generic bedrock-sample-secret --from-literal=AWS_ACCESS_KEY_ID="$(echo $AWS_ACCESS_KEY_ID)" --from-literal=AWS_ACCESS_KEY="$(echo $AWS_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: + ai: + enabled: true + secret: + name: bedrock-sample-secret + model: anthropic.claude-v2 + region: eu-central-1 + backend: amazonbedrock + noCache: false + repository: ghcr.io/k8sgpt-ai/k8sgpt + version: v0.3.29 +EOF +``` + +
+ LocalAI @@ -305,7 +339,7 @@ EOF ``` Note: ensure that the value of `baseUrl` is a properly constructed [DNS name](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#services) for the LocalAI Service. It should take the form: `http://local-ai..svc.cluster.local:8080/v1`. -4. Same as step 4. in the example above. +1. Same as step 4. in the example above.
diff --git a/api/v1alpha1/k8sgpt_types.go b/api/v1alpha1/k8sgpt_types.go index a969dbfd..ef0021bb 100644 --- a/api/v1alpha1/k8sgpt_types.go +++ b/api/v1alpha1/k8sgpt_types.go @@ -86,6 +86,7 @@ type AISpec struct { Backend string `json:"backend"` BackOff *BackOff `json:"backOff,omitempty"` BaseUrl string `json:"baseUrl,omitempty"` + Region string `json:"region,omitempty"` // +kubebuilder:default:=gpt-3.5-turbo Model string `json:"model,omitempty"` Engine string `json:"engine,omitempty"` @@ -140,6 +141,8 @@ const ( ) // K8sGPTStatus defines the observed state of K8sGPT +// show the current backend used +// +kubebuilder:printcolumn:name="Backend",type="string",JSONPath=".spec.ai.backend",description="The current backend used" type K8sGPTStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file diff --git a/chart/operator/templates/k8sgpt-crd.yaml b/chart/operator/templates/k8sgpt-crd.yaml index d1b0fac6..5258b61c 100644 --- a/chart/operator/templates/k8sgpt-crd.yaml +++ b/chart/operator/templates/k8sgpt-crd.yaml @@ -1,3 +1,4 @@ +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -74,6 +75,8 @@ spec: model: default: gpt-3.5-turbo type: string + region: + type: string secret: properties: key: @@ -194,7 +197,7 @@ spec: type: string type: object status: - description: K8sGPTStatus defines the observed state of K8sGPT + description: K8sGPTStatus defines the observed state of K8sGPT show the current backend used type: object type: object served: true @@ -206,4 +209,4 @@ status: kind: "" plural: "" conditions: [] - storedVersions: [] + storedVersions: [] \ No newline at end of file diff --git a/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml b/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml index 8985f870..cf6310c6 100644 --- a/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml +++ b/config/crd/bases/core.k8sgpt.ai_k8sgpts.yaml @@ -74,6 +74,8 @@ spec: model: default: gpt-3.5-turbo type: string + region: + type: string secret: properties: key: @@ -194,7 +196,8 @@ spec: type: string type: object status: - description: K8sGPTStatus defines the observed state of K8sGPT + description: K8sGPTStatus defines the observed state of K8sGPT show the + current backend used type: object type: object served: true diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 97592351..cee796dd 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -4,5 +4,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: controller - newName: ghcr.io/k8sgpt-ai/k8sgpt-operator - newTag: feature-integration-support-4 + newName: tibbar/k8sgpt-operator + newTag: tmp-010 diff --git a/go.mod b/go.mod index 254f3da0..569d6355 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/k8sgpt-ai/k8sgpt-operator go 1.21 require ( - buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20240128172516-6bf6a55ff115.2 - buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.32.0-20240128172516-6bf6a55ff115.1 + buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20240213144542-6e830f3fdf19.2 + buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.33.0-20240213144542-6e830f3fdf19.1 github.com/onsi/ginkgo/v2 v2.16.0 github.com/onsi/gomega v1.32.0 github.com/prometheus/client_golang v1.19.0 @@ -91,7 +91,9 @@ require ( golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 0c63388f..bedb1cb2 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,11 @@ -buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20240128172516-6bf6a55ff115.2 h1:wCvkSdoAF1di3X07+0za7cydd57yXe8Px8q8Gv5me1U= -buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20240128172516-6bf6a55ff115.2/go.mod h1:RX8MKkcN5AzJMjQ649kSzZKyYo3imsJCMQjQHJKgRpQ= -buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.28.1-20240128172516-6bf6a55ff115.4/go.mod h1:i/s4ALHwKvjA1oGNKpoHg0FpEOTbufoOm/NdTE6YQAE= -buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.32.0-20240128172516-6bf6a55ff115.1 h1:Gmrk0ASd78N487ATP3++w6i4iZ4HyUTIdYpE4Ib0CPI= -buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.32.0-20240128172516-6bf6a55ff115.1/go.mod h1:IThjyuPqz3nkYBGZMUzrHTQhYDedkvBonqAgLcG7sHc= +buf.build/gen/go/grpc-ecosystem/grpc-gateway/grpc/go v1.3.0-20231027202514-3f42134f4c56.2/go.mod h1:FL988BzZirjg37E5k8AJR39eQ6/n/esl7iEEF63xniA= +buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.28.1-20231027202514-3f42134f4c56.4/go.mod h1:92ejKVTiuvnKoAtRlpJpIxKfloI935DDqhs0NCRx+KM= +buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.33.0-20231027202514-3f42134f4c56.1/go.mod h1:DBJo17ITWaACJE5Kqb+81Ekkq0BpTsNmgv7cX1tMVpY= +buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20240213144542-6e830f3fdf19.2 h1:dHSIT1w+0rtb//msVrp/RwrgHIeflbaTMKuF9Lx9HBI= +buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20240213144542-6e830f3fdf19.2/go.mod h1:EWMdFAEvu6GDt7jvAgoBS/WgXlyDsM7yist2SrcnS3s= +buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.28.1-20240213144542-6e830f3fdf19.4/go.mod h1:WyRj8OIsAABLNsAELw73BT16v7vvJdEVv771fxX9pJI= +buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.33.0-20240213144542-6e830f3fdf19.1 h1:ctQgUFe6yA9/XRmorc4tMPK1Z5ZSA7bB2mPvtn3ZT0I= +buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.33.0-20240213144542-6e830f3fdf19.1/go.mod h1:QwDvmBCDx7mFDbKgaR6mWS2+oUs8mPLV6tNk9kV+Ctk= 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= @@ -1213,8 +1216,12 @@ google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZV google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1271,7 +1278,6 @@ 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.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/resources/k8sgpt.go b/pkg/resources/k8sgpt.go index 327a3814..7d293cf6 100644 --- a/pkg/resources/k8sgpt.go +++ b/pkg/resources/k8sgpt.go @@ -43,6 +43,32 @@ const ( DestroyOp ) +func addSecretAsEnvToDeployment(secretName string, secretKey string, + config v1alpha1.K8sGPT, c client.Client, + deployment *appsv1.Deployment) error { + secret := &corev1.Secret{} + er := c.Get(context.Background(), types.NamespacedName{Name: secretName, + Namespace: config.Namespace}, secret) + if er != nil { + return err.New("secret does not exist, cannot add to env of deployment") + } + envVar := v1.EnvVar{ + Name: secretKey, + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: secretName, + }, + Key: secretKey, + }, + }, + } + deployment.Spec.Template.Spec.Containers[0].Env = append( + deployment.Spec.Template.Spec.Containers[0].Env, envVar, + ) + return nil +} + // GetService Create service for K8sGPT func GetService(config v1alpha1.K8sGPT) (*corev1.Service, error) { // Create service @@ -179,7 +205,7 @@ func GetClusterRole(config v1alpha1.K8sGPT) (*r1.ClusterRole, error) { } // GetDeployment Create deployment with the latest K8sGPT image -func GetDeployment(config v1alpha1.K8sGPT, outOfClusterMode bool) (*appsv1.Deployment, error) { +func GetDeployment(config v1alpha1.K8sGPT, outOfClusterMode bool, c client.Client) (*appsv1.Deployment, error) { // Create deployment image := config.Spec.Repository + ":" + config.Spec.Version @@ -303,7 +329,8 @@ func GetDeployment(config v1alpha1.K8sGPT, outOfClusterMode bool) (*appsv1.Deplo }, }) } - if config.Spec.AI.Secret != nil { + // This check is necessary for the simple OpenAI journey, let's keep it here and guard from breaking other types of backend + if config.Spec.AI.Secret != nil && config.Spec.AI.Backend != v1alpha1.AmazonBedrock { password := corev1.EnvVar{ Name: "K8SGPT_PASSWORD", ValueFrom: &corev1.EnvVarSource{ @@ -367,7 +394,28 @@ func GetDeployment(config v1alpha1.K8sGPT, outOfClusterMode bool) (*appsv1.Deplo deployment.Spec.Template.Spec.Containers[0].Env, engine, ) } else if config.Spec.AI.Engine != "" && config.Spec.AI.Backend != v1alpha1.AzureOpenAI { - return &appsv1.Deployment{}, err.New("Engine is supported only by azureopenai provider.") + return &appsv1.Deployment{}, err.New("engine is supported only by azureopenai provider") + } + // Add checks for amazonbedrock + if config.Spec.AI.Backend == v1alpha1.AmazonBedrock { + if config.Spec.AI.Secret == nil { + return &appsv1.Deployment{}, err.New("secret is required for amazonbedrock backend") + } + if err := addSecretAsEnvToDeployment(config.Spec.AI.Secret.Name, "AWS_ACCESS_KEY_ID", config, c, &deployment); err != nil { + return &appsv1.Deployment{}, err + } + if err := addSecretAsEnvToDeployment(config.Spec.AI.Secret.Name, "AWS_SECRET_ACCESS_KEY", config, c, &deployment); err != nil { + return &appsv1.Deployment{}, err + } + if config.Spec.AI.Region == "" { + return &appsv1.Deployment{}, err.New("default region is required for amazonbedrock backend") + } + deployment.Spec.Template.Spec.Containers[0].Env = append( + deployment.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{ + Name: "AWS_DEFAULT_REGION", + Value: config.Spec.AI.Region, + }, + ) } return &deployment, nil } @@ -409,7 +457,7 @@ func Sync(ctx context.Context, c client.Client, objs = append(objs, svc) - deployment, er := GetDeployment(config, outOfClusterMode) + deployment, er := GetDeployment(config, outOfClusterMode, c) if er != nil { return er }