From f6b72d6671a9abf5a3e1417b77afd4236db95649 Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Tue, 10 Mar 2026 14:09:48 -0400 Subject: [PATCH 01/18] Run Koperator on Kind Cluster Locally --- run-local.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100755 run-local.sh diff --git a/run-local.sh b/run-local.sh new file mode 100755 index 000000000..33fd0758d --- /dev/null +++ b/run-local.sh @@ -0,0 +1,33 @@ +#!/bin/bash +## Create kind cluster +kind delete clusters e2e-kind +kind create cluster --config=/Users/dvaseeka/Documents/adobe/pipeline-services/koperator/tests/e2e/platforms/kind/kind_config.yaml --name=e2e-kind + +## Build/Load images +kind load docker-image docker-pipeline-upstream-mirror.dr-uw2.adobeitc.com/adobe/kafka:2.13-3.7.0 --name e2e-kind +docker build . -t koperator_e2e_test +kind load docker-image koperator_e2e_test:latest --name e2e-kind + +## Install Helm Charts and CRDs +### project contour +helm repo add contour https://projectcontour.github.io/helm-charts/ +helm install contour contour/contour --namespace projectcontour --create-namespace + +### cert-manager +helm repo add jetstack https://charts.jetstack.io --force-update +helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.16.2 --set crds.enabled=true + +### zookeeper-operator +helm repo add pravega https://charts.pravega.io +helm install zookeeper-operator pravega/zookeeper-operator --version 0.2.15 --namespace zookeeper --create-namespace --set crd.create=true + +### prometheus +helm repo add prometheus https://prometheus-community.github.io/helm-charts +helm install prometheus prometheus/kube-prometheus-stack --version 54.1.0 --namespace prometheus --create-namespace + +### koperator +helm install kafka-operator charts/kafka-operator --set operator.image.repository=koperator_e2e_test --set operator.image.tag=latest --namespace kafka --create-namespace +kubectl create -f charts/kafka-operator/crds/ + +### Initialize Kafka Cluster +k apply -f config/samples/kraft/simplekafkacluster_kraft.yaml -n kafka From 67c8acd00eff236987a9e021ace3ea52b214cb4d Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Wed, 8 Apr 2026 13:54:38 -0400 Subject: [PATCH 02/18] [CORE-149726] - Local Debug support --- .gitignore | 2 +- api/v1beta1/kafkacluster_types.go | 13 +++++++++--- charts/kafka-operator/crds/kafkaclusters.yaml | 8 ++++++++ .../kafka.banzaicloud.io_kafkaclusters.yaml | 8 ++++++++ config/samples/simplekafkacluster.yaml | 3 ++- pkg/resources/cruisecontrol/service.go | 9 ++++++++- pkg/resources/kafka/allBrokerService.go | 8 +++++++- pkg/resources/kafka/service.go | 7 ++++++- run-local.sh | 20 ++++++++++++------- tests/e2e/platforms/kind/kind_config.yaml | 14 +++++++------ 10 files changed, 71 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 009e10da1..480a9b5b4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ bin charts/**/charts charts/koperator/requirements.lock - +charts/kafka-operator/ingress # Test binary, build with `go test -c` *.test diff --git a/api/v1beta1/kafkacluster_types.go b/api/v1beta1/kafkacluster_types.go index 3cf2da28c..c96cddce8 100644 --- a/api/v1beta1/kafkacluster_types.go +++ b/api/v1beta1/kafkacluster_types.go @@ -157,9 +157,16 @@ type KafkaClusterSpec struct { // This is default to be true; if set to false, the Kafka cluster is in ZooKeeper mode. // +kubebuilder:default=false // +optional - KRaftMode bool `json:"kRaft"` - HeadlessServiceEnabled bool `json:"headlessServiceEnabled"` - ListenersConfig ListenersConfig `json:"listenersConfig"` + KRaftMode bool `json:"kRaft"` + HeadlessServiceEnabled bool `json:"headlessServiceEnabled"` + // DebugEnabled is used to decide whether to create a separate loadbalancer services for the + // Kafka and Cruise Control Pods. These services will expose the internal listener ports of the Kafka + // cluster with LoadBalancer type, which can be used for running Koperator on a local machine against + // a kafkaCluster instance on a Kind Cluster. + // +kubebuilder:default=false + // +optional + DebugEnabled bool `json:"debugEnabled"` + ListenersConfig ListenersConfig `json:"listenersConfig"` // Custom ports to expose in the container. Example use case: a custom kafka distribution, that includes an integrated metrics api endpoint AdditionalPorts []corev1.ContainerPort `json:"additionalPorts,omitempty"` // ZKAddresses specifies the ZooKeeper connection string diff --git a/charts/kafka-operator/crds/kafkaclusters.yaml b/charts/kafka-operator/crds/kafkaclusters.yaml index 402f282b5..4df25a979 100644 --- a/charts/kafka-operator/crds/kafkaclusters.yaml +++ b/charts/kafka-operator/crds/kafkaclusters.yaml @@ -19231,6 +19231,14 @@ spec: type: object type: array type: object + debugEnabled: + default: false + description: |- + DebugEnabled is used to decide whether to create a separate loadbalancer services for the + Kafka and Cruise Control Pods. These services will expose the internal listener ports of the Kafka + cluster with LoadBalancer type, which can be used for running Koperator on a local machine against + a kafkaCluster instance on a Kind Cluster. + type: boolean disruptionBudget: description: DisruptionBudget defines the configuration for PodDisruptionBudget where the workload is managed by the kafka-operator diff --git a/config/base/crds/kafka.banzaicloud.io_kafkaclusters.yaml b/config/base/crds/kafka.banzaicloud.io_kafkaclusters.yaml index 402f282b5..4df25a979 100644 --- a/config/base/crds/kafka.banzaicloud.io_kafkaclusters.yaml +++ b/config/base/crds/kafka.banzaicloud.io_kafkaclusters.yaml @@ -19231,6 +19231,14 @@ spec: type: object type: array type: object + debugEnabled: + default: false + description: |- + DebugEnabled is used to decide whether to create a separate loadbalancer services for the + Kafka and Cruise Control Pods. These services will expose the internal listener ports of the Kafka + cluster with LoadBalancer type, which can be used for running Koperator on a local machine against + a kafkaCluster instance on a Kind Cluster. + type: boolean disruptionBudget: description: DisruptionBudget defines the configuration for PodDisruptionBudget where the workload is managed by the kafka-operator diff --git a/config/samples/simplekafkacluster.yaml b/config/samples/simplekafkacluster.yaml index d890f8551..cf08d8980 100644 --- a/config/samples/simplekafkacluster.yaml +++ b/config/samples/simplekafkacluster.yaml @@ -5,10 +5,11 @@ metadata: controller-tools.k8s.io: "1.0" name: kafka spec: + debugEnabled: true kRaft: false monitoringConfig: jmxImage: "ghcr.io/adobe/koperator/jmx-javaagent:1.4.0" - headlessServiceEnabled: true + headlessServiceEnabled: false zkAddresses: - "zookeeper-server-client.zookeeper:2181" propagateLabels: false diff --git a/pkg/resources/cruisecontrol/service.go b/pkg/resources/cruisecontrol/service.go index d868eacf4..2c1c64439 100644 --- a/pkg/resources/cruisecontrol/service.go +++ b/pkg/resources/cruisecontrol/service.go @@ -26,7 +26,7 @@ import ( ) func (r *Reconciler) service() runtime.Object { - return &corev1.Service{ + svc := &corev1.Service{ ObjectMeta: templates.ObjectMeta( fmt.Sprintf(serviceNameTemplate, r.KafkaCluster.Name), apiutil.MergeLabels(ccLabelSelector(r.KafkaCluster.Name), r.KafkaCluster.Labels), @@ -34,6 +34,7 @@ func (r *Reconciler) service() runtime.Object { ), Spec: corev1.ServiceSpec{ Selector: ccLabelSelector(r.KafkaCluster.Name), + Type: corev1.ServiceTypeClusterIP, Ports: []corev1.ServicePort{ { Name: "cc", @@ -50,4 +51,10 @@ func (r *Reconciler) service() runtime.Object { }, }, } + + if r.KafkaCluster.Spec.DebugEnabled { + svc.Spec.Type = corev1.ServiceTypeLoadBalancer + } + + return svc } diff --git a/pkg/resources/kafka/allBrokerService.go b/pkg/resources/kafka/allBrokerService.go index ecfdd5b7b..ed0eed60c 100644 --- a/pkg/resources/kafka/allBrokerService.go +++ b/pkg/resources/kafka/allBrokerService.go @@ -39,7 +39,7 @@ func (r *Reconciler) allBrokerService() runtime.Object { usedPorts = append(usedPorts, generateServicePortForAdditionalPorts(r.KafkaCluster.Spec.AdditionalPorts)...) - return &corev1.Service{ + svc := &corev1.Service{ ObjectMeta: templates.ObjectMetaWithAnnotations( fmt.Sprintf(kafkautils.AllBrokerServiceTemplate, r.KafkaCluster.GetName()), apiutil.LabelsForKafka(r.KafkaCluster.GetName()), @@ -52,4 +52,10 @@ func (r *Reconciler) allBrokerService() runtime.Object { Ports: usedPorts, }, } + + if r.KafkaCluster.Spec.DebugEnabled { + svc.Spec.Type = corev1.ServiceTypeLoadBalancer + } + + return svc } diff --git a/pkg/resources/kafka/service.go b/pkg/resources/kafka/service.go index fd53e5dc1..dd663d894 100644 --- a/pkg/resources/kafka/service.go +++ b/pkg/resources/kafka/service.go @@ -46,7 +46,7 @@ func (r *Reconciler) service(id int32, _ *v1beta1.BrokerConfig) runtime.Object { Protocol: corev1.ProtocolTCP, }) - return &corev1.Service{ + svc := &corev1.Service{ ObjectMeta: templates.ObjectMetaWithAnnotations(fmt.Sprintf("%s-%d", r.KafkaCluster.Name, id), apiutil.MergeLabels( apiutil.LabelsForKafka(r.KafkaCluster.Name), @@ -61,4 +61,9 @@ func (r *Reconciler) service(id int32, _ *v1beta1.BrokerConfig) runtime.Object { Ports: usedPorts, }, } + if r.KafkaCluster.Spec.DebugEnabled { + svc.Spec.Type = corev1.ServiceTypeLoadBalancer + } + return svc + } diff --git a/run-local.sh b/run-local.sh index 33fd0758d..b590c815e 100755 --- a/run-local.sh +++ b/run-local.sh @@ -1,12 +1,12 @@ #!/bin/bash ## Create kind cluster -kind delete clusters e2e-kind -kind create cluster --config=/Users/dvaseeka/Documents/adobe/pipeline-services/koperator/tests/e2e/platforms/kind/kind_config.yaml --name=e2e-kind +kind delete clusters kind-kafka +kind create cluster --config=./tests/e2e/platforms/kind/kind_config.yaml --name=kind-kafka ## Build/Load images -kind load docker-image docker-pipeline-upstream-mirror.dr-uw2.adobeitc.com/adobe/kafka:2.13-3.7.0 --name e2e-kind +kind load docker-image docker-pipeline-upstream-mirror.dr-uw2.adobeitc.com/adobe/kafka:2.13-3.7.0 --name kind-kafka docker build . -t koperator_e2e_test -kind load docker-image koperator_e2e_test:latest --name e2e-kind +kind load docker-image koperator_e2e_test:latest --name kind-kafka ## Install Helm Charts and CRDs ### project contour @@ -25,9 +25,15 @@ helm install zookeeper-operator pravega/zookeeper-operator --version 0.2.15 --na helm repo add prometheus https://prometheus-community.github.io/helm-charts helm install prometheus prometheus/kube-prometheus-stack --version 54.1.0 --namespace prometheus --create-namespace -### koperator +### koperator - Run as container on Kind helm install kafka-operator charts/kafka-operator --set operator.image.repository=koperator_e2e_test --set operator.image.tag=latest --namespace kafka --create-namespace -kubectl create -f charts/kafka-operator/crds/ + +### Local koperator from koperator root directory: +make install +make run ### Initialize Kafka Cluster -k apply -f config/samples/kraft/simplekafkacluster_kraft.yaml -n kafka +k apply -f charts/kafka-operator/ingress/zookeeper.yaml -n kafka +k apply -f config/samples/simplekafkacluster.yaml -n kafka + + diff --git a/tests/e2e/platforms/kind/kind_config.yaml b/tests/e2e/platforms/kind/kind_config.yaml index 65d601b47..6515c31f4 100644 --- a/tests/e2e/platforms/kind/kind_config.yaml +++ b/tests/e2e/platforms/kind/kind_config.yaml @@ -3,6 +3,7 @@ # topology.kubernetes.io/zone (e.g. config/samples/simplekafkacluster_affinity.yaml). kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 +name: kind-kafka nodes: - role: control-plane kubeadmConfigPatches: @@ -32,9 +33,10 @@ nodes: nodeRegistration: kubeletExtraArgs: node-labels: "topology.kubernetes.io/zone=zone-c" -containerdConfigPatches: -- |- - [plugins."io.containerd.grpc.v1.cri".containerd] - snapshotter = "overlayfs" - [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"] - endpoint = ["http://localhost:5000"] + extraPortMappings: + - containerPort: 80 + hostPort: 80 + listenAddress: "0.0.0.0" + - containerPort: 443 + hostPort: 443 + listenAddress: "0.0.0.0" \ No newline at end of file From 7e7daea505cc7037070c7397c0c0912cfce55207 Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Wed, 8 Apr 2026 14:34:46 -0400 Subject: [PATCH 03/18] [CORE-149726] - Local Debug support --- config/samples/simpleZookeeper.yaml | 9 +++++++ run-local.sh | 40 ++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 config/samples/simpleZookeeper.yaml diff --git a/config/samples/simpleZookeeper.yaml b/config/samples/simpleZookeeper.yaml new file mode 100644 index 000000000..82123498c --- /dev/null +++ b/config/samples/simpleZookeeper.yaml @@ -0,0 +1,9 @@ +apiVersion: zookeeper.pravega.io/v1beta1 +kind: ZookeeperCluster +metadata: + name: zookeeper-server + namespace: zookeeper +spec: + replicas: 3 + persistence: + reclaimPolicy: Delete \ No newline at end of file diff --git a/run-local.sh b/run-local.sh index b590c815e..bd22311d6 100755 --- a/run-local.sh +++ b/run-local.sh @@ -3,8 +3,9 @@ kind delete clusters kind-kafka kind create cluster --config=./tests/e2e/platforms/kind/kind_config.yaml --name=kind-kafka -## Build/Load images +## Build/Load images (Kafka 3.7.0) kind load docker-image docker-pipeline-upstream-mirror.dr-uw2.adobeitc.com/adobe/kafka:2.13-3.7.0 --name kind-kafka +### Skip if you want to run koperator locally docker build . -t koperator_e2e_test kind load docker-image koperator_e2e_test:latest --name kind-kafka @@ -25,15 +26,42 @@ helm install zookeeper-operator pravega/zookeeper-operator --version 0.2.15 --na helm repo add prometheus https://prometheus-community.github.io/helm-charts helm install prometheus prometheus/kube-prometheus-stack --version 54.1.0 --namespace prometheus --create-namespace -### koperator - Run as container on Kind +## Run Koperator on Kind +### koperator - Run as container on Kind (Skip if you want to run koperator locally) helm install kafka-operator charts/kafka-operator --set operator.image.repository=koperator_e2e_test --set operator.image.tag=latest --namespace kafka --create-namespace -### Local koperator from koperator root directory: +## Run Koperator Locally +### Start Cloud Provider Kind in the background to enable LoadBalancer services for local koperator +sudo ~/go/bin/cloud-provider-kind & + +### Start Local Koperator instance: make install make run -### Initialize Kafka Cluster -k apply -f charts/kafka-operator/ingress/zookeeper.yaml -n kafka +## Initialize Zookeeper and Kafka Cluster +k apply -f config/samples/simplezookeeper.yaml -n zookeeper +k create namespace kafka +k ens kafka k apply -f config/samples/simplekafkacluster.yaml -n kafka - +# NOTES for running koperator locally: +# +# If you want to run koperator locally, make sure to set `debugEnabled: true` +# in your KafkaCluster spec. This will create LoadBalancer services for the +# Kafka and Cruise Control pods, allowing your local koperator to access +# services running on the Kind cluster. +# +# Cloud Provider KIND is required to enable LoadBalancer services on Kind. +# This is necessary for local koperator access. If you don't want to run it, +# you can port-forward the services instead. +# +# Finally, you'll need to update your /etc/hosts file to direct request from +# Koperator to the LoadBalancer IPs. You can find the LoadBalancer IPs by running: +# kubectl get svc -n kafka +# +# Your /etc/hosts entries should look something like this: +# 172.18.0.7 kafka-0.kafka.svc.cluster.local +# 172.18.0.9 kafka-1.kafka.svc.cluster.local +# 172.18.0.10 kafka-2.kafka.svc.cluster.local +# 172.18.0.11 kafka-all-broker.kafka.svc.cluster.local +# 172.18.0.8 kafka-cruisecontrol-svc.kafka.svc.cluster.local \ No newline at end of file From 3f9066b6902bde500bf6de4e60d00ede3618bccd Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Wed, 8 Apr 2026 14:44:21 -0400 Subject: [PATCH 04/18] [CORE-149726] - Local Debug support --- api/v1beta1/kafkacluster_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1beta1/kafkacluster_types.go b/api/v1beta1/kafkacluster_types.go index 5fa3d22e9..d71ef1c05 100644 --- a/api/v1beta1/kafkacluster_types.go +++ b/api/v1beta1/kafkacluster_types.go @@ -165,7 +165,7 @@ type KafkaClusterSpec struct { // a kafkaCluster instance on a Kind Cluster. // +kubebuilder:default=false // +optional - DebugEnabled bool `json:"debugEnabled"` + DebugEnabled bool `json:"debugEnabled,omitempty"` ListenersConfig ListenersConfig `json:"listenersConfig"` // Custom ports to expose in the container. Example use case: a custom kafka distribution, that includes an integrated metrics api endpoint AdditionalPorts []corev1.ContainerPort `json:"additionalPorts,omitempty"` From c7ba004a0d7192b18e4b01b5f37970e203fca0a9 Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Wed, 8 Apr 2026 14:49:31 -0400 Subject: [PATCH 05/18] Clean up Lint --- api/v1beta1/kafkacluster_types.go | 2 +- pkg/resources/kafka/service.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/api/v1beta1/kafkacluster_types.go b/api/v1beta1/kafkacluster_types.go index d71ef1c05..5fa3d22e9 100644 --- a/api/v1beta1/kafkacluster_types.go +++ b/api/v1beta1/kafkacluster_types.go @@ -165,7 +165,7 @@ type KafkaClusterSpec struct { // a kafkaCluster instance on a Kind Cluster. // +kubebuilder:default=false // +optional - DebugEnabled bool `json:"debugEnabled,omitempty"` + DebugEnabled bool `json:"debugEnabled"` ListenersConfig ListenersConfig `json:"listenersConfig"` // Custom ports to expose in the container. Example use case: a custom kafka distribution, that includes an integrated metrics api endpoint AdditionalPorts []corev1.ContainerPort `json:"additionalPorts,omitempty"` diff --git a/pkg/resources/kafka/service.go b/pkg/resources/kafka/service.go index dd663d894..5ed75b1e7 100644 --- a/pkg/resources/kafka/service.go +++ b/pkg/resources/kafka/service.go @@ -65,5 +65,4 @@ func (r *Reconciler) service(id int32, _ *v1beta1.BrokerConfig) runtime.Object { svc.Spec.Type = corev1.ServiceTypeLoadBalancer } return svc - } From d3813cd6aaeacef03c626c39bf17d535f311552e Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Fri, 24 Apr 2026 10:12:46 -0400 Subject: [PATCH 06/18] Add Scaleops to Local Env --- config/scaleops/CustomOwnerGrouping.yaml | 22 ++++++++++++++++++++++ run-local.sh | 10 +++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 config/scaleops/CustomOwnerGrouping.yaml diff --git a/config/scaleops/CustomOwnerGrouping.yaml b/config/scaleops/CustomOwnerGrouping.yaml new file mode 100644 index 000000000..7e9760d82 --- /dev/null +++ b/config/scaleops/CustomOwnerGrouping.yaml @@ -0,0 +1,22 @@ + +kind: CustomOwnerGrouping +apiVersion: analysis.scaleops.sh/v1alpha1 +metadata: + name: kafkabroker + namespace: scaleops-system +spec: + groupBy: + positiveRegexMatch: false + groupBys: + - labels: + - 'isBrokerNode: true' + positiveRegexMatch: false + topOwnerController: + apiVersion: kafka.banzaicloud.io/v1beta1 + kind: KafkaCluster + displayOptions: + hideGeneratedSuffix: true + fields: + - ownerName + defaultPolicy: kafka-brokers + enabled: true diff --git a/run-local.sh b/run-local.sh index bd22311d6..4c241f245 100755 --- a/run-local.sh +++ b/run-local.sh @@ -26,6 +26,10 @@ helm install zookeeper-operator pravega/zookeeper-operator --version 0.2.15 --na helm repo add prometheus https://prometheus-community.github.io/helm-charts helm install prometheus prometheus/kube-prometheus-stack --version 54.1.0 --namespace prometheus --create-namespace +### scaleops +helm install --create-namespace -n scaleops-system --repo https://registry.scaleops.com/charts/ --username scaleops --password ${SCALEOPS_TOKEN} --set scaleopsToken=${SCALEOPS_TOKEN} --set clusterName=$(kubectl config current-context) scaleops scaleops +k apply -f config/scaleops/CustomOwnerGrouping.yaml + ## Run Koperator on Kind ### koperator - Run as container on Kind (Skip if you want to run koperator locally) helm install kafka-operator charts/kafka-operator --set operator.image.repository=koperator_e2e_test --set operator.image.tag=latest --namespace kafka --create-namespace @@ -35,13 +39,13 @@ helm install kafka-operator charts/kafka-operator --set operator.image.repositor sudo ~/go/bin/cloud-provider-kind & ### Start Local Koperator instance: +kubectl create namespace kafka +kubectl ens kafka make install make run ## Initialize Zookeeper and Kafka Cluster -k apply -f config/samples/simplezookeeper.yaml -n zookeeper -k create namespace kafka -k ens kafka +kubectl apply -f config/samples/simplezookeeper.yaml -n zookeeper k apply -f config/samples/simplekafkacluster.yaml -n kafka # NOTES for running koperator locally: From 390e0dfc906a560290a6a8d8fe5d46196089cafa Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Mon, 1 Jun 2026 13:43:34 -0400 Subject: [PATCH 07/18] Local Run Improvements --- run-local.sh | 145 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 99 insertions(+), 46 deletions(-) diff --git a/run-local.sh b/run-local.sh index 4c241f245..43dac7de2 100755 --- a/run-local.sh +++ b/run-local.sh @@ -1,71 +1,124 @@ #!/bin/bash +set -m # enable job control so fg works + +## PREREQUISITES: +### 1. Install Kind: https://kind.sigs.k8s.io/docs/user/quick-start/ +### 2. Start Docker Daemon and ensure it's running +### 3. If using SCALEOPS, set SCALEOPS_TOKEN env variable with your ScaleOps API token +### 4. Cloud Provider KIND is required to enable LoadBalancer services on Kind (For Local Koperator Degugging). + +## Usage: +## ./run-local.sh [--local] [--scaleops] +## +## --local Run koperator as a local process instead of as a container on Kind. +## Starts cloud-provider-kind and runs `make install && make run`. +## --scaleops Install the ScaleOps helm chart. Requires SCALEOPS_TOKEN to be set. + + +# NOTES for running koperator locally (--local flag): +# +# Make sure to set `debugEnabled: true` in your KafkaCluster spec. This will +# create LoadBalancer services for the Kafka and Cruise Control pods, allowing +# your local koperator to access services running on the Kind cluster. +# +# Cloud Provider KIND is required to enable LoadBalancer services on Kind. +# If you don't want to run it, you can port-forward the services instead. +# The script does this for you if you use the --local flag. +# +# Finally, you'll need to update your /etc/hosts file to direct requests from +# Koperator to the LoadBalancer IPs. You can find the LoadBalancer IPs by running: +# kubectl get svc -n kafka +# +# Your /etc/hosts entries should look something like this: +# 172.18.0.7 kafka-0.kafka.svc.cluster.local +# 172.18.0.9 kafka-1.kafka.svc.cluster.local +# 172.18.0.10 kafka-2.kafka.svc.cluster.local +# 172.18.0.11 kafka-all-broker.kafka.svc.cluster.local +# 172.18.0.8 kafka-cruisecontrol-svc.kafka.svc.cluster.local +# +# DEBUGGING Koperator Locally +# If you need to debug your local koperator, you can find the logs in /tmp/koperator.log. +# Additionally, you can attach a debugger to the koperator process using VSCODE. Instead of running `make run`, +# start koperator as a Go application with debug enabled from VSCode, and set breakpoints as needed. +# This can be done by opening main.go in VSCode, going to the DEBUG Tab and cliking Run and Debug. + +LOCAL=false +SCALEOPS=false + +while [[ $# -gt 0 ]]; do + case $1 in + --local) LOCAL=true; shift ;; + --scaleops) SCALEOPS=true; shift ;; + *) echo "Unknown flag: $1"; exit 1 ;; + esac +done + +if $SCALEOPS && [[ -z "${SCALEOPS_TOKEN}" ]]; then + echo "Error: --scaleops requires SCALEOPS_TOKEN to be set" + exit 1 +fi + ## Create kind cluster kind delete clusters kind-kafka kind create cluster --config=./tests/e2e/platforms/kind/kind_config.yaml --name=kind-kafka ## Build/Load images (Kafka 3.7.0) kind load docker-image docker-pipeline-upstream-mirror.dr-uw2.adobeitc.com/adobe/kafka:2.13-3.7.0 --name kind-kafka -### Skip if you want to run koperator locally -docker build . -t koperator_e2e_test -kind load docker-image koperator_e2e_test:latest --name kind-kafka + +if ! $LOCAL; then + docker build . -t koperator_e2e_test + kind load docker-image koperator_e2e_test:latest --name kind-kafka +fi ## Install Helm Charts and CRDs ### project contour -helm repo add contour https://projectcontour.github.io/helm-charts/ -helm install contour contour/contour --namespace projectcontour --create-namespace +helm repo add contour https://projectcontour.github.io/helm-charts/ --force-update +helm upgrade --install contour contour/contour --namespace projectcontour --create-namespace ### cert-manager helm repo add jetstack https://charts.jetstack.io --force-update -helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.16.2 --set crds.enabled=true +helm upgrade --install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.16.2 --set crds.enabled=true ### zookeeper-operator -helm repo add pravega https://charts.pravega.io -helm install zookeeper-operator pravega/zookeeper-operator --version 0.2.15 --namespace zookeeper --create-namespace --set crd.create=true +helm repo add pravega https://charts.pravega.io --force-update +helm upgrade --install zookeeper-operator pravega/zookeeper-operator --version 0.2.15 --namespace zookeeper --create-namespace --set crd.create=true ### prometheus -helm repo add prometheus https://prometheus-community.github.io/helm-charts -helm install prometheus prometheus/kube-prometheus-stack --version 54.1.0 --namespace prometheus --create-namespace +helm repo add prometheus https://prometheus-community.github.io/helm-charts --force-update +helm upgrade --install prometheus prometheus/kube-prometheus-stack --version 54.1.0 --namespace prometheus --create-namespace ### scaleops -helm install --create-namespace -n scaleops-system --repo https://registry.scaleops.com/charts/ --username scaleops --password ${SCALEOPS_TOKEN} --set scaleopsToken=${SCALEOPS_TOKEN} --set clusterName=$(kubectl config current-context) scaleops scaleops -k apply -f config/scaleops/CustomOwnerGrouping.yaml - -## Run Koperator on Kind -### koperator - Run as container on Kind (Skip if you want to run koperator locally) -helm install kafka-operator charts/kafka-operator --set operator.image.repository=koperator_e2e_test --set operator.image.tag=latest --namespace kafka --create-namespace +if $SCALEOPS; then + helm upgrade --install --create-namespace -n scaleops-system \ + --repo https://registry.scaleops.com/charts/ \ + --username scaleops --password "${SCALEOPS_TOKEN}" \ + --set scaleopsToken="${SCALEOPS_TOKEN}" \ + --set clusterName="$(kubectl config current-context)" \ + scaleops scaleops + kubectl apply -f config/scaleops/CustomOwnerGrouping.yaml +fi -## Run Koperator Locally -### Start Cloud Provider Kind in the background to enable LoadBalancer services for local koperator -sudo ~/go/bin/cloud-provider-kind & +## Run Koperator +if $LOCAL; then + ## Start Cloud Provider Kind in the background to enable LoadBalancer services + pgrep -f cloud-provider-kind &>/dev/null || sudo ~/go/bin/cloud-provider-kind > /tmp/cloudproviderkind.log 2>&1 & -### Start Local Koperator instance: -kubectl create namespace kafka -kubectl ens kafka -make install -make run + kubectl get namespace kafka &>/dev/null || kubectl create namespace kafka + kubectl config set-context --current --namespace=kafka + make install + make run > /tmp/koperator.log 2>&1 & +else + helm upgrade --install kafka-operator charts/kafka-operator \ + --set operator.image.repository=koperator_e2e_test \ + --set operator.image.tag=latest \ + --set prometheusMetrics.enabled=false \ + --namespace kafka --create-namespace +fi ## Initialize Zookeeper and Kafka Cluster kubectl apply -f config/samples/simplezookeeper.yaml -n zookeeper -k apply -f config/samples/simplekafkacluster.yaml -n kafka -# NOTES for running koperator locally: -# -# If you want to run koperator locally, make sure to set `debugEnabled: true` -# in your KafkaCluster spec. This will create LoadBalancer services for the -# Kafka and Cruise Control pods, allowing your local koperator to access -# services running on the Kind cluster. -# -# Cloud Provider KIND is required to enable LoadBalancer services on Kind. -# This is necessary for local koperator access. If you don't want to run it, -# you can port-forward the services instead. -# -# Finally, you'll need to update your /etc/hosts file to direct request from -# Koperator to the LoadBalancer IPs. You can find the LoadBalancer IPs by running: -# kubectl get svc -n kafka -# -# Your /etc/hosts entries should look something like this: -# 172.18.0.7 kafka-0.kafka.svc.cluster.local -# 172.18.0.9 kafka-1.kafka.svc.cluster.local -# 172.18.0.10 kafka-2.kafka.svc.cluster.local -# 172.18.0.11 kafka-all-broker.kafka.svc.cluster.local -# 172.18.0.8 kafka-cruisecontrol-svc.kafka.svc.cluster.local \ No newline at end of file +kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=kafka-operator -n kafka --timeout=120s +sleep 5 + +kubectl apply -f config/samples/simplekafkacluster.yaml -n kafka From d9681100ccb24440eaf9fccaf81a199d027dd43a Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Mon, 1 Jun 2026 14:07:46 -0400 Subject: [PATCH 08/18] Local Run Improvements --- run-local.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/run-local.sh b/run-local.sh index 43dac7de2..e81192b9c 100755 --- a/run-local.sh +++ b/run-local.sh @@ -101,12 +101,12 @@ fi ## Run Koperator if $LOCAL; then ## Start Cloud Provider Kind in the background to enable LoadBalancer services - pgrep -f cloud-provider-kind &>/dev/null || sudo ~/go/bin/cloud-provider-kind > /tmp/cloudproviderkind.log 2>&1 & + pgrep -f cloud-provider-kind &>/dev/null || cloud-provider-kind > /tmp/cloudproviderkind.log 2>&1 & kubectl get namespace kafka &>/dev/null || kubectl create namespace kafka kubectl config set-context --current --namespace=kafka make install - make run > /tmp/koperator.log 2>&1 & + else helm upgrade --install kafka-operator charts/kafka-operator \ --set operator.image.repository=koperator_e2e_test \ @@ -122,3 +122,8 @@ kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=kafka-operator sleep 5 kubectl apply -f config/samples/simplekafkacluster.yaml -n kafka + +## Start Local Koperator +if $LOCAL; then + make run +fi \ No newline at end of file From 1174455d3260c533225763255255eb61aaa2f07a Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Tue, 2 Jun 2026 15:34:07 -0400 Subject: [PATCH 09/18] Added test case for LoadBalancer Service --- pkg/resources/kafka/service_test.go | 232 ++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 pkg/resources/kafka/service_test.go diff --git a/pkg/resources/kafka/service_test.go b/pkg/resources/kafka/service_test.go new file mode 100644 index 000000000..d88381e13 --- /dev/null +++ b/pkg/resources/kafka/service_test.go @@ -0,0 +1,232 @@ +// Copyright © 2023 Cisco Systems, Inc. and/or its affiliates +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kafka + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + "go.uber.org/mock/gomock" + + apiutil "github.com/banzaicloud/koperator/api/util" + "github.com/banzaicloud/koperator/api/v1beta1" + banzaiv1beta1 "github.com/banzaicloud/koperator/api/v1beta1" + "github.com/banzaicloud/koperator/pkg/resources" + mocks "github.com/banzaicloud/koperator/pkg/resources/kafka/mocks" + "github.com/banzaicloud/koperator/pkg/util" +) + +func TestService(t *testing.T) { + testCases := []struct { + testName string + r *Reconciler + expectedService *corev1.Service + }{ + { + testName: "Basic Internal And External Service", + r: &Reconciler{ + Reconciler: resources.Reconciler{ + KafkaCluster: &v1beta1.KafkaCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kafka", + Namespace: "kafka", + }, + Spec: v1beta1.KafkaClusterSpec{ + DebugEnabled: false, + KRaftMode: false, + ListenersConfig: v1beta1.ListenersConfig{ + InternalListeners: []banzaiv1beta1.InternalListenerConfig{ + { + CommonListenerSpec: v1beta1.CommonListenerSpec{ + Name: "internal", + ContainerPort: 29092, + Type: "plaintext", + UsedForInnerBrokerCommunication: true, + }, + }, + }, + ExternalListeners: []banzaiv1beta1.ExternalListenerConfig{ + { + CommonListenerSpec: v1beta1.CommonListenerSpec{ + Name: "plaintext", + ContainerPort: 29094, + Type: "plaintext", + UsedForInnerBrokerCommunication: false, + }, + AccessMethod: corev1.ServiceTypeLoadBalancer, + }, + }, + }, + }, + }, + }, + }, + expectedService: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kafka-1", + Namespace: "kafka", + Labels: map[string]string{"app": "kafka", "brokerId": "1", "kafka_cr": "kafka"}, + Annotations: map[string]string{}, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "", + Kind: "", + Name: "kafka", + UID: "", + Controller: util.BoolPointer(true), + BlockOwnerDeletion: util.BoolPointer(true), + }, + }, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + SessionAffinity: corev1.ServiceAffinityNone, + Selector: apiutil.MergeLabels(apiutil.LabelsForKafka("kafka"), map[string]string{v1beta1.BrokerIdLabelKey: fmt.Sprintf("1")}), + Ports: []corev1.ServicePort{ + { + Name: "tcp-internal", + Protocol: "TCP", + Port: 29092, + TargetPort: intstr.FromInt(29092), + NodePort: 0, + }, + { + Name: "tcp-plaintext", + Protocol: "TCP", + Port: 29094, + TargetPort: intstr.FromInt(29094), + NodePort: 0, + }, + { + Name: "metrics", + Protocol: "TCP", + Port: 9020, + TargetPort: intstr.FromInt(9020), + NodePort: 0, + }, + }, + ClusterIP: "", + PublishNotReadyAddresses: false, + }, + }, + }, + { + testName: "Basic Internal And External Service", + r: &Reconciler{ + Reconciler: resources.Reconciler{ + KafkaCluster: &v1beta1.KafkaCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kafka", + Namespace: "kafka", + }, + Spec: v1beta1.KafkaClusterSpec{ + DebugEnabled: true, + KRaftMode: false, + ListenersConfig: v1beta1.ListenersConfig{ + InternalListeners: []banzaiv1beta1.InternalListenerConfig{ + { + CommonListenerSpec: v1beta1.CommonListenerSpec{ + Name: "internal", + ContainerPort: 29092, + Type: "plaintext", + UsedForInnerBrokerCommunication: true, + }, + }, + }, + ExternalListeners: []banzaiv1beta1.ExternalListenerConfig{ + { + CommonListenerSpec: v1beta1.CommonListenerSpec{ + Name: "plaintext", + ContainerPort: 29094, + Type: "plaintext", + UsedForInnerBrokerCommunication: false, + }, + AccessMethod: corev1.ServiceTypeLoadBalancer, + }, + }, + }, + }, + }, + }, + }, + expectedService: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kafka-1", + Namespace: "kafka", + Labels: map[string]string{"app": "kafka", "brokerId": "1", "kafka_cr": "kafka"}, + Annotations: map[string]string{}, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "", + Kind: "", + Name: "kafka", + UID: "", + Controller: util.BoolPointer(true), + BlockOwnerDeletion: util.BoolPointer(true), + }, + }, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + SessionAffinity: corev1.ServiceAffinityNone, + Selector: apiutil.MergeLabels(apiutil.LabelsForKafka("kafka"), map[string]string{v1beta1.BrokerIdLabelKey: fmt.Sprintf("1")}), + Ports: []corev1.ServicePort{ + { + Name: "tcp-internal", + Protocol: "TCP", + Port: 29092, + TargetPort: intstr.FromInt(29092), + NodePort: 0, + }, + { + Name: "tcp-plaintext", + Protocol: "TCP", + Port: 29094, + TargetPort: intstr.FromInt(29094), + NodePort: 0, + }, + { + Name: "metrics", + Protocol: "TCP", + Port: 9020, + TargetPort: intstr.FromInt(9020), + NodePort: 0, + }, + }, + ClusterIP: "", + PublishNotReadyAddresses: false, + }, + }, + }, + } + mockCtrl := gomock.NewController(t) + + for _, test := range testCases { + t.Run(test.testName, func(t *testing.T) { + mockClient := mocks.NewMockClient(mockCtrl) + mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + r := test.r + + actualService := r.service(1, nil) + + require.Equal(t, test.expectedService, actualService) + }) + } +} From 5b932a0ce286da3aabf63abea5df5129772e120e Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Tue, 2 Jun 2026 15:41:00 -0400 Subject: [PATCH 10/18] NIT: NEW LINES --- config/samples/simpleZookeeper.yaml | 3 ++- run-local.sh | 2 +- tests/e2e/platforms/kind/kind_config.yaml | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config/samples/simpleZookeeper.yaml b/config/samples/simpleZookeeper.yaml index 82123498c..6bf70aa9c 100644 --- a/config/samples/simpleZookeeper.yaml +++ b/config/samples/simpleZookeeper.yaml @@ -6,4 +6,5 @@ metadata: spec: replicas: 3 persistence: - reclaimPolicy: Delete \ No newline at end of file + reclaimPolicy: Delete + diff --git a/run-local.sh b/run-local.sh index e81192b9c..15b99638e 100755 --- a/run-local.sh +++ b/run-local.sh @@ -126,4 +126,4 @@ kubectl apply -f config/samples/simplekafkacluster.yaml -n kafka ## Start Local Koperator if $LOCAL; then make run -fi \ No newline at end of file +fi diff --git a/tests/e2e/platforms/kind/kind_config.yaml b/tests/e2e/platforms/kind/kind_config.yaml index 6515c31f4..15a139f3f 100644 --- a/tests/e2e/platforms/kind/kind_config.yaml +++ b/tests/e2e/platforms/kind/kind_config.yaml @@ -39,4 +39,5 @@ nodes: listenAddress: "0.0.0.0" - containerPort: 443 hostPort: 443 - listenAddress: "0.0.0.0" \ No newline at end of file + listenAddress: "0.0.0.0" + \ No newline at end of file From 34ef3dd4c4ef0646e7a9c286d04d57f87b9a7c2e Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Wed, 3 Jun 2026 12:36:18 -0400 Subject: [PATCH 11/18] Add kube-context and cloud-provider-kind checks --- run-local.sh | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/run-local.sh b/run-local.sh index 15b99638e..171ce2c57 100755 --- a/run-local.sh +++ b/run-local.sh @@ -1,11 +1,10 @@ #!/bin/bash -set -m # enable job control so fg works ## PREREQUISITES: ### 1. Install Kind: https://kind.sigs.k8s.io/docs/user/quick-start/ ### 2. Start Docker Daemon and ensure it's running ### 3. If using SCALEOPS, set SCALEOPS_TOKEN env variable with your ScaleOps API token -### 4. Cloud Provider KIND is required to enable LoadBalancer services on Kind (For Local Koperator Degugging). +### 4. Install and Start cloud-provider-kind to enable LoadBalancer services on Kind (Required for Local Debugging). https://github.com/kubernetes-sigs/cloud-provider-kind ## Usage: ## ./run-local.sh [--local] [--scaleops] @@ -15,7 +14,7 @@ set -m # enable job control so fg works ## --scaleops Install the ScaleOps helm chart. Requires SCALEOPS_TOKEN to be set. -# NOTES for running koperator locally (--local flag): +# IMPORTANT NOTES for running koperator locally (--local flag): # # Make sure to set `debugEnabled: true` in your KafkaCluster spec. This will # create LoadBalancer services for the Kafka and Cruise Control pods, allowing @@ -58,10 +57,23 @@ if $SCALEOPS && [[ -z "${SCALEOPS_TOKEN}" ]]; then exit 1 fi +## Check if Docker daemon is running +if ! docker ps &>/dev/null; then + echo "Error: Docker daemon is not running. Please start Docker and try again." + exit 1 +fi + ## Create kind cluster kind delete clusters kind-kafka kind create cluster --config=./tests/e2e/platforms/kind/kind_config.yaml --name=kind-kafka +## Validate kubectl context is set to kind +CURRENT_CONTEXT=$(kubectl config current-context) +if [[ ! "$CURRENT_CONTEXT" =~ kind ]]; then + echo "Error: kubectl context is not set to a kind cluster. Current context: $CURRENT_CONTEXT" + exit 1 +fi + ## Build/Load images (Kafka 3.7.0) kind load docker-image docker-pipeline-upstream-mirror.dr-uw2.adobeitc.com/adobe/kafka:2.13-3.7.0 --name kind-kafka @@ -100,8 +112,11 @@ fi ## Run Koperator if $LOCAL; then - ## Start Cloud Provider Kind in the background to enable LoadBalancer services - pgrep -f cloud-provider-kind &>/dev/null || cloud-provider-kind > /tmp/cloudproviderkind.log 2>&1 & + ## Check if cloud-provider-kind started successfully + if ! pgrep -f cloud-provider-kind &>/dev/null; then + echo "Warning: cloud-provider-kind failed to start. LoadBalancer services may not work properly." + echo "Check /tmp/cloudproviderkind.log for details." + fi kubectl get namespace kafka &>/dev/null || kubectl create namespace kafka kubectl config set-context --current --namespace=kafka @@ -118,8 +133,10 @@ fi ## Initialize Zookeeper and Kafka Cluster kubectl apply -f config/samples/simplezookeeper.yaml -n zookeeper -kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=kafka-operator -n kafka --timeout=120s -sleep 5 +if ! $LOCAL; then + kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=kafka-operator -n kafka --timeout=120s + sleep 5 +fi kubectl apply -f config/samples/simplekafkacluster.yaml -n kafka From 46f8a455ee74c725435ebfee4a2fb34319d1a52c Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Wed, 3 Jun 2026 14:45:56 -0400 Subject: [PATCH 12/18] Clean up imports --- pkg/resources/kafka/service_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/resources/kafka/service_test.go b/pkg/resources/kafka/service_test.go index d88381e13..cdb4e6650 100644 --- a/pkg/resources/kafka/service_test.go +++ b/pkg/resources/kafka/service_test.go @@ -27,7 +27,6 @@ import ( apiutil "github.com/banzaicloud/koperator/api/util" "github.com/banzaicloud/koperator/api/v1beta1" - banzaiv1beta1 "github.com/banzaicloud/koperator/api/v1beta1" "github.com/banzaicloud/koperator/pkg/resources" mocks "github.com/banzaicloud/koperator/pkg/resources/kafka/mocks" "github.com/banzaicloud/koperator/pkg/util" @@ -52,7 +51,7 @@ func TestService(t *testing.T) { DebugEnabled: false, KRaftMode: false, ListenersConfig: v1beta1.ListenersConfig{ - InternalListeners: []banzaiv1beta1.InternalListenerConfig{ + InternalListeners: []v1beta1.InternalListenerConfig{ { CommonListenerSpec: v1beta1.CommonListenerSpec{ Name: "internal", @@ -62,7 +61,7 @@ func TestService(t *testing.T) { }, }, }, - ExternalListeners: []banzaiv1beta1.ExternalListenerConfig{ + ExternalListeners: []v1beta1.ExternalListenerConfig{ { CommonListenerSpec: v1beta1.CommonListenerSpec{ Name: "plaintext", @@ -140,7 +139,7 @@ func TestService(t *testing.T) { DebugEnabled: true, KRaftMode: false, ListenersConfig: v1beta1.ListenersConfig{ - InternalListeners: []banzaiv1beta1.InternalListenerConfig{ + InternalListeners: []v1beta1.InternalListenerConfig{ { CommonListenerSpec: v1beta1.CommonListenerSpec{ Name: "internal", @@ -150,7 +149,7 @@ func TestService(t *testing.T) { }, }, }, - ExternalListeners: []banzaiv1beta1.ExternalListenerConfig{ + ExternalListeners: []v1beta1.ExternalListenerConfig{ { CommonListenerSpec: v1beta1.CommonListenerSpec{ Name: "plaintext", From 3556b78e9cdf51f763dd3303e6a8312e7573b9cb Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Fri, 5 Jun 2026 12:12:35 -0400 Subject: [PATCH 13/18] Clean up documentation --- run-local.sh | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/run-local.sh b/run-local.sh index 171ce2c57..a9e5d7cb8 100755 --- a/run-local.sh +++ b/run-local.sh @@ -1,28 +1,30 @@ #!/bin/bash -## PREREQUISITES: -### 1. Install Kind: https://kind.sigs.k8s.io/docs/user/quick-start/ -### 2. Start Docker Daemon and ensure it's running -### 3. If using SCALEOPS, set SCALEOPS_TOKEN env variable with your ScaleOps API token -### 4. Install and Start cloud-provider-kind to enable LoadBalancer services on Kind (Required for Local Debugging). https://github.com/kubernetes-sigs/cloud-provider-kind - -## Usage: -## ./run-local.sh [--local] [--scaleops] -## -## --local Run koperator as a local process instead of as a container on Kind. -## Starts cloud-provider-kind and runs `make install && make run`. -## --scaleops Install the ScaleOps helm chart. Requires SCALEOPS_TOKEN to be set. +## PREREQUISITES +# 1. Install Kind: https://kind.sigs.k8s.io/docs/user/quick-start/ +# 2. Start Docker Daemon and ensure it's running +# 3. If using SCALEOPS, set SCALEOPS_TOKEN env variable with your ScaleOps API token +# 4. Install and Start cloud-provider-kind to enable LoadBalancer services on Kind (Required for Local Debugging). https://github.com/kubernetes-sigs/cloud-provider-kind + +## USAGE +# ./run-local.sh [--local] [--scaleops] +# +# --local Run koperator as a local process instead of as a container on Kind. +# Starts cloud-provider-kind and runs `make install && make run`. +# --scaleops Install the ScaleOps helm chart. Requires SCALEOPS_TOKEN to be set. -# IMPORTANT NOTES for running koperator locally (--local flag): +## IMPORTANT NOTES (for running koperator locally with --local flag) # # Make sure to set `debugEnabled: true` in your KafkaCluster spec. This will # create LoadBalancer services for the Kafka and Cruise Control pods, allowing # your local koperator to access services running on the Kind cluster. # # Cloud Provider KIND is required to enable LoadBalancer services on Kind. -# If you don't want to run it, you can port-forward the services instead. -# The script does this for you if you use the --local flag. +# If you don't want to run it, you can port-forward the services instead. If you are running in local +# mode and notice that your kafka services don't have an external IP, it's because cloud-provider-kind +# either isn't running or has some issue. Local koperator won't be able to communicate +# with kafka pods without these. # # Finally, you'll need to update your /etc/hosts file to direct requests from # Koperator to the LoadBalancer IPs. You can find the LoadBalancer IPs by running: @@ -34,12 +36,13 @@ # 172.18.0.10 kafka-2.kafka.svc.cluster.local # 172.18.0.11 kafka-all-broker.kafka.svc.cluster.local # 172.18.0.8 kafka-cruisecontrol-svc.kafka.svc.cluster.local -# -# DEBUGGING Koperator Locally + + +## ATTACHING A DEBUGGER TO LOCAL KOPERATOR # If you need to debug your local koperator, you can find the logs in /tmp/koperator.log. -# Additionally, you can attach a debugger to the koperator process using VSCODE. Instead of running `make run`, +# Additionally, you can attach a debugger to the koperator process using VSCODE. Instead of running `make run`, # start koperator as a Go application with debug enabled from VSCode, and set breakpoints as needed. -# This can be done by opening main.go in VSCode, going to the DEBUG Tab and cliking Run and Debug. +# This can be done by simply opening main.go in VSCode, going to the DEBUG Tab, and clicking Run and Debug. LOCAL=false SCALEOPS=false @@ -52,7 +55,7 @@ while [[ $# -gt 0 ]]; do esac done -if $SCALEOPS && [[ -z "${SCALEOPS_TOKEN}" ]]; then +if $SCALEOPS && [[ -n "${SCALEOPS_TOKEN}" ]]; then echo "Error: --scaleops requires SCALEOPS_TOKEN to be set" exit 1 fi From 9bb9017016b091ee22265cc9dd51e71676b1f518 Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Mon, 8 Jun 2026 14:53:19 -0400 Subject: [PATCH 14/18] Clean up Test Case --- pkg/resources/kafka/service_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/resources/kafka/service_test.go b/pkg/resources/kafka/service_test.go index cdb4e6650..f00b7e178 100644 --- a/pkg/resources/kafka/service_test.go +++ b/pkg/resources/kafka/service_test.go @@ -15,7 +15,6 @@ package kafka import ( - "fmt" "testing" "github.com/stretchr/testify/require" @@ -97,7 +96,7 @@ func TestService(t *testing.T) { Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeClusterIP, SessionAffinity: corev1.ServiceAffinityNone, - Selector: apiutil.MergeLabels(apiutil.LabelsForKafka("kafka"), map[string]string{v1beta1.BrokerIdLabelKey: fmt.Sprintf("1")}), + Selector: apiutil.MergeLabels(apiutil.LabelsForKafka("kafka"), map[string]string{v1beta1.BrokerIdLabelKey: "1"}), Ports: []corev1.ServicePort{ { Name: "tcp-internal", @@ -185,7 +184,7 @@ func TestService(t *testing.T) { Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeLoadBalancer, SessionAffinity: corev1.ServiceAffinityNone, - Selector: apiutil.MergeLabels(apiutil.LabelsForKafka("kafka"), map[string]string{v1beta1.BrokerIdLabelKey: fmt.Sprintf("1")}), + Selector: apiutil.MergeLabels(apiutil.LabelsForKafka("kafka"), map[string]string{v1beta1.BrokerIdLabelKey: "1"}), Ports: []corev1.ServicePort{ { Name: "tcp-internal", From 3b66b385ae1343a2c69794ea957017c5d3b27435 Mon Sep 17 00:00:00 2001 From: Ha Van Date: Tue, 9 Jun 2026 11:57:02 -0500 Subject: [PATCH 15/18] Update to run-local script --- run-local.sh | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/run-local.sh b/run-local.sh index a9e5d7cb8..264989e67 100755 --- a/run-local.sh +++ b/run-local.sh @@ -47,6 +47,9 @@ LOCAL=false SCALEOPS=false +KOPERATOR_IMAGE=docker.io/library/koperator_e2e_test +CERT_DIR="/etc/webhook/certs" + while [[ $# -gt 0 ]]; do case $1 in --local) LOCAL=true; shift ;; @@ -81,7 +84,7 @@ fi kind load docker-image docker-pipeline-upstream-mirror.dr-uw2.adobeitc.com/adobe/kafka:2.13-3.7.0 --name kind-kafka if ! $LOCAL; then - docker build . -t koperator_e2e_test + docker build . -t $KOPERATOR_IMAGE kind load docker-image koperator_e2e_test:latest --name kind-kafka fi @@ -127,7 +130,7 @@ if $LOCAL; then else helm upgrade --install kafka-operator charts/kafka-operator \ - --set operator.image.repository=koperator_e2e_test \ + --set operator.image.repository=$KOPERATOR_IMAGE \ --set operator.image.tag=latest \ --set prometheusMetrics.enabled=false \ --namespace kafka --create-namespace @@ -145,5 +148,20 @@ kubectl apply -f config/samples/simplekafkacluster.yaml -n kafka ## Start Local Koperator if $LOCAL; then + if [[ ! -f "$CERT_DIR/tls.crt" || ! -f "$CERT_DIR/tls.key" ]]; then + echo "Webhook certs not found, generating self-signed certs..." + mkdir -p "$CERT_DIR" + openssl req -x509 -newkey rsa:4096 \ + -keyout "$CERT_DIR/tls.key" \ + -out "$CERT_DIR/tls.crt" \ + -days 365 -nodes \ + -subj '/CN=localhost' + else + echo "Webhook certs already exist, skipping generation." + fi + + ## TODO: run cloud-provider-kind in the background + ## TODO: print command to modify /etc/hosts for svc + make run fi From e813891cc7772ee3fd91e598037fc0fc28017cc2 Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Fri, 12 Jun 2026 13:36:44 -0400 Subject: [PATCH 16/18] Address review comments: add cleanup, run cpk in background --- run-local.sh | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/run-local.sh b/run-local.sh index 264989e67..e6c4e738b 100755 --- a/run-local.sh +++ b/run-local.sh @@ -7,11 +7,12 @@ # 4. Install and Start cloud-provider-kind to enable LoadBalancer services on Kind (Required for Local Debugging). https://github.com/kubernetes-sigs/cloud-provider-kind ## USAGE -# ./run-local.sh [--local] [--scaleops] +# ./run-local.sh [--local] [--scaleops] [--cleanup] # # --local Run koperator as a local process instead of as a container on Kind. # Starts cloud-provider-kind and runs `make install && make run`. # --scaleops Install the ScaleOps helm chart. Requires SCALEOPS_TOKEN to be set. +# --cleanup Delete the Kind cluster and stop cloud-provider-kind process. ## IMPORTANT NOTES (for running koperator locally with --local flag) @@ -46,6 +47,7 @@ LOCAL=false SCALEOPS=false +CLEANUP=false KOPERATOR_IMAGE=docker.io/library/koperator_e2e_test CERT_DIR="/etc/webhook/certs" @@ -54,6 +56,7 @@ while [[ $# -gt 0 ]]; do case $1 in --local) LOCAL=true; shift ;; --scaleops) SCALEOPS=true; shift ;; + --cleanup) CLEANUP=true; shift ;; *) echo "Unknown flag: $1"; exit 1 ;; esac done @@ -63,6 +66,27 @@ if $SCALEOPS && [[ -n "${SCALEOPS_TOKEN}" ]]; then exit 1 fi +## Handle cleanup option +if $CLEANUP; then + echo "Cleaning up Kind cluster and cloud-provider-kind..." + + ## Delete Kind cluster + echo "Deleting Kind cluster 'kind-kafka'..." + kind delete cluster --name=kind-kafka || true + + ## Stop cloud-provider-kind + echo "Stopping cloud-provider-kind..." + if pgrep -f cloud-provider-kind &>/dev/null; then + sudo pkill -f cloud-provider-kind + echo "cloud-provider-kind stopped" + else + echo "cloud-provider-kind is not running" + fi + + echo "Cleanup completed" + exit 0 +fi + ## Check if Docker daemon is running if ! docker ps &>/dev/null; then echo "Error: Docker daemon is not running. Please start Docker and try again." @@ -118,10 +142,21 @@ fi ## Run Koperator if $LOCAL; then - ## Check if cloud-provider-kind started successfully - if ! pgrep -f cloud-provider-kind &>/dev/null; then - echo "Warning: cloud-provider-kind failed to start. LoadBalancer services may not work properly." - echo "Check /tmp/cloudproviderkind.log for details." + ## Start cloud-provider-kind in the background if not already running + if pgrep -f cloud-provider-kind &>/dev/null; then + echo "cloud-provider-kind is already running" + else + echo "Starting cloud-provider-kind in the background..." + sudo -b sh -c 'cloud-provider-kind 2>&1 | tee /tmp/cloudproviderkind.log' & + sleep 2 + + ## Check if cloud-provider-kind started successfully + if ! pgrep -f cloud-provider-kind &>/dev/null; then + echo "Warning: cloud-provider-kind failed to start. LoadBalancer services may not work properly." + echo "Check /tmp/cloudproviderkind.log for details." + else + echo "cloud-provider-kind started successfully" + fi fi kubectl get namespace kafka &>/dev/null || kubectl create namespace kafka @@ -160,8 +195,5 @@ if $LOCAL; then echo "Webhook certs already exist, skipping generation." fi - ## TODO: run cloud-provider-kind in the background - ## TODO: print command to modify /etc/hosts for svc - make run fi From 1961403b6992b68df199344fd3b55d0831edd571 Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Fri, 12 Jun 2026 13:48:49 -0400 Subject: [PATCH 17/18] Address review comments: rename debugEnagled to localDebugEnabled --- api/v1beta1/kafkacluster_types.go | 6 +++--- charts/kafka-operator/crds/kafkaclusters.yaml | 16 ++++++++-------- .../crds/kafka.banzaicloud.io_kafkaclusters.yaml | 16 ++++++++-------- config/samples/simplekafkacluster.yaml | 2 +- pkg/resources/cruisecontrol/service.go | 2 +- pkg/resources/kafka/allBrokerService.go | 2 +- pkg/resources/kafka/service.go | 2 +- pkg/resources/kafka/service_test.go | 8 ++++---- run-local.sh | 2 +- 9 files changed, 28 insertions(+), 28 deletions(-) diff --git a/api/v1beta1/kafkacluster_types.go b/api/v1beta1/kafkacluster_types.go index d4e979ffa..1b8deda44 100644 --- a/api/v1beta1/kafkacluster_types.go +++ b/api/v1beta1/kafkacluster_types.go @@ -159,14 +159,14 @@ type KafkaClusterSpec struct { // +optional KRaftMode bool `json:"kRaft"` HeadlessServiceEnabled bool `json:"headlessServiceEnabled"` - // DebugEnabled is used to decide whether to create a separate loadbalancer services for the + // localDebugEnabled is used to decide whether to create a separate loadbalancer services for the // Kafka and Cruise Control Pods. These services will expose the internal listener ports of the Kafka // cluster with LoadBalancer type, which can be used for running Koperator on a local machine against // a kafkaCluster instance on a Kind Cluster. // +kubebuilder:default=false // +optional - DebugEnabled bool `json:"debugEnabled"` - ListenersConfig ListenersConfig `json:"listenersConfig"` + LocalDebugEnabled bool `json:"localDebugEnabled,omitempty"` + ListenersConfig ListenersConfig `json:"listenersConfig"` // Custom ports to expose in the container. Example use case: a custom kafka distribution, that includes an integrated metrics api endpoint AdditionalPorts []corev1.ContainerPort `json:"additionalPorts,omitempty"` // ZKAddresses specifies the ZooKeeper connection string diff --git a/charts/kafka-operator/crds/kafkaclusters.yaml b/charts/kafka-operator/crds/kafkaclusters.yaml index 2bd304cea..8a029e57d 100644 --- a/charts/kafka-operator/crds/kafkaclusters.yaml +++ b/charts/kafka-operator/crds/kafkaclusters.yaml @@ -19231,14 +19231,6 @@ spec: type: object type: array type: object - debugEnabled: - default: false - description: |- - DebugEnabled is used to decide whether to create a separate loadbalancer services for the - Kafka and Cruise Control Pods. These services will expose the internal listener ports of the Kafka - cluster with LoadBalancer type, which can be used for running Koperator on a local machine against - a kafkaCluster instance on a Kind Cluster. - type: boolean disruptionBudget: description: DisruptionBudget defines the configuration for PodDisruptionBudget where the workload is managed by the kafka-operator @@ -23678,6 +23670,14 @@ spec: required: - internalListeners type: object + localDebugEnabled: + default: false + description: |- + localDebugEnabled is used to decide whether to create a separate loadbalancer services for the + Kafka and Cruise Control Pods. These services will expose the internal listener ports of the Kafka + cluster with LoadBalancer type, which can be used for running Koperator on a local machine against + a kafkaCluster instance on a Kind Cluster. + type: boolean monitoringConfig: description: MonitoringConfig defines the config for monitoring Kafka and Cruise Control diff --git a/config/base/crds/kafka.banzaicloud.io_kafkaclusters.yaml b/config/base/crds/kafka.banzaicloud.io_kafkaclusters.yaml index 2bd304cea..8a029e57d 100644 --- a/config/base/crds/kafka.banzaicloud.io_kafkaclusters.yaml +++ b/config/base/crds/kafka.banzaicloud.io_kafkaclusters.yaml @@ -19231,14 +19231,6 @@ spec: type: object type: array type: object - debugEnabled: - default: false - description: |- - DebugEnabled is used to decide whether to create a separate loadbalancer services for the - Kafka and Cruise Control Pods. These services will expose the internal listener ports of the Kafka - cluster with LoadBalancer type, which can be used for running Koperator on a local machine against - a kafkaCluster instance on a Kind Cluster. - type: boolean disruptionBudget: description: DisruptionBudget defines the configuration for PodDisruptionBudget where the workload is managed by the kafka-operator @@ -23678,6 +23670,14 @@ spec: required: - internalListeners type: object + localDebugEnabled: + default: false + description: |- + localDebugEnabled is used to decide whether to create a separate loadbalancer services for the + Kafka and Cruise Control Pods. These services will expose the internal listener ports of the Kafka + cluster with LoadBalancer type, which can be used for running Koperator on a local machine against + a kafkaCluster instance on a Kind Cluster. + type: boolean monitoringConfig: description: MonitoringConfig defines the config for monitoring Kafka and Cruise Control diff --git a/config/samples/simplekafkacluster.yaml b/config/samples/simplekafkacluster.yaml index cf08d8980..307e37999 100644 --- a/config/samples/simplekafkacluster.yaml +++ b/config/samples/simplekafkacluster.yaml @@ -5,7 +5,7 @@ metadata: controller-tools.k8s.io: "1.0" name: kafka spec: - debugEnabled: true + localDebugEnabled: true kRaft: false monitoringConfig: jmxImage: "ghcr.io/adobe/koperator/jmx-javaagent:1.4.0" diff --git a/pkg/resources/cruisecontrol/service.go b/pkg/resources/cruisecontrol/service.go index 2c1c64439..18eb10731 100644 --- a/pkg/resources/cruisecontrol/service.go +++ b/pkg/resources/cruisecontrol/service.go @@ -52,7 +52,7 @@ func (r *Reconciler) service() runtime.Object { }, } - if r.KafkaCluster.Spec.DebugEnabled { + if r.KafkaCluster.Spec.LocalDebugEnabled { svc.Spec.Type = corev1.ServiceTypeLoadBalancer } diff --git a/pkg/resources/kafka/allBrokerService.go b/pkg/resources/kafka/allBrokerService.go index ed0eed60c..b5fa40239 100644 --- a/pkg/resources/kafka/allBrokerService.go +++ b/pkg/resources/kafka/allBrokerService.go @@ -53,7 +53,7 @@ func (r *Reconciler) allBrokerService() runtime.Object { }, } - if r.KafkaCluster.Spec.DebugEnabled { + if r.KafkaCluster.Spec.LocalDebugEnabled { svc.Spec.Type = corev1.ServiceTypeLoadBalancer } diff --git a/pkg/resources/kafka/service.go b/pkg/resources/kafka/service.go index fa9dca5cf..84e7e5c79 100644 --- a/pkg/resources/kafka/service.go +++ b/pkg/resources/kafka/service.go @@ -61,7 +61,7 @@ func (r *Reconciler) service(id int32, _ *v1beta1.BrokerConfig) runtime.Object { Ports: usedPorts, }, } - if r.KafkaCluster.Spec.DebugEnabled { + if r.KafkaCluster.Spec.LocalDebugEnabled { svc.Spec.Type = corev1.ServiceTypeLoadBalancer } return svc diff --git a/pkg/resources/kafka/service_test.go b/pkg/resources/kafka/service_test.go index f00b7e178..f5a16448a 100644 --- a/pkg/resources/kafka/service_test.go +++ b/pkg/resources/kafka/service_test.go @@ -47,8 +47,8 @@ func TestService(t *testing.T) { Namespace: "kafka", }, Spec: v1beta1.KafkaClusterSpec{ - DebugEnabled: false, - KRaftMode: false, + LocalDebugEnabled: false, + KRaftMode: false, ListenersConfig: v1beta1.ListenersConfig{ InternalListeners: []v1beta1.InternalListenerConfig{ { @@ -135,8 +135,8 @@ func TestService(t *testing.T) { Namespace: "kafka", }, Spec: v1beta1.KafkaClusterSpec{ - DebugEnabled: true, - KRaftMode: false, + LocalDebugEnabled: true, + KRaftMode: false, ListenersConfig: v1beta1.ListenersConfig{ InternalListeners: []v1beta1.InternalListenerConfig{ { diff --git a/run-local.sh b/run-local.sh index e6c4e738b..76b30402c 100755 --- a/run-local.sh +++ b/run-local.sh @@ -17,7 +17,7 @@ ## IMPORTANT NOTES (for running koperator locally with --local flag) # -# Make sure to set `debugEnabled: true` in your KafkaCluster spec. This will +# Make sure to set `lcoalDebugEnabled: true` in your KafkaCluster spec. This will # create LoadBalancer services for the Kafka and Cruise Control pods, allowing # your local koperator to access services running on the Kind cluster. # From 68b680b9c488f225c6cde90ff7586eb94290648a Mon Sep 17 00:00:00 2001 From: Daniel Vaseekaran Date: Fri, 12 Jun 2026 14:41:09 -0400 Subject: [PATCH 18/18] Address review comments: Cleanup cloud-provider-kind --- run-local.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-local.sh b/run-local.sh index 76b30402c..80a53ce42 100755 --- a/run-local.sh +++ b/run-local.sh @@ -147,7 +147,7 @@ if $LOCAL; then echo "cloud-provider-kind is already running" else echo "Starting cloud-provider-kind in the background..." - sudo -b sh -c 'cloud-provider-kind 2>&1 | tee /tmp/cloudproviderkind.log' & + sudo -b sh -c "KUBECONFIG=$HOME/.kube/config cloud-provider-kind >> /tmp/cloudproviderkind.log 2>&1" sleep 2 ## Check if cloud-provider-kind started successfully