From a3a692c4ae98116b77a7d4934d3874fdfd7bfd10 Mon Sep 17 00:00:00 2001 From: Fabian von Feilitzsch Date: Tue, 20 Aug 2024 13:18:06 -0400 Subject: [PATCH] :sparkles: Add support for deploying kai (#374) Adds experimental support for deploying kai alongside konveyor in hub-importer mode. Prerequisite to run is creating a secret with the LLM credentials in it. `kubectl create secret -n konveyor generic kai-api-key-secret --fromliteral=genai_key=[BAM_KEY] --from-literal=api_base=[OPENAI_BASE] --from-literal=api_key=[OPENAI_KEY]` --------- Signed-off-by: Fabian von Feilitzsch --- ...nveyor-operator.clusterserviceversion.yaml | 6 +- helm/templates/deployment.yaml | 2 + helm/values.yaml | 3 +- roles/tackle/defaults/main.yml | 27 ++++ roles/tackle/tasks/kai.yml | 124 ++++++++++++++++++ roles/tackle/tasks/main.yml | 4 + .../templates/kai/kai-api-deployment.yaml.j2 | 83 ++++++++++++ .../templates/kai/kai-api-service.yaml.j2 | 13 ++ roles/tackle/templates/kai/kai-config.yaml.j2 | 37 ++++++ .../templates/kai/kai-db-deployment.yaml.j2 | 42 ++++++ roles/tackle/templates/kai/kai-db-pvc.yaml.j2 | 12 ++ .../templates/kai/kai-db-service.yaml.j2 | 13 ++ .../kai/kai-importer-deployment.yaml.j2 | 88 +++++++++++++ .../kai/kai-importer-service.yaml.j2 | 13 ++ 14 files changed, 465 insertions(+), 2 deletions(-) create mode 100644 roles/tackle/tasks/kai.yml create mode 100644 roles/tackle/templates/kai/kai-api-deployment.yaml.j2 create mode 100644 roles/tackle/templates/kai/kai-api-service.yaml.j2 create mode 100644 roles/tackle/templates/kai/kai-config.yaml.j2 create mode 100644 roles/tackle/templates/kai/kai-db-deployment.yaml.j2 create mode 100644 roles/tackle/templates/kai/kai-db-pvc.yaml.j2 create mode 100644 roles/tackle/templates/kai/kai-db-service.yaml.j2 create mode 100644 roles/tackle/templates/kai/kai-importer-deployment.yaml.j2 create mode 100644 roles/tackle/templates/kai/kai-importer-service.yaml.j2 diff --git a/bundle/manifests/konveyor-operator.clusterserviceversion.yaml b/bundle/manifests/konveyor-operator.clusterserviceversion.yaml index 6af743a..4c68c0b 100644 --- a/bundle/manifests/konveyor-operator.clusterserviceversion.yaml +++ b/bundle/manifests/konveyor-operator.clusterserviceversion.yaml @@ -103,7 +103,7 @@ metadata: categories: Modernization & Migration certified: "false" containerImage: quay.io/konveyor/tackle2-operator:latest - createdAt: "2024-07-24T20:21:03Z" + createdAt: "2024-08-19T17:42:17Z" description: Konveyor is an open-source application modernization platform that helps organizations safely and predictably modernize applications to Kubernetes at scale. @@ -292,6 +292,8 @@ spec: value: quay.io/konveyor/generic-external-provider:latest - name: RELATED_IMAGE_PROVIDER_JAVA value: quay.io/konveyor/java-external-provider:latest + - name: RELATED_IMAGE_KAI + value: quay.io/konveyor/kai:latest image: quay.io/konveyor/tackle2-operator:latest imagePullPolicy: Always livenessProbe: @@ -486,4 +488,6 @@ spec: name: provider-generic - image: quay.io/konveyor/java-external-provider:latest name: provider-java + - image: quay.io/konveyor/kai:latest + name: kai version: 99.0.0 diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index 7a0eea1..cf72764 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -55,6 +55,8 @@ spec: value: {{ .Values.images.provider_generic }} - name: RELATED_IMAGE_PROVIDER_JAVA value: {{ .Values.images.provider_java }} + - name: RELATED_IMAGE_KAI + value: {{ .Values.images.kai }} name: tackle-operator image: {{ .Values.images.operator }} imagePullPolicy: Always diff --git a/helm/values.yaml b/helm/values.yaml index e467406..3a4f76c 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -21,4 +21,5 @@ images: addon_analyzer: quay.io/konveyor/tackle2-addon-analyzer:latest addon_discovery: quay.io/konveyor/tackle2-addon-discovery:latest provider_generic: quay.io/konveyor/generic-external-provider:latest - provider_java: quay.io/konveyor/java-external-provider:latest \ No newline at end of file + provider_java: quay.io/konveyor/java-external-provider:latest + kai: quay.io/konveyor/kai:latest diff --git a/roles/tackle/defaults/main.yml b/roles/tackle/defaults/main.yml index 0b52c07..a9d9f46 100644 --- a/roles/tackle/defaults/main.yml +++ b/roles/tackle/defaults/main.yml @@ -1,4 +1,6 @@ --- + + # App defaults app_name: "{{ lookup('env', 'APP_NAME') or 'tackle' }}" app_namespace: "{{ lookup('env', 'WATCH_NAMESPACE') or 'konveyor-tackle' }}" @@ -219,3 +221,28 @@ rhsso_tls_enabled: true rhsso_port: "{{ '8443' if rhsso_tls_enabled | bool else '8080' }}" rhsso_proto: "{{ 'https' if rhsso_tls_enabled | bool else 'http' }}" rhsso_url: "{{ rhsso_proto }}://keycloak.{{ app_namespace }}.svc:{{ rhsso_port }}" + + +# Kai-related variables +experimental_deploy_kai: false + +kai_fqin: "{{ lookup('env', 'RELATED_IMAGE_KAI') }}" + +kai_api_key_secret_name: kai-api-keys +kai_jwt_secret_name: kai-jwt-secret +kai_bam_secret_key: genai_key +kai_openai_secret_base_key: api_base +kai_openai_secret_api_key: api_key +kai_log_level: info +kai_enable_demo_mode: "false" +kai_enable_trace: "true" +kai_model_provider: "ChatIBMGenAI" +kai_model_id: "mistralai/mixtral-8x7b-instruct-v01" + +kai_hub_importer_args: "" + +kai_database_image_fqin: "{{ keycloak_database_image_fqin }}" +kai_database_secret_name: kai-db-secret +kai_database_volume_size: "10Gi" +kai_database_volume_claim_name: "{{ hub_service_name }}-kai-database-volume-claim" +kai_database_address: kai-db.{{ app_namespace }}.svc diff --git a/roles/tackle/tasks/kai.yml b/roles/tackle/tasks/kai.yml new file mode 100644 index 0000000..54c8ab7 --- /dev/null +++ b/roles/tackle/tasks/kai.yml @@ -0,0 +1,124 @@ +--- + +- name: Verify API key secret is defined + k8s_info: + api_version: v1 + kind: Secret + name: "{{ kai_api_key_secret_name }}" + namespace: "{{ app_namespace }}" + register: kai_api_key_secret_status + +- when: (kai_api_key_secret_status.resources|length) > 0 + block: + - name: Check if JWT token secret is defined + k8s_info: + api_version: v1 + kind: Secret + name: "{{ kai_jwt_secret_name }}" + namespace: "{{ app_namespace }}" + register: kai_jwt_secret_status + + - name: Check if DB secret is defined + k8s_info: + api_version: v1 + kind: Secret + name: "{{ kai_database_secret_name }}" + namespace: "{{ app_namespace }}" + register: kai_db_secret_status + + - name: Generate random password for Postgres + set_fact: + pg_password: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}" + when: (kai_db_secret_status.resources|length) == 0 + + - name: Create DB secret + k8s: + state: present + definition: + apiVersion: v1 + kind: Secret + metadata: + name: "{{ kai_database_secret_name }}" + namespace: "{{ app_namespace }}" + stringData: + POSTGRESQL_HOST: "{{ kai_database_address }}" + POSTGRESQL_DATABASE: kai + POSTGRESQL_PASSWORD: "{{ pg_password }}" + POSTGRESQL_USER: kai + when: (kai_db_secret_status.resources|length) == 0 + + - name: Decode pg_password from secret + set_fact: + pg_password: "{{ kai_db_secret_status.resources.0.data.POSTGRESQL_PASSWORD | b64decode }}" + when: (kai_db_secret_status.resources|length) > 0 + + - name: Retrieve Hub Secret + kubernetes.core.k8s_info: + api_version: v1 + kind: Secret + name: "{{ hub_secret_name }}" + namespace: "{{ app_namespace }}" + register: hub_secret + + - name: Set Hub key + set_fact: + hub_key: "{{ hub_secret.resources[0].data.addon_token | b64decode }}" + - name: Generate JWT token for Kai + command: | + /usr/local/bin/jwt.sh {{ hub_key }} + register: kai_jwt + when: (kai_jwt_secret_status.resources|length) == 0 + changed_when: (kai_jwt_secret_status.resources|length) == 0 + + - name: Create JWT token secret + k8s: + state: present + definition: + apiVersion: v1 + kind: Secret + metadata: + name: "{{ kai_jwt_secret_name }}" + namespace: "{{ app_namespace }}" + stringData: + jwt: "{{ kai_jwt.stdout }}" + when: (kai_jwt_secret_status.resources|length) == 0 + + - name: Create KAI ConfigMap + k8s: + state: present + template: kai/kai-config.yaml.j2 + + - name: Deploy KAI DB + k8s: + state: present + template: kai/kai-db-deployment.yaml.j2 + + - name: Create KAI DB Service + k8s: + state: present + template: kai/kai-db-service.yaml.j2 + + - name: Create KAI DB PersistentVolumeClaim + k8s: + state: present + template: kai/kai-db-pvc.yaml.j2 + + - name: Deploy KAI API service + k8s: + state: present + template: kai/kai-api-deployment.yaml.j2 + + - name: Create KAI API Service + k8s: + state: present + template: kai/kai-api-service.yaml.j2 + + - name: Deploy KAI Hub Importer + k8s: + state: present + template: kai/kai-importer-deployment.yaml.j2 + + - name: Create KAI Hub Importer Service + k8s: + state: present + template: kai/kai-importer-service.yaml.j2 diff --git a/roles/tackle/tasks/main.yml b/roles/tackle/tasks/main.yml index 728c914..6ec6878 100644 --- a/roles/tackle/tasks/main.yml +++ b/roles/tackle/tasks/main.yml @@ -732,3 +732,7 @@ when: - (pathfinder_delete_db_volume|bool) - (pathfinder_pod.resources|length) == 0 + +- name: Run kai tasks + when: experimental_deploy_kai + import_tasks: kai.yml diff --git a/roles/tackle/templates/kai/kai-api-deployment.yaml.j2 b/roles/tackle/templates/kai/kai-api-deployment.yaml.j2 new file mode 100644 index 0000000..2ea488b --- /dev/null +++ b/roles/tackle/templates/kai/kai-api-deployment.yaml.j2 @@ -0,0 +1,83 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kai-api + namespace: "{{ app_namespace }}" +spec: + replicas: 1 + selector: + matchLabels: + app: kai-api + template: + metadata: + labels: + app: kai-api + spec: + containers: + - name: kai-api + image: "{{ kai_fqin }}" + ports: + - containerPort: 8080 + env: + - name: POSTGRESQL_HOST + valueFrom: + secretKeyRef: + name: "{{ kai_db_secret_name }}" + key: POSTGRESQL_HOST + - name: POSTGRESQL_DATABASE + valueFrom: + secretKeyRef: + name: "{{ kai_db_secret_name }}" + key: POSTGRESQL_DB + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: "{{ kai_db_secret_name }}" + key: POSTGRESQL_PASSWORD + - name: POSTGRESQL_USER + valueFrom: + secretKeyRef: + name: "{{ kai_db_secret_name }}" + key: POSTGRESQL_USER + - name: DEMO_MODE + value: "False" + - name: HUB_URL + value: "{{ hub_url }}" + - name: IMPORTER_ARGS + value: "" + - name: LOGLEVEL + value: "info" + - name: NUM_WORKERS + value: "8" + - name: USE_HUB_IMPORTER + value: "True" +{% if kai_api_key_secret_status.resources.0.data[kai_bam_secret_key]|default(false) %} + - name: GENAI_KEY + valueFrom: + secretKeyRef: + name: "{{ kai_api_key_secret_name }}" + key: "{{ kai_bam_secret_key }}" +{% endif %} +{% if kai_api_key_secret_status.resources.0.data[kai_openai_secret_base_key]|default(false) %} + - name: OPENAI_API_BASE + valueFrom: + secretKeyRef: + name: "{{ kai_api_key_secret_name }}" + key: "{{ kai_openai_secret_base_key }}" +{% endif %} +{% if kai_api_key_secret_status.resources.0.data[kai_openai_secret_api_key]|default(false) %} + - name: OPENAI_API_KEY + valueFrom: + secretKeyRef: + name: "{{ kai_api_key_secret_name }}" + key: "{{ kai_openai_secret_api_key }}" +{% endif %} + volumeMounts: + - name: config-volume + mountPath: /podman_compose/kai-config.toml + subPath: kai-config.toml + volumes: + - name: config-volume + configMap: + name: "kai-config" diff --git a/roles/tackle/templates/kai/kai-api-service.yaml.j2 b/roles/tackle/templates/kai/kai-api-service.yaml.j2 new file mode 100644 index 0000000..823779b --- /dev/null +++ b/roles/tackle/templates/kai/kai-api-service.yaml.j2 @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: kai-api + namespace: "{{ app_namespace }}" +spec: + selector: + app: kai-api + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 diff --git a/roles/tackle/templates/kai/kai-config.yaml.j2 b/roles/tackle/templates/kai/kai-config.yaml.j2 new file mode 100644 index 0000000..f2240f8 --- /dev/null +++ b/roles/tackle/templates/kai/kai-config.yaml.j2 @@ -0,0 +1,37 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: "kai-config" + namespace: "{{ app_namespace }}" +data: + kai-config.toml: | + # TODO: Make all these configurable via ansible + log_level = "{{ kai_log_level }}" + file_log_level = "debug" + log_dir = "/podman_compose/logs" + demo_mode = {{ kai_enable_demo_mode }} + trace_enabled = {{ kai_enable_trace }} + + solution_consumers = ["diff_only", "llm_summary"] + + [incident_store] + solution_detectors = "naive" + solution_producers = "text_only" + + [incident_store.args] + provider = "postgresql" + host = "kai-db" + database = "kai" + user = "kai" + # TODO: This may need to be envvar only + password = "{{ pg_password }}" + + [models] + provider = "{{ kai_model_provider }}" + + [models.args] + model_id = "{{ kai_model_id }}" + + [embeddings] + todo = true diff --git a/roles/tackle/templates/kai/kai-db-deployment.yaml.j2 b/roles/tackle/templates/kai/kai-db-deployment.yaml.j2 new file mode 100644 index 0000000..4fc61b8 --- /dev/null +++ b/roles/tackle/templates/kai/kai-db-deployment.yaml.j2 @@ -0,0 +1,42 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kai-db + namespace: "{{ app_namespace }}" +spec: + replicas: 1 + selector: + matchLabels: + app: kai-db + template: + metadata: + labels: + app: kai-db + spec: + containers: + - name: kai-db + image: "{{ kai_database_image_fqin }}" + env: + - name: POSTGRESQL_DATABASE + valueFrom: + secretKeyRef: + name: "{{ kai_db_secret_name }}" + key: POSTGRESQL_DATABASE + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: "{{ kai_db_secret_name }}" + key: POSTGRESQL_PASSWORD + - name: POSTGRESQL_USER + valueFrom: + secretKeyRef: + name: "{{ kai_db_secret_name }}" + key: POSTGRESQL_USER + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: kai-db-data + volumes: + - name: kai-db-data + persistentVolumeClaim: + claimName: "{{ kai_database_volume_claim_name }}" diff --git a/roles/tackle/templates/kai/kai-db-pvc.yaml.j2 b/roles/tackle/templates/kai/kai-db-pvc.yaml.j2 new file mode 100644 index 0000000..712792b --- /dev/null +++ b/roles/tackle/templates/kai/kai-db-pvc.yaml.j2 @@ -0,0 +1,12 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: "{{ kai_database_volume_claim_name }}" + namespace: "{{ app_namespace }}" +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "{{ kai_database_volume_size }}" diff --git a/roles/tackle/templates/kai/kai-db-service.yaml.j2 b/roles/tackle/templates/kai/kai-db-service.yaml.j2 new file mode 100644 index 0000000..1da34aa --- /dev/null +++ b/roles/tackle/templates/kai/kai-db-service.yaml.j2 @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: kai-db + namespace: "{{ app_namespace }}" +spec: + selector: + app: kai-db + ports: + - protocol: TCP + port: 5432 + targetPort: 5432 diff --git a/roles/tackle/templates/kai/kai-importer-deployment.yaml.j2 b/roles/tackle/templates/kai/kai-importer-deployment.yaml.j2 new file mode 100644 index 0000000..8a9899f --- /dev/null +++ b/roles/tackle/templates/kai/kai-importer-deployment.yaml.j2 @@ -0,0 +1,88 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kai-hub-importer + namespace: "{{ app_namespace }}" +spec: + replicas: 1 + selector: + matchLabels: + app: kai-hub-importer + template: + metadata: + labels: + app: kai-hub-importer + spec: + containers: + - name: kai-hub-importer + image: "{{ kai_fqin }}" + env: + - name: POSTGRESQL_HOST + valueFrom: + secretKeyRef: + name: "{{ kai_database_secret_name }}" + key: POSTGRESQL_HOST + - name: POSTGRESQL_DATABASE + valueFrom: + secretKeyRef: + name: "{{ kai_database_secret_name }}" + key: POSTGRESQL_DB + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: "{{ kai_database_secret_name }}" + key: POSTGRESQL_PASSWORD + - name: POSTGRESQL_USER + valueFrom: + secretKeyRef: + name: "{{ kai_database_secret_name }}" + key: POSTGRESQL_USER + - name: DEMO_MODE + value: "{{ kai_enable_demo_mode }}" + - name: HUB_URL + value: "{{ hub_url }}" + - name: IMPORTER_ARGS + value: "{{ kai_hub_importer_args }}" + - name: LOGLEVEL + value: "{{ kai_log_level }}" + - name: NUM_WORKERS + value: "8" + - name: USE_HUB_IMPORTER + value: "True" + - name: MODE + value: "importer" + - name: JWT + valueFrom: + secretKeyRef: + name: "{{ kai_jwt_secret_name }}" + key: jwt +{% if kai_api_key_secret_status.resources.0.data[kai_bam_secret_key]|default(false) %} + - name: GENAI_KEY + valueFrom: + secretKeyRef: + name: "{{ kai_api_key_secret_name }}" + key: "{{ kai_bam_secret_key }}" +{% endif %} +{% if kai_api_key_secret_status.resources.0.data[kai_openai_secret_base_key]|default(false) %} + - name: OPENAI_API_BASE + valueFrom: + secretKeyRef: + name: "{{ kai_api_key_secret_name }}" + key: "{{ kai_openai_secret_base_key }}" +{% endif %} +{% if kai_api_key_secret_status.resources.0.data[kai_openai_secret_api_key]|default(false) %} + - name: OPENAI_API_KEY + valueFrom: + secretKeyRef: + name: "{{ kai_api_key_secret_name }}" + key: "{{ kai_openai_secret_api_key }}" +{% endif %} + volumeMounts: + - name: config-volume + mountPath: /podman_compose/kai-config.toml + subPath: kai-config.toml + volumes: + - name: config-volume + configMap: + name: "kai-config" diff --git a/roles/tackle/templates/kai/kai-importer-service.yaml.j2 b/roles/tackle/templates/kai/kai-importer-service.yaml.j2 new file mode 100644 index 0000000..73d557c --- /dev/null +++ b/roles/tackle/templates/kai/kai-importer-service.yaml.j2 @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: kai-hub-importer + namespace: "{{ app_namespace }}" +spec: + selector: + app: kai-hub-importer + ports: + - protocol: TCP + port: 8080 + targetPort: 8080