From 44b9d0949b95a81fe8afc5c7f8df5f4fdda704e1 Mon Sep 17 00:00:00 2001 From: Kiran Vaddadi Date: Fri, 13 Mar 2026 11:20:53 +0530 Subject: [PATCH 1/4] remove kafka tls --- charts/intelligence/.helmignore | 23 - charts/intelligence/Chart.yaml | 12 - charts/intelligence/README.md | 90 ---- charts/intelligence/templates/_helpers.tpl | 170 ------- .../server/kafka-discovery-configmap.yaml | 44 -- .../templates/server/server-deployment.yml | 415 ------------------ .../templates/server/server-service.yml | 30 -- .../templates/service-accounts/role.yaml | 33 -- .../service-accounts/service-account.yml | 15 - charts/intelligence/values.yaml | 137 ------ charts/kafka/Chart.yaml | 2 +- charts/kafka/README.md | 292 +----------- charts/kafka/kafka-1.0.1.tgz | Bin 0 -> 12603 bytes charts/kafka/templates/kafka-config.yaml | 39 +- .../templates/kafka-external-services.yaml | 54 --- charts/kafka/templates/kafka-statefulset.yaml | 218 +-------- .../pre-upgrade-statefulset-cleanup.yaml | 13 +- charts/kafka/values.yaml | 107 +---- charts/portal/Chart.lock | 9 +- charts/portal/Chart.yaml | 8 +- charts/portal/README.md | 10 +- .../charts/apim-intelligence-1.0.21.tgz | Bin 10201 -> 0 bytes charts/portal/templates/_helpers.tpl | 75 ++++ charts/portal/templates/apim/apim-config.yaml | 7 +- .../portal/templates/apim/apim-service.yaml | 19 + .../portal/templates/gateway-api/gateway.yaml | 4 + .../templates/gateway-api/tlsroute.yaml | 34 ++ .../templates/ingress/contour-httpproxy.yaml | 28 ++ charts/portal/templates/ingress/ingress.yaml | 18 + charts/portal/templates/ingress/route.yaml | 20 + .../intelligence/intelligence-config.yaml} | 10 +- .../intelligence/intelligence-deployment.yaml | 174 ++++++++ .../intelligence/intelligence-role.yaml | 23 + .../intelligence-rolebinding.yaml} | 14 +- .../intelligence-service-account.yaml | 15 + .../intelligence/intelligence-service.yaml | 32 ++ .../templates/jobs/cert-update-job.yaml | 10 - charts/portal/templates/jobs/job-secret.yaml | 2 - charts/portal/values-production.yaml | 175 ++------ charts/portal/values.yaml | 200 +++------ 40 files changed, 620 insertions(+), 1961 deletions(-) delete mode 100644 charts/intelligence/.helmignore delete mode 100644 charts/intelligence/Chart.yaml delete mode 100644 charts/intelligence/README.md delete mode 100644 charts/intelligence/templates/_helpers.tpl delete mode 100644 charts/intelligence/templates/server/kafka-discovery-configmap.yaml delete mode 100644 charts/intelligence/templates/server/server-deployment.yml delete mode 100644 charts/intelligence/templates/server/server-service.yml delete mode 100644 charts/intelligence/templates/service-accounts/role.yaml delete mode 100644 charts/intelligence/templates/service-accounts/service-account.yml delete mode 100644 charts/intelligence/values.yaml create mode 100644 charts/kafka/kafka-1.0.1.tgz delete mode 100644 charts/kafka/templates/kafka-external-services.yaml delete mode 100644 charts/portal/charts/apim-intelligence-1.0.21.tgz rename charts/{intelligence/templates/server/server-config.yml => portal/templates/intelligence/intelligence-config.yaml} (67%) create mode 100644 charts/portal/templates/intelligence/intelligence-deployment.yaml create mode 100644 charts/portal/templates/intelligence/intelligence-role.yaml rename charts/{intelligence/templates/service-accounts/rolebinding.yaml => portal/templates/intelligence/intelligence-rolebinding.yaml} (51%) create mode 100644 charts/portal/templates/intelligence/intelligence-service-account.yaml create mode 100644 charts/portal/templates/intelligence/intelligence-service.yaml diff --git a/charts/intelligence/.helmignore b/charts/intelligence/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/charts/intelligence/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/charts/intelligence/Chart.yaml b/charts/intelligence/Chart.yaml deleted file mode 100644 index 8bcaeb41..00000000 --- a/charts/intelligence/Chart.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v2 -appVersion: "1.0.0" -description: APIM Intelligence -name: apim-intelligence -version: 1.0.21 -type: application -home: https://github.com/CAAPIM/apim-chart -maintainers: - - name: Layer7 -sources: - - https://github.com/CAAPIM/apim-charts -engine: gotpl diff --git a/charts/intelligence/README.md b/charts/intelligence/README.md deleted file mode 100644 index e50603b8..00000000 --- a/charts/intelligence/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# API Management Intelligence Chart - -This chart deploys the API Management Intelligence. APIM Intelligence is responsible for processing and aggregating policy code from the API Gateway and making it available to the Portal. - -## Parameters - -### Global parameters - -These values are typically provided by a parent chart. - -| Parameter | Description | Default | -| --- | --- | --- | -| `global.portalRepository` | The repository for the portal images. | `caapim/` | -| `global.pullSecret` | The name of an existing image pull secret. | `""` | -| `global.databaseType` | The type of database to use. | `mysql` | -| `global.databaseSecret` | The name of the secret containing the database password. | `intelligence-db-secret` | -| `global.databaseUsername` | The username for the database. | `intelligence_user` | -| `global.databaseHost` | The hostname of the database. | `mysql` | -| `global.databasePort` | The port of the database. | `3306` | -| `global.databaseUseSSL` | Whether to use SSL for the database connection. | `true` | -| `global.databaseRequireSSL` | Whether to require SSL for the database connection. | `false` | -| `global.legacyDatabaseNames` | Whether to use legacy database names. | `false` | -| `global.subdomainPrefix` | The prefix for the subdomain. | `dev-portal` | -| `global.podSecurityContext` | The security context for the pod. | `{}` | -| `global.containerSecurityContext` | The security context for the container. | `{}` | -| `global.schedulerName` | The name of the scheduler to use for the pods. | `""` | -| `global.additionalLabels` | Additional labels to be applied to all resources. | `{}` | - -### General parameters - -| Parameter | Description | Default | -| --- | --- | --- | -| `nameOverride` | A string to override the name of the chart. | `""` | -| `fullnameOverride` | A string to override the full name of the chart. | `""` | -| `forceRedeploy` | Whether to force redeployment of statefulsets and deployments on upgrade. | `false` | - -### Intelligence Server settings - -| Parameter | Description | Default | -| --- | --- | --- | - -| `image.intelligenceServer` | The image for the intelligence-server. | `intelligence-server:latest` | -| `image.pullPolicy` | The pull policy for the intelligence-server image. | `IfNotPresent` | -| `image.autoDiscovery` | The image for auto-discovery init container (Kafka broker discovery). | `tls-automator:5.4` | -| `serviceAccount.create` | Whether to create a service account. | `true` | -| `serviceAccount.name` | The name of the service account to use. | `""` | -| `serviceAccount.automountServiceAccountToken` | Whether to automount the service account token. | `true` | -| `rbac.create` | Whether to create RBAC resources. | `true` | -| `intelligenceServer.replicaCount` | The number of replicas for the intelligence-server. | `1` | -| `intelligenceServer.additionalLabels` | Additional labels for the intelligence-server deployment. | `{}` | -| `intelligenceServer.affinity` | The affinity for the intelligence-server pods. | `{}` | -| `intelligenceServer.nodeSelector` | The node selector for the intelligence-server pods. | `{}` | -| `intelligenceServer.tolerations` | The tolerations for the intelligence-server pods. | `[]` | -| `intelligenceServer.podSecurityContext` | The security context for the intelligence-server pod. | `{}` | -| `intelligenceServer.containerSecurityContext` | The security context for the intelligence-server container. | `{}` | -| `intelligenceServer.resources` | The resource requests and limits for the intelligence-server pods. | `{}` | -| `intelligenceServer.service.type` | The type of service for the intelligence-server. | `ClusterIP` | -| `intelligenceServer.service.port` | The port for the intelligence-server service. | `8282` | -| `intelligenceServer.service.sessionAffinity` | The session affinity for the intelligence-server service. | `None` | -| `intelligenceServer.service.externalTrafficPolicy` | The external traffic policy for the intelligence-server service. | `""` | -| `intelligenceServer.service.internalTrafficPolicy` | The internal traffic policy for the intelligence-server service. | `""` | -| `intelligenceServer.portalDataHost` | The host and port for the portal data service. | `"portal-data:8080"` | - -### Kafka settings - -The Intelligence Server requires a connection to a Kafka cluster. The following settings are used to configure the connection. - -| Parameter | Description | Default | -| --- | --- | --- | -| `intelligenceServer.kafka.autoDiscovery.enabled` | When enabled, an initContainer is used to discover Kafka broker external addresses. | `true` | -| `intelligenceServer.kafka.autoDiscovery.image.repository` | The repository for the auto-discovery initContainer image. | `docker.io/bitnami/kubectl` | -| `intelligenceServer.kafka.autoDiscovery.image.tag` | The tag for the auto-discovery initContainer image. | `1.33.3-debian-12-r0` | -| `intelligenceServer.kafka.autoDiscovery.image.pullPolicy` | The pull policy for the auto-discovery initContainer image. | `IfNotPresent` | -| `intelligenceServer.kafka.autoDiscovery.resources` | The resource requests and limits for the auto-discovery initContainer. | `{}` | -| `intelligenceServer.kafka.externalAdvertisedBrokers` | A comma-separated list of external advertised Kafka brokers. | `""` | -| `intelligenceServer.kafka.kafkaCa.caSecretName` | The name of the secret containing the Kafka CA certificate. | `""` | -| `intelligenceServer.kafka.kafkaCa.passwordSecretName` | The name of the secret containing the password for the Kafka CA certificate. | `""` | -| `intelligenceServer.kafka.kafkaCa.passwordSecretKey` | The key in the password secret that contains the password. | `keypass.txt` | - -**NOTE:** When this chart is used as a subchart, it requires Kafka configuration for its initContainer to discover brokers. The parent chart must provide these values under the `apim-intelligence.kafka` key. - -To avoid duplication, the parent chart's `values.yaml` can use a YAML alias: - -```yaml -kafka: &kafka_config - #... kafka subchart values - -apim-intelligence: - kafka: *kafka_config -``` \ No newline at end of file diff --git a/charts/intelligence/templates/_helpers.tpl b/charts/intelligence/templates/_helpers.tpl deleted file mode 100644 index 20683d09..00000000 --- a/charts/intelligence/templates/_helpers.tpl +++ /dev/null @@ -1,170 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "intelligence.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "intelligence.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "intelligence.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - - -{{/* - Set the service account name for the Intelligence Server - */}} -{{- define "intelligence.serviceAccountName" -}} -{{- if .Values.global.serviceAccountName }} - {{ default "default" .Values.global.serviceAccountName }} -{{- else }} -{{- if .Values.serviceAccount.create -}} - {{ default (include "intelligence.fullname" .) .Values.serviceAccount.name }} -{{- else -}} - {{ default "default" .Values.serviceAccount.name }} -{{- end -}} -{{- end -}} -{{- end -}} - - -{{/* -Get "intelligence" database name -*/}} -{{- define "intelligence-db-name" -}} - {{ if .Values.global.legacyDatabaseNames }} - {{- print "intelligence" }} - {{- else }} - {{- $f:= .Values.global.subdomainPrefix -}} - {{ if empty $f }} - {{- fail "Please define subdomainPrefix in values.yaml" }} - {{- else }} - {{- printf "%s_%s" $f "intelligence" | replace "-" "_" -}} - {{- end }} - {{- end }} -{{- end -}} - -{{/* -Get "database-port" based on databaseType value -*/}} -{{- define "database-port" -}} - {{- print .Values.global.databasePort -}} -{{- end -}} - -{{/* -Get "kafka" brokers -*/}} -{{- define "kafka-brokers" -}} - {{- $kafkaName := "kafka" -}} - {{- if .Values.kafka.fullnameOverride -}} - {{- $kafkaName = .Values.kafka.fullnameOverride -}} - {{- else -}} - {{- $kafkaName = printf "%s-kafka" .Release.Name -}} - {{- end -}} - {{- if and .Values.kafka.kafka .Values.kafka.kafka.listeners }} - {{- /* Custom Kafka subchart */ -}} - {{- printf "%s:%g" $kafkaName .Values.kafka.kafka.listeners.internal.port -}} - {{- else if and .Values.kafka.listeners .Values.kafka.listeners.client }} - {{- /* Bitnami Kafka chart */ -}} - {{- printf "%s:%g" $kafkaName .Values.kafka.listeners.client.containerPort -}} - {{- else }} - {{- /* Default fallback */ -}} - {{- printf "%s:9092" $kafkaName -}} - {{- end }} -{{- end -}} - -{{/* -Create Image Pull Secret -*/}} -{{- define "intelligence-imagePullSecret" }} -{{- if and (not .Values.useExistingPullSecret) (.Values.imagePullSecret.enabled) }} -{{- printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"auth\":\"%s\"}}}" .Values.global.portalRepository .Values.imagePullSecret.username .Values.imagePullSecret.password (printf "%s:%s" .Values.imagePullSecret.username .Values.imagePullSecret.password | b64enc) | b64enc }} -{{- end }} -{{- end }} - -{{- define "intelligence.validate" -}} -{{- $messages := list -}} -{{- $messages := append $messages (include "intelligence.validateValues.autoDiscoveryRBAC" .) -}} -{{- $message := join "\n" $messages -}} -{{- if $message -}} -{{- printf "\nVALUES VALIDATION:\n%s" $message | fail -}} -{{- end -}} -{{- end -}} - -{{/* Validate values of intelligence - RBAC should be enabled when autoDiscovery is enabled */}} -{{- define "intelligence.validateValues.autoDiscoveryRBAC" -}} -{{- if and .Values.intelligenceServer.kafka.autoDiscovery.enabled (not .Values.rbac.create ) }} -intelligence: rbac-create - By specifying ".Values.intelligenceServer.kafka.autoDiscovery.enabled=true" - an initContainer will be used to auto-detect the external IPs/ports by querying the - K8s API. Please note this initContainer requires specific RBAC resources. -{{- end -}} -{{- if and .Values.intelligenceServer.kafka.autoDiscovery.enabled (not .Values.serviceAccount.automountServiceAccountToken) }} -intelligence: serviceAccount-automountServiceAccountToken - By specifying ".Values.intelligenceServer.kafka.autoDiscovery.enabled=true" - an initContainer will be used to auto-detect the external IPs/ports by querying the - K8s API. Please note this initContainer requires the service account token. Please set serviceAccount.automountServiceAccountToken=true -{{- end -}} -{{- end -}} - -{{/* -Generate Intelligence public host based on global configurations -*/}} -{{- define "intelligence.publicHost" -}} - {{- /* When deployed as subchart, check if portal.domain is set and use it */ -}} - {{- /* This allows Jenkins to set portal.domain without also setting global.domain */ -}} - {{- $domain := "dev.ca.com" -}} - {{- if .Values.portal -}} - {{- if .Values.portal.domain -}} - {{- /* Subchart: use portal.domain if explicitly set */ -}} - {{- $domain = .Values.portal.domain -}} - {{- else -}} - {{- /* Fallback to global.domain */ -}} - {{- $domain = default "dev.ca.com" .Values.global.domain -}} - {{- end -}} - {{- else -}} - {{- /* Standalone: use global.domain */ -}} - {{- $domain = default "dev.ca.com" .Values.global.domain -}} - {{- end -}} - {{- $subdomainPrefix := default "dev-portal" .Values.global.subdomainPrefix -}} - {{- $defaultTenantId := default "apim" .Values.global.defaultTenantId -}} - {{- if .Values.global.legacyHostnames }} - {{- printf "%s-%s.%s" $defaultTenantId "ssg" $domain -}} - {{- else if .Values.global.saas }} - {{- printf "apim-ssg-%s.%s" $subdomainPrefix $domain -}} - {{- else }} - {{- printf "%s-ssg.%s" $subdomainPrefix $domain -}} - {{- end }} -{{- end -}} - -{{/* -Generate Intelligence public port -Defaults to 443 for HTTPS, or from global.papiPublicPort if set -*/}} -{{- define "intelligence.publicPort" -}} - {{- if .Values.global.papiPublicPort }} - {{- .Values.global.papiPublicPort -}} - {{- else }} - {{- "443" -}} - {{- end }} -{{- end -}} \ No newline at end of file diff --git a/charts/intelligence/templates/server/kafka-discovery-configmap.yaml b/charts/intelligence/templates/server/kafka-discovery-configmap.yaml deleted file mode 100644 index 2db93e52..00000000 --- a/charts/intelligence/templates/server/kafka-discovery-configmap.yaml +++ /dev/null @@ -1,44 +0,0 @@ -{{- if .Values.intelligenceServer.kafka.autoDiscovery.enabled }} -# -# Kafka Discovery ConfigMap -# -# Purpose: -# This ConfigMap stores dynamically discovered Kafka broker information that is -# populated by the kafka-broker-discovery init container at runtime. -# -# What it contains: -# - KAFKA_EXTERNAL_ADVERTIZED_BROKERS: Comma-separated list of Kafka broker IPs/hostnames -# discovered from LoadBalancer services (e.g., "10.252.148.75:9094,10.252.148.43:9094") -# - PAPI_PUBLIC_HOST: The public hostname for the API gateway, constructed from the -# namespace and portal domain (e.g., "mr6-ssg.gke-us-west-1-cluster-01.apim.broadcom.net") -# -# How it works: -# 1. Init container discovers Kafka LoadBalancer IPs and portal domain at pod startup -# 2. Init container creates/updates this ConfigMap with the discovered values -# 3. Main intelligence-server container reads these values via envFrom -# 4. Intelligence server uses these values to provide Kafka connection info to clients -# -# Why ConfigMap instead of files: -# - Works with distroless images (no shell required) -# - Kubernetes-native approach -# - Easy to inspect with kubectl -# - No volume mount dependencies -# -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "intelligence.fullname" . }}-kafka-discovery - namespace: {{ .Release.Namespace }} - labels: - app: intelligence-server - component: kafka-discovery - chart: {{ template "intelligence.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - annotations: - "helm.sh/hook": "pre-install,pre-upgrade" - "helm.sh/hook-delete-policy": "before-hook-creation" - "helm.sh/hook-weight": "-5" -data: - KAFKA_EXTERNAL_ADVERTIZED_BROKERS: "" -{{- end }} \ No newline at end of file diff --git a/charts/intelligence/templates/server/server-deployment.yml b/charts/intelligence/templates/server/server-deployment.yml deleted file mode 100644 index c48863a4..00000000 --- a/charts/intelligence/templates/server/server-deployment.yml +++ /dev/null @@ -1,415 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: intelligence-server - labels: - app: intelligence-server - chart: {{ template "intelligence.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - {{- range $key, $val := .Values.global.additionalLabels }} - {{ $key }}: "{{ $val }}" - {{- end }} - {{- range $key, $val := .Values.intelligenceServer.additionalLabels }} - {{ $key }}: "{{ $val }}" - {{- end }} -spec: - strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: 1 - maxUnavailable: 1 - replicas: {{ .Values.intelligenceServer.replicaCount }} - selector: - matchLabels: - app: intelligence-server - template: - metadata: - labels: - app: intelligence-server - release: {{ .Release.Name }} - {{- if .Values.intelligenceServer.podAnnotations }} - annotations: {{- toYaml .Values.intelligenceServer.podAnnotations | nindent 8 }} - {{- if .Values.intelligenceServer.forceRedeploy }} - timestamp: {{ now | quote }} - {{- end }} - {{- end }} - {{- if not .Values.intelligenceServer.podAnnotations }} - {{- if .Values.intelligenceServer.forceRedeploy }} - annotations: - timestamp: {{ now | quote }} - {{- end }} - {{- end }} - spec: - serviceAccountName: {{ include "intelligence.serviceAccountName" . }} - automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} - {{- if .Values.intelligenceServer.affinity }} - affinity: {{- toYaml .Values.intelligenceServer.affinity | nindent 12 }} - {{- end }} - {{- if .Values.intelligenceServer.nodeSelector }} - nodeSelector: {{- toYaml .Values.intelligenceServer.nodeSelector | nindent 12 }} - {{- end }} - {{- if .Values.intelligenceServer.tolerations }} - tolerations: {{- toYaml .Values.intelligenceServer.tolerations | nindent 12 }} - {{- end }} - {{- if .Values.intelligenceServer.podSecurityContext }} - securityContext: {{- toYaml .Values.intelligenceServer.podSecurityContext | nindent 12 }} - {{- else if .Values.global.podSecurityContext }} - securityContext: {{- toYaml .Values.global.podSecurityContext | nindent 12 }} - {{- end }} - {{- if .Values.intelligenceServer.kafka.autoDiscovery.enabled }} - {{ template "intelligence.validate" .}} - initContainers: - - name: kafka-broker-discovery - image: "{{ .Values.global.portalRepository }}{{ .Values.image.autoDiscovery }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: - - /bin/sh - - -ec - - | - # This script discovers the external addresses of Kafka brokers - # and writes them to a shared volume for the main container to use. - RELEASE_NAME="{{ .Release.Name }}" - BROKER_LIST="" - - # Function to retry a command (sh-compatible) - retry_while() { - cmd="${1:?cmd is missing}" - retries="${2:-12}" - sleep_time="${3:-5}" - return_value=1 - i=1 - while [ "$i" -le "$retries" ]; do - if eval "$cmd"; then - return_value=0 - break - fi - echo "Retrying in ${sleep_time}s... (${i}/${retries})" - sleep "$sleep_time" - i=$((i + 1)) - done - return $return_value - } - - # Function to get LoadBalancer IP or Hostname from a service (sh-compatible) - k8s_svc_lb_ip() { - service_ip=$(kubectl get svc "$1" -n "$2" -o jsonpath="{.status.loadBalancer.ingress[0].ip}" 2>/dev/null || echo "") - service_hostname=$(kubectl get svc "$1" -n "$2" -o jsonpath="{.status.loadBalancer.ingress[0].hostname}" 2>/dev/null || echo "") - if [ -n "$service_ip" ]; then - echo "$service_ip" - elif [ -n "$service_hostname" ]; then - echo "$service_hostname" - else - return 1 - fi - } - - discover_broker() { - SVC_NAME=$1 - BROKER_ENDPOINT="" - - echo "Discovering broker: ${SVC_NAME}" - - if ! kubectl get svc "$SVC_NAME" -n "$MY_POD_NAMESPACE" > /dev/null 2>&1; then - echo "Service $SVC_NAME not found, skipping." - return - fi - - AUTODISCOVERY_SERVICE_TYPE=$(kubectl get svc "$SVC_NAME" -n "$MY_POD_NAMESPACE" -o jsonpath='{.spec.type}' 2>/dev/null || echo "") - - if [ "$AUTODISCOVERY_SERVICE_TYPE" = "LoadBalancer" ]; then - if ! retry_while "kubectl get svc $SVC_NAME -n $MY_POD_NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[0]}'" ; then - echo "Timed out waiting for LoadBalancer IP for service $SVC_NAME" - exit 1 - fi - EXTERNAL_ADDRESS=$(k8s_svc_lb_ip "$SVC_NAME" "$MY_POD_NAMESPACE") - EXTERNAL_PORT=$(kubectl get svc "$SVC_NAME" -n "$MY_POD_NAMESPACE" -o jsonpath="{.spec.ports[0].port}" 2>/dev/null || echo "") - BROKER_ENDPOINT="${EXTERNAL_ADDRESS}:${EXTERNAL_PORT}" - elif [ "$AUTODISCOVERY_SERVICE_TYPE" = "NodePort" ]; then - NODE_PORT=$(kubectl get svc "$SVC_NAME" -n "$MY_POD_NAMESPACE" -o jsonpath="{.spec.ports[0].nodePort}" 2>/dev/null || echo "") - NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}' 2>/dev/null || kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null || echo "") - if [ -n "$NODE_IP" ] && [ -n "$NODE_PORT" ]; then - BROKER_ENDPOINT="${NODE_IP}:${NODE_PORT}" - fi - fi - - if [ -n "$BROKER_ENDPOINT" ]; then - if [ -z "$BROKER_LIST" ]; then - BROKER_LIST="$BROKER_ENDPOINT" - else - BROKER_LIST="$BROKER_LIST,$BROKER_ENDPOINT" - fi - fi - } - - # Discover Kafka broker nodes - Aligned with StatefulSet ordinals - # For custom Kafka subchart - {{- if and .Values.kafka (hasKey .Values.kafka "kafka") .Values.kafka.kafka .Values.kafka.kafka.replicaCount }} - BROKER_COUNT={{ .Values.kafka.kafka.replicaCount }} - {{- $ordinalStart := 0 }} - {{- if and .Values.kafka.kafka.ordinals (hasKey .Values.kafka.kafka.ordinals "start") }} - {{- $ordinalStart = int .Values.kafka.kafka.ordinals.start }} - {{- end }} - ORDINAL_START={{ $ordinalStart }} - # Use kafka.fullnameOverride if set, otherwise construct from release name - {{- $kafkaFullname := .Values.kafka.fullnameOverride | default (printf "%s-kafka" .Release.Name) }} - KAFKA_FULLNAME="{{ $kafkaFullname }}" - for i in $(seq $ORDINAL_START $((ORDINAL_START + BROKER_COUNT - 1))); do - SVC_NAME="${KAFKA_FULLNAME}-${i}-external" - discover_broker "$SVC_NAME" - done - {{- else if and .Values.kafka (hasKey .Values.kafka "controller") .Values.kafka.controller }} - # For Bitnami Kafka chart (backward compatibility) - {{- $kafkaFullname := .Values.kafka.fullnameOverride | default (printf "%s-kafka" .Release.Name) }} - KAFKA_FULLNAME="{{ $kafkaFullname }}" - {{- if and .Values.kafka.controller.replicaCount (not .Values.kafka.controller.controllerOnly) }} - CONTROLLER_BROKER_COUNT={{ .Values.kafka.controller.replicaCount }} - for i in $(seq 0 $((CONTROLLER_BROKER_COUNT - 1))); do - SVC_NAME="${KAFKA_FULLNAME}-controller-${i}-external" - discover_broker "$SVC_NAME" - done - {{- end }} - - # Discover dedicated broker nodes - {{- if and (hasKey .Values.kafka "broker") .Values.kafka.broker .Values.kafka.broker.replicaCount }} - BROKER_COUNT={{ .Values.kafka.broker.replicaCount }} - if [ "$BROKER_COUNT" -gt 0 ]; then - for i in $(seq 0 $((BROKER_COUNT - 1))); do - SVC_NAME="${KAFKA_FULLNAME}-broker-${i}-external" - discover_broker "$SVC_NAME" - done - fi - {{- end }} - {{- else }} - # Fallback: No Kafka configuration found - echo "Warning: No Kafka broker configuration found" - {{- end }} - - echo "Discovered Kafka brokers: ${BROKER_LIST}" - - # Discover portal domain from portal-data ConfigMap - PORTAL_DOMAIN="" - PORTAL_CONFIGMAP="portal-data-config" - if kubectl get configmap "$PORTAL_CONFIGMAP" -n "$MY_POD_NAMESPACE" >/dev/null 2>&1; then - PORTAL_DOMAIN=$(kubectl get configmap "$PORTAL_CONFIGMAP" -n "$MY_POD_NAMESPACE" -o jsonpath='{.data.PORTAL_SUBDOMAIN}' 2>/dev/null || echo "") - if [ -n "$PORTAL_DOMAIN" ]; then - echo "Discovered portal domain: ${PORTAL_DOMAIN}" - else - echo "Warning: PORTAL_SUBDOMAIN not found in ${PORTAL_CONFIGMAP}, using default" - fi - else - echo "Warning: ConfigMap ${PORTAL_CONFIGMAP} not found, PAPI_PUBLIC_HOST may not be set correctly" - fi - - # Construct PAPI_PUBLIC_HOST from namespace and discovered domain - # Format: {namespace}-ssg.{domain} - if [ -n "$PORTAL_DOMAIN" ]; then - PAPI_PUBLIC_HOST="${MY_POD_NAMESPACE}-ssg.${PORTAL_DOMAIN}" - echo "PAPI_PUBLIC_HOST will be set to: ${PAPI_PUBLIC_HOST}" - else - echo "ERROR: Could not discover portal domain from ${PORTAL_CONFIGMAP}" - echo "PAPI_PUBLIC_HOST will not be set" - PAPI_PUBLIC_HOST="" - fi - - # Update ConfigMap for distroless compatibility - CONFIGMAP_NAME="{{ include "intelligence.fullname" . }}-kafka-discovery" - echo "Updating ConfigMap ${CONFIGMAP_NAME}..." - - if [ -n "$PAPI_PUBLIC_HOST" ]; then - # Both Kafka brokers and PAPI_PUBLIC_HOST discovered - if kubectl get configmap "$CONFIGMAP_NAME" -n "$MY_POD_NAMESPACE" >/dev/null 2>&1; then - kubectl patch configmap "$CONFIGMAP_NAME" -n "$MY_POD_NAMESPACE" \ - --patch "{\"data\":{\"KAFKA_EXTERNAL_ADVERTIZED_BROKERS\":\"${BROKER_LIST}\",\"PAPI_PUBLIC_HOST\":\"${PAPI_PUBLIC_HOST}\"}}" - echo "ConfigMap ${CONFIGMAP_NAME} updated successfully" - else - kubectl create configmap "$CONFIGMAP_NAME" -n "$MY_POD_NAMESPACE" \ - --from-literal="KAFKA_EXTERNAL_ADVERTIZED_BROKERS=${BROKER_LIST}" \ - --from-literal="PAPI_PUBLIC_HOST=${PAPI_PUBLIC_HOST}" - echo "ConfigMap ${CONFIGMAP_NAME} created successfully" - fi - else - # Only Kafka brokers discovered (PAPI_PUBLIC_HOST discovery failed) - if kubectl get configmap "$CONFIGMAP_NAME" -n "$MY_POD_NAMESPACE" >/dev/null 2>&1; then - kubectl patch configmap "$CONFIGMAP_NAME" -n "$MY_POD_NAMESPACE" \ - --patch "{\"data\":{\"KAFKA_EXTERNAL_ADVERTIZED_BROKERS\":\"${BROKER_LIST}\"}}" - echo "ConfigMap ${CONFIGMAP_NAME} updated (without PAPI_PUBLIC_HOST)" - else - kubectl create configmap "$CONFIGMAP_NAME" -n "$MY_POD_NAMESPACE" \ - --from-literal="KAFKA_EXTERNAL_ADVERTIZED_BROKERS=${BROKER_LIST}" - echo "ConfigMap ${CONFIGMAP_NAME} created (without PAPI_PUBLIC_HOST)" - fi - fi - - # Keep file for backward compatibility - echo "export KAFKA_EXTERNAL_ADVERTIZED_BROKERS=${BROKER_LIST}" > /shared/kafka.env - if [ -n "$PAPI_PUBLIC_HOST" ]; then - echo "export PAPI_PUBLIC_HOST=${PAPI_PUBLIC_HOST}" >> /shared/kafka.env - fi - echo "Kafka discovery completed successfully" - env: - - name: MY_POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - volumeMounts: - - name: shared-data - mountPath: /shared - {{- end }} - containers: - - name: intelligence-server - image: "{{ .Values.global.portalRepository }}{{ .Values.image.intelligenceServer }}" - imagePullPolicy: "{{ .Values.image.pullPolicy }}" - {{- if .Values.intelligenceServer.containerSecurityContext }} - securityContext: {{- toYaml .Values.intelligenceServer.containerSecurityContext | nindent 12 }} - {{- else if .Values.global.containerSecurityContext }} - securityContext: {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} - {{- end }} - # Distroless compatible: no shell, direct Java execution - command: ["java"] - args: ["-jar", "/opt/app/intelligence-server.jar"] - env: - - name: RABBITMQ_DEFAULT_PASS - valueFrom: - secretKeyRef: - name: {{ .Values.rabbitmq.auth.secretName }} - key: rabbitmq-password - optional: false - - name: INTELLIGENCE_DATABASE_NAME - value: {{ template "intelligence-db-name" . }} - - name: DATABASE_USERNAME - value: {{ .Values.global.databaseUsername | quote }} - - name: DATABASE_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Values.global.databaseSecret }} - {{ if eq .Values.global.databaseType "mysql" }} - key: mysql-password - {{- end }} - # Internal Kafka connection (for intelligence-server's own use) - - name: KAFKA_BROKERS - value: {{ .Values.intelligenceServer.kafka.brokers | default "kafka:9092" | quote }} - - - name: KAFKA_SECURITY_PROTOCOL - value: {{ .Values.intelligenceServer.kafka.securityProtocol | default "PLAINTEXT" | quote }} - # Certificates that intelligence-server provides to third-party clients - {{- if .Values.intelligenceServer.kafka.kafkaCa.caSecretName }} - - name: KAFKA_CA_CERTIFICATE - valueFrom: - secretKeyRef: - name: {{ .Values.intelligenceServer.kafka.kafkaCa.caSecretName }} - key: {{ .Values.intelligenceServer.kafka.kafkaCa.caCertKey | default "apim-ssl.crt" }} - optional: false - - name: KAFKA_CA_KEY - valueFrom: - secretKeyRef: - name: {{ .Values.intelligenceServer.kafka.kafkaCa.caSecretName }} - key: {{ .Values.intelligenceServer.kafka.kafkaCa.caKeyKey | default "apim-ssl.key" }} - optional: false - - name: KAFKA_CA_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Values.intelligenceServer.kafka.kafkaCa.passwordSecretName }} - key: {{ .Values.intelligenceServer.kafka.kafkaCa.passwordSecretKey | default "keypass.txt" }} - optional: false - - name: TSSG_SSL_KEY_PASS - valueFrom: - secretKeyRef: - name: {{ .Values.intelligenceServer.kafka.kafkaCa.passwordSecretName }} - key: {{ .Values.intelligenceServer.kafka.kafkaCa.passwordSecretKey | default "keypass.txt" }} - optional: false - {{- else }} - # No certificates provided - - name: KAFKA_CA_CERTIFICATE - value: "" - - name: KAFKA_CA_KEY - value: "" - - name: KAFKA_CA_PASSWORD - value: "" - - name: TSSG_SSL_KEY_PASS - value: "" - {{- end }} - # Kafka External Advertised Brokers (for user-facing connections) - {{- if not .Values.intelligenceServer.kafka.autoDiscovery.enabled }} - {{- if .Values.intelligenceServer.kafka.externalAdvertisedBrokers }} - - name: KAFKA_EXTERNAL_ADVERTIZED_BROKERS - value: {{ .Values.intelligenceServer.kafka.externalAdvertisedBrokers | quote }} - {{- end }} - {{- end }} - {{- if .Values.intelligenceServer.env }} - {{- range $key, $value := .Values.intelligenceServer.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - {{- end }} - {{- if .Values.intelligenceServer.kafka.autoDiscovery.enabled }} - # Read PAPI_PUBLIC_HOST from ConfigMap populated by kafka-broker-discovery init container - # The init container discovers the portal domain from portal-data ConfigMap at runtime - # Format: {namespace}-ssg.{domain} - - name: PAPI_PUBLIC_HOST - valueFrom: - configMapKeyRef: - name: {{ include "intelligence.fullname" . }}-kafka-discovery - key: PAPI_PUBLIC_HOST - - name: PAPI_PUBLIC_PORT - value: {{ include "intelligence.publicPort" . | quote }} - {{- end }} - envFrom: - # Always include the main intelligence-server config - - configMapRef: - name: intelligence-server-config - {{- if .Values.intelligenceServer.kafka.autoDiscovery.enabled }} - # Include discovered Kafka broker IPs when auto-discovery is enabled - - configMapRef: - name: {{ include "intelligence.fullname" . }}-kafka-discovery - optional: true - {{- end }} - readinessProbe: - httpGet: - path: "/health/readiness" - port: 8282 - initialDelaySeconds: 90 - timeoutSeconds: 1 - periodSeconds: 15 - successThreshold: 1 - livenessProbe: - httpGet: - path: "/health/liveness" - port: 8282 - initialDelaySeconds: 120 - timeoutSeconds: 1 - periodSeconds: 15 - successThreshold: 1 - {{- if .Values.intelligenceServer.resources }} - resources: {{- toYaml .Values.intelligenceServer.resources | nindent 12 }} - {{- end }} - {{- if .Values.intelligenceServer.kafka.autoDiscovery.enabled }} - volumeMounts: - - name: shared-data - mountPath: /shared - {{- end }} - ports: - - containerPort: 8282 - volumes: - {{- if .Values.intelligenceServer.kafka.autoDiscovery.enabled }} - - name: shared-data - emptyDir: {} - {{- end }} - {{- if .Values.global.pullSecret }} - imagePullSecrets: - - name: "{{ .Values.global.pullSecret }}" - {{- end }} - {{- if .Values.intelligenceServer.affinity }} - affinity: {{- toYaml .Values.intelligenceServer.affinity | nindent 8 }} - {{- end }} - {{- if .Values.intelligenceServer.nodeSelector }} - nodeSelector: {{- toYaml .Values.intelligenceServer.nodeSelector | nindent 8 }} - {{- end }} - {{- if .Values.intelligenceServer.tolerations }} - tolerations: {{- toYaml .Values.intelligenceServer.tolerations | nindent 8 }} - {{- end }} - {{- if .Values.global.schedulerName }} - schedulerName: "{{ .Values.global.schedulerName }}" - {{- end }} - restartPolicy: Always - terminationGracePeriodSeconds: 30 \ No newline at end of file diff --git a/charts/intelligence/templates/server/server-service.yml b/charts/intelligence/templates/server/server-service.yml deleted file mode 100644 index 01ad2239..00000000 --- a/charts/intelligence/templates/server/server-service.yml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: intelligence-server - labels: - app: intelligence-server - chart: {{ template "intelligence.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - type: {{ .Values.intelligenceServer.service.type }} - ports: - - name: http - port: {{ .Values.intelligenceServer.service.port }} - targetPort: 8282 - selector: - app: intelligence-server - release: {{ .Release.Name }} - {{- if .Values.intelligenceServer.service.sessionAffinity }} - sessionAffinity: {{ .Values.intelligenceServer.service.sessionAffinity }} - {{- end }} - {{- if .Values.intelligenceServer.service.sessionAffinityConfig }} - sessionAffinityConfig: {{- toYaml .Values.intelligenceServer.service.sessionAffinityConfig | nindent 4 }} - {{- end }} - {{- if .Values.intelligenceServer.service.externalTrafficPolicy }} - externalTrafficPolicy: {{ .Values.intelligenceServer.service.externalTrafficPolicy }} - {{- end }} - {{- if .Values.intelligenceServer.service.internalTrafficPolicy }} - internalTrafficPolicy: {{ .Values.intelligenceServer.service.internalTrafficPolicy }} - {{- end }} \ No newline at end of file diff --git a/charts/intelligence/templates/service-accounts/role.yaml b/charts/intelligence/templates/service-accounts/role.yaml deleted file mode 100644 index 07576171..00000000 --- a/charts/intelligence/templates/service-accounts/role.yaml +++ /dev/null @@ -1,33 +0,0 @@ -{{- if and .Values.rbac.create .Values.serviceAccount.create (not .Values.global.serviceAccountName) }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ include "intelligence.fullname" . }} - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "intelligence.name" . }} - chart: {{ template "intelligence.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -rules: - - apiGroups: - - "" - resources: - - services - verbs: - - get - - list - - watch - {{- if .Values.intelligenceServer.kafka.autoDiscovery.enabled }} - - apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - create - - update - - patch - {{- end }} -{{- end }} \ No newline at end of file diff --git a/charts/intelligence/templates/service-accounts/service-account.yml b/charts/intelligence/templates/service-accounts/service-account.yml deleted file mode 100644 index 2c798f0a..00000000 --- a/charts/intelligence/templates/service-accounts/service-account.yml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if and .Values.serviceAccount.create (not .Values.global.serviceAccountName) }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "intelligence.serviceAccountName" . }} - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "intelligence.name" . }} - chart: {{ template "intelligence.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -secrets: - - name: {{ include "intelligence.fullname" . }} -automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} -{{- end }} \ No newline at end of file diff --git a/charts/intelligence/values.yaml b/charts/intelligence/values.yaml deleted file mode 100644 index 2bc9848c..00000000 --- a/charts/intelligence/values.yaml +++ /dev/null @@ -1,137 +0,0 @@ -# Default values for intelligence. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -nameOverride: "" -fullnameOverride: "" - -# Force redeployment of statefulsets and deployments on upgrade -forceRedeploy: false - -# Image configuration -# Note: When used as a subchart, these can be overridden by parent chart values -image: - autoDiscovery: "tls-automator:latest" # Image used for Kafka broker discovery init container - intelligenceServer: "intelligence-server:latest" - pullPolicy: IfNotPresent - -# ServiceAccount configuration -serviceAccount: - create: true - name: "" - automountServiceAccountToken: true - -# RBAC configuration -rbac: - create: true - -# RabbitMQ configuration -# Note: When used as a subchart, these values are typically provided by the parent chart -rabbitmq: - auth: - secretName: "rabbitmq" - username: "user" - service: - port: 5672 - -# Kafka configuration -# Note: When used as a subchart, kafka configuration is typically provided by the parent chart -# This section is required for the kafka-brokers helper template -kafka: - fullnameOverride: "kafka" - -# Global values that are typically provided by a parent chart. -# These are included here to allow the chart to be deployed standalone. -global: - portalRepository: caapim/ - pullSecret: "" # Name of an existing image pull secret - # Database settings for the intelligence service - databaseType: mysql - databaseSecret: "intelligence-db-secret" # A secret containing the database password - databaseUsername: "intelligence_user" - databaseHost: "mysql" - databasePort: 3306 - databaseUseSSL: true - databaseRequireSSL: false - # Used for constructing database names - legacyDatabaseNames: false - subdomainPrefix: "dev-portal" - # Pod and container security contexts - podSecurityContext: {} - # fsGroup: 1001 - containerSecurityContext: {} - # runAsUser: 1001 - # schedulerName to be applied to pods - schedulerName: "" - # Additional labels to be applied to all resources - additionalLabels: {} - -# Intelligence Server configuration -intelligenceServer: - replicaCount: 1 - kafka: - # Internal Kafka brokers (for intelligence-server's own consumption) - brokers: "kafka:9092" # Internal PLAINTEXT connection - - # Security protocol for intelligence-server's connection - securityProtocol: PLAINTEXT # Intelligence connects internally without SSL - - # Auto-discovery configuration (discovers EXTERNAL IPs for third-party clients) - autoDiscovery: - # When enabled, discovers external LoadBalancer IPs that intelligence-server provides to clients - enabled: true - # Resource requests and limits for the auto-discovery (no longer used) - # resources: {} - - externalAdvertisedBrokers: "" # Populated by auto-discovery - - # Certificates that intelligence-server provides to third-party clients - kafkaCa: - caSecretName: portal-external-secret # Certificates for clients (changed from portal-internal-secret) - passwordSecretName: portal-external-secret - passwordSecretKey: "keypass.txt" - # Keys in the secret for TSSG cert/key (uses apim-ssl certificate with CN=tssg) - # These can be overridden if using custom certificates - caCertKey: "apim-ssl.crt" # Default to apim-ssl, can be overridden - caKeyKey: "apim-ssl.key" # Use .key for intelligence, not .p8.key - # Additional labels for the intelligence deployment - additionalLabels: {} - # Pod affinity, node selector, and tolerations - affinity: {} - nodeSelector: {} - tolerations: [] - # Security contexts for the pod and container - podSecurityContext: {} - containerSecurityContext: {} - # Resource requests and limits - resources: {} - # requests: - # cpu: 200m - # memory: 512Mi - # limits: - # cpu: 500m - # memory: 1Gi - # Service configuration - service: - type: ClusterIP - port: 8282 - sessionAffinity: None - # sessionAffinityConfig: - # clientIP: - # timeoutSeconds: 10800 - externalTrafficPolicy: "" - internalTrafficPolicy: "" - # The host and port for the portal data service - portalDataHost: "portal-data:8080" - -# NOTE: When this chart is used as a subchart, it requires Kafka configuration -# for its initContainer to discover brokers. The parent chart must provide these -# values under the 'apim-intelligence.kafka' key. -# -# To avoid duplication, the parent chart's `values.yaml` can use a YAML alias: -# -# kafka: &kafka_config -# #... kafka subchart values -# -# apim-intelligence: -# kafka: *kafka_config diff --git a/charts/kafka/Chart.yaml b/charts/kafka/Chart.yaml index f1fec618..a5110f58 100644 --- a/charts/kafka/Chart.yaml +++ b/charts/kafka/Chart.yaml @@ -7,5 +7,5 @@ maintainers: - name: Gazza7205 sources: - https://github.com/CAAPIM/apim-charts -version: 1.0.0 +version: 1.0.1 appVersion: "4.0.0" diff --git a/charts/kafka/README.md b/charts/kafka/README.md index 69991281..34853f78 100644 --- a/charts/kafka/README.md +++ b/charts/kafka/README.md @@ -10,12 +10,9 @@ This Kafka subchart provides a production-ready deployment of Apache Kafka 4.0.0 - **KRaft Mode**: Zookeeper-less Kafka using the new KRaft consensus protocol - **Kafka 4.0.0**: Latest stable version with improved performance and features -- **Autodiscovery Support**: External services for each broker enabling autodiscovery by Intelligence service -- **Multi-Listener Support**: Internal, External, and Controller listeners with configurable security -- **SSL/TLS Support**: Full TLS encryption with mTLS client authentication +- **Multi-Listener Support**: Internal and Controller listeners with configurable security - **SASL Authentication**: Support for PLAIN, SCRAM-SHA-256, and SCRAM-SHA-512 - **Flexible Deployment**: StatefulSet with configurable replicas -- **External Access**: LoadBalancer or NodePort services for external connectivity - **RBAC Support**: ServiceAccount for Kubernetes API access ## Quick Start @@ -44,9 +41,6 @@ kafka: limits: cpu: 2000m memory: 4Gi - externalAccess: - enabled: true - serviceType: LoadBalancer ``` ## Configuration @@ -67,38 +61,17 @@ kafka: | `kafka.listeners.internal.enabled` | Enable internal listener | `true` | | `kafka.listeners.internal.port` | Internal listener port | `9092` | | `kafka.listeners.internal.protocol` | Internal listener protocol | `PLAINTEXT` | -| `kafka.listeners.external.enabled` | Enable external listener | `true` | -| `kafka.listeners.external.port` | External listener port | `9094` | -| `kafka.listeners.external.protocol` | External listener protocol | `SSL` | | `kafka.listeners.controller.enabled` | Enable controller listener | `true` | | `kafka.listeners.controller.port` | Controller listener port | `9093` | | `kafka.listeners.controller.protocol` | Controller listener protocol | `PLAINTEXT` | | `kafka.listeners.interBrokerListenerName` | Inter-broker listener name | `INTERNAL` | | **Retention Configuration** | | `kafka.logRetentionHours` | Log retention period in hours | `6` | -| **TLS/SSL Configuration** | -| `kafka.tls.enabled` | Enable TLS/SSL | `false` | -| `kafka.tls.type` | TLS certificate type (PEM or JKS) | `PEM` | -| `kafka.tls.clientAuth` | Client authentication (none, requested, required) | `required` | -| `kafka.tls.secretName` | Secret containing TLS certificates | `""` | -| `kafka.tls.keystoreKeyKey` | Key for keystore in secret | `"keystore.key"` | -| `kafka.tls.truststoreCertKey` | Key for truststore in secret | `"truststore.pem"` | -| `kafka.tls.passwordSecretName` | Secret containing key password | `""` | -| `kafka.tls.passwordSecretKey` | Key for password in secret | `"keypass.txt"` | | **SASL Configuration** | | `kafka.sasl.enabled` | Enable SASL authentication | `false` | | `kafka.sasl.mechanisms` | SASL mechanisms | `PLAIN` | | `kafka.sasl.interBrokerProtocol` | Inter-broker SASL mechanism | `PLAIN` | | `kafka.sasl.jaasConfigPath` | JAAS configuration file path | `/opt/ca/kafka/config/kafka_server_jaas.conf` | -| **External Access** | -| `externalAccess.enabled` | Enable external access | `true` | -| `externalAccess.serviceType` | Service type (LoadBalancer or NodePort) | `LoadBalancer` | -| `externalAccess.port` | External port | `9094` | -| `externalAccess.hostname` | External hostname for advertised listeners | `""` | -| `externalAccess.annotations` | Annotations for external services | `{}` | -| `externalAccess.loadBalancerIPs` | Static LoadBalancer IPs | `[]` | -| `externalAccess.nodePorts` | NodePort values | `[]` | -| `externalAccess.autoAdvertisedListeners` | Auto-generate advertised listeners | `true` | | **Persistence** | | `persistence.storage.kafka` | Storage size per broker | `10Gi` | | **Service Account** | @@ -142,107 +115,28 @@ Each Kafka pod is automatically assigned a node ID based on its StatefulSet ordi ## Listener Configuration -### Three-Listener Architecture +### Two-Listener Architecture -The chart configures three listeners: +The chart configures two listeners: 1. **INTERNAL** (port 9092): For inter-broker and internal client communication -2. **EXTERNAL** (port 9094): For external clients (SSL encrypted) -3. **CONTROLLER** (port 9093): For KRaft controller communication +2. **CONTROLLER** (port 9093): For KRaft controller communication + +External access to Kafka (for tenant gateways) is handled by the `KafkaTcpProxyAssertion` on the Ingress gateway, which proxies Kafka traffic with TLS termination and metadata address rewriting. Kafka itself does not need to be directly exposed outside the cluster. ### Advertised Listeners -Advertised listeners are automatically generated: +Advertised listeners are automatically generated by the init container: **Internal**: `INTERNAL://kafka-{id}.kafka:9092` -**External**: `EXTERNAL://kafka-{id}-external:9094` (or custom hostname) Example for 3 brokers: ``` -kafka-0: INTERNAL://kafka-0.kafka:9092,EXTERNAL://kafka-0-external:9094 -kafka-1: INTERNAL://kafka-1.kafka:9092,EXTERNAL://kafka-1-external:9094 -kafka-2: INTERNAL://kafka-2.kafka:9092,EXTERNAL://kafka-2-external:9094 -``` - -## External Access - -### LoadBalancer (Default) - -Creates individual LoadBalancer services for each broker: - -```yaml -externalAccess: - enabled: true - serviceType: LoadBalancer - port: 9094 - annotations: - cloud.google.com/load-balancer-type: Internal -``` - -Services created: -- `kafka-0-external` → LoadBalancer with external IP -- `kafka-1-external` → LoadBalancer with external IP -- `kafka-2-external` → LoadBalancer with external IP - -### NodePort - -Use NodePort for environments without LoadBalancer support: - -```yaml -externalAccess: - enabled: true - serviceType: NodePort - port: 9094 - nodePorts: - - 30094 - - 30095 - - 30096 -``` - -### Static IPs - -Assign static IPs to LoadBalancers: - -```yaml -externalAccess: - loadBalancerIPs: - - 10.0.0.100 - - 10.0.0.101 - - 10.0.0.102 -``` - -## SSL/TLS Configuration - -### Enable TLS - -```yaml -kafka: - kafka: - tls: - enabled: true - type: PEM - clientAuth: required - secretName: kafka-tls-secret - passwordSecretName: kafka-password-secret -``` - -### Create TLS Secret - -```bash -kubectl create secret generic kafka-tls-secret \ - --from-file=keystore.key=kafka-key.pem \ - --from-file=truststore.pem=ca-cert.pem - -kubectl create secret generic kafka-password-secret \ - --from-literal=keypass.txt=your-password +kafka-0: INTERNAL://kafka-0.kafka:9092,CONTROLLER://kafka-0.kafka:9093 +kafka-1: INTERNAL://kafka-1.kafka:9092,CONTROLLER://kafka-1.kafka:9093 +kafka-2: INTERNAL://kafka-2.kafka:9092,CONTROLLER://kafka-2.kafka:9093 ``` -### TLS Certificate Format - -The Kafka container expects PEM format certificates: -- **keystore.key**: Encrypted private key (PKCS#8 format) -- **truststore.pem**: CA certificate chain - ## SASL Authentication ### Enable SASL PLAIN @@ -280,7 +174,7 @@ Mount it in the StatefulSet by customizing the deployment. ## Production Configuration -### 3-Broker Cluster with TLS and External Access +### 3-Broker Cluster ```yaml kafka: @@ -299,10 +193,6 @@ kafka: enabled: true port: 9092 protocol: PLAINTEXT - external: - enabled: true - port: 9094 - protocol: SSL controller: enabled: true port: 9093 @@ -311,13 +201,6 @@ kafka: logRetentionHours: 168 # 7 days - tls: - enabled: true - type: PEM - clientAuth: required - secretName: kafka-tls-secret - passwordSecretName: kafka-password-secret - resources: requests: cpu: 2000m @@ -340,37 +223,8 @@ kafka: persistence: storage: kafka: 100Gi - - externalAccess: - enabled: true - serviceType: LoadBalancer - port: 9094 - annotations: - cloud.google.com/load-balancer-type: Internal - loadBalancerIPs: - - 10.0.0.100 - - 10.0.0.101 - - 10.0.0.102 -``` - -## Autodiscovery Integration - -The Intelligence service uses an init container to discover Kafka broker addresses: - -```yaml -apim-intelligence: - kafka: *kafka_config # Reference Kafka config - intelligenceServer: - kafka: - autoDiscovery: - enabled: true ``` -The autodiscovery looks for services named: -- `-kafka-0-external` -- `-kafka-1-external` -- `-kafka-2-external` - ## Migration from Zookeeper Mode If migrating from Zookeeper-based Kafka: @@ -398,27 +252,6 @@ Common issues: - Node ID conflicts - Storage not formatted -### External Access Not Working - -Verify external services: -```bash -kubectl get svc | grep kafka.*external -kubectl describe svc kafka-0-external -``` - -Check advertised listeners: -```bash -kubectl exec kafka-0 -- cat /shared/node-id.env -``` - -### TLS Connection Failures - -Verify certificates: -```bash -kubectl get secret kafka-tls-secret -o yaml -kubectl exec kafka-0 -- ls -la /opt/ca/kafka/config/certs/ -``` - ### Controller Election Issues Check controller logs: @@ -449,94 +282,30 @@ For production deployments, consider adding: - **1.1.0**: KRaft mode support, Kafka 4.0.0, multi-listener architecture - **1.0.0**: Initial release with Zookeeper mode -## References - -- [Apache Kafka Documentation](https://kafka.apache.org/documentation/) -- [KRaft Mode Documentation](https://kafka.apache.org/documentation/#kraft) -- [Kafka 4.0.0 Release Notes](https://kafka.apache.org/40/documentation.html#upgrade) - -## Certificate Configuration (New in v1.0.0) - -The Kafka subchart now supports **three different approaches** for certificate management to ensure compatibility with different deployment scenarios: - -### Option 1: Kubernetes Secrets (Recommended) - -Use native Kubernetes secrets for certificate management: - -```yaml -kafka: - tls: - enabled: true - type: PEM - clientAuth: required - secretName: kafka-tls-secret - keystoreKeyKey: "keystore.key" - truststoreCertKey: "truststore.pem" -``` - -Create the secret: -```bash -kubectl create secret generic kafka-tls-secret \ - --from-file=keystore.key=./certs/kafka-key.pem \ - --from-file=truststore.pem=./certs/ca-cert.pem -``` +## Additional Configuration -### Option 2: Inline Certificates (portal-dist Compatible) +### Portal Subdomain -Pass certificates directly as environment variables (compatible with Docker Swarm portal-dist): - -```yaml -kafka: - tls: - enabled: true - truststoreCertContent: | - -----BEGIN CERTIFICATE----- - MIIDXTCCAkWgAwIBAgIJAKL... - -----END CERTIFICATE----- - keystoreKeyContent: | - -----BEGIN ENCRYPTED PRIVATE KEY----- - MIIFHDBOBgkqhkiG9w0BBQ0w... - -----END ENCRYPTED PRIVATE KEY----- -``` - -### Option 3: CA Certificates (analytics_util Compatible) - -Use CA certificate variables (compatible with analytics_util reference implementation): - -```yaml -kafka: - tls: - enabled: true - caKey: "{{ auth.TSSG_CA_KEY }}" - caCertificate: "{{ auth.TSSG_SSL_CERT }}" - caTruststoreCertificate: "{{ auth.TSSG_TRUSTSTORE_CERT }}" -``` - -### Portal Subdomain Configuration - -For advertised listeners that include the portal subdomain: +For deployments that require the portal subdomain: ```yaml kafka: global: subdomainPrefix: "dev-portal" - externalAccess: - enabled: true - hostname: "apim-kafka.dev-portal" ``` -This sets the `APIM_PORTAL_SUBDOMAIN` environment variable and configures advertised listeners appropriately. +This sets the `APIM_PORTAL_SUBDOMAIN` environment variable. ### Custom Advertised Listeners -For full control over advertised listeners (portal-dist style): +For full control over advertised listeners: ```yaml kafka: - advertisedListeners: "INTERNAL://kafka:9092,EXTERNAL://apim-kafka.dev-portal:9094" + advertisedListeners: "INTERNAL://kafka:9092" ``` -## Portal-dist Compatibility +### Portal-dist Compatibility To match portal-dist Docker Swarm deployment configuration: @@ -553,32 +322,17 @@ kafka: nodeId: "0" controllerQuorumVoters: "0@localhost:9093" - advertisedListeners: "INTERNAL://kafka:9092,EXTERNAL://apim-kafka.dev-portal:9094" + advertisedListeners: "INTERNAL://kafka:9092" logRetentionHours: 6 - - tls: - enabled: true - type: PEM - clientAuth: required - truststoreCertContent: "{{ auth.TSSG_TRUSTSTORE_CERT }}" - keystoreKeyContent: "{{ auth.TSSG_CA_P8_KEY }}" - - sasl: - enabled: true - mechanisms: PLAIN - interBrokerProtocol: PLAIN - - externalAccess: - enabled: true - serviceType: LoadBalancer - port: 9094 - hostname: "apim-kafka.dev-portal" ``` This configuration produces the same environment variables as portal-dist `env-kafka` file. -## Additional Documentation +## References +- [Apache Kafka Documentation](https://kafka.apache.org/documentation/) +- [KRaft Mode Documentation](https://kafka.apache.org/documentation/#kraft) +- [Kafka 4.0.0 Release Notes](https://kafka.apache.org/40/documentation.html#upgrade) - **[CONFIGURATION_GUIDE.md](CONFIGURATION_GUIDE.md)** - Detailed configuration guide with examples - **[QUICK_START.md](QUICK_START.md)** - Quick start guide - **[../../KAFKA_ENV_COMPARISON.md](../../KAFKA_ENV_COMPARISON.md)** - Environment variables comparison diff --git a/charts/kafka/kafka-1.0.1.tgz b/charts/kafka/kafka-1.0.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..5aa4718b0089f7de11ffeba9403867c016bacb03 GIT binary patch literal 12603 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

8GL(SVddFa$KhB; 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 /shared/node-id.env echo "Wrote KAFKA_CFG_NODE_ID to /shared/node-id.env" - {{- if .Values.kafka.tls.enabled }} - # Note: TSSG certificates will be loaded by main container from /opt/ca/certs/ - echo "=== TSSG certificates (apim-ssl) available at /opt/ca/certs/ ===" - {{- end }} - - {{- if and .Values.externalAccess.enabled .Values.externalAccess.autoAdvertisedListeners }} - # External access is enabled (typically for intelligence feature) - # This block discovers LoadBalancer/NodePort IPs for external clients - echo "Building advertised listeners with external access..." - INTERNAL_LISTENER="INTERNAL://{{ include "kafka.statefulsetName" . }}-${NODE_ID}.{{ include "kafka.fullname" . }}:{{ .Values.kafka.listeners.internal.port }}" - echo "Internal listener: $INTERNAL_LISTENER" - - CONTROLLER_LISTENER="CONTROLLER://{{ include "kafka.statefulsetName" . }}-${NODE_ID}.{{ include "kafka.fullname" . }}:{{ .Values.kafka.listeners.controller.port }}" - echo "Controller listener: $CONTROLLER_LISTENER" - - {{- if .Values.externalAccess.hostname }} - # Use provided hostname for external listener - EXTERNAL_LISTENER="EXTERNAL://{{ .Values.externalAccess.hostname }}:{{ .Values.externalAccess.port }}" - echo "External listener (hostname): $EXTERNAL_LISTENER" - {{- else }} - # Discover external IP/hostname - EXTERNAL_SVC="{{ include "kafka.statefulsetName" . }}-${NODE_ID}-external" - echo "Discovering external address for service: $EXTERNAL_SVC" - - # Get the service type - SERVICE_TYPE=$(kubectl get svc "$EXTERNAL_SVC" -n "{{ .Release.Namespace }}" -o jsonpath='{.spec.type}' 2>/dev/null || echo "") - echo "Service type: $SERVICE_TYPE" - - EXTERNAL_ADDRESS="" - EXTERNAL_PORT="{{ .Values.externalAccess.port }}" - - if [ "$SERVICE_TYPE" = "LoadBalancer" ]; then - # Configurable timeout and retry settings - TIMEOUT_SECONDS={{ .Values.externalAccess.discovery.timeoutSeconds | default 120 }} - SLEEP_TIME={{ .Values.externalAccess.discovery.retryIntervalSeconds | default 5 }} - RETRIES=$((TIMEOUT_SECONDS / SLEEP_TIME)) - FAIL_ON_ERROR={{ .Values.externalAccess.discovery.failOnDiscoveryFailure | default true }} - - echo "Waiting for LoadBalancer IP (max ${TIMEOUT_SECONDS} seconds)..." - # Wait for LoadBalancer IP with configurable timeout and better error handling - - for i in $(seq 1 $RETRIES); do - # Try to get IP first, then hostname - LB_IP=$(kubectl get svc "$EXTERNAL_SVC" -n "{{ .Release.Namespace }}" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>&1) - KUBECTL_EXIT_CODE=$? - - # If kubectl command failed, log the error - if [ $KUBECTL_EXIT_CODE -ne 0 ]; then - echo "ERROR: kubectl command failed (exit code: $KUBECTL_EXIT_CODE)" - echo "Error details: $LB_IP" - echo "Checking RBAC permissions and ServiceAccount..." - kubectl auth can-i get services --as=system:serviceaccount:{{ .Release.Namespace }}:{{ include "kafka.serviceAccountName" . }} -n {{ .Release.Namespace }} 2>&1 || echo "RBAC check failed" - fi - - # If IP is empty, try hostname - if [ -z "$LB_IP" ]; then - LB_IP=$(kubectl get svc "$EXTERNAL_SVC" -n "{{ .Release.Namespace }}" -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>&1) - fi - - if [ -n "$LB_IP" ] && [ "$LB_IP" != "null" ]; then - EXTERNAL_ADDRESS="$LB_IP" - echo "✓ Successfully discovered LoadBalancer address: ${EXTERNAL_ADDRESS}" - break - fi - - echo "Attempt $i/$RETRIES: LoadBalancer address not ready yet, waiting ${SLEEP_TIME}s..." - sleep $SLEEP_TIME - done - - if [ -z "$EXTERNAL_ADDRESS" ]; then - echo "==========================================" - echo "ERROR: LoadBalancer IP not available after ${TIMEOUT_SECONDS} seconds" - echo "Service: $EXTERNAL_SVC" - echo "Namespace: {{ .Release.Namespace }}" - echo "==========================================" - echo "" - echo "Troubleshooting steps:" - echo "1. Check if LoadBalancer service is created: kubectl get svc $EXTERNAL_SVC -n {{ .Release.Namespace }}" - echo "2. Check LoadBalancer status: kubectl describe svc $EXTERNAL_SVC -n {{ .Release.Namespace }}" - echo "3. Check cloud provider LoadBalancer provisioning" - echo "4. Verify RBAC permissions for ServiceAccount: {{ include "kafka.serviceAccountName" . }}" - echo "5. Check if ServiceAccount token is mounted: ls -la /var/run/secrets/kubernetes.io/serviceaccount/" - echo "==========================================" - - if [ "$FAIL_ON_ERROR" = "true" ]; then - echo "FATAL: failOnDiscoveryFailure is enabled. Pod will fail and Kubernetes will retry." - echo "This prevents Kafka from starting with incorrect advertised listeners." - exit 1 - else - echo "WARNING: Using service hostname as fallback (NOT RECOMMENDED)" - echo "This may cause 'Error connecting to node' issues for external clients." - EXTERNAL_ADDRESS="${EXTERNAL_SVC}" - fi - fi - elif [ "$SERVICE_TYPE" = "NodePort" ]; then - # For NodePort, try to get node IP and NodePort - echo "Discovering NodePort configuration..." - NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}' 2>&1) - if [ -z "$NODE_IP" ]; then - NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}' 2>&1) - fi - NODE_PORT=$(kubectl get svc "$EXTERNAL_SVC" -n "{{ .Release.Namespace }}" -o jsonpath='{.spec.ports[0].nodePort}' 2>&1) - - if [ -n "$NODE_IP" ] && [ -n "$NODE_PORT" ] && [ "$NODE_IP" != "null" ] && [ "$NODE_PORT" != "null" ]; then - EXTERNAL_ADDRESS="${NODE_IP}" - EXTERNAL_PORT="${NODE_PORT}" - echo "✓ Successfully discovered NodePort: ${EXTERNAL_ADDRESS}:${EXTERNAL_PORT}" - else - echo "==========================================" - echo "FATAL ERROR: Could not discover NodePort configuration" - echo "Service: $EXTERNAL_SVC" - echo "Node IP: $NODE_IP" - echo "Node Port: $NODE_PORT" - echo "==========================================" - echo "This is a critical error. The pod will fail and Kubernetes will retry." - exit 1 - fi - else - # Fallback to service name - echo "Using service name as fallback" - EXTERNAL_ADDRESS="${EXTERNAL_SVC}" - fi - - EXTERNAL_LISTENER="EXTERNAL://${EXTERNAL_ADDRESS}:${EXTERNAL_PORT}" - echo "External listener: $EXTERNAL_LISTENER" - {{- end }} - - ADVERTISED_LISTENERS="${INTERNAL_LISTENER},${CONTROLLER_LISTENER},${EXTERNAL_LISTENER}" - echo "KAFKA_CFG_ADVERTISED_LISTENERS=$ADVERTISED_LISTENERS" >> /shared/node-id.env - echo "=== Final advertised listeners: $ADVERTISED_LISTENERS ===" - {{- else }} - # External access is disabled (intelligence not enabled) - # No LoadBalancer discovery needed - using internal listeners only - # Pod will start successfully without external dependencies - echo "Building advertised listeners (internal only)..." + echo "Building advertised listeners..." INTERNAL_LISTENER="INTERNAL://{{ include "kafka.statefulsetName" . }}-${NODE_ID}.{{ include "kafka.fullname" . }}:{{ .Values.kafka.listeners.internal.port }}" echo "Internal listener: $INTERNAL_LISTENER" @@ -276,7 +124,6 @@ spec: echo "Controller listener: $CONTROLLER_LISTENER" echo "KAFKA_CFG_ADVERTISED_LISTENERS=$INTERNAL_LISTENER,$CONTROLLER_LISTENER" >> /shared/node-id.env - {{- end }} echo "=== Configuration written to /shared/node-id.env ===" cat /shared/node-id.env @@ -329,27 +176,6 @@ spec: echo "Final KAFKA_CFG_ADVERTISED_LISTENERS: $KAFKA_CFG_ADVERTISED_LISTENERS" fi - {{- if .Values.kafka.tls.enabled }} - # Load TSSG certificates (apim-ssl) from mounted volume - # Note: Volume mount maps secret keys to apim-ssl.p8.key and apim-ssl.crt paths - echo "=== Loading TSSG certificates for Kafka SSL ===" - if [ -f /opt/ca/certs/apim-ssl.p8.key ]; then - export KAFKA_CFG_SSL_KEYSTORE_KEY=$(cat /opt/ca/certs/apim-ssl.p8.key) - echo "Loaded TSSG keystore key (PKCS#8)" - else - echo "ERROR: TSSG keystore key not found at /opt/ca/certs/apim-ssl.p8.key" - exit 1 - fi - - if [ -f /opt/ca/certs/apim-ssl.crt ]; then - export KAFKA_CFG_SSL_TRUSTSTORE_CERTIFICATES=$(cat /opt/ca/certs/apim-ssl.crt) - echo "Loaded TSSG truststore certificate" - else - echo "ERROR: TSSG truststore certificate not found at /opt/ca/certs/apim-ssl.crt" - exit 1 - fi - {{- end }} - # Check if KAFKA_CLUSTER_ID is set and format storage if needed if [ -n "$KAFKA_CLUSTER_ID" ]; then echo "=== Kafka KRaft Storage Formatting ===" @@ -406,29 +232,6 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name - - name: INTELLIGENCE_ENABLED - value: {{ .Values.externalAccess.enabled | quote }} - {{- if .Values.kafka.tls.enabled }} - {{- if .Values.kafka.tls.secretName }} - - name: KAFKA_CFG_SSL_KEYSTORE_KEY - valueFrom: - secretKeyRef: - name: {{ .Values.kafka.tls.secretName }} - key: {{ .Values.kafka.tls.keystoreKeyKey }} - - name: KAFKA_CFG_SSL_TRUSTSTORE_CERTIFICATES - valueFrom: - secretKeyRef: - name: {{ .Values.kafka.tls.secretName }} - key: {{ .Values.kafka.tls.truststoreCertKey }} - {{- end }} - {{- if .Values.kafka.tls.passwordSecretName }} - - name: KAFKA_CFG_SSL_KEY_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Values.kafka.tls.passwordSecretName }} - key: {{ .Values.kafka.tls.passwordSecretKey }} - {{- end }} - {{- end }} envFrom: - configMapRef: name: {{ include "kafka.fullname" . }}-config @@ -454,10 +257,6 @@ spec: ports: - containerPort: {{ .Values.kafka.listeners.internal.port }} name: kafka - {{- if .Values.externalAccess.enabled }} - - containerPort: {{ .Values.kafka.listeners.external.port }} - name: external - {{- end }} {{- if and .Values.kafka.kraft.enabled .Values.kafka.listeners.controller.enabled }} - containerPort: {{ .Values.kafka.listeners.controller.port }} name: controller @@ -470,11 +269,6 @@ spec: - name: shared-config mountPath: /shared {{- end }} - {{- if .Values.kafka.tls.enabled }} - - name: internal-certs - mountPath: /opt/ca/certs - readOnly: true - {{- end }} {{- if .Values.global.pullSecret }} imagePullSecrets: - name: "{{ .Values.global.pullSecret }}" @@ -484,16 +278,6 @@ spec: - name: shared-config emptyDir: {} {{- end }} - {{- if .Values.kafka.tls.enabled }} - - name: internal-certs - secret: - secretName: {{ .Values.kafka.tls.internalSecretName | default "portal-internal-secret" }} - items: - - key: {{ .Values.kafka.tls.tssgKeyKey }} - path: apim-ssl.p8.key - - key: {{ .Values.kafka.tls.tssgCertKey }} - path: apim-ssl.crt - {{- end }} restartPolicy: Always terminationGracePeriodSeconds: 60 volumeClaimTemplates: diff --git a/charts/kafka/templates/pre-upgrade-statefulset-cleanup.yaml b/charts/kafka/templates/pre-upgrade-statefulset-cleanup.yaml index da1a2d3e..dee77cb6 100644 --- a/charts/kafka/templates/pre-upgrade-statefulset-cleanup.yaml +++ b/charts/kafka/templates/pre-upgrade-statefulset-cleanup.yaml @@ -107,19 +107,12 @@ spec: {{- end }} containers: - name: sts-cleanup - {{- if .Values.image }} - {{- if .Values.image.autoDiscovery }} - # Use parent chart's autoDiscovery image (when kafka is a subchart) + {{- if and .Values.image .Values.image.autoDiscovery }} image: "{{ .Values.global.portalRepository }}{{ .Values.image.autoDiscovery }}" {{- else }} - # Use local discoveryImage configuration - image: "{{ .Values.global.portalRepository }}{{ .Values.externalAccess.discoveryImage.repository }}:{{ .Values.externalAccess.discoveryImage.tag }}" + image: "{{ .Values.global.portalRepository }}{{ .Values.kubectlImage.repository }}:{{ .Values.kubectlImage.tag }}" {{- end }} - {{- else }} - # Use local discoveryImage configuration (standalone kafka chart) - image: "{{ .Values.global.portalRepository }}{{ .Values.externalAccess.discoveryImage.repository }}:{{ .Values.externalAccess.discoveryImage.tag }}" - {{- end }} - imagePullPolicy: {{ .Values.externalAccess.discoveryImage.pullPolicy | default "IfNotPresent" }} + imagePullPolicy: {{ .Values.kubectlImage.pullPolicy | default "IfNotPresent" }} command: - /bin/sh - -c diff --git a/charts/kafka/values.yaml b/charts/kafka/values.yaml index f0920986..115e3831 100644 --- a/charts/kafka/values.yaml +++ b/charts/kafka/values.yaml @@ -85,11 +85,6 @@ kafka: enabled: true port: 9092 protocol: PLAINTEXT - # External listener for clients outside the cluster - external: - enabled: true - port: 9094 - protocol: SSL # Controller listener for KRaft mode controller: enabled: true @@ -100,62 +95,13 @@ kafka: # Advertised Listeners Configuration # Custom advertised listeners (portal-dist style) - # Format: "INTERNAL://kafka:9092,EXTERNAL://apim-kafka.subdomain:9094" - # Leave empty to auto-generate based on externalAccess settings + # Format: "INTERNAL://kafka:9092" + # Leave empty to auto-generate advertisedListeners: "" # Log retention configuration logRetentionHours: 6 - # TLS/SSL Configuration - tls: - # Enable TLS/SSL (required when external listener uses SSL protocol) - enabled: false - # TLS type (PEM or JKS) - type: PEM - # Client authentication (none, requested, required) - clientAuth: required - - # ============================================ - # Option 1: Kubernetes Secret (Recommended) - # ============================================ - # Secret containing TLS certificates - # Should contain: keystore.key, truststore.pem - # When using Intelligence, set to portal-internal-secret - secretName: "" - # Keys in the secret - keystoreKeyKey: "keystore.key" - truststoreCertKey: "truststore.pem" - # Password secret for encrypted keys (required for encrypted private keys) - # When using Intelligence, set to portal-internal-secret - passwordSecretName: "" - passwordSecretKey: "keypass.txt" - - # TSSG Certificates (use apim-ssl from internal secret by default) - # The secret name for TSSG certificates (apim-ssl) - # Changed to portal-external-secret to support static certificate deployments - internalSecretName: "portal-external-secret" - tssgKeyKey: "apim-ssl.p8.key" # Default to apim-ssl, can be overridden - tssgCertKey: "apim-ssl.crt" # Default to apim-ssl, can be overridden - passwordKey: "keypass.txt" - - # ============================================ - # Option 2: Inline Certificates (portal-dist style) - # ============================================ - # Pass certificates as environment variables - # These take precedence over secretName if both are set - # Can be base64 encoded or PEM format - truststoreCertContent: "" - keystoreKeyContent: "" - - # ============================================ - # Option 3: CA Certificates (analytics_util style) - # ============================================ - # Alternative certificate format - caKey: "" - caCertificate: "" - caTruststoreCertificate: "" - # SASL Authentication Configuration sasl: # Enable SASL authentication @@ -189,46 +135,9 @@ serviceAccount: # Automount service account token automountServiceAccountToken: true -# External access configuration for autodiscovery -externalAccess: - # External access should be enabled only when Intelligence is enabled - # Analytics (Druid) uses internal Kafka service only - enabled: false - # Service type for external access (LoadBalancer or NodePort) - serviceType: LoadBalancer - # Port for external access (should match kafka.listeners.external.port) - port: 9094 - # Hostname/domain for external access (used in advertised listeners) - # Format: apim-kafka.{subdomain} or specific hostname - hostname: "" - # Annotations for external services - annotations: {} - # LoadBalancer IPs (optional, for static IPs) - loadBalancerIPs: [] - # NodePort values (optional, for NodePort service type) - nodePorts: [] - # Auto-generate advertised listeners based on service IPs - autoAdvertisedListeners: true - - # Discovery configuration for LoadBalancer/NodePort IP detection - # NOTE: This only applies when externalAccess.enabled is true - # When intelligence is disabled, Kafka uses internal-only listeners and these settings are ignored - discovery: - # Maximum time to wait for LoadBalancer IP (in seconds) - # Default: 120 seconds (24 retries * 5 seconds) - timeoutSeconds: 120 - # Retry interval (in seconds) - retryIntervalSeconds: 5 - # Fail fast: If true, pod will fail when LoadBalancer IP cannot be discovered - # This prevents Kafka from starting with incorrect advertised listeners - # If false, falls back to service hostname (NOT RECOMMENDED - causes connection issues) - # IMPORTANT: Only affects behavior when externalAccess.enabled=true - failOnDiscoveryFailure: true - - # Discovery image for LoadBalancer IP detection (kubectl) - # Note: When used as a subchart, image.autoDiscovery should be provided by parent chart - # When used standalone, provide these values during deployment - discoveryImage: - repository: "tls-automator" - tag: "latest" # Should be overridden with specific version - pullPolicy: IfNotPresent +# kubectl image for pre-upgrade cleanup jobs +# Note: When used as a subchart, image.autoDiscovery should be provided by parent chart +kubectlImage: + repository: "tls-automator" + tag: "latest" + pullPolicy: IfNotPresent diff --git a/charts/portal/Chart.lock b/charts/portal/Chart.lock index 405c8f1a..1bd46c16 100644 --- a/charts/portal/Chart.lock +++ b/charts/portal/Chart.lock @@ -5,17 +5,14 @@ dependencies: - name: rabbitmq repository: "" version: 12.0.3 -- name: apim-intelligence - repository: file://../intelligence - version: 1.0.21 - name: seaweedfs repository: file://../seaweedfs version: 1.0.4 - name: kafka repository: file://../kafka - version: 1.0.0 + version: 1.0.1 - name: ingress-nginx repository: https://kubernetes.github.io/ingress-nginx/ version: 4.12.1 -digest: sha256:855a759dfaf53f78e48f0e4f3ea66bf072aeb997ab1e9a547ac6b9f58f6beb25 -generated: "2026-02-27T20:46:54.2953154+05:30" +digest: sha256:a3d25593eb6299015d8e8b99970bff8858a3d8cb71bb4bae34829995e9d20ecf +generated: "2026-03-13T11:18:42.831349+05:30" diff --git a/charts/portal/Chart.yaml b/charts/portal/Chart.yaml index 7013b396..89dfa439 100644 --- a/charts/portal/Chart.yaml +++ b/charts/portal/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 appVersion: "5.4.1" description: CA API Developer Portal name: portal -version: 2.4.0 +version: 2.4.1 type: application home: https://github.com/CAAPIM/apim-charts maintainers: @@ -17,16 +17,12 @@ dependencies: - name: rabbitmq version: 12.0.3 condition: rabbitmq.enabled -- name: apim-intelligence - version: ^1.0.0 - condition: portal.intelligence.enabled - repository: "file://../intelligence" - name: seaweedfs version: ^1.0.0 condition: portal.analytics.enabled repository: "file://../seaweedfs" - name: kafka - version: ^1.0.0 + version: ^1.0.1 condition: kafka.enabled repository: "file://../kafka" - name: ingress-nginx diff --git a/charts/portal/README.md b/charts/portal/README.md index 577a8107..f6abb287 100644 --- a/charts/portal/README.md +++ b/charts/portal/README.md @@ -819,7 +819,7 @@ Portal Analytics For Production, use an external MySQL Server. ## Intelligence (APIM Intelligence) -The Intelligence subchart provides advanced analytics and third-party agent integration capabilities. +The Intelligence server provides advanced analytics and third-party agent integration capabilities. **Important Dependencies:** - When `portal.intelligence.enabled: true`, the following subcharts are automatically deployed: @@ -828,10 +828,8 @@ The Intelligence subchart provides advanced analytics and third-party agent inte | Parameter | Description | Default | | --- | --- | --- | | `portal.intelligence.enabled` | Enable Intelligence service | `false` | -| `apim-intelligence.intelligenceServer.replicaCount` | Number of intelligence server replicas | `1` | -| `apim-intelligence.intelligenceServer.kafka.autoDiscovery.enabled` | Enable Kafka broker auto-discovery | `true` | - -For detailed Intelligence configuration, see the [Intelligence Chart README](../intelligence/README.md). +| `intelligence.replicaCount` | Number of intelligence server replicas | `1` | +| `intelligence.kafka.kafkaCa.caSecretName` | Secret containing the CA certificate for Kafka mTLS | `portal-external-secret` | ## SeaweedFS The SeaweedFS subchart provides S3-compatible object storage for analytics data. @@ -866,8 +864,6 @@ The Kafka subchart provides Apache Kafka 4.0.0 in KRaft mode (Zookeeper-less) fo | `kafka.enabled` | Enable Kafka subchart | `false` | | `kafka.kafka.replicaCount` | Number of Kafka broker replicas (3+ recommended for production) | `1` | | `kafka.kafka.kraft.enabled` | Enable KRaft mode (Zookeeper-less) | `true` | -| `kafka.externalAccess.enabled` | Enable external access to Kafka brokers | `true` | -| `kafka.externalAccess.serviceType` | Service type for external access | `LoadBalancer` | For detailed Kafka configuration, see the [Kafka Chart README](../kafka/README.md). diff --git a/charts/portal/charts/apim-intelligence-1.0.21.tgz b/charts/portal/charts/apim-intelligence-1.0.21.tgz deleted file mode 100644 index 1fe5ee80907ad946494667f6ba9cdee6037c1ccb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10201 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 T!Jhvc00960f__Ry0M-Bi@>%fQ diff --git a/charts/portal/templates/_helpers.tpl b/charts/portal/templates/_helpers.tpl index 75f80f28..1b13d4cf 100644 --- a/charts/portal/templates/_helpers.tpl +++ b/charts/portal/templates/_helpers.tpl @@ -419,6 +419,20 @@ Create Image Pull Secret {{- .Values.ingress.gatewayAPI.tlsRouteApiVersion -}} {{- end -}} +{{/* +Generate Kafka TCP Proxy ingress hostname based on configurations. +Used when intelligence is enabled. +*/}} +{{- define "kafka-proxy-host" -}} + {{- if .Values.global.legacyHostnames }} + {{- printf "kafka-proxy.%s" .Values.portal.domain -}} + {{- else if .Values.global.saas }} + {{- printf "kafka-proxy-%s.%s" .Values.global.subdomainPrefix .Values.portal.domain -}} + {{- else }} + {{- printf "%s-kafka-proxy.%s" .Values.global.subdomainPrefix .Values.portal.domain -}} + {{- end }} +{{- end -}} + {{/* Generate default parentRefs for routes when none are provided. Produces a list with a single parentRef pointing to the chart's Gateway. @@ -427,3 +441,64 @@ Create Image Pull Secret - name: {{ include "portal.gatewayAPI.gatewayName" . }} namespace: {{ include "portal.gatewayAPI.gatewayNamespace" . }} {{- end -}} + +{{/* + ============================================================ + Intelligence Server Helpers + ============================================================ +*/}} + +{{/* +Get "intelligence" database name +*/}} +{{- define "intelligence-db-name" -}} + {{ if .Values.global.legacyDatabaseNames }} + {{- print "intelligence" }} + {{- else }} + {{- $f:= .Values.global.subdomainPrefix -}} + {{ if empty $f }} + {{- fail "Please define subdomainPrefix in values.yaml" }} + {{- else }} + {{- printf "%s_%s" $f "intelligence" | replace "-" "_" -}} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Set the service account name for the Intelligence Server +*/}} +{{- define "intelligence.serviceAccountName" -}} +{{- if .Values.global.serviceAccountName }} + {{ default "default" .Values.global.serviceAccountName }} +{{- else }} +{{- if .Values.intelligence.serviceAccount.create -}} + {{ default "intelligence-server" .Values.intelligence.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.intelligence.serviceAccount.name }} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Get Kafka broker address for intelligence server. +Uses the kafka subchart's fullnameOverride and internal listener port. +*/}} +{{- define "intelligence.kafkaBrokers" -}} + {{- $kafkaName := default "kafka" .Values.kafka.fullnameOverride -}} + {{- if and .Values.kafka.kafka .Values.kafka.kafka.listeners }} + {{- printf "%s:%g" $kafkaName .Values.kafka.kafka.listeners.internal.port -}} + {{- else }} + {{- printf "%s:9092" $kafkaName -}} + {{- end }} +{{- end -}} + +{{/* +Generate Kafka proxy bootstrap address for intelligence server. +Format: {kafka-proxy-host}:{listenPort} +Reuses the kafka-proxy-host helper and the kafkaProxy.listenPort value. +*/}} +{{- define "intelligence.kafkaProxyBootstrap" -}} + {{- $host := include "kafka-proxy-host" . -}} + {{- $port := int (.Values.portal.intelligence.kafkaProxy.listenPort | default 9192) -}} + {{- printf "%s:%d" $host $port -}} +{{- end -}} diff --git a/charts/portal/templates/apim/apim-config.yaml b/charts/portal/templates/apim/apim-config.yaml index 0ccaf6e7..dba448e7 100644 --- a/charts/portal/templates/apim/apim-config.yaml +++ b/charts/portal/templates/apim/apim-config.yaml @@ -1,3 +1,4 @@ +# AI assistance has been used to generate some or all contents of this file. That includes, but is not limited to, new code, modifying existing code, stylistic edits. apiVersion: v1 kind: ConfigMap metadata: @@ -29,10 +30,12 @@ data: TSSG_PUBLIC_HOST: {{ include "tssg-public-host" . | quote }} TSSG_PUBLIC_PORT: {{ .Values.portal.otk.port | quote }} {{- if .Values.portal.intelligence.enabled }} - # Intelligence Feature Flag INTELLIGENCE_ENABLED: "true" + KAFKA_PROXY_TARGET_SERVERS: {{ default "kafka:9092" .Values.portal.intelligence.kafkaProxy.targetBootstrapServers | quote }} + KAFKA_PROXY_LISTEN_PORT: {{ default 9192 .Values.portal.intelligence.kafkaProxy.listenPort | quote }} + KAFKA_PROXY_BROKER_START_PORT: {{ default 9193 .Values.portal.intelligence.kafkaProxy.brokerStartPort | quote }} + KAFKA_PROXY_ADVERTISED_HOST: {{ default (include "tssg-public-host" .) .Values.portal.intelligence.kafkaProxy.advertisedHost | quote }} {{- else }} - # Intelligence Feature Flag INTELLIGENCE_ENABLED: "false" {{- end }} {{ if .Values.apim.additionalEnv }} diff --git a/charts/portal/templates/apim/apim-service.yaml b/charts/portal/templates/apim/apim-service.yaml index d91fc99d..08d8ac8a 100644 --- a/charts/portal/templates/apim/apim-service.yaml +++ b/charts/portal/templates/apim/apim-service.yaml @@ -41,6 +41,25 @@ spec: targetPort: 9448 protocol: TCP name: apim-sso +{{- if .Values.portal.intelligence.enabled }} + - port: {{ .Values.portal.intelligence.kafkaProxy.listenPort | default 9192 }} + targetPort: {{ .Values.portal.intelligence.kafkaProxy.listenPort | default 9192 }} + protocol: TCP + name: kafka-proxy-bootstrap +{{- $brokerStartPort := int (.Values.portal.intelligence.kafkaProxy.brokerStartPort | default 9193) }} +{{- $replicaCount := int (.Values.kafka.kafka.replicaCount | default 1) }} +{{- $ordinalStart := 0 }} +{{- if and .Values.kafka.kafka.ordinals (hasKey .Values.kafka.kafka.ordinals "start") }} +{{- $ordinalStart = int .Values.kafka.kafka.ordinals.start }} +{{- end }} +{{- range $i := until $replicaCount }} +{{- $brokerPort := add $brokerStartPort (add $ordinalStart $i) }} + - port: {{ $brokerPort }} + targetPort: {{ $brokerPort }} + protocol: TCP + name: kafka-proxy-broker-{{ add $ordinalStart $i }} +{{- end }} +{{- end }} selector: app: apim type: ClusterIP diff --git a/charts/portal/templates/gateway-api/gateway.yaml b/charts/portal/templates/gateway-api/gateway.yaml index 4d4ee813..4e7f42f6 100644 --- a/charts/portal/templates/gateway-api/gateway.yaml +++ b/charts/portal/templates/gateway-api/gateway.yaml @@ -41,6 +41,10 @@ spec: {{- $hostnames = append $hostnames (include "pssg-sync-host" .) }} {{- $hostnames = append $hostnames (include "pssg-sso-host" .) }} {{- $hostnames = append $hostnames (include "broker-host" .) }} + {{/* Kafka proxy route */}} + {{- if .Values.portal.intelligence.enabled }} + {{- $hostnames = append $hostnames (include "kafka-proxy-host" .) }} + {{- end }} {{/* Dynamic tenant routes */}} {{- range .Values.ingress.tenantIds }} {{- $hostnames = append $hostnames (printf "%s.%s" . $.Values.portal.domain) }} diff --git a/charts/portal/templates/gateway-api/tlsroute.yaml b/charts/portal/templates/gateway-api/tlsroute.yaml index cf05311f..eaebdf53 100644 --- a/charts/portal/templates/gateway-api/tlsroute.yaml +++ b/charts/portal/templates/gateway-api/tlsroute.yaml @@ -232,6 +232,40 @@ spec: - backendRefs: - name: apim port: 1885 +{{- if .Values.portal.intelligence.enabled }} +--- +apiVersion: {{ include "portal.gatewayAPI.tlsRouteApiVersion" $root }} +kind: TLSRoute +metadata: + name: portal-tlsroute-kafka-proxy + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "portal.name" . }} + chart: {{ template "portal.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- range $key, $val := .Values.global.additionalLabels }} + {{ $key }}: "{{ $val }}" + {{- end }} + {{- range $key, $val := .Values.ingress.gatewayAPI.labels }} + {{ $key }}: "{{ $val }}" + {{- end }} + {{- if $annotations }} + annotations: + {{- range $key, $val := $annotations }} + {{ $key }}: "{{ $val }}" + {{- end }} + {{- end }} +spec: + parentRefs: + {{- include "portal.gatewayAPI.defaultParentRefs" . | nindent 4 }} + hostnames: + - {{ include "kafka-proxy-host" . | quote }} + rules: + - backendRefs: + - name: apim + port: {{ .Values.portal.intelligence.kafkaProxy.listenPort | default 9192 }} +{{- end }} {{/* ============================================================ Dynamic TLSRoutes - generated from ingress.tenantIds diff --git a/charts/portal/templates/ingress/contour-httpproxy.yaml b/charts/portal/templates/ingress/contour-httpproxy.yaml index eea9defe..058ddd7b 100644 --- a/charts/portal/templates/ingress/contour-httpproxy.yaml +++ b/charts/portal/templates/ingress/contour-httpproxy.yaml @@ -175,6 +175,34 @@ spec: port: 1885 --- +{{- if .Values.portal.intelligence.enabled }} +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: portal-kafka-proxy-httpproxy + labels: + app: {{ template "portal.name" . }} + chart: {{ template "portal.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- range $key, $val := .Values.global.additionalLabels }} + {{ $key }}: "{{ $val }}" + {{- end }} + {{- range $key, $val := .Values.ingress.additionalLabels }} + {{ $key }}: "{{ $val }}" + {{- end }} +spec: + virtualhost: + fqdn: {{ include "kafka-proxy-host" . | quote }} + tls: + passthrough: true + tcpproxy: + services: + - name: apim + port: {{ .Values.portal.intelligence.kafkaProxy.listenPort | default 9192 }} +{{- end }} + {{- range $i,$tenantId := $.Values.ingress.tenantIds }} --- apiVersion: projectcontour.io/v1 diff --git a/charts/portal/templates/ingress/ingress.yaml b/charts/portal/templates/ingress/ingress.yaml index 547e283c..62b767fc 100644 --- a/charts/portal/templates/ingress/ingress.yaml +++ b/charts/portal/templates/ingress/ingress.yaml @@ -146,6 +146,24 @@ spec: serviceName: apim servicePort: {{ printf "%s-broker" .Values.portal.defaultTenantId | quote }} {{- end }} +{{- if .Values.portal.intelligence.enabled }} + - host: {{ include "kafka-proxy-host" . | quote }} + http: + paths: +{{- if semverCompare ">=1.19-0" $kubeTargetVersion }} + - pathType: Prefix + path: "/" + backend: + service: + name: apim + port: + name: kafka-proxy-bootstrap +{{- else }} + - backend: + serviceName: apim + servicePort: kafka-proxy-bootstrap +{{- end }} +{{- end }} {{- range .Values.ingress.customRoutes }} - host: "{{ .subdomain }}.{{ $.Values.portal.domain }}" http: diff --git a/charts/portal/templates/ingress/route.yaml b/charts/portal/templates/ingress/route.yaml index 2c1c7249..13822178 100644 --- a/charts/portal/templates/ingress/route.yaml +++ b/charts/portal/templates/ingress/route.yaml @@ -125,4 +125,24 @@ spec: name: apim weight: 100 wildcardPolicy: None +{{- if .Values.portal.intelligence.enabled }} +--- +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: apim-route-kafka-proxy + labels: + router: default +spec: + host: {{ include "kafka-proxy-host" . | quote }} + port: + targetPort: kafka-proxy-bootstrap + tls: + termination: passthrough + to: + kind: Service + name: apim + weight: 100 + wildcardPolicy: None +{{- end }} {{- end }} diff --git a/charts/intelligence/templates/server/server-config.yml b/charts/portal/templates/intelligence/intelligence-config.yaml similarity index 67% rename from charts/intelligence/templates/server/server-config.yml rename to charts/portal/templates/intelligence/intelligence-config.yaml index 4ac6649e..29f21a7e 100644 --- a/charts/intelligence/templates/server/server-config.yml +++ b/charts/portal/templates/intelligence/intelligence-config.yaml @@ -1,10 +1,11 @@ +{{- if .Values.portal.intelligence.enabled }} apiVersion: v1 kind: ConfigMap metadata: name: intelligence-server-config labels: app: intelligence-server - chart: {{ template "intelligence.chart" . }} + chart: {{ template "portal.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} data: @@ -17,7 +18,8 @@ data: DATABASE_USE_SSL: {{ .Values.global.databaseUseSSL | quote }} DATABASE_REQUIRE_SSL: {{ .Values.global.databaseRequireSSL | quote }} INTELLIGENCE_DATABASE_NAME: {{ include "intelligence-db-name" . | quote }} - KAFKA_BROKERS: {{ include "kafka-brokers" . | quote}} - PORTAL_DATA_HOST: {{ .Values.intelligenceServer.portalDataHost| default "portal-data:8080" | quote }} + KAFKA_BROKERS: {{ include "intelligence.kafkaBrokers" . | quote }} + PORTAL_DATA_HOST: {{ .Values.intelligence.portalDataHost | default "portal-data:8080" | quote }} RABBITMQ_HOST: {{ .Values.rabbitmq.host | quote }} - RABBITMQ_PORT: {{ .Values.rabbitmq.service.port | quote }} \ No newline at end of file + RABBITMQ_PORT: {{ .Values.rabbitmq.service.port | quote }} +{{- end }} diff --git a/charts/portal/templates/intelligence/intelligence-deployment.yaml b/charts/portal/templates/intelligence/intelligence-deployment.yaml new file mode 100644 index 00000000..f1351699 --- /dev/null +++ b/charts/portal/templates/intelligence/intelligence-deployment.yaml @@ -0,0 +1,174 @@ +{{- if .Values.portal.intelligence.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: intelligence-server + labels: + app: intelligence-server + chart: {{ template "portal.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- range $key, $val := .Values.global.additionalLabels }} + {{ $key }}: "{{ $val }}" + {{- end }} + {{- range $key, $val := .Values.intelligence.additionalLabels }} + {{ $key }}: "{{ $val }}" + {{- end }} +spec: + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + replicas: {{ .Values.intelligence.replicaCount }} + selector: + matchLabels: + app: intelligence-server + template: + metadata: + labels: + app: intelligence-server + release: {{ .Release.Name }} + {{- if .Values.intelligence.podAnnotations }} + annotations: {{- toYaml .Values.intelligence.podAnnotations | nindent 8 }} + {{- if .Values.intelligence.forceRedeploy }} + timestamp: {{ now | quote }} + {{- end }} + {{- end }} + {{- if not .Values.intelligence.podAnnotations }} + {{- if .Values.intelligence.forceRedeploy }} + annotations: + timestamp: {{ now | quote }} + {{- end }} + {{- end }} + spec: + serviceAccountName: {{ include "intelligence.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.intelligence.serviceAccount.automountServiceAccountToken }} + {{- if .Values.intelligence.affinity }} + affinity: {{- toYaml .Values.intelligence.affinity | nindent 12 }} + {{- end }} + {{- if .Values.intelligence.nodeSelector }} + nodeSelector: {{- toYaml .Values.intelligence.nodeSelector | nindent 12 }} + {{- end }} + {{- if .Values.intelligence.tolerations }} + tolerations: {{- toYaml .Values.intelligence.tolerations | nindent 12 }} + {{- end }} + {{- if .Values.intelligence.podSecurityContext }} + securityContext: {{- toYaml .Values.intelligence.podSecurityContext | nindent 12 }} + {{- else if .Values.global.podSecurityContext }} + securityContext: {{- toYaml .Values.global.podSecurityContext | nindent 12 }} + {{- end }} + containers: + - name: intelligence-server + image: "{{ .Values.global.portalRepository }}{{ .Values.image.intelligenceServer }}" + imagePullPolicy: "{{ .Values.intelligence.image.pullPolicy }}" + {{- if .Values.intelligence.containerSecurityContext }} + securityContext: {{- toYaml .Values.intelligence.containerSecurityContext | nindent 12 }} + {{- else if .Values.global.containerSecurityContext }} + securityContext: {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- end }} + command: ["java"] + args: ["-jar", "/opt/app/intelligence-server.jar"] + env: + - name: RABBITMQ_DEFAULT_PASS + valueFrom: + secretKeyRef: + name: {{ .Values.rabbitmq.auth.secretName | default "rabbitmq" }} + key: rabbitmq-password + optional: false + - name: INTELLIGENCE_DATABASE_NAME + value: {{ template "intelligence-db-name" . }} + - name: DATABASE_USERNAME + value: {{ .Values.global.databaseUsername | quote }} + - name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.global.databaseSecret }} + {{ if eq .Values.global.databaseType "mysql" }} + key: mysql-password + {{- end }} + - name: KAFKA_BROKERS + value: {{ .Values.intelligence.kafka.brokers | default "kafka:9092" | quote }} + - name: KAFKA_SECURITY_PROTOCOL + value: {{ .Values.intelligence.kafka.securityProtocol | default "PLAINTEXT" | quote }} + {{- if .Values.intelligence.kafka.kafkaCa.caSecretName }} + - name: KAFKA_CA_CERTIFICATE + valueFrom: + secretKeyRef: + name: {{ .Values.intelligence.kafka.kafkaCa.caSecretName }} + key: {{ .Values.intelligence.kafka.kafkaCa.caCertKey | default "apim-ssl.crt" }} + optional: false + - name: KAFKA_CA_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.intelligence.kafka.kafkaCa.caSecretName }} + key: {{ .Values.intelligence.kafka.kafkaCa.caKeyKey | default "apim-ssl.key" }} + optional: false + - name: KAFKA_CA_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.intelligence.kafka.kafkaCa.passwordSecretName }} + key: {{ .Values.intelligence.kafka.kafkaCa.passwordSecretKey | default "keypass.txt" }} + optional: false + - name: TSSG_SSL_KEY_PASS + valueFrom: + secretKeyRef: + name: {{ .Values.intelligence.kafka.kafkaCa.passwordSecretName }} + key: {{ .Values.intelligence.kafka.kafkaCa.passwordSecretKey | default "keypass.txt" }} + optional: false + {{- else }} + - name: KAFKA_CA_CERTIFICATE + value: "" + - name: KAFKA_CA_KEY + value: "" + - name: KAFKA_CA_PASSWORD + value: "" + - name: TSSG_SSL_KEY_PASS + value: "" + {{- end }} + - name: KAFKA_EXTERNAL_ADVERTIZED_BROKERS + value: {{ include "intelligence.kafkaProxyBootstrap" . | quote }} + {{- if .Values.intelligence.env }} + {{- range $key, $value := .Values.intelligence.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + - name: PAPI_PUBLIC_HOST + value: {{ include "tssg-public-host" . | quote }} + - name: PAPI_PUBLIC_PORT + value: {{ include "tssg-public-port" . | quote }} + envFrom: + - configMapRef: + name: intelligence-server-config + readinessProbe: + httpGet: + path: "/health/readiness" + port: 8282 + initialDelaySeconds: 90 + timeoutSeconds: 1 + periodSeconds: 15 + successThreshold: 1 + livenessProbe: + httpGet: + path: "/health/liveness" + port: 8282 + initialDelaySeconds: 120 + timeoutSeconds: 1 + periodSeconds: 15 + successThreshold: 1 + {{- if .Values.intelligence.resources }} + resources: {{- toYaml .Values.intelligence.resources | nindent 12 }} + {{- end }} + ports: + - containerPort: 8282 + {{- if .Values.global.pullSecret }} + imagePullSecrets: + - name: "{{ .Values.global.pullSecret }}" + {{- end }} + {{- if .Values.global.schedulerName }} + schedulerName: "{{ .Values.global.schedulerName }}" + {{- end }} + restartPolicy: Always + terminationGracePeriodSeconds: 30 +{{- end }} diff --git a/charts/portal/templates/intelligence/intelligence-role.yaml b/charts/portal/templates/intelligence/intelligence-role.yaml new file mode 100644 index 00000000..50093270 --- /dev/null +++ b/charts/portal/templates/intelligence/intelligence-role.yaml @@ -0,0 +1,23 @@ +{{- if .Values.portal.intelligence.enabled }} +{{- if and .Values.intelligence.rbac.create .Values.intelligence.serviceAccount.create (not .Values.global.serviceAccountName) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: intelligence-server + namespace: {{ .Release.Namespace }} + labels: + app: intelligence-server + chart: {{ template "portal.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +rules: + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +{{- end }} +{{- end }} diff --git a/charts/intelligence/templates/service-accounts/rolebinding.yaml b/charts/portal/templates/intelligence/intelligence-rolebinding.yaml similarity index 51% rename from charts/intelligence/templates/service-accounts/rolebinding.yaml rename to charts/portal/templates/intelligence/intelligence-rolebinding.yaml index 090ac04c..589122ad 100644 --- a/charts/intelligence/templates/service-accounts/rolebinding.yaml +++ b/charts/portal/templates/intelligence/intelligence-rolebinding.yaml @@ -1,19 +1,21 @@ -{{- if and .Values.rbac.create .Values.serviceAccount.create (not .Values.global.serviceAccountName) }} +{{- if .Values.portal.intelligence.enabled }} +{{- if and .Values.intelligence.rbac.create .Values.intelligence.serviceAccount.create (not .Values.global.serviceAccountName) }} apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: {{ include "intelligence.fullname" . }} + name: intelligence-server namespace: {{ .Release.Namespace }} labels: - app: {{ template "intelligence.name" . }} - chart: {{ template "intelligence.chart" . }} + app: intelligence-server + chart: {{ template "portal.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: {{ include "intelligence.fullname" . }} + name: intelligence-server subjects: - kind: ServiceAccount name: {{ include "intelligence.serviceAccountName" . }} -{{- end }} \ No newline at end of file +{{- end }} +{{- end }} diff --git a/charts/portal/templates/intelligence/intelligence-service-account.yaml b/charts/portal/templates/intelligence/intelligence-service-account.yaml new file mode 100644 index 00000000..a833047e --- /dev/null +++ b/charts/portal/templates/intelligence/intelligence-service-account.yaml @@ -0,0 +1,15 @@ +{{- if .Values.portal.intelligence.enabled }} +{{- if and .Values.intelligence.serviceAccount.create (not .Values.global.serviceAccountName) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "intelligence.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + app: intelligence-server + chart: {{ template "portal.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +automountServiceAccountToken: {{ .Values.intelligence.serviceAccount.automountServiceAccountToken }} +{{- end }} +{{- end }} diff --git a/charts/portal/templates/intelligence/intelligence-service.yaml b/charts/portal/templates/intelligence/intelligence-service.yaml new file mode 100644 index 00000000..99981b73 --- /dev/null +++ b/charts/portal/templates/intelligence/intelligence-service.yaml @@ -0,0 +1,32 @@ +{{- if .Values.portal.intelligence.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: intelligence-server + labels: + app: intelligence-server + chart: {{ template "portal.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + type: {{ .Values.intelligence.service.type }} + ports: + - name: http + port: {{ .Values.intelligence.service.port }} + targetPort: 8282 + selector: + app: intelligence-server + release: {{ .Release.Name }} + {{- if .Values.intelligence.service.sessionAffinity }} + sessionAffinity: {{ .Values.intelligence.service.sessionAffinity }} + {{- end }} + {{- if .Values.intelligence.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- toYaml .Values.intelligence.service.sessionAffinityConfig | nindent 4 }} + {{- end }} + {{- if .Values.intelligence.service.externalTrafficPolicy }} + externalTrafficPolicy: {{ .Values.intelligence.service.externalTrafficPolicy }} + {{- end }} + {{- if .Values.intelligence.service.internalTrafficPolicy }} + internalTrafficPolicy: {{ .Values.intelligence.service.internalTrafficPolicy }} + {{- end }} +{{- end }} diff --git a/charts/portal/templates/jobs/cert-update-job.yaml b/charts/portal/templates/jobs/cert-update-job.yaml index 0b9cfe21..8a78fb38 100644 --- a/charts/portal/templates/jobs/cert-update-job.yaml +++ b/charts/portal/templates/jobs/cert-update-job.yaml @@ -82,16 +82,6 @@ spec: secretKeyRef: name: {{ template "portal.fullname" . }}-job-secret key: INTERNAL_SECRETNAME - - name: KAFKA_SECRETNAME - valueFrom: - secretKeyRef: - name: {{ template "portal.fullname" . }}-job-secret - key: KAFKA_SECRETNAME - - name: CA_SECRETNAME - valueFrom: - secretKeyRef: - name: {{ template "portal.fullname" . }}-job-secret - key: CA_SECRETNAME - name: EXTERNAL_SECRETNAME valueFrom: secretKeyRef: diff --git a/charts/portal/templates/jobs/job-secret.yaml b/charts/portal/templates/jobs/job-secret.yaml index c24cb38f..dabd1572 100644 --- a/charts/portal/templates/jobs/job-secret.yaml +++ b/charts/portal/templates/jobs/job-secret.yaml @@ -21,6 +21,4 @@ data: PRIV_KEY_PASS: {{ default "" .Values.tls.keyPass | b64enc | quote }} {{ end }} INTERNAL_SECRETNAME: {{.Values.tls.internalSecretName | b64enc | quote }} - KAFKA_SECRETNAME: {{.Values.tls.kafkaSecretName | b64enc | quote }} - CA_SECRETNAME: {{.Values.tls.kafkaCaSecretName | b64enc | quote }} EXTERNAL_SECRETNAME: {{.Values.tls.externalSecretName | b64enc | quote }} diff --git a/charts/portal/values-production.yaml b/charts/portal/values-production.yaml index 7d81a4a8..f2aab1fd 100644 --- a/charts/portal/values-production.yaml +++ b/charts/portal/values-production.yaml @@ -135,10 +135,8 @@ tls: # These secrets are NOT Helm managed, you will need to clean them up manually. # This will force all of the deployments that use the secrets to upgrade # If you don't do this, you will have to perform a manual restart of each affected container - internalSecretName: &internalSecretName portal-internal-secret - externalSecretName: &externalSecretName portal-external-secret - kafkaCaSecretName: &kafkaCaSecretName portal-kafka-ca-secret - kafkaSecretName: portal-kafka-tls-secret + internalSecretName: portal-internal-secret + externalSecretName: portal-external-secret useSignedCertificates: false # crt, crtChain and key expect files, use --set-file or add them here @@ -601,6 +599,7 @@ image: tps: tenant-provisioning-service:5.4.1 analytics: analytics-server:5.4.1 authenticator: authenticator:5.4.1 + intelligenceServer: intelligence-server:5.4.1 dbUpgrade: db-upgrade-portal:5.4.1 rbacUpgrade: db-upgrade-rbac:5.4.1 upgradeVerify: upgrade-verify:5.4.1 @@ -1141,7 +1140,7 @@ druid: # Configuration for the custom Kafka subchart -kafka: &kafka_config +kafka: # Enable/disable Kafka deployment # Automatically enabled when analytics or intelligence is enabled # Set to false to explicitly disable Kafka even when analytics/intelligence are enabled @@ -1215,12 +1214,6 @@ kafka: &kafka_config enabled: true port: 9092 protocol: PLAINTEXT - # External listener for clients outside the cluster (third-party agents) - # NOTE: Automatically controlled by kafka.externalAccess.enabled - # External listener is created only when intelligence is enabled - external: - port: 9094 - protocol: SSL # Controller listener for KRaft mode controller: enabled: true @@ -1232,24 +1225,6 @@ kafka: &kafka_config # Log retention configuration logRetentionHours: 6 - # TLS/SSL Configuration - tls: - # Enable TLS/SSL - enabled by default for external SSL support - enabled: true - # TLS type (PEM or JKS) - type: PEM - # Client authentication (none, requested, required) - clientAuth: required - # Secret containing TLS certificates (via environment variables) - # Leave empty to use volume-mounted certificates from internalSecretName instead - secretName: "" - # Keys in the secret - keystoreKeyKey: "keystore.key" - truststoreCertKey: "truststore.pem" - # Password secret for encrypted keys - passwordSecretName: "" - passwordSecretKey: "keypass.txt" - # SASL Authentication Configuration sasl: # Enable SASL authentication @@ -1278,44 +1253,8 @@ kafka: &kafka_config name: "" automountServiceAccountToken: true - # External access configuration for autodiscovery - # NOTE: External access is automatically enabled when portal.intelligence.enabled is true - # Intelligence requires external Kafka access for third-party agents - # Default is false. Set to true manually or enable intelligence feature. - externalAccess: - enabled: false - # Service type for external access (LoadBalancer or NodePort) - serviceType: LoadBalancer - # Port for external access - port: 9094 - # Hostname/domain for external access (used in advertised listeners) - hostname: "" - # Annotations for external services - annotations: - cloud.google.com/load-balancer-type: Internal - # LoadBalancer IPs (optional, for static IPs) - loadBalancerIPs: [] - # NodePort values (optional, for NodePort service type) - nodePorts: [] - # Auto-generate advertised listeners based on service IPs - autoAdvertisedListeners: true - # Discovery configuration for LoadBalancer/NodePort IP detection - # NOTE: This only applies when externalAccess.enabled is true (i.e., when intelligence is enabled) - # When intelligence is disabled, Kafka uses internal-only listeners and will start normally - discovery: - # Maximum time to wait for LoadBalancer IP (in seconds) - # Increase this if your cloud provider takes longer to provision LoadBalancers - timeoutSeconds: 120 - # Retry interval (in seconds) - retryIntervalSeconds: 5 - # Fail fast: If true, pod will fail when LoadBalancer IP cannot be discovered - # This prevents Kafka from starting with incorrect advertised listeners - # IMPORTANT: Only affects behavior when intelligence is enabled - # Recommended: true (prevents connection issues for external clients) - failOnDiscoveryFailure: true - # Settings for RabbitMQ - https://github.com/bitnami/charts/tree/master/bitnami/rabbitmq -rabbitmq: &rabbitmq_config +rabbitmq: enabled: true host: rabbitmq fullnameOverride: rabbitmq @@ -1408,22 +1347,11 @@ rabbitmq: &rabbitmq_config - -ec - curl -f --user {{ .Values.auth.username }}:$RABBITMQ_PASSWORD 127.0.0.1:{{ .Values.containerPorts.manager }}/api/health/checks/local-alarms -# Settings for the intelligence subchart -apim-intelligence: - # Use a YAML alias (*) to reference the Kafka configuration from above. - # This avoids duplicating the configuration and ensures the intelligence - # subchart has the correct values for broker discovery. - kafka: *kafka_config - rabbitmq: *rabbitmq_config - - # NOTE: Do NOT set portal.domain here - it will override user values - # Intelligence will automatically use portal.domain from parent chart if available - # Jenkins must set: portal.domain AND apim-intelligence.portal.domain to the same value - - # Image configuration +# Intelligence Server configuration +intelligence: + forceRedeploy: false + replicaCount: 1 image: - autoDiscovery: tls-automator:5.4.1 # Used for Kafka broker discovery init container (Overridden by Jenkins with TAG_TLS_MANAGER) - intelligenceServer: intelligence-server:5.4.1 pullPolicy: IfNotPresent # RBAC configuration @@ -1436,54 +1364,43 @@ apim-intelligence: automountServiceAccountToken: true name: "" - intelligenceServer: - # Configuration for the Intelligence Server. - kafka: - # A list of external advertised Kafka brokers. - # externalAdvertisedBrokers: - # Configuration for Kafka broker auto-discovery. - autoDiscovery: - # Enables or disables Kafka broker auto-discovery. - enabled: true - image: - # The repository of the auto-discovery image. - repository: "docker.io/caapim/kubectl" - # The tag of the auto-discovery image. - tag: "1.33.3-debian-12-r0" - # The pull policy for the auto-discovery image. - pullPolicy: IfNotPresent - # Resource requests and limits for the auto-discovery init container - # resources: {} - # Configuration for the Kafka CA certificate. - kafkaCa: - # The name of the secret containing the Kafka CA certificate. - caSecretName: *externalSecretName - # The name of the secret containing the password for the Kafka CA certificate. - passwordSecretName: *externalSecretName - # The key in the password secret that contains the password. - passwordSecretKey: "keypass.txt" - forceRedeploy: false - pdb: - create: false - maxUnavailable: "" - minAvailable: "" - replicaCount: 1 - image: - pullPolicy: IfNotPresent - # ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ - podAnnotations: {} - # ref:https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod - podSecurityContext: {} - # ref:https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container - containerSecurityContext: {} - resources: - requests: {} - # memory: 4Gi - limits: {} - # memory: 4Gi - # nodeSelector: {} - # tolerations: [] - # affinity: {} + # Kafka configuration for the intelligence server + kafka: + brokers: "kafka:9092" + securityProtocol: PLAINTEXT + kafkaCa: + caSecretName: portal-external-secret + passwordSecretName: portal-external-secret + passwordSecretKey: "keypass.txt" + caCertKey: "apim-ssl.crt" + caKeyKey: "apim-ssl.key" + + # Service configuration + service: + type: ClusterIP + port: 8282 + sessionAffinity: None + externalTrafficPolicy: "" + internalTrafficPolicy: "" + + portalDataHost: "portal-data:8080" + + pdb: + create: false + maxUnavailable: "" + minAvailable: "" + podAnnotations: {} + podSecurityContext: {} + containerSecurityContext: {} + resources: + requests: {} + # memory: 4Gi + limits: {} + # memory: 4Gi + additionalLabels: {} + affinity: {} + nodeSelector: {} + tolerations: [] # Settings for Nginx-Ingress - https://github.com/kubernetes/ingress-nginx/tree/master/charts/ingress-nginx ingress-nginx: diff --git a/charts/portal/values.yaml b/charts/portal/values.yaml index 6a02f986..9dcf76fa 100644 --- a/charts/portal/values.yaml +++ b/charts/portal/values.yaml @@ -75,6 +75,18 @@ portal: # Intelligence is disabled by default. Enable it for third-party agent integration. intelligence: enabled: false + # KafkaTcpProxy configuration on APIM Gateway + kafkaProxy: + # Internal Kafka bootstrap servers that the proxy forwards traffic to + targetBootstrapServers: "kafka:9092" + # Bootstrap listen port on the APIM Gateway (KafkaTcpProxy assertion) + listenPort: 9192 + # Per-broker listen ports start from this value (9193, 9194, 9195, ...) + brokerStartPort: 9193 + # Hostname advertised to Kafka clients in metadata responses. + # Defaults to the APIM Gateway's public hostname (tssg-public-host). + # Must be externally reachable by TSSGs connecting through the proxy. + advertisedHost: "" # Please set analytics.replicaCount to a minimum of 2 aggregation: false # Specify a Gateway v9.x license file via set portal.license.value @@ -138,10 +150,8 @@ tls: # These secrets are NOT Helm managed, you will need to clean them up manually. # This will force all of the deployments that use the secrets to upgrade # If you don't do this, you will have to perform a manual restart of each affected container - internalSecretName: &internalSecretName portal-internal-secret - externalSecretName: &externalSecretName portal-external-secret - kafkaCaSecretName: &kafkaCaSecretName portal-kafka-ca-secret - kafkaSecretName: portal-kafka-tls-secret + internalSecretName: portal-internal-secret + externalSecretName: portal-external-secret useSignedCertificates: false # crt, crtChain and key expect files, use --set-file or add them here @@ -534,6 +544,7 @@ image: tps: tenant-provisioning-service:5.4.1 analytics: analytics-server:5.4.1 authenticator: authenticator:5.4.1 + intelligenceServer: intelligence-server:5.4.1 dbUpgrade: db-upgrade-portal:5.4.1 rbacUpgrade: db-upgrade-rbac:5.4.1 upgradeVerify: upgrade-verify:5.4.1 @@ -987,7 +998,7 @@ druid: # Configuration for the custom Kafka subchart -kafka: &kafka_config +kafka: # Enable/disable Kafka deployment # Automatically enabled when analytics or intelligence is enabled # Set to false to explicitly disable Kafka even when analytics/intelligence are enabled @@ -1061,12 +1072,6 @@ kafka: &kafka_config enabled: true port: 9092 protocol: PLAINTEXT - # External listener for clients outside the cluster (third-party agents) - # NOTE: Automatically controlled by kafka.externalAccess.enabled - # External listener is created only when intelligence is enabled - external: - port: 9094 - protocol: SSL # Controller listener for KRaft mode controller: enabled: true @@ -1078,24 +1083,6 @@ kafka: &kafka_config # Log retention configuration logRetentionHours: 6 - # TLS/SSL Configuration - tls: - # Enable TLS/SSL - enabled by default for external SSL support - enabled: true - # TLS type (PEM or JKS) - type: PEM - # Client authentication (none, requested, required) - clientAuth: required - # Secret containing TLS certificates (via environment variables) - # Leave empty to use volume-mounted certificates from internalSecretName instead - secretName: "" - # Keys in the secret - keystoreKeyKey: "keystore.key" - truststoreCertKey: "truststore.pem" - # Password secret for encrypted keys - passwordSecretName: "" - passwordSecretKey: "keypass.txt" - # SASL Authentication Configuration sasl: # Enable SASL authentication @@ -1119,49 +1106,13 @@ kafka: &kafka_config # Service Account serviceAccount: - create: true + create: false annotations: {} name: "" - automountServiceAccountToken: true - - # External access configuration for autodiscovery - # NOTE: External access is automatically enabled when portal.intelligence.enabled is true - # Intelligence requires external Kafka access for third-party agents - # Default is false. Set to true manually or enable intelligence feature. - externalAccess: - enabled: false - # Service type for external access (LoadBalancer or NodePort) - serviceType: LoadBalancer - # Port for external access - port: 9094 - # Hostname/domain for external access (used in advertised listeners) - hostname: "" - # Annotations for external services - annotations: - cloud.google.com/load-balancer-type: Internal - # LoadBalancer IPs (optional, for static IPs) - loadBalancerIPs: [] - # NodePort values (optional, for NodePort service type) - nodePorts: [] - # Auto-generate advertised listeners based on service IPs - autoAdvertisedListeners: true - # Discovery configuration for LoadBalancer/NodePort IP detection - # NOTE: This only applies when externalAccess.enabled is true (i.e., when intelligence is enabled) - # When intelligence is disabled, Kafka uses internal-only listeners and will start normally - discovery: - # Maximum time to wait for LoadBalancer IP (in seconds) - # Increase this if your cloud provider takes longer to provision LoadBalancers - timeoutSeconds: 120 - # Retry interval (in seconds) - retryIntervalSeconds: 5 - # Fail fast: If true, pod will fail when LoadBalancer IP cannot be discovered - # This prevents Kafka from starting with incorrect advertised listeners - # IMPORTANT: Only affects behavior when intelligence is enabled - # Recommended: true (prevents connection issues for external clients) - failOnDiscoveryFailure: true + automountServiceAccountToken: false # Settings for RabbitMQ - https://github.com/bitnami/charts/tree/master/bitnami/rabbitmq -rabbitmq: &rabbitmq_config +rabbitmq: enabled: true host: rabbitmq fullnameOverride: rabbitmq @@ -1244,82 +1195,63 @@ rabbitmq: &rabbitmq_config - -ec - curl -f --user {{ .Values.auth.username }}:$RABBITMQ_PASSWORD 127.0.0.1:{{ .Values.containerPorts.manager }}/api/health/checks/local-alarms -# Settings for the intelligence subchart -apim-intelligence: - # Use a YAML alias (*) to reference the Kafka configuration from above. - # This avoids duplicating the configuration and ensures the intelligence - # subchart has the correct values for broker discovery. - kafka: *kafka_config - rabbitmq: *rabbitmq_config - - # NOTE: Do NOT set portal.domain here - it will override user values - # Intelligence will automatically use portal.domain from parent chart if available - # Jenkins must set: portal.domain AND apim-intelligence.portal.domain to the same value - - # Image configuration +# Intelligence Server configuration +intelligence: + forceRedeploy: false + replicaCount: 1 image: - autoDiscovery: tls-automator:5.4.1 # Used for Kafka broker discovery init container (Overridden by Jenkins with TAG_TLS_MANAGER) - intelligenceServer: intelligence-server:5.4.1 pullPolicy: IfNotPresent # RBAC configuration rbac: - create: true + create: false # Service Account configuration serviceAccount: - create: true - automountServiceAccountToken: true + create: false + automountServiceAccountToken: false name: "" - intelligenceServer: - # Configuration for the Intelligence Server. - kafka: - # A list of external advertised Kafka brokers. - # externalAdvertisedBrokers: - # Configuration for Kafka broker auto-discovery. - autoDiscovery: - # Enables or disables Kafka broker auto-discovery. - enabled: true - image: - # The repository of the auto-discovery image. - repository: "docker.io/caapim/kubectl" - # The tag of the auto-discovery image. - tag: "1.33.3-debian-12-r0" - # The pull policy for the auto-discovery image. - pullPolicy: IfNotPresent - # Resource requests and limits for the auto-discovery init container - # resources: {} - # Configuration for the Kafka CA certificate. - kafkaCa: - # The name of the secret containing the Kafka CA certificate. - caSecretName: *externalSecretName - # The name of the secret containing the password for the Kafka CA certificate. - passwordSecretName: *externalSecretName - # The key in the password secret that contains the password. - passwordSecretKey: "keypass.txt" - forceRedeploy: false - pdb: - create: false - maxUnavailable: "" - minAvailable: "" - replicaCount: 1 - image: - pullPolicy: IfNotPresent - # ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ - podAnnotations: {} - # ref:https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod - podSecurityContext: {} - # ref:https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container - containerSecurityContext: {} - resources: - requests: {} - # memory: 4Gi - limits: {} - # memory: 4Gi - # nodeSelector: {} - # tolerations: [] - # affinity: {} + # Kafka configuration for the intelligence server + kafka: + brokers: "kafka:9092" + securityProtocol: PLAINTEXT + kafkaCa: + caSecretName: portal-external-secret + passwordSecretName: portal-external-secret + passwordSecretKey: "keypass.txt" + caCertKey: "apim-ssl.crt" + caKeyKey: "apim-ssl.key" + + # Service configuration + service: + type: ClusterIP + port: 8282 + sessionAffinity: None + externalTrafficPolicy: "" + internalTrafficPolicy: "" + + portalDataHost: "portal-data:8080" + + pdb: + create: false + maxUnavailable: "" + minAvailable: "" + # ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + podAnnotations: {} + # ref:https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + podSecurityContext: {} + # ref:https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + containerSecurityContext: {} + resources: + requests: {} + # memory: 4Gi + limits: {} + # memory: 4Gi + additionalLabels: {} + affinity: {} + nodeSelector: {} + tolerations: [] # Settings for Nginx-Ingress - https://github.com/kubernetes/ingress-nginx/tree/master/charts/ingress-nginx ingress-nginx: From 13cb594387cfc3be6beac5bb2487a662b9d4fee4 Mon Sep 17 00:00:00 2001 From: Kiran Vaddadi Date: Fri, 13 Mar 2026 22:04:42 +0530 Subject: [PATCH 2/4] SNI-based per-broker routing for Kafka TCP Proxy across all ingress types Add per-broker SNI hostname generation and dynamic ingress/route/tlsroute rules for Kafka TCP Proxy. Each Kafka broker gets a unique hostname (e.g., kafka-proxy-1.domain) routed via TLS passthrough on port 443, eliminating the need for multiple external ports. Shorten K8s service port names to fit 15-char limit (kfk-proxy-boot, kfk-proxy-brk-N). Update brokerStartPort default to 9194 to avoid port collision with listenPort after init-tssh.sh -1 adjustment. Add advertisedPort and brokerAddressPattern config values. Remove intelligence-specific RBAC in favor of shared service account. Made-with: Cursor --- charts/portal/templates/_helpers.tpl | 41 ++++++++++-------- charts/portal/templates/apim/apim-config.yaml | 6 ++- .../portal/templates/apim/apim-service.yaml | 9 ++-- .../portal/templates/gateway-api/gateway.yaml | 11 ++++- .../templates/gateway-api/tlsroute.yaml | 42 +++++++++++++++++++ .../templates/ingress/contour-httpproxy.yaml | 35 ++++++++++++++++ charts/portal/templates/ingress/ingress.yaml | 28 ++++++++++++- charts/portal/templates/ingress/route.yaml | 28 ++++++++++++- .../intelligence/intelligence-deployment.yaml | 3 +- .../intelligence/intelligence-role.yaml | 23 ---------- .../intelligence-rolebinding.yaml | 21 ---------- .../intelligence-service-account.yaml | 15 ------- charts/portal/values-production.yaml | 10 ----- charts/portal/values.yaml | 28 ++++++------- 14 files changed, 185 insertions(+), 115 deletions(-) delete mode 100644 charts/portal/templates/intelligence/intelligence-role.yaml delete mode 100644 charts/portal/templates/intelligence/intelligence-rolebinding.yaml delete mode 100644 charts/portal/templates/intelligence/intelligence-service-account.yaml diff --git a/charts/portal/templates/_helpers.tpl b/charts/portal/templates/_helpers.tpl index 1b13d4cf..a6048e38 100644 --- a/charts/portal/templates/_helpers.tpl +++ b/charts/portal/templates/_helpers.tpl @@ -433,6 +433,27 @@ Used when intelligence is enabled. {{- end }} {{- end -}} +{{/* +Generate per-broker hostname for a given nodeId using the kafka-proxy-host base. +Produces pattern like: dev-portal-kafka-proxy-$(nodeId).example.com +Used as the default for KAFKA_PROXY_BROKER_ADDRESS_PATTERN. +*/}} +{{- define "kafka-proxy-broker-pattern" -}} + {{- $base := include "kafka-proxy-host" . -}} + {{- $parts := splitn "." 2 $base -}} + {{- printf "%s-$(nodeId).%s" (index $parts "_0") (index $parts "_1") -}} +{{- end -}} + +{{/* +Generate per-broker hostname for a specific nodeId (for ingress routes). +Takes a dict with "root" (context) and "nodeId" (int). +*/}} +{{- define "kafka-proxy-broker-host" -}} + {{- $base := include "kafka-proxy-host" .root -}} + {{- $parts := splitn "." 2 $base -}} + {{- printf "%s-%s.%s" (index $parts "_0") (toString .nodeId) (index $parts "_1") -}} +{{- end -}} + {{/* Generate default parentRefs for routes when none are provided. Produces a list with a single parentRef pointing to the chart's Gateway. @@ -464,21 +485,6 @@ Get "intelligence" database name {{- end }} {{- end -}} -{{/* -Set the service account name for the Intelligence Server -*/}} -{{- define "intelligence.serviceAccountName" -}} -{{- if .Values.global.serviceAccountName }} - {{ default "default" .Values.global.serviceAccountName }} -{{- else }} -{{- if .Values.intelligence.serviceAccount.create -}} - {{ default "intelligence-server" .Values.intelligence.serviceAccount.name }} -{{- else -}} - {{ default "default" .Values.intelligence.serviceAccount.name }} -{{- end -}} -{{- end -}} -{{- end -}} - {{/* Get Kafka broker address for intelligence server. Uses the kafka subchart's fullnameOverride and internal listener port. @@ -494,11 +500,10 @@ Uses the kafka subchart's fullnameOverride and internal listener port. {{/* Generate Kafka proxy bootstrap address for intelligence server. -Format: {kafka-proxy-host}:{listenPort} -Reuses the kafka-proxy-host helper and the kafkaProxy.listenPort value. +Uses the advertised port (default 443) for SNI-based routing. */}} {{- define "intelligence.kafkaProxyBootstrap" -}} {{- $host := include "kafka-proxy-host" . -}} - {{- $port := int (.Values.portal.intelligence.kafkaProxy.listenPort | default 9192) -}} + {{- $port := int (.Values.portal.intelligence.kafkaProxy.advertisedPort | default 443) -}} {{- printf "%s:%d" $host $port -}} {{- end -}} diff --git a/charts/portal/templates/apim/apim-config.yaml b/charts/portal/templates/apim/apim-config.yaml index dba448e7..2aff5f47 100644 --- a/charts/portal/templates/apim/apim-config.yaml +++ b/charts/portal/templates/apim/apim-config.yaml @@ -33,8 +33,10 @@ data: INTELLIGENCE_ENABLED: "true" KAFKA_PROXY_TARGET_SERVERS: {{ default "kafka:9092" .Values.portal.intelligence.kafkaProxy.targetBootstrapServers | quote }} KAFKA_PROXY_LISTEN_PORT: {{ default 9192 .Values.portal.intelligence.kafkaProxy.listenPort | quote }} - KAFKA_PROXY_BROKER_START_PORT: {{ default 9193 .Values.portal.intelligence.kafkaProxy.brokerStartPort | quote }} - KAFKA_PROXY_ADVERTISED_HOST: {{ default (include "tssg-public-host" .) .Values.portal.intelligence.kafkaProxy.advertisedHost | quote }} + KAFKA_PROXY_BROKER_START_PORT: {{ default 9194 .Values.portal.intelligence.kafkaProxy.brokerStartPort | quote }} + KAFKA_PROXY_ADVERTISED_HOST: {{ default (include "kafka-proxy-host" .) .Values.portal.intelligence.kafkaProxy.advertisedHost | quote }} + KAFKA_PROXY_ADVERTISED_PORT: {{ default 443 .Values.portal.intelligence.kafkaProxy.advertisedPort | quote }} + KAFKA_PROXY_BROKER_ADDRESS_PATTERN: {{ default (include "kafka-proxy-broker-pattern" .) .Values.portal.intelligence.kafkaProxy.brokerAddressPattern | quote }} {{- else }} INTELLIGENCE_ENABLED: "false" {{- end }} diff --git a/charts/portal/templates/apim/apim-service.yaml b/charts/portal/templates/apim/apim-service.yaml index 08d8ac8a..5e46ca23 100644 --- a/charts/portal/templates/apim/apim-service.yaml +++ b/charts/portal/templates/apim/apim-service.yaml @@ -45,19 +45,20 @@ spec: - port: {{ .Values.portal.intelligence.kafkaProxy.listenPort | default 9192 }} targetPort: {{ .Values.portal.intelligence.kafkaProxy.listenPort | default 9192 }} protocol: TCP - name: kafka-proxy-bootstrap -{{- $brokerStartPort := int (.Values.portal.intelligence.kafkaProxy.brokerStartPort | default 9193) }} + name: kfk-proxy-boot +{{- $brokerStartPort := int (.Values.portal.intelligence.kafkaProxy.brokerStartPort | default 9194) }} {{- $replicaCount := int (.Values.kafka.kafka.replicaCount | default 1) }} {{- $ordinalStart := 0 }} {{- if and .Values.kafka.kafka.ordinals (hasKey .Values.kafka.kafka.ordinals "start") }} {{- $ordinalStart = int .Values.kafka.kafka.ordinals.start }} {{- end }} {{- range $i := until $replicaCount }} -{{- $brokerPort := add $brokerStartPort (add $ordinalStart $i) }} +{{- $nodeId := add $ordinalStart $i }} +{{- $brokerPort := add (sub $brokerStartPort 1) $nodeId }} - port: {{ $brokerPort }} targetPort: {{ $brokerPort }} protocol: TCP - name: kafka-proxy-broker-{{ add $ordinalStart $i }} + name: kfk-proxy-brk-{{ $nodeId }} {{- end }} {{- end }} selector: diff --git a/charts/portal/templates/gateway-api/gateway.yaml b/charts/portal/templates/gateway-api/gateway.yaml index 4e7f42f6..4ec521a8 100644 --- a/charts/portal/templates/gateway-api/gateway.yaml +++ b/charts/portal/templates/gateway-api/gateway.yaml @@ -41,9 +41,18 @@ spec: {{- $hostnames = append $hostnames (include "pssg-sync-host" .) }} {{- $hostnames = append $hostnames (include "pssg-sso-host" .) }} {{- $hostnames = append $hostnames (include "broker-host" .) }} - {{/* Kafka proxy route */}} + {{/* Kafka proxy routes: bootstrap + per-broker SNI hostnames */}} {{- if .Values.portal.intelligence.enabled }} {{- $hostnames = append $hostnames (include "kafka-proxy-host" .) }} + {{- $replicaCount := int (.Values.kafka.kafka.replicaCount | default 1) }} + {{- $ordinalStart := 0 }} + {{- if and .Values.kafka.kafka.ordinals (hasKey .Values.kafka.kafka.ordinals "start") }} + {{- $ordinalStart = int .Values.kafka.kafka.ordinals.start }} + {{- end }} + {{- range $i := until $replicaCount }} + {{- $nodeId := add $ordinalStart $i }} + {{- $hostnames = append $hostnames (include "kafka-proxy-broker-host" (dict "root" $ "nodeId" $nodeId)) }} + {{- end }} {{- end }} {{/* Dynamic tenant routes */}} {{- range .Values.ingress.tenantIds }} diff --git a/charts/portal/templates/gateway-api/tlsroute.yaml b/charts/portal/templates/gateway-api/tlsroute.yaml index eaebdf53..3fbb9e20 100644 --- a/charts/portal/templates/gateway-api/tlsroute.yaml +++ b/charts/portal/templates/gateway-api/tlsroute.yaml @@ -265,6 +265,48 @@ spec: - backendRefs: - name: apim port: {{ .Values.portal.intelligence.kafkaProxy.listenPort | default 9192 }} +{{- $brokerStartPort := int (.Values.portal.intelligence.kafkaProxy.brokerStartPort | default 9193) }} +{{- $replicaCount := int (.Values.kafka.kafka.replicaCount | default 1) }} +{{- $ordinalStart := 0 }} +{{- if and .Values.kafka.kafka.ordinals (hasKey .Values.kafka.kafka.ordinals "start") }} +{{- $ordinalStart = int .Values.kafka.kafka.ordinals.start }} +{{- end }} +{{- range $i := until $replicaCount }} +{{- $nodeId := add $ordinalStart $i }} +{{- $brokerPort := add $brokerStartPort $nodeId }} +--- +apiVersion: {{ include "portal.gatewayAPI.tlsRouteApiVersion" $root }} +kind: TLSRoute +metadata: + name: portal-tlsroute-kafka-broker-{{ $nodeId }} + namespace: {{ $.Release.Namespace }} + labels: + app: {{ template "portal.name" $root }} + chart: {{ template "portal.chart" $root }} + release: {{ $root.Release.Name }} + heritage: {{ $root.Release.Service }} + {{- range $key, $val := $root.Values.global.additionalLabels }} + {{ $key }}: "{{ $val }}" + {{- end }} + {{- range $key, $val := $root.Values.ingress.gatewayAPI.labels }} + {{ $key }}: "{{ $val }}" + {{- end }} + {{- if $annotations }} + annotations: + {{- range $key, $val := $annotations }} + {{ $key }}: "{{ $val }}" + {{- end }} + {{- end }} +spec: + parentRefs: + {{- include "portal.gatewayAPI.defaultParentRefs" $root | nindent 4 }} + hostnames: + - {{ include "kafka-proxy-broker-host" (dict "root" $root "nodeId" $nodeId) | quote }} + rules: + - backendRefs: + - name: apim + port: {{ $brokerPort }} +{{- end }} {{- end }} {{/* ============================================================ diff --git a/charts/portal/templates/ingress/contour-httpproxy.yaml b/charts/portal/templates/ingress/contour-httpproxy.yaml index 058ddd7b..b2a7bc1d 100644 --- a/charts/portal/templates/ingress/contour-httpproxy.yaml +++ b/charts/portal/templates/ingress/contour-httpproxy.yaml @@ -201,6 +201,41 @@ spec: services: - name: apim port: {{ .Values.portal.intelligence.kafkaProxy.listenPort | default 9192 }} +{{- $brokerStartPort := int (.Values.portal.intelligence.kafkaProxy.brokerStartPort | default 9193) }} +{{- $replicaCount := int (.Values.kafka.kafka.replicaCount | default 1) }} +{{- $ordinalStart := 0 }} +{{- if and .Values.kafka.kafka.ordinals (hasKey .Values.kafka.kafka.ordinals "start") }} +{{- $ordinalStart = int .Values.kafka.kafka.ordinals.start }} +{{- end }} +{{- range $i := until $replicaCount }} +{{- $nodeId := add $ordinalStart $i }} +{{- $brokerPort := add $brokerStartPort $nodeId }} +--- +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: portal-kafka-broker-{{ $nodeId }}-httpproxy + labels: + app: {{ template "portal.name" $ }} + chart: {{ template "portal.chart" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} + {{- range $key, $val := $.Values.global.additionalLabels }} + {{ $key }}: "{{ $val }}" + {{- end }} + {{- range $key, $val := $.Values.ingress.additionalLabels }} + {{ $key }}: "{{ $val }}" + {{- end }} +spec: + virtualhost: + fqdn: {{ include "kafka-proxy-broker-host" (dict "root" $ "nodeId" $nodeId) | quote }} + tls: + passthrough: true + tcpproxy: + services: + - name: apim + port: {{ $brokerPort }} +{{- end }} {{- end }} {{- range $i,$tenantId := $.Values.ingress.tenantIds }} diff --git a/charts/portal/templates/ingress/ingress.yaml b/charts/portal/templates/ingress/ingress.yaml index 62b767fc..d022ff49 100644 --- a/charts/portal/templates/ingress/ingress.yaml +++ b/charts/portal/templates/ingress/ingress.yaml @@ -157,11 +157,35 @@ spec: service: name: apim port: - name: kafka-proxy-bootstrap + name: kfk-proxy-boot {{- else }} - backend: serviceName: apim - servicePort: kafka-proxy-bootstrap + servicePort: kfk-proxy-boot +{{- end }} +{{- $replicaCount := int (.Values.kafka.kafka.replicaCount | default 1) }} +{{- $ordinalStart := 0 }} +{{- if and .Values.kafka.kafka.ordinals (hasKey .Values.kafka.kafka.ordinals "start") }} +{{- $ordinalStart = int .Values.kafka.kafka.ordinals.start }} +{{- end }} +{{- range $i := until $replicaCount }} +{{- $nodeId := add $ordinalStart $i }} + - host: {{ include "kafka-proxy-broker-host" (dict "root" $ "nodeId" $nodeId) | quote }} + http: + paths: +{{- if semverCompare ">=1.19-0" $kubeTargetVersion }} + - pathType: Prefix + path: "/" + backend: + service: + name: apim + port: + name: kfk-proxy-brk-{{ $nodeId }} +{{- else }} + - backend: + serviceName: apim + servicePort: kfk-proxy-brk-{{ $nodeId }} +{{- end }} {{- end }} {{- end }} {{- range .Values.ingress.customRoutes }} diff --git a/charts/portal/templates/ingress/route.yaml b/charts/portal/templates/ingress/route.yaml index 13822178..6b6508a7 100644 --- a/charts/portal/templates/ingress/route.yaml +++ b/charts/portal/templates/ingress/route.yaml @@ -136,7 +136,7 @@ metadata: spec: host: {{ include "kafka-proxy-host" . | quote }} port: - targetPort: kafka-proxy-bootstrap + targetPort: kfk-proxy-boot tls: termination: passthrough to: @@ -144,5 +144,31 @@ spec: name: apim weight: 100 wildcardPolicy: None +{{- $replicaCount := int (.Values.kafka.kafka.replicaCount | default 1) }} +{{- $ordinalStart := 0 }} +{{- if and .Values.kafka.kafka.ordinals (hasKey .Values.kafka.kafka.ordinals "start") }} +{{- $ordinalStart = int .Values.kafka.kafka.ordinals.start }} +{{- end }} +{{- range $i := until $replicaCount }} +{{- $nodeId := add $ordinalStart $i }} +--- +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: apim-route-kafka-broker-{{ $nodeId }} + labels: + router: default +spec: + host: {{ include "kafka-proxy-broker-host" (dict "root" $ "nodeId" $nodeId) | quote }} + port: + targetPort: kfk-proxy-brk-{{ $nodeId }} + tls: + termination: passthrough + to: + kind: Service + name: apim + weight: 100 + wildcardPolicy: None +{{- end }} {{- end }} {{- end }} diff --git a/charts/portal/templates/intelligence/intelligence-deployment.yaml b/charts/portal/templates/intelligence/intelligence-deployment.yaml index f1351699..b7fd098c 100644 --- a/charts/portal/templates/intelligence/intelligence-deployment.yaml +++ b/charts/portal/templates/intelligence/intelligence-deployment.yaml @@ -42,8 +42,7 @@ spec: {{- end }} {{- end }} spec: - serviceAccountName: {{ include "intelligence.serviceAccountName" . }} - automountServiceAccountToken: {{ .Values.intelligence.serviceAccount.automountServiceAccountToken }} + serviceAccountName: {{ include "portal.serviceAccountName" . }} {{- if .Values.intelligence.affinity }} affinity: {{- toYaml .Values.intelligence.affinity | nindent 12 }} {{- end }} diff --git a/charts/portal/templates/intelligence/intelligence-role.yaml b/charts/portal/templates/intelligence/intelligence-role.yaml deleted file mode 100644 index 50093270..00000000 --- a/charts/portal/templates/intelligence/intelligence-role.yaml +++ /dev/null @@ -1,23 +0,0 @@ -{{- if .Values.portal.intelligence.enabled }} -{{- if and .Values.intelligence.rbac.create .Values.intelligence.serviceAccount.create (not .Values.global.serviceAccountName) }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: intelligence-server - namespace: {{ .Release.Namespace }} - labels: - app: intelligence-server - chart: {{ template "portal.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -rules: - - apiGroups: - - "" - resources: - - services - verbs: - - get - - list - - watch -{{- end }} -{{- end }} diff --git a/charts/portal/templates/intelligence/intelligence-rolebinding.yaml b/charts/portal/templates/intelligence/intelligence-rolebinding.yaml deleted file mode 100644 index 589122ad..00000000 --- a/charts/portal/templates/intelligence/intelligence-rolebinding.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if .Values.portal.intelligence.enabled }} -{{- if and .Values.intelligence.rbac.create .Values.intelligence.serviceAccount.create (not .Values.global.serviceAccountName) }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: intelligence-server - namespace: {{ .Release.Namespace }} - labels: - app: intelligence-server - chart: {{ template "portal.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: intelligence-server -subjects: - - kind: ServiceAccount - name: {{ include "intelligence.serviceAccountName" . }} -{{- end }} -{{- end }} diff --git a/charts/portal/templates/intelligence/intelligence-service-account.yaml b/charts/portal/templates/intelligence/intelligence-service-account.yaml deleted file mode 100644 index a833047e..00000000 --- a/charts/portal/templates/intelligence/intelligence-service-account.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if .Values.portal.intelligence.enabled }} -{{- if and .Values.intelligence.serviceAccount.create (not .Values.global.serviceAccountName) }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "intelligence.serviceAccountName" . }} - namespace: {{ .Release.Namespace }} - labels: - app: intelligence-server - chart: {{ template "portal.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -automountServiceAccountToken: {{ .Values.intelligence.serviceAccount.automountServiceAccountToken }} -{{- end }} -{{- end }} diff --git a/charts/portal/values-production.yaml b/charts/portal/values-production.yaml index f2aab1fd..10ccd2f3 100644 --- a/charts/portal/values-production.yaml +++ b/charts/portal/values-production.yaml @@ -1354,16 +1354,6 @@ intelligence: image: pullPolicy: IfNotPresent - # RBAC configuration - rbac: - create: true - - # Service Account configuration - serviceAccount: - create: true - automountServiceAccountToken: true - name: "" - # Kafka configuration for the intelligence server kafka: brokers: "kafka:9092" diff --git a/charts/portal/values.yaml b/charts/portal/values.yaml index 9dcf76fa..057db9b5 100644 --- a/charts/portal/values.yaml +++ b/charts/portal/values.yaml @@ -81,12 +81,20 @@ portal: targetBootstrapServers: "kafka:9092" # Bootstrap listen port on the APIM Gateway (KafkaTcpProxy assertion) listenPort: 9192 - # Per-broker listen ports start from this value (9193, 9194, 9195, ...) - brokerStartPort: 9193 + # Per-broker listen ports start from this value (9194, 9195, 9196, ...) + # init-tssg.sh subtracts 1 for the assertion's internal brokerStartPort, + # so actual ports are (brokerStartPort-1) + nodeId where nodeId starts at 1. + brokerStartPort: 9194 # Hostname advertised to Kafka clients in metadata responses. - # Defaults to the APIM Gateway's public hostname (tssg-public-host). - # Must be externally reachable by TSSGs connecting through the proxy. + # Defaults to kafka-proxy-host (the ingress hostname). advertisedHost: "" + # Port advertised to Kafka clients. Default 443 for SNI-based routing + # through the ingress on standard HTTPS port. + advertisedPort: 443 + # Per-broker hostname pattern for SNI routing. Must contain $(nodeId). + # Defaults to kafka-proxy-broker-pattern helper (e.g., dev-portal-kafka-proxy-$(nodeId).example.com). + # Leave empty to use the auto-generated default. + brokerAddressPattern: "" # Please set analytics.replicaCount to a minimum of 2 aggregation: false # Specify a Gateway v9.x license file via set portal.license.value @@ -1202,16 +1210,6 @@ intelligence: image: pullPolicy: IfNotPresent - # RBAC configuration - rbac: - create: false - - # Service Account configuration - serviceAccount: - create: false - automountServiceAccountToken: false - name: "" - # Kafka configuration for the intelligence server kafka: brokers: "kafka:9092" @@ -1275,8 +1273,6 @@ ingress-nginx: # enabled: true # default: false # controllerValue: "k8s.io/ingress-nginx" -# tcp: -# 9443: "/dispatcher:9443" # Settings for portal jobs jobs: From ade3a93446f28430354cb7213bc367d5315cdd82 Mon Sep 17 00:00:00 2001 From: Kiran Vaddadi Date: Mon, 16 Mar 2026 14:04:14 +0530 Subject: [PATCH 3/4] replication --- charts/kafka/Chart.yaml | 2 +- charts/kafka/templates/kafka-config.yaml | 29 ++ .../pre-scale-partition-reassignment.yaml | 278 ++++++++++++++++++ charts/kafka/values.yaml | 16 + charts/portal/Chart.lock | 6 +- charts/portal/Chart.yaml | 2 +- charts/portal/charts/kafka-1.0.2.tgz | Bin 0 -> 29170 bytes charts/portal/values.yaml | 9 + 8 files changed, 337 insertions(+), 5 deletions(-) create mode 100644 charts/kafka/templates/pre-scale-partition-reassignment.yaml create mode 100644 charts/portal/charts/kafka-1.0.2.tgz diff --git a/charts/kafka/Chart.yaml b/charts/kafka/Chart.yaml index a5110f58..7180352c 100644 --- a/charts/kafka/Chart.yaml +++ b/charts/kafka/Chart.yaml @@ -7,5 +7,5 @@ maintainers: - name: Gazza7205 sources: - https://github.com/CAAPIM/apim-charts -version: 1.0.1 +version: 1.0.2 appVersion: "4.0.0" diff --git a/charts/kafka/templates/kafka-config.yaml b/charts/kafka/templates/kafka-config.yaml index a5b35278..a59ca37e 100644 --- a/charts/kafka/templates/kafka-config.yaml +++ b/charts/kafka/templates/kafka-config.yaml @@ -128,6 +128,35 @@ data: # ============================================ KAFKA_CFG_LOG_RETENTION_HOURS: {{ .Values.kafka.logRetentionHours | quote }} + # ============================================ + # Topic Replication Configuration + # ============================================ + # Auto-calculate replication factors: use configured value, or min(replicaCount, 3) when 0 + {{- $replicaCount := int .Values.kafka.replicaCount }} + {{- $maxReplication := 3 }} + {{- $autoRF := min $replicaCount $maxReplication }} + + {{- $defaultRF := int .Values.kafka.defaultReplicationFactor }} + {{- if eq $defaultRF 0 }} + {{- $defaultRF = $autoRF }} + {{- end }} + KAFKA_CFG_DEFAULT_REPLICATION_FACTOR: {{ $defaultRF | quote }} + + KAFKA_CFG_MIN_INSYNC_REPLICAS: {{ .Values.kafka.minInsyncReplicas | quote }} + + {{- $offsetsRF := int .Values.kafka.offsetsTopicReplicationFactor }} + {{- if eq $offsetsRF 0 }} + {{- $offsetsRF = $autoRF }} + {{- end }} + KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR: {{ $offsetsRF | quote }} + + {{- $txnRF := int .Values.kafka.transactionStateLogReplicationFactor }} + {{- if eq $txnRF 0 }} + {{- $txnRF = $autoRF }} + {{- end }} + KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: {{ $txnRF | quote }} + KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR: {{ .Values.kafka.transactionStateLogMinIsr | quote }} + # ============================================ # SASL Authentication Configuration # ============================================ diff --git a/charts/kafka/templates/pre-scale-partition-reassignment.yaml b/charts/kafka/templates/pre-scale-partition-reassignment.yaml new file mode 100644 index 00000000..72a576dd --- /dev/null +++ b/charts/kafka/templates/pre-scale-partition-reassignment.yaml @@ -0,0 +1,278 @@ +# AI assistance has been used to generate some or all contents of this file. That includes, but is not limited to, new code, modifying existing code, stylistic edits. +{{- if .Values.kafka.kraft.enabled }} +# +# Pre-Upgrade Partition Reassignment Job +# +# Purpose: +# Runs BEFORE each Helm upgrade to detect Kafka broker scale-down and reassign +# topic partitions to the surviving brokers. Without this, partitions whose +# replicas reside only on removed brokers become leaderless and unavailable. +# +# How It Works: +# 1. Reads the current StatefulSet replica count from K8s +# 2. Compares it with the new desired replica count from Helm values +# 3. If scaling DOWN, connects to a surviving broker and: +# a. Lists all topic partitions +# b. Identifies partitions with replicas on brokers being removed +# c. Generates a reassignment plan moving them to surviving brokers +# d. Executes the reassignment and waits for completion +# 4. If NOT scaling down, exits immediately (no-op) +# +# Safety: +# - Only runs on scale-down (current > desired replicas) +# - Waits for ISR sync before exiting so brokers can be safely terminated +# - Uses Kafka's built-in reassignment tooling +# - Idempotent: safe to run multiple times +# +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "kafka.fullname" . }}-partition-reassign + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-weight": "-10" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "kafka.fullname" . }}-partition-reassign + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-weight": "-10" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +rules: +- apiGroups: ["apps"] + resources: ["statefulsets"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "kafka.fullname" . }}-partition-reassign + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-weight": "-10" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "kafka.fullname" . }}-partition-reassign +subjects: +- kind: ServiceAccount + name: {{ include "kafka.fullname" . }}-partition-reassign + namespace: {{ .Release.Namespace }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "kafka.fullname" . }}-partition-reassign-{{ .Release.Revision }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-weight": "-8" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + backoffLimit: 2 + activeDeadlineSeconds: 600 + template: + metadata: + labels: + {{- include "kafka.labels" . | nindent 8 }} + job: partition-reassign + spec: + serviceAccountName: {{ include "kafka.fullname" . }}-partition-reassign + restartPolicy: Never + {{- if .Values.global.pullSecret }} + imagePullSecrets: + - name: "{{ .Values.global.pullSecret }}" + {{- end }} + containers: + - name: reassign + image: "{{ .Values.global.portalRepository }}{{ .Values.image.kafka }}" + imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }} + env: + - name: KAFKA_HOME + value: /opt/ca/kafka + command: + - /bin/sh + - -c + - | + set -e + echo "=== Kafka Pre-Scale Partition Reassignment ===" + + NAMESPACE="{{ .Release.Namespace }}" + STS_NAME="{{ include "kafka.statefulsetName" . }}" + SERVICE_NAME="{{ include "kafka.fullname" . }}" + DESIRED_REPLICAS={{ int .Values.kafka.replicaCount }} + KAFKA_BIN="$KAFKA_HOME/bin" + KAFKA_PORT={{ int .Values.kafka.listeners.internal.port }} + {{- $startOrdinal := 0 }} + {{- if .Values.kafka.ordinals }} + {{- $startOrdinal = int .Values.kafka.ordinals.start }} + {{- end }} + ORDINAL_START={{ $startOrdinal }} + + echo "Desired replicas: $DESIRED_REPLICAS (ordinal start: $ORDINAL_START)" + + # Detect current replica count from the running StatefulSet + if ! command -v kubectl >/dev/null 2>&1; then + echo "kubectl not available in this image. Checking if scale-down is needed via Kafka metadata..." + + # Connect to the first surviving broker to discover current brokers + BOOTSTRAP="${STS_NAME}-${ORDINAL_START}.${SERVICE_NAME}:${KAFKA_PORT}" + echo "Connecting to bootstrap: $BOOTSTRAP" + + # Get broker list from Kafka metadata + BROKER_IDS=$($KAFKA_BIN/kafka-broker-api-versions.sh --bootstrap-server "$BOOTSTRAP" 2>/dev/null \ + | grep "^${STS_NAME}-" | sed "s/${STS_NAME}-//" | cut -d'.' -f1 | sort -n) + + if [ -z "$BROKER_IDS" ]; then + echo "Could not discover brokers. Kafka may not be running yet. Skipping reassignment." + exit 0 + fi + + CURRENT_BROKER_COUNT=$(echo "$BROKER_IDS" | wc -l | tr -d ' ') + echo "Current brokers ($CURRENT_BROKER_COUNT): $(echo $BROKER_IDS | tr '\n' ' ')" + else + # Use kubectl to get current replica count + CURRENT_REPLICAS=$(kubectl get statefulset "$STS_NAME" -n "$NAMESPACE" -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "0") + + if [ "$CURRENT_REPLICAS" = "0" ] || [ -z "$CURRENT_REPLICAS" ]; then + echo "StatefulSet not found or has 0 replicas. Fresh install. Skipping." + exit 0 + fi + + echo "Current replicas: $CURRENT_REPLICAS" + CURRENT_BROKER_COUNT=$CURRENT_REPLICAS + fi + + if [ "$DESIRED_REPLICAS" -ge "$CURRENT_BROKER_COUNT" ]; then + echo "Not scaling down ($CURRENT_BROKER_COUNT -> $DESIRED_REPLICAS). No partition reassignment needed." + exit 0 + fi + + echo "=== SCALE-DOWN DETECTED: $CURRENT_BROKER_COUNT -> $DESIRED_REPLICAS ===" + + # Build the list of surviving broker IDs + SURVIVING_BROKERS="" + i=0 + while [ $i -lt $DESIRED_REPLICAS ]; do + BID=$((ORDINAL_START + i)) + if [ -z "$SURVIVING_BROKERS" ]; then + SURVIVING_BROKERS="$BID" + else + SURVIVING_BROKERS="$SURVIVING_BROKERS,$BID" + fi + i=$((i + 1)) + done + echo "Surviving broker IDs: $SURVIVING_BROKERS" + + # Build the list of brokers being removed + REMOVED_BROKERS="" + i=$DESIRED_REPLICAS + while [ $i -lt $CURRENT_BROKER_COUNT ]; do + BID=$((ORDINAL_START + i)) + if [ -z "$REMOVED_BROKERS" ]; then + REMOVED_BROKERS="$BID" + else + REMOVED_BROKERS="$REMOVED_BROKERS,$BID" + fi + i=$((i + 1)) + done + echo "Brokers being removed: $REMOVED_BROKERS" + + BOOTSTRAP="${STS_NAME}-${ORDINAL_START}.${SERVICE_NAME}:${KAFKA_PORT}" + echo "Bootstrap server: $BOOTSTRAP" + + # Use the Kafka reassign tool's --generate mode with a topics JSON + TOPICS=$($KAFKA_BIN/kafka-topics.sh --bootstrap-server "$BOOTSTRAP" --list 2>/dev/null | grep -v "^__") + + if [ -z "$TOPICS" ]; then + echo "No user topics found. Skipping reassignment." + exit 0 + fi + + echo "Topics to check: $(echo $TOPICS | tr '\n' ' ')" + + # Build topics-to-move JSON + TOPICS_JSON='{"version":1,"topics":[' + TFIRST=true + for TOPIC in $TOPICS; do + if [ "$TFIRST" = "true" ]; then + TFIRST=false + else + TOPICS_JSON="${TOPICS_JSON}," + fi + TOPICS_JSON="${TOPICS_JSON}{\"topic\":\"$TOPIC\"}" + done + TOPICS_JSON="${TOPICS_JSON}]}" + + echo "$TOPICS_JSON" > /tmp/topics-to-move.json + + # Generate the reassignment plan targeting surviving brokers only + echo "Generating reassignment plan for brokers: $SURVIVING_BROKERS" + GENERATED=$($KAFKA_BIN/kafka-reassign-partitions.sh \ + --bootstrap-server "$BOOTSTRAP" \ + --generate \ + --topics-to-move-json-file /tmp/topics-to-move.json \ + --broker-list "$SURVIVING_BROKERS" 2>&1) + + echo "$GENERATED" + + # Extract the proposed reassignment (second JSON block in output) + PROPOSED=$(echo "$GENERATED" | grep "^{" | tail -1) + + if [ -z "$PROPOSED" ]; then + echo "No reassignment plan generated. Skipping." + exit 0 + fi + + echo "$PROPOSED" > /tmp/reassignment.json + echo "=== Executing partition reassignment ===" + cat /tmp/reassignment.json + + $KAFKA_BIN/kafka-reassign-partitions.sh \ + --bootstrap-server "$BOOTSTRAP" \ + --execute \ + --reassignment-json-file /tmp/reassignment.json + + echo "=== Waiting for reassignment to complete ===" + TIMEOUT=300 + ELAPSED=0 + while [ $ELAPSED -lt $TIMEOUT ]; do + STATUS=$($KAFKA_BIN/kafka-reassign-partitions.sh \ + --bootstrap-server "$BOOTSTRAP" \ + --verify \ + --reassignment-json-file /tmp/reassignment.json 2>&1) + + echo "$STATUS" | tail -5 + + if echo "$STATUS" | grep -q "is complete"; then + if ! echo "$STATUS" | grep -q "still in progress"; then + echo "=== All partition reassignments completed ===" + exit 0 + fi + fi + + sleep 5 + ELAPSED=$((ELAPSED + 5)) + echo "Waiting... ($ELAPSED/${TIMEOUT}s)" + done + + echo "WARNING: Reassignment did not complete within ${TIMEOUT}s. Proceeding anyway." + echo "Partitions may need manual reassignment after scale-down." + exit 0 +{{- end }} diff --git a/charts/kafka/values.yaml b/charts/kafka/values.yaml index 115e3831..46a2bd5f 100644 --- a/charts/kafka/values.yaml +++ b/charts/kafka/values.yaml @@ -102,6 +102,22 @@ kafka: # Log retention configuration logRetentionHours: 6 + # Topic replication configuration + # default.replication.factor: applied to auto-created topics + # Set to min(replicaCount, 3) by default for resilience during scale-down + # When set to 0 (default), the chart auto-calculates: min(replicaCount, 3) + defaultReplicationFactor: 0 + # min.insync.replicas: minimum replicas that must acknowledge a write + # Must be <= replicaCount. Set to 1 to allow single-broker operation. + minInsyncReplicas: 1 + # offsets.topic.replication.factor: replication for __consumer_offsets + # When set to 0 (default), the chart auto-calculates: min(replicaCount, 3) + offsetsTopicReplicationFactor: 0 + # transaction.state.log.replication.factor + transactionStateLogReplicationFactor: 0 + # transaction.state.log.min.isr + transactionStateLogMinIsr: 1 + # SASL Authentication Configuration sasl: # Enable SASL authentication diff --git a/charts/portal/Chart.lock b/charts/portal/Chart.lock index 1bd46c16..865d7131 100644 --- a/charts/portal/Chart.lock +++ b/charts/portal/Chart.lock @@ -10,9 +10,9 @@ dependencies: version: 1.0.4 - name: kafka repository: file://../kafka - version: 1.0.1 + version: 1.0.2 - name: ingress-nginx repository: https://kubernetes.github.io/ingress-nginx/ version: 4.12.1 -digest: sha256:a3d25593eb6299015d8e8b99970bff8858a3d8cb71bb4bae34829995e9d20ecf -generated: "2026-03-13T11:18:42.831349+05:30" +digest: sha256:fb4f9aa9bdde4b986cfd0c8fd56e60b8432e79994254d1720a8da9a093739bb1 +generated: "2026-03-16T12:59:25.278734+05:30" diff --git a/charts/portal/Chart.yaml b/charts/portal/Chart.yaml index 89dfa439..caca4567 100644 --- a/charts/portal/Chart.yaml +++ b/charts/portal/Chart.yaml @@ -22,7 +22,7 @@ dependencies: condition: portal.analytics.enabled repository: "file://../seaweedfs" - name: kafka - version: ^1.0.1 + version: ^1.0.2 condition: kafka.enabled repository: "file://../kafka" - name: ingress-nginx diff --git a/charts/portal/charts/kafka-1.0.2.tgz b/charts/portal/charts/kafka-1.0.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..aadb0a09fa5cd9c1f722d9f9db979c1ad5419ab6 GIT binary patch literal 29170 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 N|Nk*Squ&5V0RW$%5%T~5 literal 0 HcmV?d00001 diff --git a/charts/portal/values.yaml b/charts/portal/values.yaml index 057db9b5..a1151f9e 100644 --- a/charts/portal/values.yaml +++ b/charts/portal/values.yaml @@ -1091,6 +1091,15 @@ kafka: # Log retention configuration logRetentionHours: 6 + # Topic replication configuration + # 0 = auto-calculate as min(replicaCount, 3) + defaultReplicationFactor: 0 + # Must be <= replicaCount; set to 1 for single-broker tolerance + minInsyncReplicas: 1 + offsetsTopicReplicationFactor: 0 + transactionStateLogReplicationFactor: 0 + transactionStateLogMinIsr: 1 + # SASL Authentication Configuration sasl: # Enable SASL authentication From 101b292c84cff47224684fb0f1947622d2bc6b0e Mon Sep 17 00:00:00 2001 From: Kiran Vaddadi Date: Mon, 16 Mar 2026 14:29:45 +0530 Subject: [PATCH 4/4] remove intelligence sub-chart lint check --- .github/ct-lint.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ct-lint.yaml b/.github/ct-lint.yaml index cd631fa1..45d70eb6 100644 --- a/.github/ct-lint.yaml +++ b/.github/ct-lint.yaml @@ -4,7 +4,6 @@ charts: - charts/gateway - charts/seaweedfs - charts/kafka - - charts/intelligence chart-repos: - hazelcast=https://hazelcast-charts.s3.amazonaws.com/ - nginx=https://kubernetes.github.io/ingress-nginx/