Repository: VictoriaMetrics/operator

Issue:
vlcluster vlinsert storageNode argument missing commas.
since updating to the latest version of k8s-stack (and therefore the operator), my vlinsert nodes fail to connect to vlstorage since the -storageNode just puts all the nodes together without commas in between.

i think this is a result of this commit: https://github.com/VictoriaMetrics/operator/commit/ff722eb3ba4e72765548b4353b5f370b42d143f7#diff-935a27d82c61eccfc9fb9fd33ad10a1e4166983d4589bb61486816e997c99cd5 

which broke it (see build.go changes)

Hints from issue comments:
@finomen: same issue after update to v0.64.0, revert to 0.63.0 fixed it@AndrewChubatiuk: thanks for spotting this issue, new option for `storageNode` flag generation was not properly ported to all components, that are using it, fixed in #1584@AndrewChubatiuk: fix released in 0.64.1, operator chart 0.55.3 and k8s-stack 0.62.2

Solution patch:
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 59a8cdb0..76829dd4 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -13,6 +13,8 @@ aliases:
 
 ## tip
 
+* BUGFIX: [VLCluster](https://docs.victoriametrics.com/operator/resources/vlcluster/): fix `-storageNode` argument generation for vlinsert. Bug was introduced in [ff722eb](https://github.com/VictoriaMetrics/operator/commit/ff722eb3ba4e72765548b4353b5f370b42d143f7).
+
 ## [v0.64.0](https://github.com/VictoriaMetrics/operator/releases/tag/v0.64.0)
 
 **Release date:** 29 October 2025
diff --git a/internal/controller/operator/factory/vlcluster/vlinsert.go b/internal/controller/operator/factory/vlcluster/vlinsert.go
index 652d3f7e..ccdce46f 100644
--- a/internal/controller/operator/factory/vlcluster/vlinsert.go
+++ b/internal/controller/operator/factory/vlcluster/vlinsert.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"path"
 	"sort"
-	"strings"
 
 	appsv1 "k8s.io/api/apps/v1"
 	autoscalingv2 "k8s.io/api/autoscaling/v2"
@@ -138,13 +137,14 @@ func buildVLInsertPodSpec(cr *vmv1.VLCluster) (*corev1.PodTemplateSpec, error) {
 	}
 
 	if cr.Spec.VLStorage != nil && cr.Spec.VLStorage.ReplicaCount != nil {
-		storageArg := "-storageNode="
-		for _, i := range cr.AvailableStorageNodeIDs("insert") {
+		storageNodeFlag := build.NewFlag("-storageNode", "")
+		storageNodeIds := cr.AvailableStorageNodeIDs("insert")
+		for idx, i := range storageNodeIds {
 			// TODO: introduce TLS webserver config for storage nodes
-			storageArg += build.PodDNSAddress(cr.GetVLStorageName(), i, cr.Namespace, cr.Spec.VLStorage.Port, cr.Spec.ClusterDomainName)
+			storageNodeFlag.Add(build.PodDNSAddress(cr.GetVLStorageName(), i, cr.Namespace, cr.Spec.VLStorage.Port, cr.Spec.ClusterDomainName), idx)
 		}
-		storageArg = strings.TrimSuffix(storageArg, ",")
-		args = append(args, storageArg)
+		totalNodes := len(storageNodeIds)
+		args = build.AppendFlagsToArgs(args, totalNodes, storageNodeFlag)
 	}
 	if len(cr.Spec.VLInsert.ExtraEnvs) > 0 || len(cr.Spec.VLInsert.ExtraEnvsFrom) > 0 {
 		args = append(args, "-envflag.enable=true")



Test patch:
diff --git a/internal/controller/operator/factory/vlcluster/vlcluster_test.go b/internal/controller/operator/factory/vlcluster/vlcluster_test.go
index da684939..1503911b 100644
--- a/internal/controller/operator/factory/vlcluster/vlcluster_test.go
+++ b/internal/controller/operator/factory/vlcluster/vlcluster_test.go
@@ -155,17 +155,17 @@ func TestCreateOrUpdate(t *testing.T) {
 		Spec: vmv1.VLClusterSpec{
 			VLInsert: &vmv1.VLInsert{
 				CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
-					ReplicaCount: ptr.To(int32(1)),
+					ReplicaCount: ptr.To(int32(2)),
 				},
 			},
 			VLStorage: &vmv1.VLStorage{
 				CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
-					ReplicaCount: ptr.To(int32(1)),
+					ReplicaCount: ptr.To(int32(2)),
 				},
 			},
 			VLSelect: &vmv1.VLSelect{
 				CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
-					ReplicaCount: ptr.To(int32(1)),
+					ReplicaCount: ptr.To(int32(2)),
 				},
 			},
 		},
@@ -182,7 +182,7 @@ func TestCreateOrUpdate(t *testing.T) {
 		assert.Nil(t, rclient.Get(ctx, types.NamespacedName{Name: cr.GetVLInsertName(), Namespace: cr.Namespace}, &dep))
 		assert.Len(t, dep.Spec.Template.Spec.Containers, 1)
 		cnt := dep.Spec.Template.Spec.Containers[0]
-		assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:9481", "-internalselect.disable=true", "-storageNode=vlstorage-base-0.vlstorage-base.default:9491"})
+		assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:9481", "-internalselect.disable=true", "-storageNode=vlstorage-base-0.vlstorage-base.default:9491,vlstorage-base-1.vlstorage-base.default:9491"})
 		assert.Nil(t, dep.Annotations)
 		assert.Equal(t, dep.Labels, cr.FinalLabels(cr.VLInsertSelectorLabels()))
 
@@ -190,7 +190,7 @@ func TestCreateOrUpdate(t *testing.T) {
 		assert.Nil(t, rclient.Get(ctx, types.NamespacedName{Name: cr.GetVLSelectName(), Namespace: cr.Namespace}, &dep))
 		assert.Len(t, dep.Spec.Template.Spec.Containers, 1)
 		cnt = dep.Spec.Template.Spec.Containers[0]
-		assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:9471", "-internalinsert.disable=true", "-storageNode=vlstorage-base-0.vlstorage-base.default:9491"})
+		assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:9471", "-internalinsert.disable=true", "-storageNode=vlstorage-base-0.vlstorage-base.default:9491,vlstorage-base-1.vlstorage-base.default:9491"})
 		assert.Nil(t, dep.Annotations)
 		assert.Equal(t, dep.Labels, cr.FinalLabels(cr.VLSelectSelectorLabels()))
 
diff --git a/internal/controller/operator/factory/vtcluster/cluster_test.go b/internal/controller/operator/factory/vtcluster/cluster_test.go
index a4372a8f..aecb322e 100644
--- a/internal/controller/operator/factory/vtcluster/cluster_test.go
+++ b/internal/controller/operator/factory/vtcluster/cluster_test.go
@@ -161,17 +161,17 @@ func TestCreateOrUpdate(t *testing.T) {
 			Spec: vmv1.VTClusterSpec{
 				Insert: &vmv1.VTInsert{
 					CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
-						ReplicaCount: ptr.To(int32(1)),
+						ReplicaCount: ptr.To(int32(2)),
 					},
 				},
 				Storage: &vmv1.VTStorage{
 					CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
-						ReplicaCount: ptr.To(int32(1)),
+						ReplicaCount: ptr.To(int32(2)),
 					},
 				},
 				Select: &vmv1.VTSelect{
 					CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
-						ReplicaCount: ptr.To(int32(1)),
+						ReplicaCount: ptr.To(int32(2)),
 					},
 				},
 			},
@@ -188,7 +188,7 @@ func TestCreateOrUpdate(t *testing.T) {
 			assert.Nil(t, rclient.Get(ctx, types.NamespacedName{Name: cr.GetVTInsertName(), Namespace: cr.Namespace}, &dep))
 			assert.Len(t, dep.Spec.Template.Spec.Containers, 1)
 			cnt := dep.Spec.Template.Spec.Containers[0]
-			assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:10481", "-internalselect.disable=true", "-storageNode=vtstorage-base-0.vtstorage-base.default:10491"})
+			assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:10481", "-internalselect.disable=true", "-storageNode=vtstorage-base-0.vtstorage-base.default:10491,vtstorage-base-1.vtstorage-base.default:10491"})
 			assert.Nil(t, dep.Annotations)
 			assert.Equal(t, dep.Labels, cr.FinalLabels(cr.VTInsertSelectorLabels()))
 
@@ -196,7 +196,7 @@ func TestCreateOrUpdate(t *testing.T) {
 			assert.Nil(t, rclient.Get(ctx, types.NamespacedName{Name: cr.GetVTSelectName(), Namespace: cr.Namespace}, &dep))
 			assert.Len(t, dep.Spec.Template.Spec.Containers, 1)
 			cnt = dep.Spec.Template.Spec.Containers[0]
-			assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:10471", "-internalinsert.disable=true", "-storageNode=vtstorage-base-0.vtstorage-base.default:10491"})
+			assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:10471", "-internalinsert.disable=true", "-storageNode=vtstorage-base-0.vtstorage-base.default:10491,vtstorage-base-1.vtstorage-base.default:10491"})
 			assert.Nil(t, dep.Annotations)
 			assert.Equal(t, dep.Labels, cr.FinalLabels(cr.VTSelectSelectorLabels()))