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/ 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/rolebinding.yaml b/charts/intelligence/templates/service-accounts/rolebinding.yaml deleted file mode 100644 index 090ac04c..00000000 --- a/charts/intelligence/templates/service-accounts/rolebinding.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if and .Values.rbac.create .Values.serviceAccount.create (not .Values.global.serviceAccountName) }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ include "intelligence.fullname" . }} - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "intelligence.name" . }} - chart: {{ template "intelligence.chart" . }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ include "intelligence.fullname" . }} -subjects: - - kind: ServiceAccount - name: {{ include "intelligence.serviceAccountName" . }} -{{- 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..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.0 +version: 1.0.2 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 00000000..5aa4718b Binary files /dev/null and b/charts/kafka/kafka-1.0.1.tgz differ diff --git a/charts/kafka/templates/kafka-config.yaml b/charts/kafka/templates/kafka-config.yaml index bf778753..694e52fb 100644 --- a/charts/kafka/templates/kafka-config.yaml +++ b/charts/kafka/templates/kafka-config.yaml @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. apiVersion: v1 kind: ConfigMap metadata: @@ -13,10 +14,7 @@ metadata: data: SERVICE_NAME: {{ include "kafka.fullname" . }} - # Intelligence enabled flag (controls EXTERNAL listener) - INTELLIGENCE_ENABLED: {{ .Values.global.intelligenceEnabled | quote }} - - # Portal subdomain for advertised listeners + # Portal subdomain {{- if .Values.global.subdomainPrefix }} APIM_PORTAL_SUBDOMAIN: {{ .Values.global.subdomainPrefix | quote }} {{- end }} @@ -93,9 +91,6 @@ data: {{- if .Values.kafka.listeners.internal.enabled }} {{- $listeners = append $listeners (printf "INTERNAL://0.0.0.0:%d" (int .Values.kafka.listeners.internal.port)) }} {{- end }} - {{- if .Values.externalAccess.enabled }} - {{- $listeners = append $listeners (printf "EXTERNAL://0.0.0.0:%d" (int .Values.kafka.listeners.external.port)) }} - {{- end }} {{- if .Values.kafka.listeners.controller.enabled }} {{- $listeners = append $listeners (printf "CONTROLLER://0.0.0.0:%d" (int .Values.kafka.listeners.controller.port)) }} {{- end }} @@ -106,9 +101,6 @@ data: {{- if .Values.kafka.listeners.internal.enabled }} {{- $protocolMap = append $protocolMap (printf "INTERNAL:%s" .Values.kafka.listeners.internal.protocol) }} {{- end }} - {{- if .Values.externalAccess.enabled }} - {{- $protocolMap = append $protocolMap (printf "EXTERNAL:%s" .Values.kafka.listeners.external.protocol) }} - {{- end }} {{- if .Values.kafka.listeners.controller.enabled }} {{- $protocolMap = append $protocolMap (printf "CONTROLLER:%s" .Values.kafka.listeners.controller.protocol) }} {{- end }} @@ -119,20 +111,10 @@ data: # Advertised listeners {{- if .Values.kafka.advertisedListeners }} - # Custom advertised listeners (portal-dist style) + # Custom advertised listeners KAFKA_CFG_ADVERTISED_LISTENERS: {{ .Values.kafka.advertisedListeners | quote }} - {{- else if and .Values.externalAccess.enabled .Values.externalAccess.hostname }} - # Use provided hostname for external access - {{- $fullName := include "kafka.fullname" . }} - {{- $internalPort := int .Values.kafka.listeners.internal.port }} - {{- $externalPort := int .Values.externalAccess.port }} - KAFKA_CFG_ADVERTISED_LISTENERS: "INTERNAL://{{ $fullName }}:{{ $internalPort }},EXTERNAL://{{ .Values.externalAccess.hostname }}:{{ $externalPort }}" - {{- else if and .Values.externalAccess.enabled .Values.externalAccess.autoAdvertisedListeners }} - # Will be set at runtime by init container - DO NOT set here to avoid override - # KAFKA_CFG_ADVERTISED_LISTENERS will be sourced from /shared/node-id.env {{- else }} - # Internal only - KAFKA_CFG_ADVERTISED_LISTENERS: "INTERNAL://{{ include "kafka.fullname" . }}:{{ .Values.kafka.listeners.internal.port }}" + # Will be set at runtime by init container from /shared/node-id.env {{- end }} {{- else }} @@ -148,18 +130,33 @@ data: KAFKA_CFG_LOG_RETENTION_HOURS: {{ .Values.kafka.logRetentionHours | quote }} # ============================================ - # SSL/TLS Configuration + # Topic Replication Configuration # ============================================ - {{- if .Values.kafka.tls.enabled }} - KAFKA_TLS_TYPE: {{ .Values.kafka.tls.type | quote }} - KAFKA_CFG_SSL_CLIENT_AUTH: {{ .Values.kafka.tls.clientAuth | quote }} - KAFKA_CFG_SSL_KEYSTORE_TYPE: {{ .Values.kafka.tls.type | quote }} - KAFKA_CFG_SSL_TRUSTSTORE_TYPE: {{ .Values.kafka.tls.type | quote }} - - # Note: KAFKA_CFG_SSL_TRUSTSTORE_CERTIFICATES and KAFKA_CFG_SSL_KEYSTORE_KEY - # are loaded from mounted files in the StatefulSet command and exported as env vars - # This allows the certificate content to be passed to the Kafka startup script + # 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/kafka-external-services.yaml b/charts/kafka/templates/kafka-external-services.yaml deleted file mode 100644 index 9a4d1caf..00000000 --- a/charts/kafka/templates/kafka-external-services.yaml +++ /dev/null @@ -1,54 +0,0 @@ -{{- if .Values.externalAccess.enabled }} -{{- $statefulsetName := include "kafka.statefulsetName" . -}} -{{- $replicaCount := int .Values.kafka.replicaCount }} -{{- $serviceType := .Values.externalAccess.serviceType }} -{{- $port := int .Values.externalAccess.port }} -{{- $targetPort := int .Values.kafka.listeners.external.port }} -{{- $ordinalStart := 0 }} -{{- if .Values.kafka.ordinals }} -{{- $ordinalStart = int .Values.kafka.ordinals.start }} -{{- end }} -{{- range $i := until $replicaCount }} -{{- $podIndex := add $ordinalStart $i }} ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ $statefulsetName }}-{{ $podIndex }}-external - labels: - {{- include "kafka.labels" $ | nindent 4 }} - {{- range $key, $val := $.Values.global.additionalLabels }} - {{ $key }}: "{{ $val }}" - {{- end }} - statefulset.kubernetes.io/pod-name: {{ $statefulsetName }}-{{ $podIndex }} - kafka-broker-id: "{{ $podIndex }}" - {{- with $.Values.externalAccess.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - type: {{ $serviceType }} - {{- if eq $serviceType "LoadBalancer" }} - {{- if $.Values.externalAccess.loadBalancerIPs }} - {{- if gt (len $.Values.externalAccess.loadBalancerIPs) $podIndex }} - loadBalancerIP: {{ index $.Values.externalAccess.loadBalancerIPs $podIndex }} - {{- end }} - {{- end }} - {{- end }} - ports: - - name: external - port: {{ $port }} - targetPort: {{ $targetPort }} - protocol: TCP - {{- if eq $serviceType "NodePort" }} - {{- if $.Values.externalAccess.nodePorts }} - {{- if gt (len $.Values.externalAccess.nodePorts) $podIndex }} - nodePort: {{ index $.Values.externalAccess.nodePorts $podIndex }} - {{- end }} - {{- end }} - {{- end }} - selector: - {{- include "kafka.selectorLabels" $ | nindent 4 }} - statefulset.kubernetes.io/pod-name: {{ $statefulsetName }}-{{ $podIndex }} -{{- end }} -{{- end }} diff --git a/charts/kafka/templates/kafka-statefulset.yaml b/charts/kafka/templates/kafka-statefulset.yaml index 1f264b71..b0441bcd 100644 --- a/charts/kafka/templates/kafka-statefulset.yaml +++ b/charts/kafka/templates/kafka-statefulset.yaml @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. apiVersion: apps/v1 kind: StatefulSet metadata: @@ -63,26 +64,8 @@ spec: {{- end }} # Init container to set node ID and configure advertised listeners - name: kafka-init - {{- if and .Values.externalAccess.enabled .Values.externalAccess.autoAdvertisedListeners (not .Values.externalAccess.hostname) }} - # Use kubectl image for service discovery - {{- if .Values.image }} - {{- if .Values.image.autoDiscovery }} - # Use parent chart's autoDiscovery image (when kafka is a subchart) - image: "{{ .Values.global.portalRepository }}{{ .Values.image.autoDiscovery }}" - {{- else }} - # Use local discoveryImage configuration - image: "{{ .Values.global.portalRepository }}{{ .Values.externalAccess.discoveryImage.repository }}:{{ .Values.externalAccess.discoveryImage.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 }}" - {{- else }} - # Use Kafka image when discovery not needed image: "{{ .Values.global.portalRepository }}{{ .Values.image.kafka }}" imagePullPolicy: "{{ .Values.image.pullPolicy }}" - {{- end }} env: - name: POD_NAME valueFrom: @@ -134,141 +117,7 @@ spec: echo "KAFKA_CFG_NODE_ID=$NODE_ID" > /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 +125,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 +177,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 +233,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 +258,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 +270,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 +279,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-scale-partition-reassignment.yaml b/charts/kafka/templates/pre-scale-partition-reassignment.yaml new file mode 100644 index 00000000..d4d189dc --- /dev/null +++ b/charts/kafka/templates/pre-scale-partition-reassignment.yaml @@ -0,0 +1,279 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. +# 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/templates/pre-upgrade-statefulset-cleanup.yaml b/charts/kafka/templates/pre-upgrade-statefulset-cleanup.yaml index da1a2d3e..fa1f5c86 100644 --- a/charts/kafka/templates/pre-upgrade-statefulset-cleanup.yaml +++ b/charts/kafka/templates/pre-upgrade-statefulset-cleanup.yaml @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. {{- if .Values.kafka.kraft.enabled }} # # Pre-Upgrade StatefulSet Cleanup Job @@ -107,19 +108,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 22fa236b..9876b02b 100644 --- a/charts/kafka/values.yaml +++ b/charts/kafka/values.yaml @@ -1,4 +1,3 @@ -# Global values (inherited from parent chart) global: portalRepository: "" storageClass: "" @@ -61,6 +60,7 @@ kafka: # limits: # cpu: 1000m # memory: 1.5Gi + # KRaft Mode Configuration (Kafka 4.0.0+) kraft: # Enable KRaft mode (Zookeeper-less) @@ -76,6 +76,7 @@ kafka: # Controller quorum voters format: id@host:port # For StatefulSet with 3 replicas: "0@kafka-0.kafka:9093,1@kafka-1.kafka:9093,2@kafka-2.kafka:9093" controllerQuorumVoters: "" + # Listener Configuration listeners: # Internal listener for inter-broker and client communication within cluster @@ -83,11 +84,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 @@ -95,59 +91,32 @@ kafka: protocol: PLAINTEXT # Inter-broker listener name interBrokerListenerName: INTERNAL + # 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: "" + # 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 @@ -181,44 +150,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..865d7131 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.2 - 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:fb4f9aa9bdde4b986cfd0c8fd56e60b8432e79994254d1720a8da9a093739bb1 +generated: "2026-03-16T12:59:25.278734+05:30" diff --git a/charts/portal/Chart.yaml b/charts/portal/Chart.yaml index 7ceac8fe..2a081e66 100644 --- a/charts/portal/Chart.yaml +++ b/charts/portal/Chart.yaml @@ -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.2 condition: kafka.enabled repository: "file://../kafka" - name: ingress-nginx diff --git a/charts/portal/README.md b/charts/portal/README.md index 11de5db4..aa8055bc 100644 --- a/charts/portal/README.md +++ b/charts/portal/README.md @@ -825,19 +825,30 @@ 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: - - **Kafka**: Required for message streaming (should be configured with 3+ replicas for production) +- Kafka is always deployed - intelligence consumes from the same in-cluster Kafka used by Portal deployment messaging, analytics. +- For production with intelligence enabled, set `kafka.kafka.replicaCount: 3`. | 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` | +| `intelligence.replicaCount` | Number of intelligence server replicas | `1` | +| `intelligence.kafka.kafkaCa.caSecretName` | Secret containing the CA certificate for Kafka mTLS | `portal-external-secret` | -For detailed Intelligence configuration, see the [Intelligence Chart README](../intelligence/README.md). +## Kafka and KafkaTcpProxy on APIM +Portal 5.4.2 (rmq2kafka / F161288) uses Kafka for deployment messaging. Kafka is always deployed; the brokers' internal listener (9092) is never exposed externally. All external Kafka traffic is fronted by the `KafkaTcpProxyAssertion` on the APIM gateway, with SNI-based per-broker routing on port 443. + +| Parameter | Description | Default | +| --- | --- | --- | +| `portal.kafka.proxy.targetBootstrapServers` | Upstream Kafka the proxy forwards to | `kafka:9092` | +| `portal.kafka.proxy.listenPort` | APIM bootstrap listen port for Kafka clients | `9191` | +| `portal.kafka.proxy.brokerStartPort` | Per-broker start port; per-broker port = `brokerStartPort + nodeId` | `9193` | +| `portal.kafka.proxy.advertisedHost` | Hostname advertised to Kafka clients (defaults to `kafka-proxy-host` helper) | `""` | +| `portal.kafka.proxy.advertisedPort` | Port advertised to Kafka clients; `443` enables SNI routing through ingress | `443` | +| `portal.kafka.proxy.brokerAddressPattern` | Per-broker SNI hostname pattern with `$(nodeId)` (defaults to helper) | `""` | +| `portal.kafka.proxy.configBrokerHost` | Value baked into the enrollment bundle CWP `portal.config.broker.kafka.host` | `localhost` | ## SeaweedFS The SeaweedFS subchart provides S3-compatible object storage for analytics data. @@ -860,20 +871,15 @@ The SeaweedFS subchart provides S3-compatible object storage for analytics data. For detailed SeaweedFS configuration and migration guide, see [Minio to SeaweedFS Migration Guide](../../utils/MINIO-TO-SEAWEEDFS-MIGRATION.md). -## Kafka -The Kafka subchart provides Apache Kafka 4.0.0 in KRaft mode (Zookeeper-less) for message streaming. - -**Automatic Deployment:** -- Deployed when `kafka.enabled: true` -- Required for Intelligence service +## Kafka Subchart +The Kafka subchart provides Apache Kafka 4.0.0 in KRaft mode (Zookeeper-less). Kafka is the messaging backbone for Portal. | Parameter | Description | Default | | --- | --- | --- | -| `kafka.enabled` | Enable Kafka subchart | `false` | -| `kafka.kafka.replicaCount` | Number of Kafka broker replicas (3+ recommended for production) | `1` | +| `kafka.enabled` | Deploy the Kafka subchart | `true` | +| `kafka.kafka.replicaCount` | Number of Kafka broker replicas (3+ recommended for production with intelligence) | `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` | +| `kafka.kafka.ordinals.start` | StatefulSet pod start ordinal (1 preserves KRaft nodeId across Zookeeper upgrade) | `1` | 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 b0bb5cb9..00000000 Binary files a/charts/portal/charts/apim-intelligence-1.0.21.tgz and /dev/null differ diff --git a/charts/portal/charts/druid-1.0.20.tgz b/charts/portal/charts/druid-1.0.20.tgz index 2c6e86f0..1c9cfb39 100644 Binary files a/charts/portal/charts/druid-1.0.20.tgz and b/charts/portal/charts/druid-1.0.20.tgz differ diff --git a/charts/portal/charts/kafka-1.0.0.tgz b/charts/portal/charts/kafka-1.0.0.tgz deleted file mode 100644 index d8242c36..00000000 Binary files a/charts/portal/charts/kafka-1.0.0.tgz and /dev/null differ 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 00000000..d4b18c5a Binary files /dev/null and b/charts/portal/charts/kafka-1.0.2.tgz differ diff --git a/charts/portal/charts/seaweedfs-1.0.4.tgz b/charts/portal/charts/seaweedfs-1.0.4.tgz index a00c492d..b135bc5b 100644 Binary files a/charts/portal/charts/seaweedfs-1.0.4.tgz and b/charts/portal/charts/seaweedfs-1.0.4.tgz differ diff --git a/charts/portal/templates/_helpers.tpl b/charts/portal/templates/_helpers.tpl index 75f80f28..f023580d 100644 --- a/charts/portal/templates/_helpers.tpl +++ b/charts/portal/templates/_helpers.tpl @@ -419,6 +419,41 @@ 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 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. @@ -427,3 +462,57 @@ 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 -}} + +{{/* + ============================================================ + Kafka Helpers (shared by portal-data, analytics-server, + tenant-provisioner, authenticator, intelligence) + ============================================================ +*/}} + +{{/* +In-cluster Kafka bootstrap broker list. Resolves to the kafka subchart's +service name + internal listener port (default kafka:9092). Used by every +in-cluster Kafka producer / consumer +*/}} +{{- define "kafka.brokers" -}} + {{- $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 -}} + +{{/* +External Kafka proxy bootstrap address advertised to tenant Gateways via SNI +on port 443 (default). Used by intelligence server and exposed via APIM +KafkaTcpProxy assertion. +*/}} +{{- define "kafka.proxyBootstrap" -}} + {{- $host := include "kafka-proxy-host" . -}} + {{- $port := int (.Values.portal.kafka.proxy.advertisedPort | default 443) -}} + {{- printf "%s:%d" $host $port -}} +{{- end -}} diff --git a/charts/portal/templates/analytics-server/analytics-config.yaml b/charts/portal/templates/analytics-server/analytics-config.yaml index 420afe17..ed93f999 100644 --- a/charts/portal/templates/analytics-server/analytics-config.yaml +++ b/charts/portal/templates/analytics-server/analytics-config.yaml @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. apiVersion: v1 kind: ConfigMap metadata: @@ -27,6 +28,7 @@ data: PORTAL_VERSION: {{ .Chart.AppVersion | quote }} RABBITMQ_HOST: {{ .Values.rabbitmq.host | quote }} RABBITMQ_PORT: {{ .Values.rabbitmq.service.port | quote }} + KAFKA_BROKERS: {{ include "kafka.brokers" . | quote }} PAPI_PUBLIC_HOST: {{ include "tssg-public-host" . | quote }} PAPI_INGRESS_TENANT: {{ include "default-tenant-id" . | quote }} {{ if .Values.analytics.additionalEnv }} diff --git a/charts/portal/templates/apim/apim-config.yaml b/charts/portal/templates/apim/apim-config.yaml index 0ccaf6e7..8986a656 100644 --- a/charts/portal/templates/apim/apim-config.yaml +++ b/charts/portal/templates/apim/apim-config.yaml @@ -1,3 +1,5 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. +# 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: @@ -28,13 +30,14 @@ data: TENANT_ID: {{ include "default-tenant-id" . | quote }} 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" -{{- else }} - # Intelligence Feature Flag - INTELLIGENCE_ENABLED: "false" -{{- end }} + INTELLIGENCE_ENABLED: {{ .Values.portal.intelligence.enabled | default false | quote }} + KAFKA_PROXY_TARGET_SERVERS: {{ default "kafka:9092" .Values.portal.kafka.proxy.targetBootstrapServers | quote }} + KAFKA_PROXY_LISTEN_PORT: {{ default 9191 .Values.portal.kafka.proxy.listenPort | quote }} + KAFKA_PROXY_BROKER_START_PORT: {{ default 9193 .Values.portal.kafka.proxy.brokerStartPort | quote }} + KAFKA_PROXY_ADVERTISED_HOST: {{ default (include "kafka-proxy-host" .) .Values.portal.kafka.proxy.advertisedHost | quote }} + KAFKA_PROXY_ADVERTISED_PORT: {{ default 443 .Values.portal.kafka.proxy.advertisedPort | quote }} + KAFKA_PROXY_BROKER_ADDRESS_PATTERN: {{ default (include "kafka-proxy-broker-pattern" .) .Values.portal.kafka.proxy.brokerAddressPattern | quote }} + PORTAL_CONFIG_BROKER_KAFKA_HOST: {{ default "localhost" .Values.portal.kafka.proxy.configBrokerHost | quote }} {{ if .Values.apim.additionalEnv }} {{- range $key, $val := .Values.apim.additionalEnv }} {{ $key }}: {{ $val | quote }} diff --git a/charts/portal/templates/apim/apim-service.yaml b/charts/portal/templates/apim/apim-service.yaml index d91fc99d..35e0ed33 100644 --- a/charts/portal/templates/apim/apim-service.yaml +++ b/charts/portal/templates/apim/apim-service.yaml @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. apiVersion: v1 kind: Service metadata: @@ -41,6 +42,24 @@ spec: targetPort: 9448 protocol: TCP name: apim-sso + - port: {{ .Values.portal.kafka.proxy.listenPort | default 9191 }} + targetPort: {{ .Values.portal.kafka.proxy.listenPort | default 9191 }} + protocol: TCP + name: kfk-proxy-boot +{{- $brokerStartPort := int (.Values.portal.kafka.proxy.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 }} + - port: {{ $brokerPort }} + targetPort: {{ $brokerPort }} + protocol: TCP + name: kfk-proxy-brk-{{ $nodeId }} +{{- 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..98d9544a 100644 --- a/charts/portal/templates/gateway-api/gateway.yaml +++ b/charts/portal/templates/gateway-api/gateway.yaml @@ -41,6 +41,17 @@ spec: {{- $hostnames = append $hostnames (include "pssg-sync-host" .) }} {{- $hostnames = append $hostnames (include "pssg-sso-host" .) }} {{- $hostnames = append $hostnames (include "broker-host" .) }} + {{/* Kafka proxy routes: bootstrap + per-broker SNI hostnames */}} + {{- $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 }} {{/* 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..8636fd29 100644 --- a/charts/portal/templates/gateway-api/tlsroute.yaml +++ b/charts/portal/templates/gateway-api/tlsroute.yaml @@ -232,6 +232,80 @@ spec: - backendRefs: - name: apim port: 1885 +--- +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.kafka.proxy.listenPort | default 9191 }} +{{- $brokerStartPort := int (.Values.portal.kafka.proxy.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 }} {{/* ============================================================ 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 a06f7408..18791aee 100644 --- a/charts/portal/templates/ingress/contour-httpproxy.yaml +++ b/charts/portal/templates/ingress/contour-httpproxy.yaml @@ -176,6 +176,67 @@ spec: port: 1885 --- +--- +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.kafka.proxy.listenPort | default 9191 }} +{{- $brokerStartPort := int (.Values.portal.kafka.proxy.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 }} + {{- 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..976da54e 100644 --- a/charts/portal/templates/ingress/ingress.yaml +++ b/charts/portal/templates/ingress/ingress.yaml @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. {{- $kubeTargetVersion := .Capabilities.KubeVersion.GitVersion }} {{ if .Values.ingress.type.kubernetes }} {{- if semverCompare ">=1.19-0" $kubeTargetVersion -}} @@ -145,6 +146,46 @@ spec: - backend: serviceName: apim servicePort: {{ printf "%s-broker" .Values.portal.defaultTenantId | quote }} +{{- end }} + - host: {{ include "kafka-proxy-host" . | quote }} + http: + paths: +{{- if semverCompare ">=1.19-0" $kubeTargetVersion }} + - pathType: Prefix + path: "/" + backend: + service: + name: apim + port: + name: kfk-proxy-boot +{{- else }} + - backend: + serviceName: apim + 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 }} {{- range .Values.ingress.customRoutes }} - host: "{{ .subdomain }}.{{ $.Values.portal.domain }}" diff --git a/charts/portal/templates/ingress/route.yaml b/charts/portal/templates/ingress/route.yaml index 2c1c7249..8741db2c 100644 --- a/charts/portal/templates/ingress/route.yaml +++ b/charts/portal/templates/ingress/route.yaml @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. {{- if .Values.ingress.type.openshift }} --- apiVersion: route.openshift.io/v1 @@ -125,4 +126,48 @@ spec: name: apim weight: 100 wildcardPolicy: None +--- +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: kfk-proxy-boot + tls: + termination: passthrough + to: + kind: Service + 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 }} diff --git a/charts/intelligence/templates/server/server-config.yml b/charts/portal/templates/intelligence/intelligence-config.yaml similarity index 63% rename from charts/intelligence/templates/server/server-config.yml rename to charts/portal/templates/intelligence/intelligence-config.yaml index 4ac6649e..24d96b19 100644 --- a/charts/intelligence/templates/server/server-config.yml +++ b/charts/portal/templates/intelligence/intelligence-config.yaml @@ -1,10 +1,12 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. +{{- 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 +19,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 "kafka.brokers" . | 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..c84434ca --- /dev/null +++ b/charts/portal/templates/intelligence/intelligence-deployment.yaml @@ -0,0 +1,174 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. +{{- 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 "portal.serviceAccountName" . }} + {{- 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: {{ default (include "kafka.brokers" .) .Values.intelligence.kafka.brokers | 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 "kafka.proxyBootstrap" . | 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-service.yaml b/charts/portal/templates/intelligence/intelligence-service.yaml new file mode 100644 index 00000000..43bb1cf7 --- /dev/null +++ b/charts/portal/templates/intelligence/intelligence-service.yaml @@ -0,0 +1,33 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. +{{- 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..309b607b 100644 --- a/charts/portal/templates/jobs/cert-update-job.yaml +++ b/charts/portal/templates/jobs/cert-update-job.yaml @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. {{- if .Values.tls.job.enabled }} apiVersion: batch/v1 kind: Job @@ -82,16 +83,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..f3d69138 100644 --- a/charts/portal/templates/jobs/job-secret.yaml +++ b/charts/portal/templates/jobs/job-secret.yaml @@ -1,3 +1,4 @@ +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. apiVersion: v1 kind: Secret metadata: @@ -21,6 +22,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/templates/portal-data/portal-data-config.yaml b/charts/portal/templates/portal-data/portal-data-config.yaml index a7149261..6ae48e89 100644 --- a/charts/portal/templates/portal-data/portal-data-config.yaml +++ b/charts/portal/templates/portal-data/portal-data-config.yaml @@ -1,4 +1,5 @@ -# Copyright (c) 2025 Broadcom Inc. and its subsidiaries. All Rights Reserved. +# Copyright (c) 2026 Broadcom Inc. and its subsidiaries. All Rights Reserved. +# 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: @@ -35,6 +36,9 @@ data: PORTAL_VERSION: {{ .Chart.AppVersion | quote }} RABBITMQ_HOST: {{ .Values.rabbitmq.host | quote }} RABBITMQ_PORT: {{ .Values.rabbitmq.service.port | quote }} + KAFKA_BROKERS: {{ include "kafka.brokers" . | quote }} + KAFKA_PROXY_ADVERTISED_HOST: {{ default (include "kafka-proxy-host" .) .Values.portal.kafka.proxy.advertisedHost | quote }} + CONFIG_BROKER_KAFKA_PORT: {{ default 443 .Values.portal.kafka.proxy.advertisedPort | quote }} RBAC_DATABASE_NAME: {{ include "rbac-db-name" . | quote }} RBAC_DATABASE_PORT: {{ include "database-port" . | quote }} RBAC_DATABASE_TYPE: {{ required "Please fill in databaseType in values.yaml" .Values.global.databaseType | quote }} diff --git a/charts/portal/values-production.yaml b/charts/portal/values-production.yaml index 2de69cfa..c7ac924a 100644 --- a/charts/portal/values-production.yaml +++ b/charts/portal/values-production.yaml @@ -66,14 +66,22 @@ portal: enrollNotificationEmail: noreply@mail.example.com analytics: enabled: true - # Intelligence feature for advanced analytics and third-party agent integration - # IMPORTANT: When intelligence.enabled is true: - # - kafka.kafka.replicaCount should be set to 3 (recommended for production) + aggregation: true + # Kafka is the messaging backbone for Portal + # Production recommendation: kafka.kafka.replicaCount: 3 (see Kafka subchart block below). + kafka: + proxy: + targetBootstrapServers: "kafka:9092" + listenPort: 9191 + brokerStartPort: 9193 + advertisedHost: "" + advertisedPort: 443 + brokerAddressPattern: "" + configBrokerHost: "localhost" + # Intelligence feature for advanced analytics and third-party agent integration. # Intelligence is disabled by default. Enable it for third-party agent integration. intelligence: enabled: false - # Please set analytics.replicaCount to a minimum of 2 - aggregation: true # Specify a Gateway v9.x license file via set portal.license.value # This bootstraps a license to apim # To renew a license toggle license.secretName and specify your new license with helm upgrade @@ -135,10 +143,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 @@ -600,7 +606,8 @@ image: data: portal-data:5.4.1 tps: tenant-provisioning-service:5.4.1 analytics: analytics-server:5.4.1 - authenticator: authenticator:5.4.1.2 + 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 @@ -1140,11 +1147,10 @@ druid: ingestion: ingestion-server:5.4.1 -# Configuration for the custom Kafka subchart -kafka: &kafka_config - # 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 +# Configuration for the custom Kafka subchart. +# Kafka is the messaging backbone for Portal and is always deployed. The kafka pod's internal listener (9092) is never exposed +# externally - all external Kafka traffic flows through the APIM Kafka Proxy assertion. +kafka: enabled: true # Global configuration passed to Kafka subchart @@ -1215,12 +1221,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 +1232,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 +1260,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,82 +1354,50 @@ 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.2 pullPolicy: IfNotPresent - # RBAC configuration - rbac: - create: true + # 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 Account configuration - serviceAccount: - create: true - automountServiceAccountToken: true - name: "" + # Service configuration + service: + type: ClusterIP + port: 8282 + sessionAffinity: None + externalTrafficPolicy: "" + internalTrafficPolicy: "" - 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: {} + 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 0c2e1993..162904d9 100644 --- a/charts/portal/values.yaml +++ b/charts/portal/values.yaml @@ -69,6 +69,36 @@ portal: enrollNotificationEmail: noreply@mail.example.com analytics: enabled: true + + # Kafka is the messaging backbone for Portal : + # Kafka is always deployed and proxied through APIM + # the kafka subchart itself is governed by kafka.enabled. + kafka: + # Kafka Proxy configuration on APIM Gateway (server mode, SNI on :443). + proxy: + # Internal Kafka bootstrap servers that the proxy forwards traffic to. + targetBootstrapServers: "kafka:9092" + # Bootstrap listen port on the APIM Gateway (KafkaTcpProxy assertion). + listenPort: 9191 + # Per-broker port = brokerStartPort + nodeId; with kafka.kafka.ordinals.start=1 + # this yields 9194, 9195, 9196 ... for KRaft brokers internally + brokerStartPort: 9193 + # Hostname advertised to Kafka clients in metadata responses. + # 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: "" + # Value baked into the enrollment bundle for the tenant Gateway CWP + # portal.config.broker.kafka.host. Production policy is "localhost" + # the tenant Gateway's Kafka client connects to its local + # Kafka Proxy relay; the relay handles upstream TLS / mTLS / SNI / + # HTTP CONNECT. The port baked into the bundle is KAFKA_PROXY_LISTEN_PORT + configBrokerHost: "localhost" # Intelligence feature for advanced analytics and third-party agent integration # IMPORTANT: When intelligence.enabled is true: # - kafka.kafka.replicaCount should be set to 3 (recommended for production) @@ -138,10 +168,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 @@ -533,7 +561,8 @@ image: data: portal-data:5.4.1 tps: tenant-provisioning-service:5.4.1 analytics: analytics-server:5.4.1 - authenticator: authenticator:5.4.1.2 + 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 @@ -986,11 +1015,11 @@ druid: ingestion: ingestion-server:5.4.1 -# Configuration for the custom Kafka subchart -kafka: &kafka_config - # 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 +# Configuration for the custom Kafka subchart. +# Kafka is the messaging backbone for Portal and always deployed. +# all external Kafka traffic flows through the APIM Kafka Proxy +# assertion. +kafka: enabled: true # Global configuration passed to Kafka subchart @@ -1061,12 +1090,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,23 +1101,14 @@ 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" + # 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: @@ -1119,49 +1133,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 +1222,53 @@ 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.2 pullPolicy: IfNotPresent - # RBAC configuration - rbac: - create: true + # 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 Account configuration - serviceAccount: - create: true - automountServiceAccountToken: true - name: "" + # Service configuration + service: + type: ClusterIP + port: 8282 + sessionAffinity: None + externalTrafficPolicy: "" + internalTrafficPolicy: "" - 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: {} + 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: @@ -1343,8 +1292,6 @@ ingress-nginx: # enabled: true # default: false # controllerValue: "k8s.io/ingress-nginx" -# tcp: -# 9443: "/dispatcher:9443" # Settings for portal jobs jobs: