RockteMQ-AI commented on code in PR #51:
URL: https://github.com/apache/rocketmq-operator/pull/51#discussion_r3902509290


##########
pkg/apis/rocketmq/v1alpha1/nameservice_types.go:
##########
@@ -49,6 +49,17 @@ type NameServiceSpec struct {
        HostPath string `json:"hostPath"`
        // VolumeClaimTemplates defines the StorageClass
        VolumeClaimTemplates []corev1.PersistentVolumeClaim 
`json:"volumeClaimTemplates"`
+       // rocketmq exporter
+       Exporter RocketmqExporter `json:"exporter,omitempty"`
+}
+
+// RocketmqExporter defines the specification for the rocketmq exporter
+type RocketmqExporter struct {
+       Enabled bool `json:"enabled,omitempty"`
+       Image string `json:"image,omitempty"`

Review Comment:
   The `Env` field is missing `omitempty` in its JSON tag (`json:"env"`), 
making it effectively required for deserialization. However, the CRD schema 
does not enforce this. If a user enables the exporter without specifying `env`, 
the controller may encounter unexpected behavior. Add `omitempty` if env is 
optional, or add CRD validation if it is required.



##########
deploy/crds/rocketmq_v1alpha1_nameservice_crd.yaml:
##########
@@ -63,6 +63,9 @@ spec:
               items:
                 type: object
               type: array
+            exporter:

Review Comment:
   The `exporter` field is defined as `type: object` with no sub-schema 
properties. This means Kubernetes will accept any arbitrary data under 
`exporter` without validation. The full sub-schema (enabled, image, 
imagePullPolicy, env, resources) should be declared here to match the Go type 
`RocketmqExporter` and provide proper CRD validation at admission time.



##########
pkg/controller/nameservice/nameservice_controller.go:
##########
@@ -342,8 +343,42 @@ func (r *ReconcileNameService) 
statefulSetForNameService(nameService *rocketmqv1
                        VolumeClaimTemplates: 
getVolumeClaimTemplates(nameService),
                },
        }
+       if nameService.Spec.Exporter.Enabled {
+               exporter := r.createRocketMQExporterContainer(nameService)
+               dep.Spec.Template.Spec.Containers = 
append(dep.Spec.Template.Spec.Containers, exporter)
+       }
        // Set Broker instance as the owner and controller
        controllerutil.SetControllerReference(nameService, dep, r.scheme)
 
        return dep
 }
+
+func (r *ReconcileNameService) createRocketMQExporterContainer(nameService 
*rocketmqv1alpha1.NameService) (container corev1.Container) {
+       container = corev1.Container{

Review Comment:
   No readiness or liveness probe is configured for the exporter sidecar. If 
the exporter process hangs or crashes, Kubernetes will not detect the failure 
or restart the container. Add at minimum an HTTP readiness probe against the 
metrics endpoint (e.g., `GET /metrics` on port 5557).



##########
pkg/controller/nameservice/nameservice_controller.go:
##########
@@ -342,8 +343,42 @@ func (r *ReconcileNameService) 
statefulSetForNameService(nameService *rocketmqv1
                        VolumeClaimTemplates: 
getVolumeClaimTemplates(nameService),
                },
        }
+       if nameService.Spec.Exporter.Enabled {
+               exporter := r.createRocketMQExporterContainer(nameService)
+               dep.Spec.Template.Spec.Containers = 
append(dep.Spec.Template.Spec.Containers, exporter)
+       }
        // Set Broker instance as the owner and controller
        controllerutil.SetControllerReference(nameService, dep, r.scheme)
 
        return dep
 }
+
+func (r *ReconcileNameService) createRocketMQExporterContainer(nameService 
*rocketmqv1alpha1.NameService) (container corev1.Container) {
+       container = corev1.Container{

Review Comment:
   No container port is defined on the exporter sidecar. The exporter listens 
on port 5557 (per `application.yml`), and the example YAML uses 
`prometheus.io/port: "5557"` annotations. Adding `Ports: 
[]corev1.ContainerPort{{ContainerPort: 5557, Name: "metrics"}}` improves 
discoverability and enables Service-based scraping, not just annotation-based 
pod scraping.



##########
pkg/controller/nameservice/nameservice_controller.go:
##########
@@ -342,8 +343,42 @@ func (r *ReconcileNameService) 
statefulSetForNameService(nameService *rocketmqv1
                        VolumeClaimTemplates: 
getVolumeClaimTemplates(nameService),
                },
        }
+       if nameService.Spec.Exporter.Enabled {
+               exporter := r.createRocketMQExporterContainer(nameService)
+               dep.Spec.Template.Spec.Containers = 
append(dep.Spec.Template.Spec.Containers, exporter)
+       }
        // Set Broker instance as the owner and controller
        controllerutil.SetControllerReference(nameService, dep, r.scheme)
 
        return dep
 }
+
+func (r *ReconcileNameService) createRocketMQExporterContainer(nameService 
*rocketmqv1alpha1.NameService) (container corev1.Container) {
+       container = corev1.Container{
+               Name: cons.ExporterContainerName,
+               Image: nameService.Spec.Exporter.Image,
+               Env: nameService.Spec.Exporter.Env,
+               ImagePullPolicy: func(specPolicy corev1.PullPolicy) 
corev1.PullPolicy {
+                       if specPolicy == "" {
+                               return corev1.PullAlways
+                       }
+                       return specPolicy

Review Comment:
   The resource-defaults logic only falls back to defaults when *both* 
`Limits.Memory()` and `Requests.Memory()` are zero. If a user provides only CPU 
resources (or only one of requests/limits for memory), the defaults are skipped 
and the user-provided partial spec is used as-is, potentially leaving memory 
unconstrained. Consider checking all four resource dimensions independently.



##########
images/rocketmq-exporter/alpine/Dockerfile:
##########
@@ -0,0 +1,51 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+FROM openjdk:8-alpine
+
+RUN apk add --no-cache bash gettext nmap-ncat curl git openssl busybox-extras
+
+ARG MAVEN_VERSION=3.6.3
+ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries
+ARG 
SHA=c35a1803a6e70a126e80b2b3ae33eed961f83ed74d18fcd16909b2d44d7dada3203f1ffe726c17ef8dcca2dcaa9fca676987befeadc9b9f759967a8cb77181c0
+
+RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
+  && echo "Downlaoding maven" \
+  && curl -fsSL -o /tmp/apache-maven.tar.gz 
${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
+  \
+  && echo "Checking download hash" \
+  && echo "${SHA}  /tmp/apache-maven.tar.gz" | sha512sum -c - \
+  \
+  && echo "Unziping maven" \
+  && tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven 
--strip-components=1 \
+  \
+  && echo "Cleaning and setting links" \
+  && rm -f /tmp/apache-maven.tar.gz \

Review Comment:
   The Dockerfile clones the `rocketmq-exporter` repo at HEAD without pinning 
to a specific tag or commit SHA. This makes builds non-reproducible — the same 
Dockerfile can produce different binaries over time. Pin to a specific release 
tag (e.g., `git clone --branch v0.0.2 --depth 1`). Also, building from source 
inside the Dockerfile bloats the image with Maven, Git, and the full JDK. 
Consider a multi-stage build or a pre-built release JAR.



##########
example/rocketmq_v1alpha1_rocketmq_exporter_cluster.yaml:
##########
@@ -0,0 +1,157 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: broker-config
+data:
+  # BROKER_MEM sets the broker JVM, if set to "" then Xms = Xmx = max(min(1/2 
ram, 1024MB), min(1/4 ram, 8GB))
+  BROKER_MEM: " -Xms2g -Xmx2g -Xmn1g "
+  broker-common.conf: |
+    # brokerClusterName, brokerName, brokerId are automatically generated by 
the operator and do not set it manually!!!
+    deleteWhen=04
+    fileReservedTime=48
+    flushDiskType=ASYNC_FLUSH
+    # set brokerRole to ASYNC_MASTER or SYNC_MASTER. DO NOT set to SLAVE 
because the replica instance will automatically be set!!!
+    brokerRole=ASYNC_MASTER
+
+---
+apiVersion: rocketmq.apache.org/v1alpha1
+kind: Broker
+metadata:
+  # name of broker cluster
+  name: broker
+spec:
+  # size is the number of the broker cluster, each broker cluster contains a 
master broker and [replicaPerGroup] replica brokers.
+  size: 1
+  # nameServers is the [ip:port] list of name service
+  nameServers: ""
+  # replicaPerGroup is the number of each broker cluster
+  replicaPerGroup: 0
+  # brokerImage is the customized docker image repo of the RocketMQ broker
+  brokerImage: apacherocketmq/rocketmq-broker:4.5.0-alpine-operator-0.3.0
+  # imagePullPolicy is the image pull policy
+  imagePullPolicy: Always
+  # resources describes the compute resource requirements and limits
+  resources:
+    requests:
+      memory: "2048Mi"
+      cpu: "250m"
+    limits:
+      memory: "12288Mi"
+      cpu: "500m"
+  # allowRestart defines whether allow pod restart
+  allowRestart: true
+  # storageMode can be EmptyDir, HostPath, StorageClass
+  storageMode: EmptyDir
+  # hostPath is the local path to store data
+  hostPath: /tmp/data/rocketmq/broker
+  # scalePodName is [Broker name]-[broker group number]-master-0
+  scalePodName: broker-0-master-0
+  # env defines custom env, e.g. BROKER_MEM
+  env:
+    - name: BROKER_MEM
+      valueFrom:
+        configMapKeyRef:
+          name: broker-config
+          key: BROKER_MEM
+  # volumes defines the broker.conf
+  volumes:
+    - name: broker-config
+      configMap:
+        name: broker-config
+        items:
+          - key: broker-common.conf
+            path: broker-common.conf
+  # volumeClaimTemplates defines the storageClass
+  volumeClaimTemplates:
+    - metadata:
+        name: broker-storage
+      spec:
+        accessModes:
+          - ReadWriteOnce
+        resources:
+          requests:
+            storage: 8Gi
+        selector:
+          matchLabels:
+            app: broker-storage-pv
+---
+apiVersion: rocketmq.apache.org/v1alpha1
+kind: NameService
+metadata:
+  name: name-service
+spec:
+  # size is the the name service instance number of the name service cluster
+  size: 1
+  # nameServiceImage is the customized docker image repo of the RocketMQ name 
service
+  nameServiceImage: 
apacherocketmq/rocketmq-nameserver:4.5.0-alpine-operator-0.3.0
+  # imagePullPolicy is the image pull policy
+  imagePullPolicy: Always
+  # hostNetwork can be true or false
+  hostNetwork: true
+  #  Set DNS policy for the pod.
+  #  Defaults to "ClusterFirst".
+  #  Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 
'None'.
+  #  DNS parameters given in DNSConfig will be merged with the policy selected 
with DNSPolicy.
+  #  To have DNS options set along with hostNetwork, you have to specify DNS 
policy
+  #  explicitly to 'ClusterFirstWithHostNet'.
+  dnsPolicy: ClusterFirstWithHostNet
+  # resources describes the compute resource requirements and limits
+  resources:
+    requests:
+      memory: "512Mi"
+      cpu: "250m"
+    limits:
+      memory: "1024Mi"
+      cpu: "500m"
+  # storageMode can be EmptyDir, HostPath, StorageClass
+  storageMode: EmptyDir
+  # hostPath is the local path to store data
+  hostPath: /data/rocketmq/nameserver
+  exporter:
+    enabled: true
+    annotations:
+      prometheus.io/path: /metrics
+      prometheus.io/port: "5557"
+      prometheus.io/scrape: "true"
+    env:
+      - name: WEB_TELEMETRY_PATH
+        value: /metrics
+      - name: ROCKETMQ_VERSION
+        value: V4_3_2
+      - name: NAMESRV_ADDR
+        value: 127.0.0.1:9876
+    image: miaolinjie/rocketmq-exporter:latest
+    imagePullPolicy: Always
+    resources:

Review Comment:
   The example uses a personal Docker Hub image 
(`miaolinjie/rocketmq-exporter:latest`) instead of the official 
`apacherocketmq/rocketmq-exporter` registry used elsewhere in this project. 
This should be updated to the official image before merge, and `:latest` should 
be replaced with a pinned version tag for reproducibility.



##########
images/rocketmq-exporter/alpine/Dockerfile:
##########
@@ -0,0 +1,51 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+FROM openjdk:8-alpine
+
+RUN apk add --no-cache bash gettext nmap-ncat curl git openssl busybox-extras
+
+ARG MAVEN_VERSION=3.6.3

Review Comment:
   Several packages installed here (`openssl`, `busybox-extras`, `git`) appear 
unnecessary for running the exporter JAR at runtime. `git` is only needed for 
the build step — consider a multi-stage Dockerfile to keep the final image 
minimal and reduce the attack surface.



##########
pkg/controller/nameservice/nameservice_controller.go:
##########
@@ -342,8 +343,42 @@ func (r *ReconcileNameService) 
statefulSetForNameService(nameService *rocketmqv1
                        VolumeClaimTemplates: 
getVolumeClaimTemplates(nameService),
                },
        }

Review Comment:
   No test coverage for the new exporter sidecar injection logic. The 
`createRocketMQExporterContainer` function and the conditional injection path 
in `statefulSetForNameService` should have unit tests covering: (1) exporter 
enabled with full spec, (2) exporter enabled with empty/default resources, (3) 
exporter disabled, (4) custom imagePullPolicy vs empty.



##########
pkg/controller/nameservice/nameservice_controller.go:
##########
@@ -342,8 +343,42 @@ func (r *ReconcileNameService) 
statefulSetForNameService(nameService *rocketmqv1
                        VolumeClaimTemplates: 
getVolumeClaimTemplates(nameService),
                },
        }
+       if nameService.Spec.Exporter.Enabled {
+               exporter := r.createRocketMQExporterContainer(nameService)
+               dep.Spec.Template.Spec.Containers = 
append(dep.Spec.Template.Spec.Containers, exporter)
+       }

Review Comment:
   The comment on line 349 says 'Set Broker instance as the owner and 
controller' but the code sets the NameService as the owner. This is a 
pre-existing misleading comment, but since this PR touches this area, consider 
correcting it to 'Set NameService instance as the owner and controller'.



##########
images/rocketmq-exporter/alpine/build-rocketmq-exporter-image.sh:
##########
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+checkVersion()
+{
+    echo "Version = $1"
+         echo $1 |grep -E "^[0-9]+\.[0-9]+\.[0-9]+" > /dev/null
+    if [ $? = 0 ]; then
+        return 1
+    fi

Review Comment:
   The `checkVersion` function has inverted return codes. It returns 1 
(failure) when the version IS valid, and falls through to `exit 2` when 
invalid. The caller `if [ $? = 0 ]` checks for success (0), which is never 
returned. This means every valid version (e.g., `4.5.0`) triggers the error 
message and `exit 2`. The return should be `return 0` on match, or the caller 
should check `$? != 0`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to