Repository: VictoriaMetrics/operator

Issue:
Support VictoraLogs Cluster mode
Hi,
VictoriaLogs supports cluster mode starting from version 1.18.0. You can find more information here: https://docs.victoriametrics.com/victorialogs/cluster/.
It would be great to implement a new CRD, VLCluster, similar to VMCluster.
Thanks.

Discussion:
@makasim: Related to https://github.com/VictoriaMetrics/helm-charts/issues/2127@f41gh7:  It's on our road map. Currently, we're working on HA version for VLogsCluster. @valyala: > It's on our road map. Currently, we're working on HA version for VLogsCluster.

It is better to make a separate simple CRD for VictoriaLogs cluster, which will allow to setup and run a regular VictoriaLogs cluster in Kubernetes with simple easy to understand configs. This CRD could be used by the majority of VictoriaLogs users who need horizontal scalability in a Kubernetes cluster with Kubernetes nodes running in a single AZ.

Later we could add an advanced CRD for multi-AZ VictoriaLogs cluster setup. This must be a separate CRD in order to keep the simple CRD for VictoriaLogs cluster simple.

Also, it would be great idea to create distinct CRDs for multi-AZ VictoriaLogs setup for single-node VictoriaLogs and single-node VictoriaMetrics according to https://victoriametrics.com/blog/dont-default-to-microservices-you-will-thank-us-later/@f41gh7:  Feature was included into [v0.59.0](https://github.com/VictoriaMetrics/operator/releases/tag/v0.59.0) and follow-up [v0.59.1](https://github.com/VictoriaMetrics/operator/releases/tag/v0.59.1) releases.

Solution patch:
diff --git a/PROJECT b/PROJECT
index 1b0d53fd..83b85b8f 100644
--- a/PROJECT
+++ b/PROJECT
@@ -213,4 +213,16 @@ resources:
   webhooks:
     validation: true
     webhookVersion: v1
+- api:
+    crdVersion: v1
+    namespaced: true
+  controller: true
+  domain: victoriametrics.com
+  group: operator
+  kind: VLCluster
+  path: github.com/VictoriaMetrics/operator/api/operator/v1
+  version: v1
+  webhooks:
+    validation: true
+    webhookVersion: v1
 version: "3"
diff --git a/api/operator/v1/common.go b/api/operator/v1/common.go
index 86cb930f..7bd1298b 100644
--- a/api/operator/v1/common.go
+++ b/api/operator/v1/common.go
@@ -1,6 +1,36 @@
 package v1
 
+import (
+	"fmt"
+
+	corev1 "k8s.io/api/core/v1"
+)
+
 const (
 	healthPath = "/health"
 	metricPath = "/metrics"
 )
+
+func prefixedName(name, prefix string) string {
+	return fmt.Sprintf("%s-%s", prefix, name)
+}
+
+// TLSServerConfig defines VictoriaMetrics TLS configuration for the application's server
+type TLSServerConfig struct {
+	// CertSecretRef defines reference for secret with certificate content under given key
+	// mutually exclusive with CertFile
+	// +optional
+	CertSecret *corev1.SecretKeySelector `json:"certSecret,omitempty"`
+	// CertFile defines path to the pre-mounted file with certificate
+	// mutually exclusive with CertSecretRef
+	// +optional
+	CertFile string `json:"certFile,omitempty"`
+	// Key defines reference for secret with certificate key content under given key
+	// mutually exclusive with KeyFile
+	// +optional
+	KeySecret *corev1.SecretKeySelector `json:"keySecret,omitempty"`
+	// KeyFile defines path to the pre-mounted file with certificate key
+	// mutually exclusive with KeySecretRef
+	// +optional
+	KeyFile string `json:"keyFile,omitempty"`
+}
diff --git a/api/operator/v1/vlcluster_types.go b/api/operator/v1/vlcluster_types.go
new file mode 100644
index 00000000..7b6a6d10
--- /dev/null
+++ b/api/operator/v1/vlcluster_types.go
@@ -0,0 +1,908 @@
+/*
+
+
+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 v1
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+)
+
+// VLClusterSpec defines the desired state of VLCluster
+type VLClusterSpec struct {
+	// ParsingError contents error with context if operator was failed to parse json object from kubernetes api server
+	ParsingError string `json:"-" yaml:"-"`
+
+	// ServiceAccountName is the name of the ServiceAccount to use to run the
+	// VLSelect, VLInsert and VLStorage Pods.
+	// +optional
+	ServiceAccountName string `json:"serviceAccountName,omitempty"`
+
+	// ClusterVersion defines default images tag for all components.
+	// it can be overwritten with component specific image.tag value.
+	// +optional
+	ClusterVersion string `json:"clusterVersion,omitempty"`
+	// ClusterDomainName defines domain name suffix for in-cluster dns addresses
+	// aka .cluster.local
+	// used by vlinsert and vlselect to build vlstorage address
+	// +optional
+	ClusterDomainName string `json:"clusterDomainName,omitempty"`
+
+	// ImagePullSecrets An optional list of references to secrets in the same namespace
+	// to use for pulling images from registries
+	// see https://kubernetes.io/docs/concepts/containers/images/#referring-to-an-imagepullsecrets-on-a-pod
+	// +optional
+	ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
+
+	VLInsert  *VLInsert  `json:"vlinsert,omitempty"`
+	VLSelect  *VLSelect  `json:"vlselect,omitempty"`
+	VLStorage *VLStorage `json:"vlstorage,omitempty"`
+
+	// Paused If set to true all actions on the underlying managed objects are not
+	// going to be performed, except for delete actions.
+	// +optional
+	Paused bool `json:"paused,omitempty"`
+	// UseStrictSecurity enables strict security mode for component
+	// it restricts disk writes access
+	// uses non-root user out of the box
+	// drops not needed security permissions
+	// +optional
+	UseStrictSecurity *bool `json:"useStrictSecurity,omitempty"`
+
+	// RequestsLoadBalancer configures load-balancing for vlinsert and vlselect requests.
+	// It helps to evenly spread load across pods.
+	// Usually it's not possible with Kubernetes TCP-based services.
+	RequestsLoadBalancer vmv1beta1.VMAuthLoadBalancer `json:"requestsLoadBalancer,omitempty"`
+	// ManagedMetadata defines metadata that will be added to the all objects
+	// created by operator for the given CustomResource
+	ManagedMetadata *vmv1beta1.ManagedObjectsMetadata `json:"managedMetadata,omitempty"`
+}
+
+// UnmarshalJSON implements json.Unmarshaler interface
+func (cr *VLClusterSpec) UnmarshalJSON(src []byte) error {
+	type pcr VLClusterSpec
+	if err := json.Unmarshal(src, (*pcr)(cr)); err != nil {
+		cr.ParsingError = fmt.Sprintf("cannot parse vlcluster spec: %s, err: %s", string(src), err)
+		return nil
+	}
+	return nil
+}
+
+// VLClusterStatus defines the observed state of VLCluster
+type VLClusterStatus struct {
+	vmv1beta1.StatusMetadata `json:",inline"`
+}
+
+// GetStatusMetadata returns metadata for object status
+func (cr *VLClusterStatus) GetStatusMetadata() *vmv1beta1.StatusMetadata {
+	return &cr.StatusMetadata
+}
+
+// VLInsert defines vlinsert component configuration at victoria-logs cluster
+type VLInsert struct {
+	// PodMetadata configures Labels and Annotations which are propagated to the VLSelect pods.
+	PodMetadata *vmv1beta1.EmbeddedObjectMetadata `json:"podMetadata,omitempty"`
+	// LogFormat for VLSelect to be configured with.
+	// default or json
+	// +optional
+	// +kubebuilder:validation:Enum=default;json
+	LogFormat string `json:"logFormat,omitempty"`
+	// LogLevel for VLSelect to be configured with.
+	// +optional
+	// +kubebuilder:validation:Enum=INFO;WARN;ERROR;FATAL;PANIC
+	LogLevel string `json:"logLevel,omitempty"`
+
+	// ServiceSpec that will be added to vlselect service spec
+	// +optional
+	ServiceSpec *vmv1beta1.AdditionalServiceSpec `json:"serviceSpec,omitempty"`
+	// ServiceScrapeSpec that will be added to vlselect VMServiceScrape spec
+	// +optional
+	ServiceScrapeSpec *vmv1beta1.VMServiceScrapeSpec `json:"serviceScrapeSpec,omitempty"`
+	// PodDisruptionBudget created by operator
+	// +optional
+	PodDisruptionBudget       *vmv1beta1.EmbeddedPodDisruptionBudgetSpec `json:"podDisruptionBudget,omitempty"`
+	*vmv1beta1.EmbeddedProbes `json:",inline"`
+	// Configures horizontal pod autoscaling.
+	// +optional
+	HPA *vmv1beta1.EmbeddedHPA `json:"hpa,omitempty"`
+	// SyslogSpec defines syslog listener configuration
+	// +optional
+	SyslogSpec *SyslogServerSpec `json:"syslogSpec,omitempty"`
+
+	// UpdateStrategy - overrides default update strategy.
+	// +kubebuilder:validation:Enum=Recreate;RollingUpdate
+	// +optional
+	UpdateStrategy *appsv1.DeploymentStrategyType `json:"updateStrategy,omitempty"`
+	// RollingUpdate - overrides deployment update params.
+	// +optional
+	RollingUpdate *appsv1.RollingUpdateDeployment `json:"rollingUpdate,omitempty"`
+
+	vmv1beta1.CommonDefaultableParams           `json:",inline"`
+	vmv1beta1.CommonApplicationDeploymentParams `json:",inline"`
+}
+
+// Probe implements build.probeCRD interface
+func (cr *VLInsert) Probe() *vmv1beta1.EmbeddedProbes {
+	return cr.EmbeddedProbes
+}
+
+// ProbePath implements build.probeCRD interface
+func (cr *VLInsert) ProbePath() string {
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, healthPath)
+}
+
+// ProbeScheme implements build.probeCRD interface
+func (cr *VLInsert) ProbeScheme() string {
+	return strings.ToUpper(vmv1beta1.HTTPProtoFromFlags(cr.ExtraArgs))
+}
+
+// ProbePort implements build.probeCRD interface
+func (cr *VLInsert) ProbePort() string {
+	return cr.Port
+}
+
+// ProbeNeedLiveness implements build.probeCRD interface
+func (*VLInsert) ProbeNeedLiveness() bool {
+	return true
+}
+
+// GetMetricPath returns prefixed path for metric requests
+func (cr *VLInsert) GetMetricPath() string {
+	if cr == nil {
+		return healthPath
+	}
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, metricPath)
+}
+
+// ExtraArgs returns additionally configured command-line arguments
+func (cr *VLInsert) GetExtraArgs() map[string]string {
+	return cr.ExtraArgs
+}
+
+// ServiceScrape returns overrides for serviceScrape builder
+func (cr *VLInsert) GetServiceScrape() *vmv1beta1.VMServiceScrapeSpec {
+	return cr.ServiceScrapeSpec
+}
+
+// GetAdditionalService returns AdditionalServiceSpec settings
+func (cr *VLInsert) GetAdditionalService() *vmv1beta1.AdditionalServiceSpec {
+	return cr.ServiceSpec
+}
+
+// SyslogServerSpec defines syslog servers configuration
+type SyslogServerSpec struct {
+	// TCPListeners defines syslog server TCP listener configuration
+	TCPListeners []*SyslogTCPListener `json:"tcpListeners,omitempty"`
+	// UDPListeners defines syslog server UDP listener configuration
+	UDPListeners []*SyslogUDPListener `json:"udpListeners,omitempty"`
+}
+
+// SyslogTCPListener defines configuration for TCP syslog server listen
+type SyslogTCPListener struct {
+	// ListenPort defines listen port
+	ListenPort int32 `json:"listenPort"`
+	// StreamFields to use as log stream labels
+	// see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#stream-fields
+	// +optional
+	StreamFields FieldsListString `json:"streamFields,omitempty"`
+	// IgnoreFields to ignore at logs
+	// see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#dropping-fields
+	// +optional
+	IgnoreFields FieldsListString `json:"ingoreFields,omitempty"`
+	// DecolorizeFields to remove ANSI color codes across logs
+	// see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#decolorizing-fields
+	// +optional
+	DecolorizeFields FieldsListString `json:"decolorizeFields,omitempty"`
+	// TenantID for logs ingested in form of accountID:projectID
+	// see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#multiple-configs
+	// +optional
+	TenantID string `json:"tenantID,omitempty"`
+	// CompressMethod for syslog messages
+	// see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#compression
+	// +kubebuilder:validation:Pattern:="^(none|zstd|gzip|deflate)$"
+	// +optional
+	CompressMethod string `json:"compressMethod,omitempty"`
+	// +optional
+	TLSConfig *TLSServerConfig `json:"tlsConfig,omitempty"`
+}
+
+// SyslogUDPListener defines configuration for UDP syslog server listen
+type SyslogUDPListener struct {
+	// ListenPort defines listen port
+	ListenPort int32 `json:"listenPort"`
+	// StreamFields to use as log stream labels
+	// see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#stream-fields
+	// +optional
+	StreamFields FieldsListString `json:"streamFields,omitempty"`
+	// IgnoreFields to ignore at logs
+	// see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#dropping-fields
+	// +optional
+	IgnoreFields FieldsListString `json:"ingoreFields,omitempty"`
+	// DecolorizeFields to remove ANSI color codes across logs
+	// see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#decolorizing-fields
+	// +optional
+	DecolorizeFields FieldsListString `json:"decolorizeFields,omitempty"`
+	// TenantID for logs ingested in form of accountID:projectID
+	// see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#multiple-configs
+	// +optional
+	TenantID string `json:"tenantID,omitempty"`
+	// CompressMethod for syslog messages
+	// see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#compression
+	// +kubebuilder:validation:Pattern:="^(none|zstd|gzip|deflate)$"
+	// +optional
+	CompressMethod string `json:"compressMethod,omitempty"`
+}
+
+// FieldsListString reprents list of json encoded strings
+// ["field"] or ["field1","field2"]
+type FieldsListString string
+
+var _ json.Unmarshaler = (*FieldsListString)(nil)
+
+// UnmarshalJSON implements json.Unmarshaller interface
+func (sf *FieldsListString) UnmarshalJSON(src []byte) error {
+	var str string
+	if err := json.Unmarshal(src, &str); err != nil {
+		return fmt.Errorf("cannot parse FieldsListString value as string: %w", err)
+	}
+	*sf = FieldsListString(str)
+
+	var a []string
+	if err := json.Unmarshal([]byte(str), &a); err != nil {
+		return fmt.Errorf("cannot unpack FieldsListString as array of strings: %w", err)
+	}
+	return nil
+}
+
+// VLSelect defines vlselect component configuration at victoria-logs cluster
+type VLSelect struct {
+	// PodMetadata configures Labels and Annotations which are propagated to the VLSelect pods.
+	PodMetadata *vmv1beta1.EmbeddedObjectMetadata `json:"podMetadata,omitempty"`
+	// LogFormat for VLSelect to be configured with.
+	// default or json
+	// +optional
+	// +kubebuilder:validation:Enum=default;json
+	LogFormat string `json:"logFormat,omitempty"`
+	// LogLevel for VLSelect to be configured with.
+	// +optional
+	// +kubebuilder:validation:Enum=INFO;WARN;ERROR;FATAL;PANIC
+	LogLevel string `json:"logLevel,omitempty"`
+
+	// ServiceSpec that will be added to vlselect service spec
+	// +optional
+	ServiceSpec *vmv1beta1.AdditionalServiceSpec `json:"serviceSpec,omitempty"`
+	// ServiceScrapeSpec that will be added to vlselect VMServiceScrape spec
+	// +optional
+	ServiceScrapeSpec *vmv1beta1.VMServiceScrapeSpec `json:"serviceScrapeSpec,omitempty"`
+	// PodDisruptionBudget created by operator
+	// +optional
+	PodDisruptionBudget       *vmv1beta1.EmbeddedPodDisruptionBudgetSpec `json:"podDisruptionBudget,omitempty"`
+	*vmv1beta1.EmbeddedProbes `json:",inline"`
+	// Configures horizontal pod autoscaling.
+	// +optional
+	HPA *vmv1beta1.EmbeddedHPA `json:"hpa,omitempty"`
+
+	// UpdateStrategy - overrides default update strategy.
+	// +kubebuilder:validation:Enum=Recreate;RollingUpdate
+	// +optional
+	UpdateStrategy *appsv1.DeploymentStrategyType `json:"updateStrategy,omitempty"`
+	// RollingUpdate - overrides deployment update params.
+	// +optional
+	RollingUpdate *appsv1.RollingUpdateDeployment `json:"rollingUpdate,omitempty"`
+
+	vmv1beta1.CommonDefaultableParams           `json:",inline"`
+	vmv1beta1.CommonApplicationDeploymentParams `json:",inline"`
+}
+
+// GetMetricPath returns prefixed path for metric requests
+func (cr *VLSelect) GetMetricPath() string {
+	if cr == nil {
+		return healthPath
+	}
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, metricPath)
+}
+
+// ExtraArgs returns additionally configured command-line arguments
+func (cr *VLSelect) GetExtraArgs() map[string]string {
+	return cr.ExtraArgs
+}
+
+// ServiceScrape returns overrides for serviceScrape builder
+func (cr *VLSelect) GetServiceScrape() *vmv1beta1.VMServiceScrapeSpec {
+	return cr.ServiceScrapeSpec
+}
+
+// Probe implements build.probeCRD interface
+func (cr *VLSelect) Probe() *vmv1beta1.EmbeddedProbes {
+	return cr.EmbeddedProbes
+}
+
+// ProbePath implements build.probeCRD interface
+func (cr *VLSelect) ProbePath() string {
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, healthPath)
+}
+
+// ProbeScheme implements build.probeCRD interface
+func (cr *VLSelect) ProbeScheme() string {
+	return strings.ToUpper(vmv1beta1.HTTPProtoFromFlags(cr.ExtraArgs))
+}
+
+// ProbePort implements build.probeCRD interface
+func (cr *VLSelect) ProbePort() string {
+	return cr.Port
+}
+
+// ProbeNeedLiveness implements build.probeCRD interface
+func (*VLSelect) ProbeNeedLiveness() bool {
+	return true
+}
+
+// GetAdditionalService returns AdditionalServiceSpec settings
+func (cr *VLSelect) GetAdditionalService() *vmv1beta1.AdditionalServiceSpec {
+	return cr.ServiceSpec
+}
+
+// VLStorage defines vlstorage component configuration at victoria-logs cluster
+type VLStorage struct {
+	// RetentionPeriod for the stored logs
+	// https://docs.victoriametrics.com/victorialogs/#retention
+	// +optional
+	// +kubebuilder:validation:Pattern:="^[0-9]+(h|d|w|y)?$"
+	RetentionPeriod string `json:"retentionPeriod,omitempty"`
+	// RetentionMaxDiskSpaceUsageBytes for the stored logs
+	// VictoriaLogs keeps at least two last days of data in order to guarantee that the logs for the last day can be returned in queries.
+	// This means that the total disk space usage may exceed the -retention.maxDiskSpaceUsageBytes,
+	// if the size of the last two days of data exceeds the -retention.maxDiskSpaceUsageBytes.
+	// https://docs.victoriametrics.com/victorialogs/#retention-by-disk-space-usage
+	// +optional
+	RetentionMaxDiskSpaceUsageBytes vmv1beta1.BytesString `json:"retentionMaxDiskSpaceUsageBytes,omitempty"`
+	// FutureRetention for the stored logs
+	// Log entries with timestamps bigger than now+futureRetention are rejected during data ingestion; see https://docs.victoriametrics.com/victorialogs/#retention
+	// +optional
+	// +kubebuilder:validation:Pattern:="^[0-9]+(h|d|w|y)?$"
+	FutureRetention string `json:"futureRetention,omitempty"`
+	// LogNewStreams Whether to log creation of new streams; this can be useful for debugging of high cardinality issues with log streams; see https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields
+	LogNewStreams bool `json:"logNewStreams,omitempty"`
+	// Whether to log all the ingested log entries; this can be useful for debugging of data ingestion; see https://docs.victoriametrics.com/victorialogs/data-ingestion/
+	LogIngestedRows bool `json:"logIngestedRows,omitempty"`
+
+	// PodMetadata configures Labels and Annotations which are propagated to the VLStorage pods.
+	PodMetadata *vmv1beta1.EmbeddedObjectMetadata `json:"podMetadata,omitempty"`
+	// LogFormat for VLStorage to be configured with.
+	// default or json
+	// +optional
+	// +kubebuilder:validation:Enum=default;json
+	LogFormat string `json:"logFormat,omitempty"`
+	// LogLevel for VLStorage to be configured with.
+	// +optional
+	// +kubebuilder:validation:Enum=INFO;WARN;ERROR;FATAL;PANIC
+	LogLevel string `json:"logLevel,omitempty"`
+
+	// ServiceSpec that will be added to vlselect service spec
+	// +optional
+	ServiceSpec *vmv1beta1.AdditionalServiceSpec `json:"serviceSpec,omitempty"`
+	// ServiceScrapeSpec that will be added to vlselect VMServiceScrape spec
+	// +optional
+	ServiceScrapeSpec *vmv1beta1.VMServiceScrapeSpec `json:"serviceScrapeSpec,omitempty"`
+	// PodDisruptionBudget created by operator
+	// +optional
+	PodDisruptionBudget       *vmv1beta1.EmbeddedPodDisruptionBudgetSpec `json:"podDisruptionBudget,omitempty"`
+	*vmv1beta1.EmbeddedProbes `json:",inline"`
+	// RollingUpdateStrategy defines strategy for application updates
+	// Default is OnDelete, in this case operator handles update process
+	// Can be changed for RollingUpdate
+	// +optional
+	RollingUpdateStrategy appsv1.StatefulSetUpdateStrategyType `json:"rollingUpdateStrategy,omitempty"`
+	// ClaimTemplates allows adding additional VolumeClaimTemplates for StatefulSet
+	ClaimTemplates []corev1.PersistentVolumeClaim `json:"claimTemplates,omitempty"`
+
+	// StorageDataPath - path to storage data
+	// +optional
+	StorageDataPath string `json:"storageDataPath,omitempty"`
+	// Storage configures persistent volume for VLStorage
+	// +optional
+	Storage *vmv1beta1.StorageSpec `json:"storage,omitempty"`
+
+	// MaintenanceInsertNodeIDs - excludes given node ids from insert requests routing, must contain pod suffixes - for pod-0, id will be 0 and etc.
+	// lets say, you have pod-0, pod-1, pod-2, pod-3. to exclude pod-0 and pod-3 from insert routing, define nodeIDs: [0,3].
+	// Useful at storage expanding, when you want to rebalance some data at cluster.
+	// +optional
+	MaintenanceInsertNodeIDs []int32 `json:"maintenanceInsertNodeIDs,omitempty"`
+	// MaintenanceInsertNodeIDs - excludes given node ids from select requests routing, must contain pod suffixes - for pod-0, id will be 0 and etc.
+	MaintenanceSelectNodeIDs []int32 `json:"maintenanceSelectNodeIDs,omitempty"`
+
+	vmv1beta1.CommonDefaultableParams           `json:",inline"`
+	vmv1beta1.CommonApplicationDeploymentParams `json:",inline"`
+}
+
+// GetStorageVolumeName returns formatted name for vlstorage volume
+func (cr *VLStorage) GetStorageVolumeName() string {
+	if cr.Storage != nil && cr.Storage.VolumeClaimTemplate.Name != "" {
+		return cr.Storage.VolumeClaimTemplate.Name
+	}
+	return "vlstorage-db"
+}
+
+// GetMetricPath returns prefixed path for metric requests
+func (cr *VLStorage) GetMetricPath() string {
+	if cr == nil {
+		return healthPath
+	}
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, metricPath)
+}
+
+// ExtraArgs returns additionally configured command-line arguments
+func (cr *VLStorage) GetExtraArgs() map[string]string {
+	return cr.ExtraArgs
+}
+
+// ServiceScrape returns overrides for serviceScrape builder
+func (cr *VLStorage) GetServiceScrape() *vmv1beta1.VMServiceScrapeSpec {
+	return cr.ServiceScrapeSpec
+}
+
+// Probe implements build.probeCRD interface
+func (cr *VLStorage) Probe() *vmv1beta1.EmbeddedProbes {
+	return cr.EmbeddedProbes
+}
+
+// ProbePath implements build.probeCRD interface
+func (cr *VLStorage) ProbePath() string {
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, healthPath)
+}
+
+// ProbeScheme implements build.probeCRD interface
+func (cr *VLStorage) ProbeScheme() string {
+	return strings.ToUpper(vmv1beta1.HTTPProtoFromFlags(cr.ExtraArgs))
+}
+
+// ProbePort implements build.probeCRD interface
+func (cr *VLStorage) ProbePort() string {
+	return cr.Port
+}
+
+// ProbeNeedLiveness implements build.probeCRD interface
+func (*VLStorage) ProbeNeedLiveness() bool {
+	return false
+}
+
+// GetAdditionalService returns AdditionalServiceSpec settings
+func (cr *VLStorage) GetAdditionalService() *vmv1beta1.AdditionalServiceSpec {
+	return cr.ServiceSpec
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+
+// VLCluster is the Schema for the vlclusters API
+// +kubebuilder:printcolumn:name="Insert Count",type="string",JSONPath=".spec.vlinsert.replicaCount",description="replicas of VLInsert"
+// +kubebuilder:printcolumn:name="Storage Count",type="string",JSONPath=".spec.vlstorage.replicaCount",description="replicas of VLStorage"
+// +kubebuilder:printcolumn:name="Select Count",type="string",JSONPath=".spec.vlselect.replicaCount",description="replicas of VLSelect"
+// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
+// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.updateStatus",description="Current status of cluster"
+type VLCluster struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   VLClusterSpec   `json:"spec,omitempty"`
+	Status VLClusterStatus `json:"status,omitempty"`
+	// ParsedLastAppliedSpec contains last-applied configuration spec
+	ParsedLastAppliedSpec *VLClusterSpec `json:"-" yaml:"-"`
+}
+
+// SetLastSpec implements objectWithLastAppliedState interface
+func (cr *VLCluster) SetLastSpec(prevSpec VLClusterSpec) {
+	cr.ParsedLastAppliedSpec = &prevSpec
+}
+
+// UnmarshalJSON implements json.Unmarshaler interface
+func (cr *VLCluster) UnmarshalJSON(src []byte) error {
+	type pcr VLCluster
+	if err := json.Unmarshal(src, (*pcr)(cr)); err != nil {
+		return err
+	}
+	if err := vmv1beta1.ParseLastAppliedStateTo(cr); err != nil {
+		return err
+	}
+	return nil
+}
+
+// GetStatus implements reconcile.ObjectWithDeepCopyAndStatus interface
+func (cr *VLCluster) GetStatus() *VLClusterStatus {
+	return &cr.Status
+}
+
+// DefaultStatusFields implements reconcile.ObjectWithDeepCopyAndStatus interface
+func (cr *VLCluster) DefaultStatusFields(vs *VLClusterStatus) {
+}
+
+// AsOwner returns owner references with current object as owner
+func (cr *VLCluster) AsOwner() []metav1.OwnerReference {
+	return []metav1.OwnerReference{
+		{
+			APIVersion:         cr.APIVersion,
+			Kind:               cr.Kind,
+			Name:               cr.Name,
+			UID:                cr.UID,
+			Controller:         ptr.To(true),
+			BlockOwnerDeletion: ptr.To(true),
+		},
+	}
+}
+
+// VMAuthLBSelectorLabels defines selector labels for vmauth balancer
+func (cr *VLCluster) VMAuthLBSelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vlclusterlb-vmauth-balancer",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// VMAuthLBPodLabels returns pod labels for vlclusterlb-vmauth-balancer cluster component
+func (cr *VLCluster) VMAuthLBPodLabels() map[string]string {
+	selectorLabels := cr.VMAuthLBSelectorLabels()
+	if cr.Spec.RequestsLoadBalancer.Spec.PodMetadata == nil {
+		return selectorLabels
+	}
+	return labels.Merge(cr.Spec.RequestsLoadBalancer.Spec.PodMetadata.Labels, selectorLabels)
+}
+
+// VMAuthLBPodAnnotations returns pod annotations for vmstorage cluster component
+func (cr *VLCluster) VMAuthLBPodAnnotations() map[string]string {
+	if cr.Spec.RequestsLoadBalancer.Spec.PodMetadata == nil {
+		return make(map[string]string)
+	}
+	return cr.Spec.RequestsLoadBalancer.Spec.PodMetadata.Annotations
+}
+
+// GetVMAuthLBName returns prefixed name for the loadbalanacer components
+func (cr *VLCluster) GetVMAuthLBName() string {
+	return fmt.Sprintf("vlclusterlb-%s", cr.Name)
+}
+
+// GetVLSelectLBName returns headless proxy service name for select component
+func (cr *VLCluster) GetVLSelectLBName() string {
+	return prefixedName(cr.Name, "vlselectint")
+}
+
+// GetVLInsertLBName returns headless proxy service name for insert component
+func (cr *VLCluster) GetVLInsertLBName() string {
+	return prefixedName(cr.Name, "vlinsertint")
+}
+
+// GetVLInsertName returns insert component name
+func (cr *VLCluster) GetVLInsertName() string {
+	return prefixedName(cr.Name, "vlinsert")
+}
+
+// GetLVSelectName returns select component name
+func (cr *VLCluster) GetVLSelectName() string {
+	return prefixedName(cr.Name, "vlselect")
+}
+
+// GetVLStorageName returns select component name
+func (cr *VLCluster) GetVLStorageName() string {
+	return prefixedName(cr.Name, "vlstorage")
+}
+
+func (cr *VLCluster) Validate() error {
+	if vmv1beta1.MustSkipCRValidation(cr) {
+		return nil
+	}
+	if cr.Spec.VLSelect != nil {
+		vms := cr.Spec.VLSelect
+		if vms.ServiceSpec != nil && vms.ServiceSpec.Name == cr.GetVLSelectName() {
+			return fmt.Errorf(".serviceSpec.Name cannot be equal to prefixed name=%q", cr.GetVLSelectName())
+		}
+		if vms.HPA != nil {
+			if err := vms.HPA.Validate(); err != nil {
+				return err
+			}
+		}
+	}
+	if cr.Spec.VLInsert != nil {
+		vli := cr.Spec.VLInsert
+		if vli.ServiceSpec != nil && vli.ServiceSpec.Name == cr.GetVLInsertName() {
+			return fmt.Errorf(".serviceSpec.Name cannot be equal to prefixed name=%q", cr.GetVLInsertName())
+		}
+		if vli.HPA != nil {
+			if err := vli.HPA.Validate(); err != nil {
+				return err
+			}
+		}
+	}
+	if cr.Spec.VLStorage != nil {
+		vls := cr.Spec.VLStorage
+		if vls.ServiceSpec != nil && vls.ServiceSpec.Name == cr.GetVLStorageName() {
+			return fmt.Errorf(".serviceSpec.Name cannot be equal to prefixed name=%q", cr.GetVLStorageName())
+		}
+	}
+	if cr.Spec.RequestsLoadBalancer.Enabled {
+		rlb := cr.Spec.RequestsLoadBalancer.Spec
+		if rlb.AdditionalServiceSpec != nil && rlb.AdditionalServiceSpec.Name == cr.GetVMAuthLBName() {
+			return fmt.Errorf(".serviceSpec.Name cannot be equal to prefixed name=%q", cr.GetVMAuthLBName())
+		}
+	}
+
+	return nil
+}
+
+// VLSelectSelectorLabels returns selector labels for select cluster component
+func (cr *VLCluster) VLSelectSelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vlselect",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// VLSelectPodLabels returns pod labels for select cluster component
+func (cr *VLCluster) VLSelectPodLabels() map[string]string {
+	selectorLabels := cr.VLSelectSelectorLabels()
+	if cr.Spec.VLSelect == nil || cr.Spec.VLSelect.PodMetadata == nil {
+		return selectorLabels
+	}
+	return labels.Merge(cr.Spec.VLSelect.PodMetadata.Labels, selectorLabels)
+}
+
+// VLInsertSelectorLabels returns selector labels for insert cluster component
+func (cr *VLCluster) VLInsertSelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vlinsert",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// VLInsertPodLabels returns pod labels for vlinsert cluster component
+func (cr *VLCluster) VLInsertPodLabels() map[string]string {
+	selectorLabels := cr.VLInsertSelectorLabels()
+	if cr.Spec.VLInsert == nil || cr.Spec.VLInsert.PodMetadata == nil {
+		return selectorLabels
+	}
+	return labels.Merge(cr.Spec.VLInsert.PodMetadata.Labels, selectorLabels)
+}
+
+// VLStorageSelectorLabels  returns pod labels for vlstorage cluster component
+func (cr *VLCluster) VLStorageSelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vlstorage",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// VLStoragePodLabels returns pod labels for the vmstorage cluster component
+func (cr *VLCluster) VLStoragePodLabels() map[string]string {
+	selectorLabels := cr.VLStorageSelectorLabels()
+	if cr.Spec.VLStorage == nil || cr.Spec.VLStorage.PodMetadata == nil {
+		return selectorLabels
+	}
+	return labels.Merge(cr.Spec.VLStorage.PodMetadata.Labels, selectorLabels)
+}
+
+// AvailableStorageNodeIDs returns ids of the storage nodes for the provided component
+func (cr *VLCluster) AvailableStorageNodeIDs(requestsType string) []int32 {
+	var result []int32
+	if cr.Spec.VLStorage == nil || cr.Spec.VLStorage.ReplicaCount == nil {
+		return result
+	}
+	maintenanceNodes := make(map[int32]struct{})
+	switch requestsType {
+	case "select":
+		for _, i := range cr.Spec.VLStorage.MaintenanceSelectNodeIDs {
+			maintenanceNodes[i] = struct{}{}
+		}
+	case "insert":
+		for _, i := range cr.Spec.VLStorage.MaintenanceInsertNodeIDs {
+			maintenanceNodes[i] = struct{}{}
+		}
+	default:
+		panic("BUG unsupported requestsType: " + requestsType)
+	}
+	for i := int32(0); i < *cr.Spec.VLStorage.ReplicaCount; i++ {
+		if _, ok := maintenanceNodes[i]; ok {
+			continue
+		}
+		result = append(result, i)
+	}
+	return result
+}
+
+var globalClusterLabels = map[string]string{"app.kubernetes.io/part-of": "vlcluster"}
+
+// FinalLabels adds cluster labels to the base labels and filters by prefix if needed
+func (cr *VLCluster) FinalLabels(selectorLabels map[string]string) map[string]string {
+	baseLabels := labels.Merge(globalClusterLabels, selectorLabels)
+	if cr.Spec.ManagedMetadata == nil {
+		// fast path
+		return baseLabels
+	}
+	return labels.Merge(cr.Spec.ManagedMetadata.Labels, baseLabels)
+}
+
+// VLSelectPodAnnotations returns pod annotations for select cluster component
+func (cr *VLCluster) VLSelectPodAnnotations() map[string]string {
+	if cr.Spec.VLSelect == nil || cr.Spec.VLSelect.PodMetadata == nil {
+		return make(map[string]string)
+	}
+	return cr.Spec.VLSelect.PodMetadata.Annotations
+}
+
+// VLInsertPodAnnotations returns pod annotations for insert cluster component
+func (cr *VLCluster) VLInsertPodAnnotations() map[string]string {
+	if cr.Spec.VLInsert == nil || cr.Spec.VLInsert.PodMetadata == nil {
+		return make(map[string]string)
+	}
+	return cr.Spec.VLInsert.PodMetadata.Annotations
+}
+
+// VLStoragePodAnnotations returns pod annotations for storage cluster component
+func (cr *VLCluster) VLStoragePodAnnotations() map[string]string {
+	if cr.Spec.VLStorage == nil || cr.Spec.VLStorage.PodMetadata == nil {
+		return make(map[string]string)
+	}
+	return cr.Spec.VLStorage.PodMetadata.Annotations
+}
+
+// FinalAnnotations returns global annotations to be applied by objects generate for vlcluster
+func (cr *VLCluster) FinalAnnotations() map[string]string {
+	if cr.Spec.ManagedMetadata == nil {
+		return map[string]string{}
+	}
+	return cr.Spec.ManagedMetadata.Annotations
+}
+
+// AnnotationsFiltered implements finalize.crdObject interface
+func (cr *VLCluster) AnnotationsFiltered() map[string]string {
+	return cr.FinalAnnotations()
+}
+
+// LastAppliedSpecAsPatch return last applied cluster spec as patch annotation
+func (cr *VLCluster) LastAppliedSpecAsPatch() (client.Patch, error) {
+	return vmv1beta1.LastAppliedChangesAsPatch(cr.ObjectMeta, cr.Spec)
+}
+
+// HasSpecChanges compares cluster spec with last applied cluster spec stored in annotation
+func (cr *VLCluster) HasSpecChanges() (bool, error) {
+	return vmv1beta1.HasStateChanges(cr.ObjectMeta, cr.Spec)
+}
+
+func (cr *VLCluster) Paused() bool {
+	return cr.Spec.Paused
+}
+
+// GetServiceAccountName returns service account name for all vlcluster components
+func (cr *VLCluster) GetServiceAccountName() string {
+	if cr.Spec.ServiceAccountName == "" {
+		return cr.PrefixedName()
+	}
+	return cr.Spec.ServiceAccountName
+}
+
+func (cr *VLCluster) IsOwnsServiceAccount() bool {
+	return cr.Spec.ServiceAccountName == ""
+}
+
+// PrefixedName format name of the component with hard-coded prefix
+func (cr *VLCluster) PrefixedName() string {
+	return fmt.Sprintf("vlcluster-%s", cr.Name)
+}
+
+// SelectorLabels defines labels for objects generated used by all cluster components
+func (cr *VLCluster) SelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vlcluster",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// AsURL implements stub for interface.
+func (cr *VLCluster) AsURL() string {
+	return "unknown"
+}
+
+// SelectURL returns url to access VLSelect component
+func (cr *VLCluster) SelectURL() string {
+	if cr.Spec.VLSelect == nil {
+		return ""
+	}
+	port := cr.Spec.VLSelect.Port
+	if port == "" {
+		port = "8481"
+	}
+	if cr.Spec.VLSelect.ServiceSpec != nil && cr.Spec.VLSelect.ServiceSpec.UseAsDefault {
+		for _, svcPort := range cr.Spec.VLSelect.ServiceSpec.Spec.Ports {
+			if svcPort.Name == "http" {
+				port = fmt.Sprintf("%d", svcPort.Port)
+			}
+		}
+	}
+	return fmt.Sprintf("%s://%s.%s.svc:%s", vmv1beta1.HTTPProtoFromFlags(cr.Spec.VLSelect.ExtraArgs), cr.GetVLSelectName(), cr.Namespace, port)
+}
+
+// InsertURL returns url to access VLInsert component
+func (cr *VLCluster) InsertURL() string {
+	if cr.Spec.VLInsert == nil {
+		return ""
+	}
+	port := cr.Spec.VLInsert.Port
+	if port == "" {
+		port = "8480"
+	}
+	if cr.Spec.VLInsert.ServiceSpec != nil && cr.Spec.VLInsert.ServiceSpec.UseAsDefault {
+		for _, svcPort := range cr.Spec.VLInsert.ServiceSpec.Spec.Ports {
+			if svcPort.Name == "http" {
+				port = fmt.Sprintf("%d", svcPort.Port)
+			}
+		}
+	}
+	return fmt.Sprintf("%s://%s.%s.svc:%s", vmv1beta1.HTTPProtoFromFlags(cr.Spec.VLInsert.ExtraArgs), cr.GetVLInsertName(), cr.Namespace, port)
+}
+
+// StorageURL returns url to access VLStorage component
+func (cr *VLCluster) StorageURL() string {
+	if cr.Spec.VLStorage == nil {
+		return ""
+	}
+	port := cr.Spec.VLStorage.Port
+	if port == "" {
+		port = "8482"
+	}
+	if cr.Spec.VLStorage.ServiceSpec != nil && cr.Spec.VLStorage.ServiceSpec.UseAsDefault {
+		for _, svcPort := range cr.Spec.VLStorage.ServiceSpec.Spec.Ports {
+			if svcPort.Name == "http" {
+				port = fmt.Sprintf("%d", svcPort.Port)
+			}
+		}
+	}
+	return fmt.Sprintf("%s://%s.%s.svc:%s", vmv1beta1.HTTPProtoFromFlags(cr.Spec.VLStorage.ExtraArgs), cr.GetVLStorageName(), cr.Namespace, port)
+}
+
+// GetNSName implements build.builderOpts interface
+func (cr *VLCluster) GetNSName() string {
+	return cr.GetNamespace()
+}
+
+// +kubebuilder:object:root=true
+
+// VLClusterList contains a list of VLCluster
+type VLClusterList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []VLCluster `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&VLCluster{}, &VLClusterList{})
+}
diff --git a/api/operator/v1/zz_generated.deepcopy.go b/api/operator/v1/zz_generated.deepcopy.go
index a401b42f..954ebad7 100644
--- a/api/operator/v1/zz_generated.deepcopy.go
+++ b/api/operator/v1/zz_generated.deepcopy.go
@@ -22,10 +22,353 @@ package v1
 
 import (
 	"github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SyslogServerSpec) DeepCopyInto(out *SyslogServerSpec) {
+	*out = *in
+	if in.TCPListeners != nil {
+		in, out := &in.TCPListeners, &out.TCPListeners
+		*out = make([]*SyslogTCPListener, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(SyslogTCPListener)
+				(*in).DeepCopyInto(*out)
+			}
+		}
+	}
+	if in.UDPListeners != nil {
+		in, out := &in.UDPListeners, &out.UDPListeners
+		*out = make([]*SyslogUDPListener, len(*in))
+		for i := range *in {
+			if (*in)[i] != nil {
+				in, out := &(*in)[i], &(*out)[i]
+				*out = new(SyslogUDPListener)
+				**out = **in
+			}
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyslogServerSpec.
+func (in *SyslogServerSpec) DeepCopy() *SyslogServerSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(SyslogServerSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SyslogTCPListener) DeepCopyInto(out *SyslogTCPListener) {
+	*out = *in
+	if in.TLSConfig != nil {
+		in, out := &in.TLSConfig, &out.TLSConfig
+		*out = new(TLSServerConfig)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyslogTCPListener.
+func (in *SyslogTCPListener) DeepCopy() *SyslogTCPListener {
+	if in == nil {
+		return nil
+	}
+	out := new(SyslogTCPListener)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SyslogUDPListener) DeepCopyInto(out *SyslogUDPListener) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SyslogUDPListener.
+func (in *SyslogUDPListener) DeepCopy() *SyslogUDPListener {
+	if in == nil {
+		return nil
+	}
+	out := new(SyslogUDPListener)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TLSServerConfig) DeepCopyInto(out *TLSServerConfig) {
+	*out = *in
+	if in.CertSecret != nil {
+		in, out := &in.CertSecret, &out.CertSecret
+		*out = new(corev1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.KeySecret != nil {
+		in, out := &in.KeySecret, &out.KeySecret
+		*out = new(corev1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSServerConfig.
+func (in *TLSServerConfig) DeepCopy() *TLSServerConfig {
+	if in == nil {
+		return nil
+	}
+	out := new(TLSServerConfig)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VLCluster) DeepCopyInto(out *VLCluster) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+	if in.ParsedLastAppliedSpec != nil {
+		in, out := &in.ParsedLastAppliedSpec, &out.ParsedLastAppliedSpec
+		*out = new(VLClusterSpec)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VLCluster.
+func (in *VLCluster) DeepCopy() *VLCluster {
+	if in == nil {
+		return nil
+	}
+	out := new(VLCluster)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *VLCluster) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VLClusterList) DeepCopyInto(out *VLClusterList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]VLCluster, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VLClusterList.
+func (in *VLClusterList) DeepCopy() *VLClusterList {
+	if in == nil {
+		return nil
+	}
+	out := new(VLClusterList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *VLClusterList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VLClusterSpec) DeepCopyInto(out *VLClusterSpec) {
+	*out = *in
+	if in.ImagePullSecrets != nil {
+		in, out := &in.ImagePullSecrets, &out.ImagePullSecrets
+		*out = make([]corev1.LocalObjectReference, len(*in))
+		copy(*out, *in)
+	}
+	if in.VLInsert != nil {
+		in, out := &in.VLInsert, &out.VLInsert
+		*out = new(VLInsert)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.VLSelect != nil {
+		in, out := &in.VLSelect, &out.VLSelect
+		*out = new(VLSelect)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.VLStorage != nil {
+		in, out := &in.VLStorage, &out.VLStorage
+		*out = new(VLStorage)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.UseStrictSecurity != nil {
+		in, out := &in.UseStrictSecurity, &out.UseStrictSecurity
+		*out = new(bool)
+		**out = **in
+	}
+	in.RequestsLoadBalancer.DeepCopyInto(&out.RequestsLoadBalancer)
+	if in.ManagedMetadata != nil {
+		in, out := &in.ManagedMetadata, &out.ManagedMetadata
+		*out = new(v1beta1.ManagedObjectsMetadata)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VLClusterSpec.
+func (in *VLClusterSpec) DeepCopy() *VLClusterSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(VLClusterSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VLClusterStatus) DeepCopyInto(out *VLClusterStatus) {
+	*out = *in
+	in.StatusMetadata.DeepCopyInto(&out.StatusMetadata)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VLClusterStatus.
+func (in *VLClusterStatus) DeepCopy() *VLClusterStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(VLClusterStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VLInsert) DeepCopyInto(out *VLInsert) {
+	*out = *in
+	if in.PodMetadata != nil {
+		in, out := &in.PodMetadata, &out.PodMetadata
+		*out = new(v1beta1.EmbeddedObjectMetadata)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ServiceSpec != nil {
+		in, out := &in.ServiceSpec, &out.ServiceSpec
+		*out = new(v1beta1.AdditionalServiceSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ServiceScrapeSpec != nil {
+		in, out := &in.ServiceScrapeSpec, &out.ServiceScrapeSpec
+		*out = new(v1beta1.VMServiceScrapeSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.PodDisruptionBudget != nil {
+		in, out := &in.PodDisruptionBudget, &out.PodDisruptionBudget
+		*out = new(v1beta1.EmbeddedPodDisruptionBudgetSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.EmbeddedProbes != nil {
+		in, out := &in.EmbeddedProbes, &out.EmbeddedProbes
+		*out = new(v1beta1.EmbeddedProbes)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.HPA != nil {
+		in, out := &in.HPA, &out.HPA
+		*out = new(v1beta1.EmbeddedHPA)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.SyslogSpec != nil {
+		in, out := &in.SyslogSpec, &out.SyslogSpec
+		*out = new(SyslogServerSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.UpdateStrategy != nil {
+		in, out := &in.UpdateStrategy, &out.UpdateStrategy
+		*out = new(appsv1.DeploymentStrategyType)
+		**out = **in
+	}
+	if in.RollingUpdate != nil {
+		in, out := &in.RollingUpdate, &out.RollingUpdate
+		*out = new(appsv1.RollingUpdateDeployment)
+		(*in).DeepCopyInto(*out)
+	}
+	in.CommonDefaultableParams.DeepCopyInto(&out.CommonDefaultableParams)
+	in.CommonApplicationDeploymentParams.DeepCopyInto(&out.CommonApplicationDeploymentParams)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VLInsert.
+func (in *VLInsert) DeepCopy() *VLInsert {
+	if in == nil {
+		return nil
+	}
+	out := new(VLInsert)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VLSelect) DeepCopyInto(out *VLSelect) {
+	*out = *in
+	if in.PodMetadata != nil {
+		in, out := &in.PodMetadata, &out.PodMetadata
+		*out = new(v1beta1.EmbeddedObjectMetadata)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ServiceSpec != nil {
+		in, out := &in.ServiceSpec, &out.ServiceSpec
+		*out = new(v1beta1.AdditionalServiceSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ServiceScrapeSpec != nil {
+		in, out := &in.ServiceScrapeSpec, &out.ServiceScrapeSpec
+		*out = new(v1beta1.VMServiceScrapeSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.PodDisruptionBudget != nil {
+		in, out := &in.PodDisruptionBudget, &out.PodDisruptionBudget
+		*out = new(v1beta1.EmbeddedPodDisruptionBudgetSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.EmbeddedProbes != nil {
+		in, out := &in.EmbeddedProbes, &out.EmbeddedProbes
+		*out = new(v1beta1.EmbeddedProbes)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.HPA != nil {
+		in, out := &in.HPA, &out.HPA
+		*out = new(v1beta1.EmbeddedHPA)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.UpdateStrategy != nil {
+		in, out := &in.UpdateStrategy, &out.UpdateStrategy
+		*out = new(appsv1.DeploymentStrategyType)
+		**out = **in
+	}
+	if in.RollingUpdate != nil {
+		in, out := &in.RollingUpdate, &out.RollingUpdate
+		*out = new(appsv1.RollingUpdateDeployment)
+		(*in).DeepCopyInto(*out)
+	}
+	in.CommonDefaultableParams.DeepCopyInto(&out.CommonDefaultableParams)
+	in.CommonApplicationDeploymentParams.DeepCopyInto(&out.CommonApplicationDeploymentParams)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VLSelect.
+func (in *VLSelect) DeepCopy() *VLSelect {
+	if in == nil {
+		return nil
+	}
+	out := new(VLSelect)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *VLSingle) DeepCopyInto(out *VLSingle) {
 	*out = *in
@@ -153,3 +496,67 @@ func (in *VLSingleStatus) DeepCopy() *VLSingleStatus {
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VLStorage) DeepCopyInto(out *VLStorage) {
+	*out = *in
+	if in.PodMetadata != nil {
+		in, out := &in.PodMetadata, &out.PodMetadata
+		*out = new(v1beta1.EmbeddedObjectMetadata)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ServiceSpec != nil {
+		in, out := &in.ServiceSpec, &out.ServiceSpec
+		*out = new(v1beta1.AdditionalServiceSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ServiceScrapeSpec != nil {
+		in, out := &in.ServiceScrapeSpec, &out.ServiceScrapeSpec
+		*out = new(v1beta1.VMServiceScrapeSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.PodDisruptionBudget != nil {
+		in, out := &in.PodDisruptionBudget, &out.PodDisruptionBudget
+		*out = new(v1beta1.EmbeddedPodDisruptionBudgetSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.EmbeddedProbes != nil {
+		in, out := &in.EmbeddedProbes, &out.EmbeddedProbes
+		*out = new(v1beta1.EmbeddedProbes)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ClaimTemplates != nil {
+		in, out := &in.ClaimTemplates, &out.ClaimTemplates
+		*out = make([]corev1.PersistentVolumeClaim, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+	if in.Storage != nil {
+		in, out := &in.Storage, &out.Storage
+		*out = new(v1beta1.StorageSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.MaintenanceInsertNodeIDs != nil {
+		in, out := &in.MaintenanceInsertNodeIDs, &out.MaintenanceInsertNodeIDs
+		*out = make([]int32, len(*in))
+		copy(*out, *in)
+	}
+	if in.MaintenanceSelectNodeIDs != nil {
+		in, out := &in.MaintenanceSelectNodeIDs, &out.MaintenanceSelectNodeIDs
+		*out = make([]int32, len(*in))
+		copy(*out, *in)
+	}
+	in.CommonDefaultableParams.DeepCopyInto(&out.CommonDefaultableParams)
+	in.CommonApplicationDeploymentParams.DeepCopyInto(&out.CommonApplicationDeploymentParams)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VLStorage.
+func (in *VLStorage) DeepCopy() *VLStorage {
+	if in == nil {
+		return nil
+	}
+	out := new(VLStorage)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/api/operator/v1beta1/vmcluster_types.go b/api/operator/v1beta1/vmcluster_types.go
index 2523b0ad..1badf457 100644
--- a/api/operator/v1beta1/vmcluster_types.go
+++ b/api/operator/v1beta1/vmcluster_types.go
@@ -589,7 +589,7 @@ func (cr *VMCluster) Validate() error {
 			return fmt.Errorf(".serviceSpec.Name cannot be equal to prefixed name=%q", cr.GetVMSelectName())
 		}
 		if vms.HPA != nil {
-			if err := vms.HPA.validate(); err != nil {
+			if err := vms.HPA.Validate(); err != nil {
 				return err
 			}
 		}
@@ -600,14 +600,14 @@ func (cr *VMCluster) Validate() error {
 			return fmt.Errorf(".serviceSpec.Name cannot be equal to prefixed name=%q", cr.GetVMInsertName())
 		}
 		if vmi.HPA != nil {
-			if err := vmi.HPA.validate(); err != nil {
+			if err := vmi.HPA.Validate(); err != nil {
 				return err
 			}
 		}
 	}
 	if cr.Spec.VMStorage != nil {
 		vms := cr.Spec.VMStorage
-		if vms.ServiceSpec != nil && vms.ServiceSpec.Name == cr.GetVMInsertName() {
+		if vms.ServiceSpec != nil && vms.ServiceSpec.Name == cr.GetVMStorageName() {
 			return fmt.Errorf(".serviceSpec.Name cannot be equal to prefixed name=%q", cr.GetVMStorageName())
 		}
 		if cr.Spec.VMStorage.VMBackup != nil {
diff --git a/api/operator/v1beta1/vmextra_types.go b/api/operator/v1beta1/vmextra_types.go
index acde63e9..08cba245 100644
--- a/api/operator/v1beta1/vmextra_types.go
+++ b/api/operator/v1beta1/vmextra_types.go
@@ -461,7 +461,8 @@ type EmbeddedHPA struct {
 	Behaviour   *autoscalingv2.HorizontalPodAutoscalerBehavior `json:"behaviour,omitempty"`
 }
 
-func (cr *EmbeddedHPA) validate() error {
+// Validate validates resource configuration
+func (cr *EmbeddedHPA) Validate() error {
 	if cr.MinReplicas != nil && *cr.MinReplicas > cr.MaxReplicas {
 		return fmt.Errorf("minReplicas cannot be greater then maxReplicas")
 	}
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index 40038456..e9b6c1f2 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -19,6 +19,7 @@ resources:
 - bases/operator.victoriametrics.com_vmalertmanagerconfigs.yaml
 - bases/operator.victoriametrics.com_vlogs.yaml
 - bases/operator.victoriametrics.com_vlsingles.yaml
+- bases/operator.victoriametrics.com_vlclusters.yaml
 patches:
 # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
 # patches here are for enabling the conversion webhook for each CRD
@@ -58,6 +59,10 @@ patches:
   target:
     kind: CustomResourceDefinition
     name: vlsingles.operator.victoriametrics.com
+- path: patches/operator.victoriametrics.com_vlclusters.yaml
+  target:
+    kind: CustomResourceDefinition
+    name: vlclusters.operator.victoriametrics.com
 # - path: patches/webhook_in_operator_vmagents.yaml
 # - path: patches/webhook_in_operator_vmsingles.yaml
 # - path: patches/webhook_in_operator_vmalertmanagers.yaml
@@ -89,6 +94,7 @@ patches:
 #- path: patches/cainjection_in_operator_vmscrapeconfigs.yaml
 #- path: patches/cainjection_in_operator_vlogs.yaml
 #- path: patches/cainjection_in_operator_vlsingles.yaml
+#- path: patches/cainjection_in_operator_vlclusters.yaml
 # +kubebuilder:scaffold:crdkustomizecainjectionpatch
 
 # [WEBHOOK] To enable webhook, uncomment the following section
diff --git a/config/crd/overlay/crd.yaml b/config/crd/overlay/crd.yaml
index 6dd280f8..6ef9875a 100644
--- a/config/crd/overlay/crd.yaml
+++ b/config/crd/overlay/crd.yaml
@@ -1,5 +1,2729 @@
 apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.17.2
+  name: vlclusters.operator.victoriametrics.com
+spec:
+  group: operator.victoriametrics.com
+  names:
+    kind: VLCluster
+    listKind: VLClusterList
+    plural: vlclusters
+    singular: vlcluster
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: replicas of VLInsert
+      jsonPath: .spec.vlinsert.replicaCount
+      name: Insert Count
+      type: string
+    - description: replicas of VLStorage
+      jsonPath: .spec.vlstorage.replicaCount
+      name: Storage Count
+      type: string
+    - description: replicas of VLSelect
+      jsonPath: .spec.vlselect.replicaCount
+      name: Select Count
+      type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    - description: Current status of cluster
+      jsonPath: .status.updateStatus
+      name: Status
+      type: string
+    name: v1
+    schema:
+      openAPIV3Schema:
+        description: VLCluster is the Schema for the vlclusters API
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: VLClusterSpec defines the desired state of VLCluster
+            properties:
+              clusterDomainName:
+                description: |-
+                  ClusterDomainName defines domain name suffix for in-cluster dns addresses
+                  aka .cluster.local
+                  used by vlinsert and vlselect to build vlstorage address
+                type: string
+              clusterVersion:
+                description: |-
+                  ClusterVersion defines default images tag for all components.
+                  it can be overwritten with component specific image.tag value.
+                type: string
+              imagePullSecrets:
+                description: |-
+                  ImagePullSecrets An optional list of references to secrets in the same namespace
+                  to use for pulling images from registries
+                  see https://kubernetes.io/docs/concepts/containers/images/#referring-to-an-imagepullsecrets-on-a-pod
+                items:
+                  description: |-
+                    LocalObjectReference contains enough information to let you locate the
+                    referenced object inside the same namespace.
+                  properties:
+                    name:
+                      default: ""
+                      description: |-
+                        Name of the referent.
+                        This field is effectively required, but due to backwards compatibility is
+                        allowed to be empty. Instances of this type with an empty value here are
+                        almost certainly wrong.
+                        More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                      type: string
+                  type: object
+                  x-kubernetes-map-type: atomic
+                type: array
+              managedMetadata:
+                description: |-
+                  ManagedMetadata defines metadata that will be added to the all objects
+                  created by operator for the given CustomResource
+                properties:
+                  annotations:
+                    additionalProperties:
+                      type: string
+                    description: |-
+                      Annotations is an unstructured key value map stored with a resource that may be
+                      set by external tools to store and retrieve arbitrary metadata. They are not
+                      queryable and should be preserved when modifying objects.
+                      More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations
+                    type: object
+                  labels:
+                    additionalProperties:
+                      type: string
+                    description: |-
+                      Labels Map of string keys and values that can be used to organize and categorize
+                      (scope and select) objects.
+                      More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
+                    type: object
+                type: object
+              paused:
+                description: |-
+                  Paused If set to true all actions on the underlying managed objects are not
+                  going to be performed, except for delete actions.
+                type: boolean
+              requestsLoadBalancer:
+                description: |-
+                  RequestsLoadBalancer configures load-balancing for vlinsert and vlselect requests.
+                  It helps to evenly spread load across pods.
+                  Usually it's not possible with Kubernetes TCP-based services.
+                properties:
+                  disableInsertBalancing:
+                    type: boolean
+                  disableSelectBalancing:
+                    type: boolean
+                  enabled:
+                    type: boolean
+                  spec:
+                    description: |-
+                      VMAuthLoadBalancerSpec defines configuration spec for VMAuth used as load-balancer
+                      for VMCluster component
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                type: object
+              serviceAccountName:
+                description: |-
+                  ServiceAccountName is the name of the ServiceAccount to use to run the
+                  VLSelect, VLInsert and VLStorage Pods.
+                type: string
+              useStrictSecurity:
+                description: |-
+                  UseStrictSecurity enables strict security mode for component
+                  it restricts disk writes access
+                  uses non-root user out of the box
+                  drops not needed security permissions
+                type: boolean
+              vlinsert:
+                description: VLInsert defines vlinsert component configuration at
+                  victoria-logs cluster
+                properties:
+                  affinity:
+                    description: Affinity If specified, the pod's scheduling constraints.
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  configMaps:
+                    description: |-
+                      ConfigMaps is a list of ConfigMaps in the same namespace as the Application
+                      object, which shall be mounted into the Application container
+                      at /etc/vm/configs/CONFIGMAP_NAME folder
+                    items:
+                      type: string
+                    type: array
+                  containers:
+                    description: |-
+                      Containers property allows to inject additions sidecars or to patch existing containers.
+                      It can be useful for proxies, backup, etc.
+                    items:
+                      description: A single application container that you want to
+                        run within a pod.
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  disableAutomountServiceAccountToken:
+                    description: |-
+                      DisableAutomountServiceAccountToken whether to disable serviceAccount auto mount by Kubernetes (available from v0.54.0).
+                      Operator will conditionally create volumes and volumeMounts for containers if it requires k8s API access.
+                      For example, vmagent and vm-config-reloader requires k8s API access.
+                      Operator creates volumes with name: "kube-api-access", which can be used as volumeMount for extraContainers if needed.
+                      And also adds VolumeMounts at /var/run/secrets/kubernetes.io/serviceaccount.
+                    type: boolean
+                  disableSelfServiceScrape:
+                    description: |-
+                      DisableSelfServiceScrape controls creation of VMServiceScrape by operator
+                      for the application.
+                      Has priority over `VM_DISABLESELFSERVICESCRAPECREATION` operator env variable
+                    type: boolean
+                  dnsConfig:
+                    description: |-
+                      Specifies the DNS parameters of a pod.
+                      Parameters specified here will be merged to the generated DNS
+                      configuration based on DNSPolicy.
+                    items:
+                      x-kubernetes-preserve-unknown-fields: true
+                    properties:
+                      nameservers:
+                        description: |-
+                          A list of DNS name server IP addresses.
+                          This will be appended to the base nameservers generated from DNSPolicy.
+                          Duplicated nameservers will be removed.
+                        items:
+                          type: string
+                        type: array
+                        x-kubernetes-list-type: atomic
+                      options:
+                        description: |-
+                          A list of DNS resolver options.
+                          This will be merged with the base options generated from DNSPolicy.
+                          Duplicated entries will be removed. Resolution options given in Options
+                          will override those that appear in the base DNSPolicy.
+                        items:
+                          description: PodDNSConfigOption defines DNS resolver options
+                            of a pod.
+                          properties:
+                            name:
+                              description: |-
+                                Name is this DNS resolver option's name.
+                                Required.
+                              type: string
+                            value:
+                              description: Value is this DNS resolver option's value.
+                              type: string
+                          type: object
+                        type: array
+                        x-kubernetes-list-type: atomic
+                      searches:
+                        description: |-
+                          A list of DNS search domains for host-name lookup.
+                          This will be appended to the base search paths generated from DNSPolicy.
+                          Duplicated search paths will be removed.
+                        items:
+                          type: string
+                        type: array
+                        x-kubernetes-list-type: atomic
+                    type: object
+                  dnsPolicy:
+                    description: DNSPolicy sets DNS policy for the pod
+                    type: string
+                  extraArgs:
+                    additionalProperties:
+                      type: string
+                    description: |-
+                      ExtraArgs that will be passed to the application container
+                      for example remoteWrite.tmpDataPath: /tmp
+                    type: object
+                  extraEnvs:
+                    description: ExtraEnvs that will be passed to the application
+                      container
+                    items:
+                      description: EnvVar represents an environment variable present
+                        in a Container.
+                      properties:
+                        name:
+                          description: Name of the environment variable. Must be a
+                            C_IDENTIFIER.
+                          type: string
+                        value:
+                          description: |-
+                            Variable references $(VAR_NAME) are expanded
+                            using the previously defined environment variables in the container and
+                            any service environment variables. If a variable cannot be resolved,
+                            the reference in the input string will be unchanged. Double $$ are reduced
+                            to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e.
+                            "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)".
+                            Escaped references will never be expanded, regardless of whether the variable
+                            exists or not.
+                            Defaults to "".
+                          type: string
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  extraEnvsFrom:
+                    description: |-
+                      ExtraEnvsFrom defines source of env variables for the application container
+                      could either be secret or configmap
+                    items:
+                      description: EnvFromSource represents the source of a set of
+                        ConfigMaps
+                      properties:
+                        configMapRef:
+                          description: The ConfigMap to select from
+                          properties:
+                            name:
+                              default: ""
+                              description: |-
+                                Name of the referent.
+                                This field is effectively required, but due to backwards compatibility is
+                                allowed to be empty. Instances of this type with an empty value here are
+                                almost certainly wrong.
+                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                              type: string
+                            optional:
+                              description: Specify whether the ConfigMap must be defined
+                              type: boolean
+                          type: object
+                          x-kubernetes-map-type: atomic
+                        prefix:
+                          description: An optional identifier to prepend to each key
+                            in the ConfigMap. Must be a C_IDENTIFIER.
+                          type: string
+                        secretRef:
+                          description: The Secret to select from
+                          properties:
+                            name:
+                              default: ""
+                              description: |-
+                                Name of the referent.
+                                This field is effectively required, but due to backwards compatibility is
+                                allowed to be empty. Instances of this type with an empty value here are
+                                almost certainly wrong.
+                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                              type: string
+                            optional:
+                              description: Specify whether the Secret must be defined
+                              type: boolean
+                          type: object
+                          x-kubernetes-map-type: atomic
+                      type: object
+                    type: array
+                  host_aliases:
+                    description: |-
+                      HostAliasesUnderScore provides mapping for ip and hostname,
+                      that would be propagated to pod,
+                      cannot be used with HostNetwork.
+                      Has Priority over hostAliases field
+                    items:
+                      description: |-
+                        HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
+                        pod's hosts file.
+                      properties:
+                        hostnames:
+                          description: Hostnames for the above IP address.
+                          items:
+                            type: string
+                          type: array
+                          x-kubernetes-list-type: atomic
+                        ip:
+                          description: IP address of the host file entry.
+                          type: string
+                      required:
+                      - ip
+                      type: object
+                    type: array
+                  hostAliases:
+                    description: |-
+                      HostAliases provides mapping for ip and hostname,
+                      that would be propagated to pod,
+                      cannot be used with HostNetwork.
+                    items:
+                      description: |-
+                        HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
+                        pod's hosts file.
+                      properties:
+                        hostnames:
+                          description: Hostnames for the above IP address.
+                          items:
+                            type: string
+                          type: array
+                          x-kubernetes-list-type: atomic
+                        ip:
+                          description: IP address of the host file entry.
+                          type: string
+                      required:
+                      - ip
+                      type: object
+                    type: array
+                  hostNetwork:
+                    description: HostNetwork controls whether the pod may use the
+                      node network namespace
+                    type: boolean
+                  hpa:
+                    description: Configures horizontal pod autoscaling.
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  image:
+                    description: |-
+                      Image - docker image settings
+                      if no specified operator uses default version from operator config
+                    properties:
+                      pullPolicy:
+                        description: PullPolicy describes how to pull docker image
+                        type: string
+                      repository:
+                        description: Repository contains name of docker image + it's
+                          repository if needed
+                        type: string
+                      tag:
+                        description: Tag contains desired docker image version
+                        type: string
+                    type: object
+                  imagePullSecrets:
+                    description: |-
+                      ImagePullSecrets An optional list of references to secrets in the same namespace
+                      to use for pulling images from registries
+                      see https://kubernetes.io/docs/concepts/containers/images/#referring-to-an-imagepullsecrets-on-a-pod
+                    items:
+                      description: |-
+                        LocalObjectReference contains enough information to let you locate the
+                        referenced object inside the same namespace.
+                      properties:
+                        name:
+                          default: ""
+                          description: |-
+                            Name of the referent.
+                            This field is effectively required, but due to backwards compatibility is
+                            allowed to be empty. Instances of this type with an empty value here are
+                            almost certainly wrong.
+                            More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                          type: string
+                      type: object
+                      x-kubernetes-map-type: atomic
+                    type: array
+                  initContainers:
+                    description: |-
+                      InitContainers allows adding initContainers to the pod definition.
+                      Any errors during the execution of an initContainer will lead to a restart of the Pod.
+                      More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
+                    items:
+                      description: A single application container that you want to
+                        run within a pod.
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  livenessProbe:
+                    description: LivenessProbe that will be added CRD pod
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  logFormat:
+                    description: |-
+                      LogFormat for VLSelect to be configured with.
+                      default or json
+                    enum:
+                    - default
+                    - json
+                    type: string
+                  logLevel:
+                    description: LogLevel for VLSelect to be configured with.
+                    enum:
+                    - INFO
+                    - WARN
+                    - ERROR
+                    - FATAL
+                    - PANIC
+                    type: string
+                  minReadySeconds:
+                    description: |-
+                      MinReadySeconds defines a minimum number of seconds to wait before starting update next pod
+                      if previous in healthy state
+                      Has no effect for VLogs and VMSingle
+                    format: int32
+                    type: integer
+                  nodeSelector:
+                    additionalProperties:
+                      type: string
+                    description: NodeSelector Define which Nodes the Pods are scheduled
+                      on.
+                    type: object
+                  paused:
+                    description: |-
+                      Paused If set to true all actions on the underlying managed objects are not
+                      going to be performed, except for delete actions.
+                    type: boolean
+                  podDisruptionBudget:
+                    description: PodDisruptionBudget created by operator
+                    properties:
+                      maxUnavailable:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          An eviction is allowed if at most "maxUnavailable" pods selected by
+                          "selector" are unavailable after the eviction, i.e. even in absence of
+                          the evicted pod. For example, one can prevent all voluntary evictions
+                          by specifying 0. This is a mutually exclusive setting with "minAvailable".
+                        x-kubernetes-int-or-string: true
+                      minAvailable:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          An eviction is allowed if at least "minAvailable" pods selected by
+                          "selector" will still be available after the eviction, i.e. even in the
+                          absence of the evicted pod.  So for example you can prevent all voluntary
+                          evictions by specifying "100%".
+                        x-kubernetes-int-or-string: true
+                      selectorLabels:
+                        additionalProperties:
+                          type: string
+                        description: |-
+                          replaces default labels selector generated by operator
+                          it's useful when you need to create custom budget
+                        type: object
+                    type: object
+                  podMetadata:
+                    description: PodMetadata configures Labels and Annotations which
+                      are propagated to the VLSelect pods.
+                    properties:
+                      annotations:
+                        additionalProperties:
+                          type: string
+                        description: |-
+                          Annotations is an unstructured key value map stored with a resource that may be
+                          set by external tools to store and retrieve arbitrary metadata. They are not
+                          queryable and should be preserved when modifying objects.
+                          More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations
+                        type: object
+                      labels:
+                        additionalProperties:
+                          type: string
+                        description: |-
+                          Labels Map of string keys and values that can be used to organize and categorize
+                          (scope and select) objects. May match selectors of replication controllers
+                          and services.
+                          More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
+                        type: object
+                      name:
+                        description: |-
+                          Name must be unique within a namespace. Is required when creating resources, although
+                          some resources may allow a client to request the generation of an appropriate name
+                          automatically. Name is primarily intended for creation idempotence and configuration
+                          definition.
+                          Cannot be updated.
+                          More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names
+                        type: string
+                    type: object
+                  port:
+                    description: Port listen address
+                    type: string
+                  priorityClassName:
+                    description: PriorityClassName class assigned to the Pods
+                    type: string
+                  readinessGates:
+                    description: ReadinessGates defines pod readiness gates
+                    items:
+                      description: PodReadinessGate contains the reference to a pod
+                        condition
+                      properties:
+                        conditionType:
+                          description: ConditionType refers to a condition in the
+                            pod's condition list with matching type.
+                          type: string
+                      required:
+                      - conditionType
+                      type: object
+                    type: array
+                  readinessProbe:
+                    description: ReadinessProbe that will be added CRD pod
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  replicaCount:
+                    description: ReplicaCount is the expected size of the Application.
+                    format: int32
+                    type: integer
+                  resources:
+                    description: |-
+                      Resources container resource request and limits, https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+                      if not defined default resources from operator config will be used
+                    properties:
+                      claims:
+                        description: |-
+                          Claims lists the names of resources, defined in spec.resourceClaims,
+                          that are used by this container.
+
+                          This is an alpha field and requires enabling the
+                          DynamicResourceAllocation feature gate.
+
+                          This field is immutable. It can only be set for containers.
+                        items:
+                          description: ResourceClaim references one entry in PodSpec.ResourceClaims.
+                          properties:
+                            name:
+                              description: |-
+                                Name must match the name of one entry in pod.spec.resourceClaims of
+                                the Pod where this field is used. It makes that resource available
+                                inside a container.
+                              type: string
+                            request:
+                              description: |-
+                                Request is the name chosen for a request in the referenced claim.
+                                If empty, everything from the claim is made available, otherwise
+                                only the result of this request.
+                              type: string
+                          required:
+                          - name
+                          type: object
+                        type: array
+                        x-kubernetes-list-map-keys:
+                        - name
+                        x-kubernetes-list-type: map
+                      limits:
+                        additionalProperties:
+                          anyOf:
+                          - type: integer
+                          - type: string
+                          pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                          x-kubernetes-int-or-string: true
+                        description: |-
+                          Limits describes the maximum amount of compute resources allowed.
+                          More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+                        type: object
+                      requests:
+                        additionalProperties:
+                          anyOf:
+                          - type: integer
+                          - type: string
+                          pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                          x-kubernetes-int-or-string: true
+                        description: |-
+                          Requests describes the minimum amount of compute resources required.
+                          If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
+                          otherwise to an implementation-defined value. Requests cannot exceed Limits.
+                          More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+                        type: object
+                    type: object
+                  revisionHistoryLimitCount:
+                    description: |-
+                      The number of old ReplicaSets to retain to allow rollback in deployment or
+                      maximum number of revisions that will be maintained in the Deployment revision history.
+                      Has no effect at StatefulSets
+                      Defaults to 10.
+                    format: int32
+                    type: integer
+                  rollingUpdate:
+                    description: RollingUpdate - overrides deployment update params.
+                    properties:
+                      maxSurge:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          The maximum number of pods that can be scheduled above the desired number of
+                          pods.
+                          Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+                          This can not be 0 if MaxUnavailable is 0.
+                          Absolute number is calculated from percentage by rounding up.
+                          Defaults to 25%.
+                          Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when
+                          the rolling update starts, such that the total number of old and new pods do not exceed
+                          130% of desired pods. Once old pods have been killed,
+                          new ReplicaSet can be scaled up further, ensuring that total number of pods running
+                          at any time during the update is at most 130% of desired pods.
+                        x-kubernetes-int-or-string: true
+                      maxUnavailable:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          The maximum number of pods that can be unavailable during the update.
+                          Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+                          Absolute number is calculated from percentage by rounding down.
+                          This can not be 0 if MaxSurge is 0.
+                          Defaults to 25%.
+                          Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods
+                          immediately when the rolling update starts. Once new pods are ready, old ReplicaSet
+                          can be scaled down further, followed by scaling up the new ReplicaSet, ensuring
+                          that the total number of pods available at all times during the update is at
+                          least 70% of desired pods.
+                        x-kubernetes-int-or-string: true
+                    type: object
+                  runtimeClassName:
+                    description: |-
+                      RuntimeClassName - defines runtime class for kubernetes pod.
+                      https://kubernetes.io/docs/concepts/containers/runtime-class/
+                    type: string
+                  schedulerName:
+                    description: SchedulerName - defines kubernetes scheduler name
+                    type: string
+                  secrets:
+                    description: |-
+                      Secrets is a list of Secrets in the same namespace as the Application
+                      object, which shall be mounted into the Application container
+                      at /etc/vm/secrets/SECRET_NAME folder
+                    items:
+                      type: string
+                    type: array
+                  securityContext:
+                    description: |-
+                      SecurityContext holds pod-level security attributes and common container settings.
+                      This defaults to the default PodSecurityContext.
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  serviceScrapeSpec:
+                    description: ServiceScrapeSpec that will be added to vlselect
+                      VMServiceScrape spec
+                    required:
+                    - endpoints
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  serviceSpec:
+                    description: ServiceSpec that will be added to vlselect service
+                      spec
+                    properties:
+                      metadata:
+                        description: EmbeddedObjectMetadata defines objectMeta for
+                          additional service.
+                        properties:
+                          annotations:
+                            additionalProperties:
+                              type: string
+                            description: |-
+                              Annotations is an unstructured key value map stored with a resource that may be
+                              set by external tools to store and retrieve arbitrary metadata. They are not
+                              queryable and should be preserved when modifying objects.
+                              More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations
+                            type: object
+                          labels:
+                            additionalProperties:
+                              type: string
+                            description: |-
+                              Labels Map of string keys and values that can be used to organize and categorize
+                              (scope and select) objects. May match selectors of replication controllers
+                              and services.
+                              More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
+                            type: object
+                          name:
+                            description: |-
+                              Name must be unique within a namespace. Is required when creating resources, although
+                              some resources may allow a client to request the generation of an appropriate name
+                              automatically. Name is primarily intended for creation idempotence and configuration
+                              definition.
+                              Cannot be updated.
+                              More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names
+                            type: string
+                        type: object
+                      spec:
+                        description: |-
+                          ServiceSpec describes the attributes that a user creates on a service.
+                          More info: https://kubernetes.io/docs/concepts/services-networking/service/
+                        type: object
+                        x-kubernetes-preserve-unknown-fields: true
+                      useAsDefault:
+                        description: |-
+                          UseAsDefault applies changes from given service definition to the main object Service
+                          Changing from headless service to clusterIP or loadbalancer may break cross-component communication
+                        type: boolean
+                    required:
+                    - spec
+                    type: object
+                  startupProbe:
+                    description: StartupProbe that will be added to CRD pod
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  syslogSpec:
+                    description: SyslogSpec defines syslog listener configuration
+                    properties:
+                      tcpListeners:
+                        description: TCPListeners defines syslog server TCP listener
+                          configuration
+                        items:
+                          description: SyslogTCPListener defines configuration for
+                            TCP syslog server listen
+                          properties:
+                            compressMethod:
+                              description: |-
+                                CompressMethod for syslog messages
+                                see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#compression
+                              pattern: ^(none|zstd|gzip|deflate)$
+                              type: string
+                            decolorizeFields:
+                              description: |-
+                                DecolorizeFields to remove ANSI color codes across logs
+                                see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#decolorizing-fields
+                              type: string
+                            ingoreFields:
+                              description: |-
+                                IgnoreFields to ignore at logs
+                                see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#dropping-fields
+                              type: string
+                            listenPort:
+                              description: ListenPort defines listen port
+                              format: int32
+                              type: integer
+                            streamFields:
+                              description: |-
+                                StreamFields to use as log stream labels
+                                see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#stream-fields
+                              type: string
+                            tenantID:
+                              description: |-
+                                TenantID for logs ingested in form of accountID:projectID
+                                see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#multiple-configs
+                              type: string
+                            tlsConfig:
+                              description: TLSServerConfig defines VictoriaMetrics
+                                TLS configuration for the application's server
+                              properties:
+                                certFile:
+                                  description: |-
+                                    CertFile defines path to the pre-mounted file with certificate
+                                    mutually exclusive with CertSecretRef
+                                  type: string
+                                certSecret:
+                                  description: |-
+                                    CertSecretRef defines reference for secret with certificate content under given key
+                                    mutually exclusive with CertFile
+                                  properties:
+                                    key:
+                                      description: The key of the secret to select
+                                        from.  Must be a valid secret key.
+                                      type: string
+                                    name:
+                                      default: ""
+                                      description: |-
+                                        Name of the referent.
+                                        This field is effectively required, but due to backwards compatibility is
+                                        allowed to be empty. Instances of this type with an empty value here are
+                                        almost certainly wrong.
+                                        More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                                      type: string
+                                    optional:
+                                      description: Specify whether the Secret or its
+                                        key must be defined
+                                      type: boolean
+                                  required:
+                                  - key
+                                  type: object
+                                  x-kubernetes-map-type: atomic
+                                keyFile:
+                                  description: |-
+                                    KeyFile defines path to the pre-mounted file with certificate key
+                                    mutually exclusive with KeySecretRef
+                                  type: string
+                                keySecret:
+                                  description: |-
+                                    Key defines reference for secret with certificate key content under given key
+                                    mutually exclusive with KeyFile
+                                  properties:
+                                    key:
+                                      description: The key of the secret to select
+                                        from.  Must be a valid secret key.
+                                      type: string
+                                    name:
+                                      default: ""
+                                      description: |-
+                                        Name of the referent.
+                                        This field is effectively required, but due to backwards compatibility is
+                                        allowed to be empty. Instances of this type with an empty value here are
+                                        almost certainly wrong.
+                                        More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                                      type: string
+                                    optional:
+                                      description: Specify whether the Secret or its
+                                        key must be defined
+                                      type: boolean
+                                  required:
+                                  - key
+                                  type: object
+                                  x-kubernetes-map-type: atomic
+                              type: object
+                          required:
+                          - listenPort
+                          type: object
+                        type: array
+                      udpListeners:
+                        description: UDPListeners defines syslog server UDP listener
+                          configuration
+                        items:
+                          description: SyslogUDPListener defines configuration for
+                            UDP syslog server listen
+                          properties:
+                            compressMethod:
+                              description: |-
+                                CompressMethod for syslog messages
+                                see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#compression
+                              pattern: ^(none|zstd|gzip|deflate)$
+                              type: string
+                            decolorizeFields:
+                              description: |-
+                                DecolorizeFields to remove ANSI color codes across logs
+                                see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#decolorizing-fields
+                              type: string
+                            ingoreFields:
+                              description: |-
+                                IgnoreFields to ignore at logs
+                                see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#dropping-fields
+                              type: string
+                            listenPort:
+                              description: ListenPort defines listen port
+                              format: int32
+                              type: integer
+                            streamFields:
+                              description: |-
+                                StreamFields to use as log stream labels
+                                see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#stream-fields
+                              type: string
+                            tenantID:
+                              description: |-
+                                TenantID for logs ingested in form of accountID:projectID
+                                see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#multiple-configs
+                              type: string
+                          required:
+                          - listenPort
+                          type: object
+                        type: array
+                    type: object
+                  terminationGracePeriodSeconds:
+                    description: TerminationGracePeriodSeconds period for container
+                      graceful termination
+                    format: int64
+                    type: integer
+                  tolerations:
+                    description: Tolerations If specified, the pod's tolerations.
+                    items:
+                      description: |-
+                        The pod this Toleration is attached to tolerates any taint that matches
+                        the triple <key,value,effect> using the matching operator <operator>.
+                      properties:
+                        effect:
+                          description: |-
+                            Effect indicates the taint effect to match. Empty means match all taint effects.
+                            When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.
+                          type: string
+                        key:
+                          description: |-
+                            Key is the taint key that the toleration applies to. Empty means match all taint keys.
+                            If the key is empty, operator must be Exists; this combination means to match all values and all keys.
+                          type: string
+                        operator:
+                          description: |-
+                            Operator represents a key's relationship to the value.
+                            Valid operators are Exists and Equal. Defaults to Equal.
+                            Exists is equivalent to wildcard for value, so that a pod can
+                            tolerate all taints of a particular category.
+                          type: string
+                        tolerationSeconds:
+                          description: |-
+                            TolerationSeconds represents the period of time the toleration (which must be
+                            of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,
+                            it is not set, which means tolerate the taint forever (do not evict). Zero and
+                            negative values will be treated as 0 (evict immediately) by the system.
+                          format: int64
+                          type: integer
+                        value:
+                          description: |-
+                            Value is the taint value the toleration matches to.
+                            If the operator is Exists, the value should be empty, otherwise just a regular string.
+                          type: string
+                      type: object
+                    type: array
+                  topologySpreadConstraints:
+                    description: |-
+                      TopologySpreadConstraints embedded kubernetes pod configuration option,
+                      controls how pods are spread across your cluster among failure-domains
+                      such as regions, zones, nodes, and other user-defined topology domains
+                      https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
+                    items:
+                      description: TopologySpreadConstraint specifies how to spread
+                        matching pods among the given topology.
+                      required:
+                      - maxSkew
+                      - topologyKey
+                      - whenUnsatisfiable
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  updateStrategy:
+                    description: UpdateStrategy - overrides default update strategy.
+                    enum:
+                    - Recreate
+                    - RollingUpdate
+                    type: string
+                  useDefaultResources:
+                    description: |-
+                      UseDefaultResources controls resource settings
+                      By default, operator sets built-in resource requirements
+                    type: boolean
+                  useStrictSecurity:
+                    description: |-
+                      UseStrictSecurity enables strict security mode for component
+                      it restricts disk writes access
+                      uses non-root user out of the box
+                      drops not needed security permissions
+                    type: boolean
+                  volumeMounts:
+                    description: |-
+                      VolumeMounts allows configuration of additional VolumeMounts on the output Deployment/StatefulSet definition.
+                      VolumeMounts specified will be appended to other VolumeMounts in the Application container
+                    items:
+                      description: VolumeMount describes a mounting of a Volume within
+                        a container.
+                      properties:
+                        mountPath:
+                          description: |-
+                            Path within the container at which the volume should be mounted.  Must
+                            not contain ':'.
+                          type: string
+                        mountPropagation:
+                          description: |-
+                            mountPropagation determines how mounts are propagated from the host
+                            to container and the other way around.
+                            When not set, MountPropagationNone is used.
+                            This field is beta in 1.10.
+                            When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified
+                            (which defaults to None).
+                          type: string
+                        name:
+                          description: This must match the Name of a Volume.
+                          type: string
+                        readOnly:
+                          description: |-
+                            Mounted read-only if true, read-write otherwise (false or unspecified).
+                            Defaults to false.
+                          type: boolean
+                        recursiveReadOnly:
+                          description: |-
+                            RecursiveReadOnly specifies whether read-only mounts should be handled
+                            recursively.
+
+                            If ReadOnly is false, this field has no meaning and must be unspecified.
+
+                            If ReadOnly is true, and this field is set to Disabled, the mount is not made
+                            recursively read-only.  If this field is set to IfPossible, the mount is made
+                            recursively read-only, if it is supported by the container runtime.  If this
+                            field is set to Enabled, the mount is made recursively read-only if it is
+                            supported by the container runtime, otherwise the pod will not be started and
+                            an error will be generated to indicate the reason.
+
+                            If this field is set to IfPossible or Enabled, MountPropagation must be set to
+                            None (or be unspecified, which defaults to None).
+
+                            If this field is not specified, it is treated as an equivalent of Disabled.
+                          type: string
+                        subPath:
+                          description: |-
+                            Path within the volume from which the container's volume should be mounted.
+                            Defaults to "" (volume's root).
+                          type: string
+                        subPathExpr:
+                          description: |-
+                            Expanded path within the volume from which the container's volume should be mounted.
+                            Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment.
+                            Defaults to "" (volume's root).
+                            SubPathExpr and SubPath are mutually exclusive.
+                          type: string
+                      required:
+                      - mountPath
+                      - name
+                      type: object
+                    type: array
+                  volumes:
+                    description: |-
+                      Volumes allows configuration of additional volumes on the output Deployment/StatefulSet definition.
+                      Volumes specified will be appended to other volumes that are generated.
+                      / +optional
+                    items:
+                      description: Volume represents a named volume in a pod that
+                        may be accessed by any container in the pod.
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                type: object
+              vlselect:
+                description: VLSelect defines vlselect component configuration at
+                  victoria-logs cluster
+                properties:
+                  affinity:
+                    description: Affinity If specified, the pod's scheduling constraints.
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  configMaps:
+                    description: |-
+                      ConfigMaps is a list of ConfigMaps in the same namespace as the Application
+                      object, which shall be mounted into the Application container
+                      at /etc/vm/configs/CONFIGMAP_NAME folder
+                    items:
+                      type: string
+                    type: array
+                  containers:
+                    description: |-
+                      Containers property allows to inject additions sidecars or to patch existing containers.
+                      It can be useful for proxies, backup, etc.
+                    items:
+                      description: A single application container that you want to
+                        run within a pod.
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  disableAutomountServiceAccountToken:
+                    description: |-
+                      DisableAutomountServiceAccountToken whether to disable serviceAccount auto mount by Kubernetes (available from v0.54.0).
+                      Operator will conditionally create volumes and volumeMounts for containers if it requires k8s API access.
+                      For example, vmagent and vm-config-reloader requires k8s API access.
+                      Operator creates volumes with name: "kube-api-access", which can be used as volumeMount for extraContainers if needed.
+                      And also adds VolumeMounts at /var/run/secrets/kubernetes.io/serviceaccount.
+                    type: boolean
+                  disableSelfServiceScrape:
+                    description: |-
+                      DisableSelfServiceScrape controls creation of VMServiceScrape by operator
+                      for the application.
+                      Has priority over `VM_DISABLESELFSERVICESCRAPECREATION` operator env variable
+                    type: boolean
+                  dnsConfig:
+                    description: |-
+                      Specifies the DNS parameters of a pod.
+                      Parameters specified here will be merged to the generated DNS
+                      configuration based on DNSPolicy.
+                    items:
+                      x-kubernetes-preserve-unknown-fields: true
+                    properties:
+                      nameservers:
+                        description: |-
+                          A list of DNS name server IP addresses.
+                          This will be appended to the base nameservers generated from DNSPolicy.
+                          Duplicated nameservers will be removed.
+                        items:
+                          type: string
+                        type: array
+                        x-kubernetes-list-type: atomic
+                      options:
+                        description: |-
+                          A list of DNS resolver options.
+                          This will be merged with the base options generated from DNSPolicy.
+                          Duplicated entries will be removed. Resolution options given in Options
+                          will override those that appear in the base DNSPolicy.
+                        items:
+                          description: PodDNSConfigOption defines DNS resolver options
+                            of a pod.
+                          properties:
+                            name:
+                              description: |-
+                                Name is this DNS resolver option's name.
+                                Required.
+                              type: string
+                            value:
+                              description: Value is this DNS resolver option's value.
+                              type: string
+                          type: object
+                        type: array
+                        x-kubernetes-list-type: atomic
+                      searches:
+                        description: |-
+                          A list of DNS search domains for host-name lookup.
+                          This will be appended to the base search paths generated from DNSPolicy.
+                          Duplicated search paths will be removed.
+                        items:
+                          type: string
+                        type: array
+                        x-kubernetes-list-type: atomic
+                    type: object
+                  dnsPolicy:
+                    description: DNSPolicy sets DNS policy for the pod
+                    type: string
+                  extraArgs:
+                    additionalProperties:
+                      type: string
+                    description: |-
+                      ExtraArgs that will be passed to the application container
+                      for example remoteWrite.tmpDataPath: /tmp
+                    type: object
+                  extraEnvs:
+                    description: ExtraEnvs that will be passed to the application
+                      container
+                    items:
+                      description: EnvVar represents an environment variable present
+                        in a Container.
+                      properties:
+                        name:
+                          description: Name of the environment variable. Must be a
+                            C_IDENTIFIER.
+                          type: string
+                        value:
+                          description: |-
+                            Variable references $(VAR_NAME) are expanded
+                            using the previously defined environment variables in the container and
+                            any service environment variables. If a variable cannot be resolved,
+                            the reference in the input string will be unchanged. Double $$ are reduced
+                            to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e.
+                            "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)".
+                            Escaped references will never be expanded, regardless of whether the variable
+                            exists or not.
+                            Defaults to "".
+                          type: string
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  extraEnvsFrom:
+                    description: |-
+                      ExtraEnvsFrom defines source of env variables for the application container
+                      could either be secret or configmap
+                    items:
+                      description: EnvFromSource represents the source of a set of
+                        ConfigMaps
+                      properties:
+                        configMapRef:
+                          description: The ConfigMap to select from
+                          properties:
+                            name:
+                              default: ""
+                              description: |-
+                                Name of the referent.
+                                This field is effectively required, but due to backwards compatibility is
+                                allowed to be empty. Instances of this type with an empty value here are
+                                almost certainly wrong.
+                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                              type: string
+                            optional:
+                              description: Specify whether the ConfigMap must be defined
+                              type: boolean
+                          type: object
+                          x-kubernetes-map-type: atomic
+                        prefix:
+                          description: An optional identifier to prepend to each key
+                            in the ConfigMap. Must be a C_IDENTIFIER.
+                          type: string
+                        secretRef:
+                          description: The Secret to select from
+                          properties:
+                            name:
+                              default: ""
+                              description: |-
+                                Name of the referent.
+                                This field is effectively required, but due to backwards compatibility is
+                                allowed to be empty. Instances of this type with an empty value here are
+                                almost certainly wrong.
+                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                              type: string
+                            optional:
+                              description: Specify whether the Secret must be defined
+                              type: boolean
+                          type: object
+                          x-kubernetes-map-type: atomic
+                      type: object
+                    type: array
+                  host_aliases:
+                    description: |-
+                      HostAliasesUnderScore provides mapping for ip and hostname,
+                      that would be propagated to pod,
+                      cannot be used with HostNetwork.
+                      Has Priority over hostAliases field
+                    items:
+                      description: |-
+                        HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
+                        pod's hosts file.
+                      properties:
+                        hostnames:
+                          description: Hostnames for the above IP address.
+                          items:
+                            type: string
+                          type: array
+                          x-kubernetes-list-type: atomic
+                        ip:
+                          description: IP address of the host file entry.
+                          type: string
+                      required:
+                      - ip
+                      type: object
+                    type: array
+                  hostAliases:
+                    description: |-
+                      HostAliases provides mapping for ip and hostname,
+                      that would be propagated to pod,
+                      cannot be used with HostNetwork.
+                    items:
+                      description: |-
+                        HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
+                        pod's hosts file.
+                      properties:
+                        hostnames:
+                          description: Hostnames for the above IP address.
+                          items:
+                            type: string
+                          type: array
+                          x-kubernetes-list-type: atomic
+                        ip:
+                          description: IP address of the host file entry.
+                          type: string
+                      required:
+                      - ip
+                      type: object
+                    type: array
+                  hostNetwork:
+                    description: HostNetwork controls whether the pod may use the
+                      node network namespace
+                    type: boolean
+                  hpa:
+                    description: Configures horizontal pod autoscaling.
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  image:
+                    description: |-
+                      Image - docker image settings
+                      if no specified operator uses default version from operator config
+                    properties:
+                      pullPolicy:
+                        description: PullPolicy describes how to pull docker image
+                        type: string
+                      repository:
+                        description: Repository contains name of docker image + it's
+                          repository if needed
+                        type: string
+                      tag:
+                        description: Tag contains desired docker image version
+                        type: string
+                    type: object
+                  imagePullSecrets:
+                    description: |-
+                      ImagePullSecrets An optional list of references to secrets in the same namespace
+                      to use for pulling images from registries
+                      see https://kubernetes.io/docs/concepts/containers/images/#referring-to-an-imagepullsecrets-on-a-pod
+                    items:
+                      description: |-
+                        LocalObjectReference contains enough information to let you locate the
+                        referenced object inside the same namespace.
+                      properties:
+                        name:
+                          default: ""
+                          description: |-
+                            Name of the referent.
+                            This field is effectively required, but due to backwards compatibility is
+                            allowed to be empty. Instances of this type with an empty value here are
+                            almost certainly wrong.
+                            More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                          type: string
+                      type: object
+                      x-kubernetes-map-type: atomic
+                    type: array
+                  initContainers:
+                    description: |-
+                      InitContainers allows adding initContainers to the pod definition.
+                      Any errors during the execution of an initContainer will lead to a restart of the Pod.
+                      More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
+                    items:
+                      description: A single application container that you want to
+                        run within a pod.
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  livenessProbe:
+                    description: LivenessProbe that will be added CRD pod
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  logFormat:
+                    description: |-
+                      LogFormat for VLSelect to be configured with.
+                      default or json
+                    enum:
+                    - default
+                    - json
+                    type: string
+                  logLevel:
+                    description: LogLevel for VLSelect to be configured with.
+                    enum:
+                    - INFO
+                    - WARN
+                    - ERROR
+                    - FATAL
+                    - PANIC
+                    type: string
+                  minReadySeconds:
+                    description: |-
+                      MinReadySeconds defines a minimum number of seconds to wait before starting update next pod
+                      if previous in healthy state
+                      Has no effect for VLogs and VMSingle
+                    format: int32
+                    type: integer
+                  nodeSelector:
+                    additionalProperties:
+                      type: string
+                    description: NodeSelector Define which Nodes the Pods are scheduled
+                      on.
+                    type: object
+                  paused:
+                    description: |-
+                      Paused If set to true all actions on the underlying managed objects are not
+                      going to be performed, except for delete actions.
+                    type: boolean
+                  podDisruptionBudget:
+                    description: PodDisruptionBudget created by operator
+                    properties:
+                      maxUnavailable:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          An eviction is allowed if at most "maxUnavailable" pods selected by
+                          "selector" are unavailable after the eviction, i.e. even in absence of
+                          the evicted pod. For example, one can prevent all voluntary evictions
+                          by specifying 0. This is a mutually exclusive setting with "minAvailable".
+                        x-kubernetes-int-or-string: true
+                      minAvailable:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          An eviction is allowed if at least "minAvailable" pods selected by
+                          "selector" will still be available after the eviction, i.e. even in the
+                          absence of the evicted pod.  So for example you can prevent all voluntary
+                          evictions by specifying "100%".
+                        x-kubernetes-int-or-string: true
+                      selectorLabels:
+                        additionalProperties:
+                          type: string
+                        description: |-
+                          replaces default labels selector generated by operator
+                          it's useful when you need to create custom budget
+                        type: object
+                    type: object
+                  podMetadata:
+                    description: PodMetadata configures Labels and Annotations which
+                      are propagated to the VLSelect pods.
+                    properties:
+                      annotations:
+                        additionalProperties:
+                          type: string
+                        description: |-
+                          Annotations is an unstructured key value map stored with a resource that may be
+                          set by external tools to store and retrieve arbitrary metadata. They are not
+                          queryable and should be preserved when modifying objects.
+                          More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations
+                        type: object
+                      labels:
+                        additionalProperties:
+                          type: string
+                        description: |-
+                          Labels Map of string keys and values that can be used to organize and categorize
+                          (scope and select) objects. May match selectors of replication controllers
+                          and services.
+                          More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
+                        type: object
+                      name:
+                        description: |-
+                          Name must be unique within a namespace. Is required when creating resources, although
+                          some resources may allow a client to request the generation of an appropriate name
+                          automatically. Name is primarily intended for creation idempotence and configuration
+                          definition.
+                          Cannot be updated.
+                          More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names
+                        type: string
+                    type: object
+                  port:
+                    description: Port listen address
+                    type: string
+                  priorityClassName:
+                    description: PriorityClassName class assigned to the Pods
+                    type: string
+                  readinessGates:
+                    description: ReadinessGates defines pod readiness gates
+                    items:
+                      description: PodReadinessGate contains the reference to a pod
+                        condition
+                      properties:
+                        conditionType:
+                          description: ConditionType refers to a condition in the
+                            pod's condition list with matching type.
+                          type: string
+                      required:
+                      - conditionType
+                      type: object
+                    type: array
+                  readinessProbe:
+                    description: ReadinessProbe that will be added CRD pod
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  replicaCount:
+                    description: ReplicaCount is the expected size of the Application.
+                    format: int32
+                    type: integer
+                  resources:
+                    description: |-
+                      Resources container resource request and limits, https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+                      if not defined default resources from operator config will be used
+                    properties:
+                      claims:
+                        description: |-
+                          Claims lists the names of resources, defined in spec.resourceClaims,
+                          that are used by this container.
+
+                          This is an alpha field and requires enabling the
+                          DynamicResourceAllocation feature gate.
+
+                          This field is immutable. It can only be set for containers.
+                        items:
+                          description: ResourceClaim references one entry in PodSpec.ResourceClaims.
+                          properties:
+                            name:
+                              description: |-
+                                Name must match the name of one entry in pod.spec.resourceClaims of
+                                the Pod where this field is used. It makes that resource available
+                                inside a container.
+                              type: string
+                            request:
+                              description: |-
+                                Request is the name chosen for a request in the referenced claim.
+                                If empty, everything from the claim is made available, otherwise
+                                only the result of this request.
+                              type: string
+                          required:
+                          - name
+                          type: object
+                        type: array
+                        x-kubernetes-list-map-keys:
+                        - name
+                        x-kubernetes-list-type: map
+                      limits:
+                        additionalProperties:
+                          anyOf:
+                          - type: integer
+                          - type: string
+                          pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                          x-kubernetes-int-or-string: true
+                        description: |-
+                          Limits describes the maximum amount of compute resources allowed.
+                          More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+                        type: object
+                      requests:
+                        additionalProperties:
+                          anyOf:
+                          - type: integer
+                          - type: string
+                          pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                          x-kubernetes-int-or-string: true
+                        description: |-
+                          Requests describes the minimum amount of compute resources required.
+                          If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
+                          otherwise to an implementation-defined value. Requests cannot exceed Limits.
+                          More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+                        type: object
+                    type: object
+                  revisionHistoryLimitCount:
+                    description: |-
+                      The number of old ReplicaSets to retain to allow rollback in deployment or
+                      maximum number of revisions that will be maintained in the Deployment revision history.
+                      Has no effect at StatefulSets
+                      Defaults to 10.
+                    format: int32
+                    type: integer
+                  rollingUpdate:
+                    description: RollingUpdate - overrides deployment update params.
+                    properties:
+                      maxSurge:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          The maximum number of pods that can be scheduled above the desired number of
+                          pods.
+                          Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+                          This can not be 0 if MaxUnavailable is 0.
+                          Absolute number is calculated from percentage by rounding up.
+                          Defaults to 25%.
+                          Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when
+                          the rolling update starts, such that the total number of old and new pods do not exceed
+                          130% of desired pods. Once old pods have been killed,
+                          new ReplicaSet can be scaled up further, ensuring that total number of pods running
+                          at any time during the update is at most 130% of desired pods.
+                        x-kubernetes-int-or-string: true
+                      maxUnavailable:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          The maximum number of pods that can be unavailable during the update.
+                          Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
+                          Absolute number is calculated from percentage by rounding down.
+                          This can not be 0 if MaxSurge is 0.
+                          Defaults to 25%.
+                          Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods
+                          immediately when the rolling update starts. Once new pods are ready, old ReplicaSet
+                          can be scaled down further, followed by scaling up the new ReplicaSet, ensuring
+                          that the total number of pods available at all times during the update is at
+                          least 70% of desired pods.
+                        x-kubernetes-int-or-string: true
+                    type: object
+                  runtimeClassName:
+                    description: |-
+                      RuntimeClassName - defines runtime class for kubernetes pod.
+                      https://kubernetes.io/docs/concepts/containers/runtime-class/
+                    type: string
+                  schedulerName:
+                    description: SchedulerName - defines kubernetes scheduler name
+                    type: string
+                  secrets:
+                    description: |-
+                      Secrets is a list of Secrets in the same namespace as the Application
+                      object, which shall be mounted into the Application container
+                      at /etc/vm/secrets/SECRET_NAME folder
+                    items:
+                      type: string
+                    type: array
+                  securityContext:
+                    description: |-
+                      SecurityContext holds pod-level security attributes and common container settings.
+                      This defaults to the default PodSecurityContext.
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  serviceScrapeSpec:
+                    description: ServiceScrapeSpec that will be added to vlselect
+                      VMServiceScrape spec
+                    required:
+                    - endpoints
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  serviceSpec:
+                    description: ServiceSpec that will be added to vlselect service
+                      spec
+                    properties:
+                      metadata:
+                        description: EmbeddedObjectMetadata defines objectMeta for
+                          additional service.
+                        properties:
+                          annotations:
+                            additionalProperties:
+                              type: string
+                            description: |-
+                              Annotations is an unstructured key value map stored with a resource that may be
+                              set by external tools to store and retrieve arbitrary metadata. They are not
+                              queryable and should be preserved when modifying objects.
+                              More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations
+                            type: object
+                          labels:
+                            additionalProperties:
+                              type: string
+                            description: |-
+                              Labels Map of string keys and values that can be used to organize and categorize
+                              (scope and select) objects. May match selectors of replication controllers
+                              and services.
+                              More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
+                            type: object
+                          name:
+                            description: |-
+                              Name must be unique within a namespace. Is required when creating resources, although
+                              some resources may allow a client to request the generation of an appropriate name
+                              automatically. Name is primarily intended for creation idempotence and configuration
+                              definition.
+                              Cannot be updated.
+                              More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names
+                            type: string
+                        type: object
+                      spec:
+                        description: |-
+                          ServiceSpec describes the attributes that a user creates on a service.
+                          More info: https://kubernetes.io/docs/concepts/services-networking/service/
+                        type: object
+                        x-kubernetes-preserve-unknown-fields: true
+                      useAsDefault:
+                        description: |-
+                          UseAsDefault applies changes from given service definition to the main object Service
+                          Changing from headless service to clusterIP or loadbalancer may break cross-component communication
+                        type: boolean
+                    required:
+                    - spec
+                    type: object
+                  startupProbe:
+                    description: StartupProbe that will be added to CRD pod
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  terminationGracePeriodSeconds:
+                    description: TerminationGracePeriodSeconds period for container
+                      graceful termination
+                    format: int64
+                    type: integer
+                  tolerations:
+                    description: Tolerations If specified, the pod's tolerations.
+                    items:
+                      description: |-
+                        The pod this Toleration is attached to tolerates any taint that matches
+                        the triple <key,value,effect> using the matching operator <operator>.
+                      properties:
+                        effect:
+                          description: |-
+                            Effect indicates the taint effect to match. Empty means match all taint effects.
+                            When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.
+                          type: string
+                        key:
+                          description: |-
+                            Key is the taint key that the toleration applies to. Empty means match all taint keys.
+                            If the key is empty, operator must be Exists; this combination means to match all values and all keys.
+                          type: string
+                        operator:
+                          description: |-
+                            Operator represents a key's relationship to the value.
+                            Valid operators are Exists and Equal. Defaults to Equal.
+                            Exists is equivalent to wildcard for value, so that a pod can
+                            tolerate all taints of a particular category.
+                          type: string
+                        tolerationSeconds:
+                          description: |-
+                            TolerationSeconds represents the period of time the toleration (which must be
+                            of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,
+                            it is not set, which means tolerate the taint forever (do not evict). Zero and
+                            negative values will be treated as 0 (evict immediately) by the system.
+                          format: int64
+                          type: integer
+                        value:
+                          description: |-
+                            Value is the taint value the toleration matches to.
+                            If the operator is Exists, the value should be empty, otherwise just a regular string.
+                          type: string
+                      type: object
+                    type: array
+                  topologySpreadConstraints:
+                    description: |-
+                      TopologySpreadConstraints embedded kubernetes pod configuration option,
+                      controls how pods are spread across your cluster among failure-domains
+                      such as regions, zones, nodes, and other user-defined topology domains
+                      https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
+                    items:
+                      description: TopologySpreadConstraint specifies how to spread
+                        matching pods among the given topology.
+                      required:
+                      - maxSkew
+                      - topologyKey
+                      - whenUnsatisfiable
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  updateStrategy:
+                    description: UpdateStrategy - overrides default update strategy.
+                    enum:
+                    - Recreate
+                    - RollingUpdate
+                    type: string
+                  useDefaultResources:
+                    description: |-
+                      UseDefaultResources controls resource settings
+                      By default, operator sets built-in resource requirements
+                    type: boolean
+                  useStrictSecurity:
+                    description: |-
+                      UseStrictSecurity enables strict security mode for component
+                      it restricts disk writes access
+                      uses non-root user out of the box
+                      drops not needed security permissions
+                    type: boolean
+                  volumeMounts:
+                    description: |-
+                      VolumeMounts allows configuration of additional VolumeMounts on the output Deployment/StatefulSet definition.
+                      VolumeMounts specified will be appended to other VolumeMounts in the Application container
+                    items:
+                      description: VolumeMount describes a mounting of a Volume within
+                        a container.
+                      properties:
+                        mountPath:
+                          description: |-
+                            Path within the container at which the volume should be mounted.  Must
+                            not contain ':'.
+                          type: string
+                        mountPropagation:
+                          description: |-
+                            mountPropagation determines how mounts are propagated from the host
+                            to container and the other way around.
+                            When not set, MountPropagationNone is used.
+                            This field is beta in 1.10.
+                            When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified
+                            (which defaults to None).
+                          type: string
+                        name:
+                          description: This must match the Name of a Volume.
+                          type: string
+                        readOnly:
+                          description: |-
+                            Mounted read-only if true, read-write otherwise (false or unspecified).
+                            Defaults to false.
+                          type: boolean
+                        recursiveReadOnly:
+                          description: |-
+                            RecursiveReadOnly specifies whether read-only mounts should be handled
+                            recursively.
+
+                            If ReadOnly is false, this field has no meaning and must be unspecified.
+
+                            If ReadOnly is true, and this field is set to Disabled, the mount is not made
+                            recursively read-only.  If this field is set to IfPossible, the mount is made
+                            recursively read-only, if it is supported by the container runtime.  If this
+                            field is set to Enabled, the mount is made recursively read-only if it is
+                            supported by the container runtime, otherwise the pod will not be started and
+                            an error will be generated to indicate the reason.
+
+                            If this field is set to IfPossible or Enabled, MountPropagation must be set to
+                            None (or be unspecified, which defaults to None).
+
+                            If this field is not specified, it is treated as an equivalent of Disabled.
+                          type: string
+                        subPath:
+                          description: |-
+                            Path within the volume from which the container's volume should be mounted.
+                            Defaults to "" (volume's root).
+                          type: string
+                        subPathExpr:
+                          description: |-
+                            Expanded path within the volume from which the container's volume should be mounted.
+                            Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment.
+                            Defaults to "" (volume's root).
+                            SubPathExpr and SubPath are mutually exclusive.
+                          type: string
+                      required:
+                      - mountPath
+                      - name
+                      type: object
+                    type: array
+                  volumes:
+                    description: |-
+                      Volumes allows configuration of additional volumes on the output Deployment/StatefulSet definition.
+                      Volumes specified will be appended to other volumes that are generated.
+                      / +optional
+                    items:
+                      description: Volume represents a named volume in a pod that
+                        may be accessed by any container in the pod.
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                type: object
+              vlstorage:
+                description: VLStorage defines vlstorage component configuration at
+                  victoria-logs cluster
+                properties:
+                  affinity:
+                    description: Affinity If specified, the pod's scheduling constraints.
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  claimTemplates:
+                    description: ClaimTemplates allows adding additional VolumeClaimTemplates
+                      for StatefulSet
+                    items:
+                      description: PersistentVolumeClaim is a user's request for and
+                        claim to a persistent volume
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  configMaps:
+                    description: |-
+                      ConfigMaps is a list of ConfigMaps in the same namespace as the Application
+                      object, which shall be mounted into the Application container
+                      at /etc/vm/configs/CONFIGMAP_NAME folder
+                    items:
+                      type: string
+                    type: array
+                  containers:
+                    description: |-
+                      Containers property allows to inject additions sidecars or to patch existing containers.
+                      It can be useful for proxies, backup, etc.
+                    items:
+                      description: A single application container that you want to
+                        run within a pod.
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  disableAutomountServiceAccountToken:
+                    description: |-
+                      DisableAutomountServiceAccountToken whether to disable serviceAccount auto mount by Kubernetes (available from v0.54.0).
+                      Operator will conditionally create volumes and volumeMounts for containers if it requires k8s API access.
+                      For example, vmagent and vm-config-reloader requires k8s API access.
+                      Operator creates volumes with name: "kube-api-access", which can be used as volumeMount for extraContainers if needed.
+                      And also adds VolumeMounts at /var/run/secrets/kubernetes.io/serviceaccount.
+                    type: boolean
+                  disableSelfServiceScrape:
+                    description: |-
+                      DisableSelfServiceScrape controls creation of VMServiceScrape by operator
+                      for the application.
+                      Has priority over `VM_DISABLESELFSERVICESCRAPECREATION` operator env variable
+                    type: boolean
+                  dnsConfig:
+                    description: |-
+                      Specifies the DNS parameters of a pod.
+                      Parameters specified here will be merged to the generated DNS
+                      configuration based on DNSPolicy.
+                    items:
+                      x-kubernetes-preserve-unknown-fields: true
+                    properties:
+                      nameservers:
+                        description: |-
+                          A list of DNS name server IP addresses.
+                          This will be appended to the base nameservers generated from DNSPolicy.
+                          Duplicated nameservers will be removed.
+                        items:
+                          type: string
+                        type: array
+                        x-kubernetes-list-type: atomic
+                      options:
+                        description: |-
+                          A list of DNS resolver options.
+                          This will be merged with the base options generated from DNSPolicy.
+                          Duplicated entries will be removed. Resolution options given in Options
+                          will override those that appear in the base DNSPolicy.
+                        items:
+                          description: PodDNSConfigOption defines DNS resolver options
+                            of a pod.
+                          properties:
+                            name:
+                              description: |-
+                                Name is this DNS resolver option's name.
+                                Required.
+                              type: string
+                            value:
+                              description: Value is this DNS resolver option's value.
+                              type: string
+                          type: object
+                        type: array
+                        x-kubernetes-list-type: atomic
+                      searches:
+                        description: |-
+                          A list of DNS search domains for host-name lookup.
+                          This will be appended to the base search paths generated from DNSPolicy.
+                          Duplicated search paths will be removed.
+                        items:
+                          type: string
+                        type: array
+                        x-kubernetes-list-type: atomic
+                    type: object
+                  dnsPolicy:
+                    description: DNSPolicy sets DNS policy for the pod
+                    type: string
+                  extraArgs:
+                    additionalProperties:
+                      type: string
+                    description: |-
+                      ExtraArgs that will be passed to the application container
+                      for example remoteWrite.tmpDataPath: /tmp
+                    type: object
+                  extraEnvs:
+                    description: ExtraEnvs that will be passed to the application
+                      container
+                    items:
+                      description: EnvVar represents an environment variable present
+                        in a Container.
+                      properties:
+                        name:
+                          description: Name of the environment variable. Must be a
+                            C_IDENTIFIER.
+                          type: string
+                        value:
+                          description: |-
+                            Variable references $(VAR_NAME) are expanded
+                            using the previously defined environment variables in the container and
+                            any service environment variables. If a variable cannot be resolved,
+                            the reference in the input string will be unchanged. Double $$ are reduced
+                            to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e.
+                            "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)".
+                            Escaped references will never be expanded, regardless of whether the variable
+                            exists or not.
+                            Defaults to "".
+                          type: string
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  extraEnvsFrom:
+                    description: |-
+                      ExtraEnvsFrom defines source of env variables for the application container
+                      could either be secret or configmap
+                    items:
+                      description: EnvFromSource represents the source of a set of
+                        ConfigMaps
+                      properties:
+                        configMapRef:
+                          description: The ConfigMap to select from
+                          properties:
+                            name:
+                              default: ""
+                              description: |-
+                                Name of the referent.
+                                This field is effectively required, but due to backwards compatibility is
+                                allowed to be empty. Instances of this type with an empty value here are
+                                almost certainly wrong.
+                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                              type: string
+                            optional:
+                              description: Specify whether the ConfigMap must be defined
+                              type: boolean
+                          type: object
+                          x-kubernetes-map-type: atomic
+                        prefix:
+                          description: An optional identifier to prepend to each key
+                            in the ConfigMap. Must be a C_IDENTIFIER.
+                          type: string
+                        secretRef:
+                          description: The Secret to select from
+                          properties:
+                            name:
+                              default: ""
+                              description: |-
+                                Name of the referent.
+                                This field is effectively required, but due to backwards compatibility is
+                                allowed to be empty. Instances of this type with an empty value here are
+                                almost certainly wrong.
+                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                              type: string
+                            optional:
+                              description: Specify whether the Secret must be defined
+                              type: boolean
+                          type: object
+                          x-kubernetes-map-type: atomic
+                      type: object
+                    type: array
+                  futureRetention:
+                    description: |-
+                      FutureRetention for the stored logs
+                      Log entries with timestamps bigger than now+futureRetention are rejected during data ingestion; see https://docs.victoriametrics.com/victorialogs/#retention
+                    pattern: ^[0-9]+(h|d|w|y)?$
+                    type: string
+                  host_aliases:
+                    description: |-
+                      HostAliasesUnderScore provides mapping for ip and hostname,
+                      that would be propagated to pod,
+                      cannot be used with HostNetwork.
+                      Has Priority over hostAliases field
+                    items:
+                      description: |-
+                        HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
+                        pod's hosts file.
+                      properties:
+                        hostnames:
+                          description: Hostnames for the above IP address.
+                          items:
+                            type: string
+                          type: array
+                          x-kubernetes-list-type: atomic
+                        ip:
+                          description: IP address of the host file entry.
+                          type: string
+                      required:
+                      - ip
+                      type: object
+                    type: array
+                  hostAliases:
+                    description: |-
+                      HostAliases provides mapping for ip and hostname,
+                      that would be propagated to pod,
+                      cannot be used with HostNetwork.
+                    items:
+                      description: |-
+                        HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the
+                        pod's hosts file.
+                      properties:
+                        hostnames:
+                          description: Hostnames for the above IP address.
+                          items:
+                            type: string
+                          type: array
+                          x-kubernetes-list-type: atomic
+                        ip:
+                          description: IP address of the host file entry.
+                          type: string
+                      required:
+                      - ip
+                      type: object
+                    type: array
+                  hostNetwork:
+                    description: HostNetwork controls whether the pod may use the
+                      node network namespace
+                    type: boolean
+                  image:
+                    description: |-
+                      Image - docker image settings
+                      if no specified operator uses default version from operator config
+                    properties:
+                      pullPolicy:
+                        description: PullPolicy describes how to pull docker image
+                        type: string
+                      repository:
+                        description: Repository contains name of docker image + it's
+                          repository if needed
+                        type: string
+                      tag:
+                        description: Tag contains desired docker image version
+                        type: string
+                    type: object
+                  imagePullSecrets:
+                    description: |-
+                      ImagePullSecrets An optional list of references to secrets in the same namespace
+                      to use for pulling images from registries
+                      see https://kubernetes.io/docs/concepts/containers/images/#referring-to-an-imagepullsecrets-on-a-pod
+                    items:
+                      description: |-
+                        LocalObjectReference contains enough information to let you locate the
+                        referenced object inside the same namespace.
+                      properties:
+                        name:
+                          default: ""
+                          description: |-
+                            Name of the referent.
+                            This field is effectively required, but due to backwards compatibility is
+                            allowed to be empty. Instances of this type with an empty value here are
+                            almost certainly wrong.
+                            More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                          type: string
+                      type: object
+                      x-kubernetes-map-type: atomic
+                    type: array
+                  initContainers:
+                    description: |-
+                      InitContainers allows adding initContainers to the pod definition.
+                      Any errors during the execution of an initContainer will lead to a restart of the Pod.
+                      More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
+                    items:
+                      description: A single application container that you want to
+                        run within a pod.
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  livenessProbe:
+                    description: LivenessProbe that will be added CRD pod
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  logFormat:
+                    description: |-
+                      LogFormat for VLStorage to be configured with.
+                      default or json
+                    enum:
+                    - default
+                    - json
+                    type: string
+                  logIngestedRows:
+                    description: Whether to log all the ingested log entries; this
+                      can be useful for debugging of data ingestion; see https://docs.victoriametrics.com/victorialogs/data-ingestion/
+                    type: boolean
+                  logLevel:
+                    description: LogLevel for VLStorage to be configured with.
+                    enum:
+                    - INFO
+                    - WARN
+                    - ERROR
+                    - FATAL
+                    - PANIC
+                    type: string
+                  logNewStreams:
+                    description: LogNewStreams Whether to log creation of new streams;
+                      this can be useful for debugging of high cardinality issues
+                      with log streams; see https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields
+                    type: boolean
+                  maintenanceInsertNodeIDs:
+                    description: |-
+                      MaintenanceInsertNodeIDs - excludes given node ids from insert requests routing, must contain pod suffixes - for pod-0, id will be 0 and etc.
+                      lets say, you have pod-0, pod-1, pod-2, pod-3. to exclude pod-0 and pod-3 from insert routing, define nodeIDs: [0,3].
+                      Useful at storage expanding, when you want to rebalance some data at cluster.
+                    items:
+                      format: int32
+                      type: integer
+                    type: array
+                  maintenanceSelectNodeIDs:
+                    description: MaintenanceInsertNodeIDs - excludes given node ids
+                      from select requests routing, must contain pod suffixes - for
+                      pod-0, id will be 0 and etc.
+                    items:
+                      format: int32
+                      type: integer
+                    type: array
+                  minReadySeconds:
+                    description: |-
+                      MinReadySeconds defines a minimum number of seconds to wait before starting update next pod
+                      if previous in healthy state
+                      Has no effect for VLogs and VMSingle
+                    format: int32
+                    type: integer
+                  nodeSelector:
+                    additionalProperties:
+                      type: string
+                    description: NodeSelector Define which Nodes the Pods are scheduled
+                      on.
+                    type: object
+                  paused:
+                    description: |-
+                      Paused If set to true all actions on the underlying managed objects are not
+                      going to be performed, except for delete actions.
+                    type: boolean
+                  podDisruptionBudget:
+                    description: PodDisruptionBudget created by operator
+                    properties:
+                      maxUnavailable:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          An eviction is allowed if at most "maxUnavailable" pods selected by
+                          "selector" are unavailable after the eviction, i.e. even in absence of
+                          the evicted pod. For example, one can prevent all voluntary evictions
+                          by specifying 0. This is a mutually exclusive setting with "minAvailable".
+                        x-kubernetes-int-or-string: true
+                      minAvailable:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          An eviction is allowed if at least "minAvailable" pods selected by
+                          "selector" will still be available after the eviction, i.e. even in the
+                          absence of the evicted pod.  So for example you can prevent all voluntary
+                          evictions by specifying "100%".
+                        x-kubernetes-int-or-string: true
+                      selectorLabels:
+                        additionalProperties:
+                          type: string
+                        description: |-
+                          replaces default labels selector generated by operator
+                          it's useful when you need to create custom budget
+                        type: object
+                    type: object
+                  podMetadata:
+                    description: PodMetadata configures Labels and Annotations which
+                      are propagated to the VLStorage pods.
+                    properties:
+                      annotations:
+                        additionalProperties:
+                          type: string
+                        description: |-
+                          Annotations is an unstructured key value map stored with a resource that may be
+                          set by external tools to store and retrieve arbitrary metadata. They are not
+                          queryable and should be preserved when modifying objects.
+                          More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations
+                        type: object
+                      labels:
+                        additionalProperties:
+                          type: string
+                        description: |-
+                          Labels Map of string keys and values that can be used to organize and categorize
+                          (scope and select) objects. May match selectors of replication controllers
+                          and services.
+                          More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
+                        type: object
+                      name:
+                        description: |-
+                          Name must be unique within a namespace. Is required when creating resources, although
+                          some resources may allow a client to request the generation of an appropriate name
+                          automatically. Name is primarily intended for creation idempotence and configuration
+                          definition.
+                          Cannot be updated.
+                          More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names
+                        type: string
+                    type: object
+                  port:
+                    description: Port listen address
+                    type: string
+                  priorityClassName:
+                    description: PriorityClassName class assigned to the Pods
+                    type: string
+                  readinessGates:
+                    description: ReadinessGates defines pod readiness gates
+                    items:
+                      description: PodReadinessGate contains the reference to a pod
+                        condition
+                      properties:
+                        conditionType:
+                          description: ConditionType refers to a condition in the
+                            pod's condition list with matching type.
+                          type: string
+                      required:
+                      - conditionType
+                      type: object
+                    type: array
+                  readinessProbe:
+                    description: ReadinessProbe that will be added CRD pod
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  replicaCount:
+                    description: ReplicaCount is the expected size of the Application.
+                    format: int32
+                    type: integer
+                  resources:
+                    description: |-
+                      Resources container resource request and limits, https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+                      if not defined default resources from operator config will be used
+                    properties:
+                      claims:
+                        description: |-
+                          Claims lists the names of resources, defined in spec.resourceClaims,
+                          that are used by this container.
+
+                          This is an alpha field and requires enabling the
+                          DynamicResourceAllocation feature gate.
+
+                          This field is immutable. It can only be set for containers.
+                        items:
+                          description: ResourceClaim references one entry in PodSpec.ResourceClaims.
+                          properties:
+                            name:
+                              description: |-
+                                Name must match the name of one entry in pod.spec.resourceClaims of
+                                the Pod where this field is used. It makes that resource available
+                                inside a container.
+                              type: string
+                            request:
+                              description: |-
+                                Request is the name chosen for a request in the referenced claim.
+                                If empty, everything from the claim is made available, otherwise
+                                only the result of this request.
+                              type: string
+                          required:
+                          - name
+                          type: object
+                        type: array
+                        x-kubernetes-list-map-keys:
+                        - name
+                        x-kubernetes-list-type: map
+                      limits:
+                        additionalProperties:
+                          anyOf:
+                          - type: integer
+                          - type: string
+                          pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                          x-kubernetes-int-or-string: true
+                        description: |-
+                          Limits describes the maximum amount of compute resources allowed.
+                          More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+                        type: object
+                      requests:
+                        additionalProperties:
+                          anyOf:
+                          - type: integer
+                          - type: string
+                          pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                          x-kubernetes-int-or-string: true
+                        description: |-
+                          Requests describes the minimum amount of compute resources required.
+                          If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
+                          otherwise to an implementation-defined value. Requests cannot exceed Limits.
+                          More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+                        type: object
+                    type: object
+                  retentionMaxDiskSpaceUsageBytes:
+                    description: |-
+                      RetentionMaxDiskSpaceUsageBytes for the stored logs
+                      VictoriaLogs keeps at least two last days of data in order to guarantee that the logs for the last day can be returned in queries.
+                      This means that the total disk space usage may exceed the -retention.maxDiskSpaceUsageBytes,
+                      if the size of the last two days of data exceeds the -retention.maxDiskSpaceUsageBytes.
+                      https://docs.victoriametrics.com/victorialogs/#retention-by-disk-space-usage
+                    type: string
+                  retentionPeriod:
+                    description: |-
+                      RetentionPeriod for the stored logs
+                      https://docs.victoriametrics.com/victorialogs/#retention
+                    pattern: ^[0-9]+(h|d|w|y)?$
+                    type: string
+                  revisionHistoryLimitCount:
+                    description: |-
+                      The number of old ReplicaSets to retain to allow rollback in deployment or
+                      maximum number of revisions that will be maintained in the Deployment revision history.
+                      Has no effect at StatefulSets
+                      Defaults to 10.
+                    format: int32
+                    type: integer
+                  rollingUpdateStrategy:
+                    description: |-
+                      RollingUpdateStrategy defines strategy for application updates
+                      Default is OnDelete, in this case operator handles update process
+                      Can be changed for RollingUpdate
+                    type: string
+                  runtimeClassName:
+                    description: |-
+                      RuntimeClassName - defines runtime class for kubernetes pod.
+                      https://kubernetes.io/docs/concepts/containers/runtime-class/
+                    type: string
+                  schedulerName:
+                    description: SchedulerName - defines kubernetes scheduler name
+                    type: string
+                  secrets:
+                    description: |-
+                      Secrets is a list of Secrets in the same namespace as the Application
+                      object, which shall be mounted into the Application container
+                      at /etc/vm/secrets/SECRET_NAME folder
+                    items:
+                      type: string
+                    type: array
+                  securityContext:
+                    description: |-
+                      SecurityContext holds pod-level security attributes and common container settings.
+                      This defaults to the default PodSecurityContext.
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  serviceScrapeSpec:
+                    description: ServiceScrapeSpec that will be added to vlselect
+                      VMServiceScrape spec
+                    required:
+                    - endpoints
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  serviceSpec:
+                    description: ServiceSpec that will be added to vlselect service
+                      spec
+                    properties:
+                      metadata:
+                        description: EmbeddedObjectMetadata defines objectMeta for
+                          additional service.
+                        properties:
+                          annotations:
+                            additionalProperties:
+                              type: string
+                            description: |-
+                              Annotations is an unstructured key value map stored with a resource that may be
+                              set by external tools to store and retrieve arbitrary metadata. They are not
+                              queryable and should be preserved when modifying objects.
+                              More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations
+                            type: object
+                          labels:
+                            additionalProperties:
+                              type: string
+                            description: |-
+                              Labels Map of string keys and values that can be used to organize and categorize
+                              (scope and select) objects. May match selectors of replication controllers
+                              and services.
+                              More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
+                            type: object
+                          name:
+                            description: |-
+                              Name must be unique within a namespace. Is required when creating resources, although
+                              some resources may allow a client to request the generation of an appropriate name
+                              automatically. Name is primarily intended for creation idempotence and configuration
+                              definition.
+                              Cannot be updated.
+                              More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names
+                            type: string
+                        type: object
+                      spec:
+                        description: |-
+                          ServiceSpec describes the attributes that a user creates on a service.
+                          More info: https://kubernetes.io/docs/concepts/services-networking/service/
+                        type: object
+                        x-kubernetes-preserve-unknown-fields: true
+                      useAsDefault:
+                        description: |-
+                          UseAsDefault applies changes from given service definition to the main object Service
+                          Changing from headless service to clusterIP or loadbalancer may break cross-component communication
+                        type: boolean
+                    required:
+                    - spec
+                    type: object
+                  startupProbe:
+                    description: StartupProbe that will be added to CRD pod
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  storage:
+                    description: Storage configures persistent volume for VLStorage
+                    properties:
+                      disableMountSubPath:
+                        description: |-
+                          Deprecated: subPath usage will be disabled by default in a future release, this option will become unnecessary.
+                          DisableMountSubPath allows to remove any subPath usage in volume mounts.
+                        type: boolean
+                      emptyDir:
+                        description: |-
+                          EmptyDirVolumeSource to be used by the Prometheus StatefulSets. If specified, used in place of any volumeClaimTemplate. More
+                          info: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir
+                        properties:
+                          medium:
+                            description: |-
+                              medium represents what type of storage medium should back this directory.
+                              The default is "" which means to use the node's default medium.
+                              Must be an empty string (default) or Memory.
+                              More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir
+                            type: string
+                          sizeLimit:
+                            anyOf:
+                            - type: integer
+                            - type: string
+                            description: |-
+                              sizeLimit is the total amount of local storage required for this EmptyDir volume.
+                              The size limit is also applicable for memory medium.
+                              The maximum usage on memory medium EmptyDir would be the minimum value between
+                              the SizeLimit specified here and the sum of memory limits of all containers in a pod.
+                              The default is nil which means that the limit is undefined.
+                              More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir
+                            pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                            x-kubernetes-int-or-string: true
+                        type: object
+                      volumeClaimTemplate:
+                        description: A PVC spec to be used by the VMAlertManager StatefulSets.
+                        type: object
+                        x-kubernetes-preserve-unknown-fields: true
+                    type: object
+                  storageDataPath:
+                    description: StorageDataPath - path to storage data
+                    type: string
+                  terminationGracePeriodSeconds:
+                    description: TerminationGracePeriodSeconds period for container
+                      graceful termination
+                    format: int64
+                    type: integer
+                  tolerations:
+                    description: Tolerations If specified, the pod's tolerations.
+                    items:
+                      description: |-
+                        The pod this Toleration is attached to tolerates any taint that matches
+                        the triple <key,value,effect> using the matching operator <operator>.
+                      properties:
+                        effect:
+                          description: |-
+                            Effect indicates the taint effect to match. Empty means match all taint effects.
+                            When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.
+                          type: string
+                        key:
+                          description: |-
+                            Key is the taint key that the toleration applies to. Empty means match all taint keys.
+                            If the key is empty, operator must be Exists; this combination means to match all values and all keys.
+                          type: string
+                        operator:
+                          description: |-
+                            Operator represents a key's relationship to the value.
+                            Valid operators are Exists and Equal. Defaults to Equal.
+                            Exists is equivalent to wildcard for value, so that a pod can
+                            tolerate all taints of a particular category.
+                          type: string
+                        tolerationSeconds:
+                          description: |-
+                            TolerationSeconds represents the period of time the toleration (which must be
+                            of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,
+                            it is not set, which means tolerate the taint forever (do not evict). Zero and
+                            negative values will be treated as 0 (evict immediately) by the system.
+                          format: int64
+                          type: integer
+                        value:
+                          description: |-
+                            Value is the taint value the toleration matches to.
+                            If the operator is Exists, the value should be empty, otherwise just a regular string.
+                          type: string
+                      type: object
+                    type: array
+                  topologySpreadConstraints:
+                    description: |-
+                      TopologySpreadConstraints embedded kubernetes pod configuration option,
+                      controls how pods are spread across your cluster among failure-domains
+                      such as regions, zones, nodes, and other user-defined topology domains
+                      https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
+                    items:
+                      description: TopologySpreadConstraint specifies how to spread
+                        matching pods among the given topology.
+                      required:
+                      - maxSkew
+                      - topologyKey
+                      - whenUnsatisfiable
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                  useDefaultResources:
+                    description: |-
+                      UseDefaultResources controls resource settings
+                      By default, operator sets built-in resource requirements
+                    type: boolean
+                  useStrictSecurity:
+                    description: |-
+                      UseStrictSecurity enables strict security mode for component
+                      it restricts disk writes access
+                      uses non-root user out of the box
+                      drops not needed security permissions
+                    type: boolean
+                  volumeMounts:
+                    description: |-
+                      VolumeMounts allows configuration of additional VolumeMounts on the output Deployment/StatefulSet definition.
+                      VolumeMounts specified will be appended to other VolumeMounts in the Application container
+                    items:
+                      description: VolumeMount describes a mounting of a Volume within
+                        a container.
+                      properties:
+                        mountPath:
+                          description: |-
+                            Path within the container at which the volume should be mounted.  Must
+                            not contain ':'.
+                          type: string
+                        mountPropagation:
+                          description: |-
+                            mountPropagation determines how mounts are propagated from the host
+                            to container and the other way around.
+                            When not set, MountPropagationNone is used.
+                            This field is beta in 1.10.
+                            When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified
+                            (which defaults to None).
+                          type: string
+                        name:
+                          description: This must match the Name of a Volume.
+                          type: string
+                        readOnly:
+                          description: |-
+                            Mounted read-only if true, read-write otherwise (false or unspecified).
+                            Defaults to false.
+                          type: boolean
+                        recursiveReadOnly:
+                          description: |-
+                            RecursiveReadOnly specifies whether read-only mounts should be handled
+                            recursively.
+
+                            If ReadOnly is false, this field has no meaning and must be unspecified.
+
+                            If ReadOnly is true, and this field is set to Disabled, the mount is not made
+                            recursively read-only.  If this field is set to IfPossible, the mount is made
+                            recursively read-only, if it is supported by the container runtime.  If this
+                            field is set to Enabled, the mount is made recursively read-only if it is
+                            supported by the container runtime, otherwise the pod will not be started and
+                            an error will be generated to indicate the reason.
+
+                            If this field is set to IfPossible or Enabled, MountPropagation must be set to
+                            None (or be unspecified, which defaults to None).
+
+                            If this field is not specified, it is treated as an equivalent of Disabled.
+                          type: string
+                        subPath:
+                          description: |-
+                            Path within the volume from which the container's volume should be mounted.
+                            Defaults to "" (volume's root).
+                          type: string
+                        subPathExpr:
+                          description: |-
+                            Expanded path within the volume from which the container's volume should be mounted.
+                            Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment.
+                            Defaults to "" (volume's root).
+                            SubPathExpr and SubPath are mutually exclusive.
+                          type: string
+                      required:
+                      - mountPath
+                      - name
+                      type: object
+                    type: array
+                  volumes:
+                    description: |-
+                      Volumes allows configuration of additional volumes on the output Deployment/StatefulSet definition.
+                      Volumes specified will be appended to other volumes that are generated.
+                      / +optional
+                    items:
+                      description: Volume represents a named volume in a pod that
+                        may be accessed by any container in the pod.
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                type: object
+            type: object
+          status:
+            description: VLClusterStatus defines the observed state of VLCluster
+            properties:
+              conditions:
+                description: 'Known .status.conditions.type are: "Available", "Progressing",
+                  and "Degraded"'
+                items:
+                  description: Condition defines status condition of the resource
+                  properties:
+                    lastTransitionTime:
+                      description: lastTransitionTime is the last time the condition
+                        transitioned from one status to another.
+                      format: date-time
+                      type: string
+                    lastUpdateTime:
+                      description: |-
+                        LastUpdateTime is the last time of given type update.
+                        This value is used for status TTL update and removal
+                      format: date-time
+                      type: string
+                    message:
+                      description: |-
+                        message is a human readable message indicating details about the transition.
+                        This may be an empty string.
+                      maxLength: 32768
+                      type: string
+                    observedGeneration:
+                      description: |-
+                        observedGeneration represents the .metadata.generation that the condition was set based upon.
+                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+                        with respect to the current state of the instance.
+                      format: int64
+                      minimum: 0
+                      type: integer
+                    reason:
+                      description: |-
+                        reason contains a programmatic identifier indicating the reason for the condition's last transition.
+                        Producers of specific condition types may define expected values and meanings for this field,
+                        and whether the values are considered a guaranteed API.
+                        The value should be a CamelCase string.
+                        This field may not be empty.
+                      maxLength: 1024
+                      minLength: 1
+                      type: string
+                    status:
+                      description: status of the condition, one of True, False, Unknown.
+                      enum:
+                      - "True"
+                      - "False"
+                      - Unknown
+                      type: string
+                    type:
+                      description: Type of condition in CamelCase or in name.namespace.resource.victoriametrics.com/CamelCase.
+                      maxLength: 316
+                      type: string
+                  required:
+                  - lastTransitionTime
+                  - lastUpdateTime
+                  - reason
+                  - status
+                  - type
+                  type: object
+                type: array
+                x-kubernetes-list-map-keys:
+                - type
+                x-kubernetes-list-type: map
+              observedGeneration:
+                description: |-
+                  ObservedGeneration defines current generation picked by operator for the
+                  reconcile
+                format: int64
+                type: integer
+              reason:
+                description: Reason defines human readable error reason
+                type: string
+              updateStatus:
+                description: UpdateStatus defines a status for update rollout
+                type: string
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
 metadata:
   annotations:
     controller-gen.kubebuilder.io/version: v0.17.2
@@ -17559,491 +20283,75 @@ spec:
                             Mounted read-only if true, read-write otherwise (false or unspecified).
                             Defaults to false.
                           type: boolean
-                        recursiveReadOnly:
-                          description: |-
-                            RecursiveReadOnly specifies whether read-only mounts should be handled
-                            recursively.
-
-                            If ReadOnly is false, this field has no meaning and must be unspecified.
-
-                            If ReadOnly is true, and this field is set to Disabled, the mount is not made
-                            recursively read-only.  If this field is set to IfPossible, the mount is made
-                            recursively read-only, if it is supported by the container runtime.  If this
-                            field is set to Enabled, the mount is made recursively read-only if it is
-                            supported by the container runtime, otherwise the pod will not be started and
-                            an error will be generated to indicate the reason.
-
-                            If this field is set to IfPossible or Enabled, MountPropagation must be set to
-                            None (or be unspecified, which defaults to None).
-
-                            If this field is not specified, it is treated as an equivalent of Disabled.
-                          type: string
-                        subPath:
-                          description: |-
-                            Path within the volume from which the container's volume should be mounted.
-                            Defaults to "" (volume's root).
-                          type: string
-                        subPathExpr:
-                          description: |-
-                            Expanded path within the volume from which the container's volume should be mounted.
-                            Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment.
-                            Defaults to "" (volume's root).
-                            SubPathExpr and SubPath are mutually exclusive.
-                          type: string
-                      required:
-                      - mountPath
-                      - name
-                      type: object
-                    type: array
-                  volumes:
-                    description: |-
-                      Volumes allows configuration of additional volumes on the output Deployment/StatefulSet definition.
-                      Volumes specified will be appended to other volumes that are generated.
-                      / +optional
-                    items:
-                      description: Volume represents a named volume in a pod that
-                        may be accessed by any container in the pod.
-                      required:
-                      - name
-                      type: object
-                      x-kubernetes-preserve-unknown-fields: true
-                    type: array
-                type: object
-              vmselect:
-                description: VMSelect defines configuration section for vmselect components
-                  of the victoria-metrics cluster
-                properties:
-                  affinity:
-                    description: Affinity If specified, the pod's scheduling constraints.
-                    type: object
-                    x-kubernetes-preserve-unknown-fields: true
-                  cacheMountPath:
-                    description: |-
-                      CacheMountPath allows to add cache persistent for VMSelect,
-                      will use "/cache" as default if not specified.
-                    type: string
-                  claimTemplates:
-                    description: ClaimTemplates allows adding additional VolumeClaimTemplates
-                      for StatefulSet
-                    items:
-                      description: PersistentVolumeClaim is a user's request for and
-                        claim to a persistent volume
-                      properties:
-                        apiVersion:
-                          description: |-
-                            APIVersion defines the versioned schema of this representation of an object.
-                            Servers should convert recognized schemas to the latest internal value, and
-                            may reject unrecognized values.
-                            More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
-                          type: string
-                        kind:
-                          description: |-
-                            Kind is a string value representing the REST resource this object represents.
-                            Servers may infer this from the endpoint the client submits requests to.
-                            Cannot be updated.
-                            In CamelCase.
-                            More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
-                          type: string
-                        metadata:
-                          description: |-
-                            Standard object's metadata.
-                            More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
-                          type: object
-                          x-kubernetes-preserve-unknown-fields: true
-                        spec:
-                          description: |-
-                            spec defines the desired characteristics of a volume requested by a pod author.
-                            More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims
-                          properties:
-                            accessModes:
-                              description: |-
-                                accessModes contains the desired access modes the volume should have.
-                                More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1
-                              items:
-                                type: string
-                              type: array
-                              x-kubernetes-list-type: atomic
-                            dataSource:
-                              description: |-
-                                dataSource field can be used to specify either:
-                                * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)
-                                * An existing PVC (PersistentVolumeClaim)
-                                If the provisioner or an external controller can support the specified data source,
-                                it will create a new volume based on the contents of the specified data source.
-                                When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef,
-                                and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified.
-                                If the namespace is specified, then dataSourceRef will not be copied to dataSource.
-                              properties:
-                                apiGroup:
-                                  description: |-
-                                    APIGroup is the group for the resource being referenced.
-                                    If APIGroup is not specified, the specified Kind must be in the core API group.
-                                    For any other third-party types, APIGroup is required.
-                                  type: string
-                                kind:
-                                  description: Kind is the type of resource being
-                                    referenced
-                                  type: string
-                                name:
-                                  description: Name is the name of resource being
-                                    referenced
-                                  type: string
-                              required:
-                              - kind
-                              - name
-                              type: object
-                              x-kubernetes-map-type: atomic
-                            dataSourceRef:
-                              description: |-
-                                dataSourceRef specifies the object from which to populate the volume with data, if a non-empty
-                                volume is desired. This may be any object from a non-empty API group (non
-                                core object) or a PersistentVolumeClaim object.
-                                When this field is specified, volume binding will only succeed if the type of
-                                the specified object matches some installed volume populator or dynamic
-                                provisioner.
-                                This field will replace the functionality of the dataSource field and as such
-                                if both fields are non-empty, they must have the same value. For backwards
-                                compatibility, when namespace isn't specified in dataSourceRef,
-                                both fields (dataSource and dataSourceRef) will be set to the same
-                                value automatically if one of them is empty and the other is non-empty.
-                                When namespace is specified in dataSourceRef,
-                                dataSource isn't set to the same value and must be empty.
-                                There are three important differences between dataSource and dataSourceRef:
-                                * While dataSource only allows two specific types of objects, dataSourceRef
-                                  allows any non-core object, as well as PersistentVolumeClaim objects.
-                                * While dataSource ignores disallowed values (dropping them), dataSourceRef
-                                  preserves all values, and generates an error if a disallowed value is
-                                  specified.
-                                * While dataSource only allows local objects, dataSourceRef allows objects
-                                  in any namespaces.
-                                (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled.
-                                (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled.
-                              properties:
-                                apiGroup:
-                                  description: |-
-                                    APIGroup is the group for the resource being referenced.
-                                    If APIGroup is not specified, the specified Kind must be in the core API group.
-                                    For any other third-party types, APIGroup is required.
-                                  type: string
-                                kind:
-                                  description: Kind is the type of resource being
-                                    referenced
-                                  type: string
-                                name:
-                                  description: Name is the name of resource being
-                                    referenced
-                                  type: string
-                                namespace:
-                                  description: |-
-                                    Namespace is the namespace of resource being referenced
-                                    Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.
-                                    (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled.
-                                  type: string
-                              required:
-                              - kind
-                              - name
-                              type: object
-                            resources:
-                              description: |-
-                                resources represents the minimum resources the volume should have.
-                                If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements
-                                that are lower than previous value but must still be higher than capacity recorded in the
-                                status field of the claim.
-                                More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
-                              properties:
-                                limits:
-                                  additionalProperties:
-                                    anyOf:
-                                    - type: integer
-                                    - type: string
-                                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
-                                    x-kubernetes-int-or-string: true
-                                  description: |-
-                                    Limits describes the maximum amount of compute resources allowed.
-                                    More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
-                                  type: object
-                                requests:
-                                  additionalProperties:
-                                    anyOf:
-                                    - type: integer
-                                    - type: string
-                                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
-                                    x-kubernetes-int-or-string: true
-                                  description: |-
-                                    Requests describes the minimum amount of compute resources required.
-                                    If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
-                                    otherwise to an implementation-defined value. Requests cannot exceed Limits.
-                                    More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
-                                  type: object
-                              type: object
-                            selector:
-                              description: selector is a label query over volumes
-                                to consider for binding.
-                              properties:
-                                matchExpressions:
-                                  description: matchExpressions is a list of label
-                                    selector requirements. The requirements are ANDed.
-                                  items:
-                                    description: |-
-                                      A label selector requirement is a selector that contains values, a key, and an operator that
-                                      relates the key and values.
-                                    properties:
-                                      key:
-                                        description: key is the label key that the
-                                          selector applies to.
-                                        type: string
-                                      operator:
-                                        description: |-
-                                          operator represents a key's relationship to a set of values.
-                                          Valid operators are In, NotIn, Exists and DoesNotExist.
-                                        type: string
-                                      values:
-                                        description: |-
-                                          values is an array of string values. If the operator is In or NotIn,
-                                          the values array must be non-empty. If the operator is Exists or DoesNotExist,
-                                          the values array must be empty. This array is replaced during a strategic
-                                          merge patch.
-                                        items:
-                                          type: string
-                                        type: array
-                                        x-kubernetes-list-type: atomic
-                                    required:
-                                    - key
-                                    - operator
-                                    type: object
-                                  type: array
-                                  x-kubernetes-list-type: atomic
-                                matchLabels:
-                                  additionalProperties:
-                                    type: string
-                                  description: |-
-                                    matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
-                                    map is equivalent to an element of matchExpressions, whose key field is "key", the
-                                    operator is "In", and the values array contains only "value". The requirements are ANDed.
-                                  type: object
-                              type: object
-                              x-kubernetes-map-type: atomic
-                            storageClassName:
-                              description: |-
-                                storageClassName is the name of the StorageClass required by the claim.
-                                More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1
-                              type: string
-                            volumeAttributesClassName:
-                              description: |-
-                                volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim.
-                                If specified, the CSI driver will create or update the volume with the attributes defined
-                                in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName,
-                                it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass
-                                will be applied to the claim but it's not allowed to reset this field to empty string once it is set.
-                                If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass
-                                will be set by the persistentvolume controller if it exists.
-                                If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be
-                                set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource
-                                exists.
-                                More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/
-                                (Beta) Using this field requires the VolumeAttributesClass feature gate to be enabled (off by default).
-                              type: string
-                            volumeMode:
-                              description: |-
-                                volumeMode defines what type of volume is required by the claim.
-                                Value of Filesystem is implied when not included in claim spec.
-                              type: string
-                            volumeName:
-                              description: volumeName is the binding reference to
-                                the PersistentVolume backing this claim.
-                              type: string
-                          type: object
-                        status:
+                        recursiveReadOnly:
                           description: |-
-                            status represents the current information/status of a persistent volume claim.
-                            Read-only.
-                            More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims
-                          properties:
-                            accessModes:
-                              description: |-
-                                accessModes contains the actual access modes the volume backing the PVC has.
-                                More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1
-                              items:
-                                type: string
-                              type: array
-                              x-kubernetes-list-type: atomic
-                            allocatedResourceStatuses:
-                              additionalProperties:
-                                description: |-
-                                  When a controller receives persistentvolume claim update with ClaimResourceStatus for a resource
-                                  that it does not recognizes, then it should ignore that update and let other controllers
-                                  handle it.
-                                type: string
-                              description: "allocatedResourceStatuses stores status
-                                of resource being resized for the given PVC.\nKey
-                                names follow standard Kubernetes label syntax. Valid
-                                values are either:\n\t* Un-prefixed keys:\n\t\t- storage
-                                - the capacity of the volume.\n\t* Custom resources
-                                must use implementation-defined prefixed names such
-                                as \"example.com/my-custom-resource\"\nApart from
-                                above values - keys that are unprefixed or have kubernetes.io
-                                prefix are considered\nreserved and hence may not
-                                be used.\n\nClaimResourceStatus can be in any of following
-                                states:\n\t- ControllerResizeInProgress:\n\t\tState
-                                set when resize controller starts resizing the volume
-                                in control-plane.\n\t- ControllerResizeFailed:\n\t\tState
-                                set when resize has failed in resize controller with
-                                a terminal error.\n\t- NodeResizePending:\n\t\tState
-                                set when resize controller has finished resizing the
-                                volume but further resizing of\n\t\tvolume is needed
-                                on the node.\n\t- NodeResizeInProgress:\n\t\tState
-                                set when kubelet starts resizing the volume.\n\t-
-                                NodeResizeFailed:\n\t\tState set when resizing has
-                                failed in kubelet with a terminal error. Transient
-                                errors don't set\n\t\tNodeResizeFailed.\nFor example:
-                                if expanding a PVC for more capacity - this field
-                                can be one of the following states:\n\t- pvc.status.allocatedResourceStatus['storage']
-                                = \"ControllerResizeInProgress\"\n     - pvc.status.allocatedResourceStatus['storage']
-                                = \"ControllerResizeFailed\"\n     - pvc.status.allocatedResourceStatus['storage']
-                                = \"NodeResizePending\"\n     - pvc.status.allocatedResourceStatus['storage']
-                                = \"NodeResizeInProgress\"\n     - pvc.status.allocatedResourceStatus['storage']
-                                = \"NodeResizeFailed\"\nWhen this field is not set,
-                                it means that no resize operation is in progress for
-                                the given PVC.\n\nA controller that receives PVC update
-                                with previously unknown resourceName or ClaimResourceStatus\nshould
-                                ignore the update for the purpose it was designed.
-                                For example - a controller that\nonly is responsible
-                                for resizing capacity of the volume, should ignore
-                                PVC updates that change other valid\nresources associated
-                                with PVC.\n\nThis is an alpha field and requires enabling
-                                RecoverVolumeExpansionFailure feature."
-                              type: object
-                              x-kubernetes-map-type: granular
-                            allocatedResources:
-                              additionalProperties:
-                                anyOf:
-                                - type: integer
-                                - type: string
-                                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
-                                x-kubernetes-int-or-string: true
-                              description: "allocatedResources tracks the resources
-                                allocated to a PVC including its capacity.\nKey names
-                                follow standard Kubernetes label syntax. Valid values
-                                are either:\n\t* Un-prefixed keys:\n\t\t- storage
-                                - the capacity of the volume.\n\t* Custom resources
-                                must use implementation-defined prefixed names such
-                                as \"example.com/my-custom-resource\"\nApart from
-                                above values - keys that are unprefixed or have kubernetes.io
-                                prefix are considered\nreserved and hence may not
-                                be used.\n\nCapacity reported here may be larger than
-                                the actual capacity when a volume expansion operation\nis
-                                requested.\nFor storage quota, the larger value from
-                                allocatedResources and PVC.spec.resources is used.\nIf
-                                allocatedResources is not set, PVC.spec.resources
-                                alone is used for quota calculation.\nIf a volume
-                                expansion capacity request is lowered, allocatedResources
-                                is only\nlowered if there are no expansion operations
-                                in progress and if the actual volume capacity\nis
-                                equal or lower than the requested capacity.\n\nA controller
-                                that receives PVC update with previously unknown resourceName\nshould
-                                ignore the update for the purpose it was designed.
-                                For example - a controller that\nonly is responsible
-                                for resizing capacity of the volume, should ignore
-                                PVC updates that change other valid\nresources associated
-                                with PVC.\n\nThis is an alpha field and requires enabling
-                                RecoverVolumeExpansionFailure feature."
-                              type: object
-                            capacity:
-                              additionalProperties:
-                                anyOf:
-                                - type: integer
-                                - type: string
-                                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
-                                x-kubernetes-int-or-string: true
-                              description: capacity represents the actual resources
-                                of the underlying volume.
-                              type: object
-                            conditions:
-                              description: |-
-                                conditions is the current Condition of persistent volume claim. If underlying persistent volume is being
-                                resized then the Condition will be set to 'Resizing'.
-                              items:
-                                description: PersistentVolumeClaimCondition contains
-                                  details about state of pvc
-                                properties:
-                                  lastProbeTime:
-                                    description: lastProbeTime is the time we probed
-                                      the condition.
-                                    format: date-time
-                                    type: string
-                                  lastTransitionTime:
-                                    description: lastTransitionTime is the time the
-                                      condition transitioned from one status to another.
-                                    format: date-time
-                                    type: string
-                                  message:
-                                    description: message is the human-readable message
-                                      indicating details about last transition.
-                                    type: string
-                                  reason:
-                                    description: |-
-                                      reason is a unique, this should be a short, machine understandable string that gives the reason
-                                      for condition's last transition. If it reports "Resizing" that means the underlying
-                                      persistent volume is being resized.
-                                    type: string
-                                  status:
-                                    description: |-
-                                      Status is the status of the condition.
-                                      Can be True, False, Unknown.
-                                      More info: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/persistent-volume-claim-v1/#:~:text=state%20of%20pvc-,conditions.status,-(string)%2C%20required
-                                    type: string
-                                  type:
-                                    description: |-
-                                      Type is the type of the condition.
-                                      More info: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/persistent-volume-claim-v1/#:~:text=set%20to%20%27ResizeStarted%27.-,PersistentVolumeClaimCondition,-contains%20details%20about
-                                    type: string
-                                required:
-                                - status
-                                - type
-                                type: object
-                              type: array
-                              x-kubernetes-list-map-keys:
-                              - type
-                              x-kubernetes-list-type: map
-                            currentVolumeAttributesClassName:
-                              description: |-
-                                currentVolumeAttributesClassName is the current name of the VolumeAttributesClass the PVC is using.
-                                When unset, there is no VolumeAttributeClass applied to this PersistentVolumeClaim
-                                This is a beta field and requires enabling VolumeAttributesClass feature (off by default).
-                              type: string
-                            modifyVolumeStatus:
-                              description: |-
-                                ModifyVolumeStatus represents the status object of ControllerModifyVolume operation.
-                                When this is unset, there is no ModifyVolume operation being attempted.
-                                This is a beta field and requires enabling VolumeAttributesClass feature (off by default).
-                              properties:
-                                status:
-                                  description: "status is the status of the ControllerModifyVolume
-                                    operation. It can be in any of following states:\n
-                                    - Pending\n   Pending indicates that the PersistentVolumeClaim
-                                    cannot be modified due to unmet requirements,
-                                    such as\n   the specified VolumeAttributesClass
-                                    not existing.\n - InProgress\n   InProgress indicates
-                                    that the volume is being modified.\n - Infeasible\n
-                                    \ Infeasible indicates that the request has been
-                                    rejected as invalid by the CSI driver. To\n\t
-                                    \ resolve the error, a valid VolumeAttributesClass
-                                    needs to be specified.\nNote: New statuses can
-                                    be added in the future. Consumers should check
-                                    for unknown statuses and fail appropriately."
-                                  type: string
-                                targetVolumeAttributesClassName:
-                                  description: targetVolumeAttributesClassName is
-                                    the name of the VolumeAttributesClass the PVC
-                                    currently being reconciled
-                                  type: string
-                              required:
-                              - status
-                              type: object
-                            phase:
-                              description: phase represents the current phase of PersistentVolumeClaim.
-                              type: string
-                          type: object
+                            RecursiveReadOnly specifies whether read-only mounts should be handled
+                            recursively.
+
+                            If ReadOnly is false, this field has no meaning and must be unspecified.
+
+                            If ReadOnly is true, and this field is set to Disabled, the mount is not made
+                            recursively read-only.  If this field is set to IfPossible, the mount is made
+                            recursively read-only, if it is supported by the container runtime.  If this
+                            field is set to Enabled, the mount is made recursively read-only if it is
+                            supported by the container runtime, otherwise the pod will not be started and
+                            an error will be generated to indicate the reason.
+
+                            If this field is set to IfPossible or Enabled, MountPropagation must be set to
+                            None (or be unspecified, which defaults to None).
+
+                            If this field is not specified, it is treated as an equivalent of Disabled.
+                          type: string
+                        subPath:
+                          description: |-
+                            Path within the volume from which the container's volume should be mounted.
+                            Defaults to "" (volume's root).
+                          type: string
+                        subPathExpr:
+                          description: |-
+                            Expanded path within the volume from which the container's volume should be mounted.
+                            Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment.
+                            Defaults to "" (volume's root).
+                            SubPathExpr and SubPath are mutually exclusive.
+                          type: string
+                      required:
+                      - mountPath
+                      - name
+                      type: object
+                    type: array
+                  volumes:
+                    description: |-
+                      Volumes allows configuration of additional volumes on the output Deployment/StatefulSet definition.
+                      Volumes specified will be appended to other volumes that are generated.
+                      / +optional
+                    items:
+                      description: Volume represents a named volume in a pod that
+                        may be accessed by any container in the pod.
+                      required:
+                      - name
+                      type: object
+                      x-kubernetes-preserve-unknown-fields: true
+                    type: array
+                type: object
+              vmselect:
+                description: VMSelect defines configuration section for vmselect components
+                  of the victoria-metrics cluster
+                properties:
+                  affinity:
+                    description: Affinity If specified, the pod's scheduling constraints.
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  cacheMountPath:
+                    description: |-
+                      CacheMountPath allows to add cache persistent for VMSelect,
+                      will use "/cache" as default if not specified.
+                    type: string
+                  claimTemplates:
+                    description: ClaimTemplates allows adding additional VolumeClaimTemplates
+                      for StatefulSet
+                    items:
+                      description: PersistentVolumeClaim is a user's request for and
+                        claim to a persistent volume
                       type: object
                     type: array
                   clusterNativeListenPort:
@@ -19308,423 +21616,8 @@ spec:
                     items:
                       description: PersistentVolumeClaim is a user's request for and
                         claim to a persistent volume
-                      properties:
-                        apiVersion:
-                          description: |-
-                            APIVersion defines the versioned schema of this representation of an object.
-                            Servers should convert recognized schemas to the latest internal value, and
-                            may reject unrecognized values.
-                            More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
-                          type: string
-                        kind:
-                          description: |-
-                            Kind is a string value representing the REST resource this object represents.
-                            Servers may infer this from the endpoint the client submits requests to.
-                            Cannot be updated.
-                            In CamelCase.
-                            More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
-                          type: string
-                        metadata:
-                          description: |-
-                            Standard object's metadata.
-                            More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
-                          type: object
-                          x-kubernetes-preserve-unknown-fields: true
-                        spec:
-                          description: |-
-                            spec defines the desired characteristics of a volume requested by a pod author.
-                            More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims
-                          properties:
-                            accessModes:
-                              description: |-
-                                accessModes contains the desired access modes the volume should have.
-                                More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1
-                              items:
-                                type: string
-                              type: array
-                              x-kubernetes-list-type: atomic
-                            dataSource:
-                              description: |-
-                                dataSource field can be used to specify either:
-                                * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)
-                                * An existing PVC (PersistentVolumeClaim)
-                                If the provisioner or an external controller can support the specified data source,
-                                it will create a new volume based on the contents of the specified data source.
-                                When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef,
-                                and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified.
-                                If the namespace is specified, then dataSourceRef will not be copied to dataSource.
-                              properties:
-                                apiGroup:
-                                  description: |-
-                                    APIGroup is the group for the resource being referenced.
-                                    If APIGroup is not specified, the specified Kind must be in the core API group.
-                                    For any other third-party types, APIGroup is required.
-                                  type: string
-                                kind:
-                                  description: Kind is the type of resource being
-                                    referenced
-                                  type: string
-                                name:
-                                  description: Name is the name of resource being
-                                    referenced
-                                  type: string
-                              required:
-                              - kind
-                              - name
-                              type: object
-                              x-kubernetes-map-type: atomic
-                            dataSourceRef:
-                              description: |-
-                                dataSourceRef specifies the object from which to populate the volume with data, if a non-empty
-                                volume is desired. This may be any object from a non-empty API group (non
-                                core object) or a PersistentVolumeClaim object.
-                                When this field is specified, volume binding will only succeed if the type of
-                                the specified object matches some installed volume populator or dynamic
-                                provisioner.
-                                This field will replace the functionality of the dataSource field and as such
-                                if both fields are non-empty, they must have the same value. For backwards
-                                compatibility, when namespace isn't specified in dataSourceRef,
-                                both fields (dataSource and dataSourceRef) will be set to the same
-                                value automatically if one of them is empty and the other is non-empty.
-                                When namespace is specified in dataSourceRef,
-                                dataSource isn't set to the same value and must be empty.
-                                There are three important differences between dataSource and dataSourceRef:
-                                * While dataSource only allows two specific types of objects, dataSourceRef
-                                  allows any non-core object, as well as PersistentVolumeClaim objects.
-                                * While dataSource ignores disallowed values (dropping them), dataSourceRef
-                                  preserves all values, and generates an error if a disallowed value is
-                                  specified.
-                                * While dataSource only allows local objects, dataSourceRef allows objects
-                                  in any namespaces.
-                                (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled.
-                                (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled.
-                              properties:
-                                apiGroup:
-                                  description: |-
-                                    APIGroup is the group for the resource being referenced.
-                                    If APIGroup is not specified, the specified Kind must be in the core API group.
-                                    For any other third-party types, APIGroup is required.
-                                  type: string
-                                kind:
-                                  description: Kind is the type of resource being
-                                    referenced
-                                  type: string
-                                name:
-                                  description: Name is the name of resource being
-                                    referenced
-                                  type: string
-                                namespace:
-                                  description: |-
-                                    Namespace is the namespace of resource being referenced
-                                    Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.
-                                    (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled.
-                                  type: string
-                              required:
-                              - kind
-                              - name
-                              type: object
-                            resources:
-                              description: |-
-                                resources represents the minimum resources the volume should have.
-                                If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements
-                                that are lower than previous value but must still be higher than capacity recorded in the
-                                status field of the claim.
-                                More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources
-                              properties:
-                                limits:
-                                  additionalProperties:
-                                    anyOf:
-                                    - type: integer
-                                    - type: string
-                                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
-                                    x-kubernetes-int-or-string: true
-                                  description: |-
-                                    Limits describes the maximum amount of compute resources allowed.
-                                    More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
-                                  type: object
-                                requests:
-                                  additionalProperties:
-                                    anyOf:
-                                    - type: integer
-                                    - type: string
-                                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
-                                    x-kubernetes-int-or-string: true
-                                  description: |-
-                                    Requests describes the minimum amount of compute resources required.
-                                    If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
-                                    otherwise to an implementation-defined value. Requests cannot exceed Limits.
-                                    More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
-                                  type: object
-                              type: object
-                            selector:
-                              description: selector is a label query over volumes
-                                to consider for binding.
-                              properties:
-                                matchExpressions:
-                                  description: matchExpressions is a list of label
-                                    selector requirements. The requirements are ANDed.
-                                  items:
-                                    description: |-
-                                      A label selector requirement is a selector that contains values, a key, and an operator that
-                                      relates the key and values.
-                                    properties:
-                                      key:
-                                        description: key is the label key that the
-                                          selector applies to.
-                                        type: string
-                                      operator:
-                                        description: |-
-                                          operator represents a key's relationship to a set of values.
-                                          Valid operators are In, NotIn, Exists and DoesNotExist.
-                                        type: string
-                                      values:
-                                        description: |-
-                                          values is an array of string values. If the operator is In or NotIn,
-                                          the values array must be non-empty. If the operator is Exists or DoesNotExist,
-                                          the values array must be empty. This array is replaced during a strategic
-                                          merge patch.
-                                        items:
-                                          type: string
-                                        type: array
-                                        x-kubernetes-list-type: atomic
-                                    required:
-                                    - key
-                                    - operator
-                                    type: object
-                                  type: array
-                                  x-kubernetes-list-type: atomic
-                                matchLabels:
-                                  additionalProperties:
-                                    type: string
-                                  description: |-
-                                    matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
-                                    map is equivalent to an element of matchExpressions, whose key field is "key", the
-                                    operator is "In", and the values array contains only "value". The requirements are ANDed.
-                                  type: object
-                              type: object
-                              x-kubernetes-map-type: atomic
-                            storageClassName:
-                              description: |-
-                                storageClassName is the name of the StorageClass required by the claim.
-                                More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1
-                              type: string
-                            volumeAttributesClassName:
-                              description: |-
-                                volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim.
-                                If specified, the CSI driver will create or update the volume with the attributes defined
-                                in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName,
-                                it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass
-                                will be applied to the claim but it's not allowed to reset this field to empty string once it is set.
-                                If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass
-                                will be set by the persistentvolume controller if it exists.
-                                If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be
-                                set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource
-                                exists.
-                                More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/
-                                (Beta) Using this field requires the VolumeAttributesClass feature gate to be enabled (off by default).
-                              type: string
-                            volumeMode:
-                              description: |-
-                                volumeMode defines what type of volume is required by the claim.
-                                Value of Filesystem is implied when not included in claim spec.
-                              type: string
-                            volumeName:
-                              description: volumeName is the binding reference to
-                                the PersistentVolume backing this claim.
-                              type: string
-                          type: object
-                        status:
-                          description: |-
-                            status represents the current information/status of a persistent volume claim.
-                            Read-only.
-                            More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims
-                          properties:
-                            accessModes:
-                              description: |-
-                                accessModes contains the actual access modes the volume backing the PVC has.
-                                More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1
-                              items:
-                                type: string
-                              type: array
-                              x-kubernetes-list-type: atomic
-                            allocatedResourceStatuses:
-                              additionalProperties:
-                                description: |-
-                                  When a controller receives persistentvolume claim update with ClaimResourceStatus for a resource
-                                  that it does not recognizes, then it should ignore that update and let other controllers
-                                  handle it.
-                                type: string
-                              description: "allocatedResourceStatuses stores status
-                                of resource being resized for the given PVC.\nKey
-                                names follow standard Kubernetes label syntax. Valid
-                                values are either:\n\t* Un-prefixed keys:\n\t\t- storage
-                                - the capacity of the volume.\n\t* Custom resources
-                                must use implementation-defined prefixed names such
-                                as \"example.com/my-custom-resource\"\nApart from
-                                above values - keys that are unprefixed or have kubernetes.io
-                                prefix are considered\nreserved and hence may not
-                                be used.\n\nClaimResourceStatus can be in any of following
-                                states:\n\t- ControllerResizeInProgress:\n\t\tState
-                                set when resize controller starts resizing the volume
-                                in control-plane.\n\t- ControllerResizeFailed:\n\t\tState
-                                set when resize has failed in resize controller with
-                                a terminal error.\n\t- NodeResizePending:\n\t\tState
-                                set when resize controller has finished resizing the
-                                volume but further resizing of\n\t\tvolume is needed
-                                on the node.\n\t- NodeResizeInProgress:\n\t\tState
-                                set when kubelet starts resizing the volume.\n\t-
-                                NodeResizeFailed:\n\t\tState set when resizing has
-                                failed in kubelet with a terminal error. Transient
-                                errors don't set\n\t\tNodeResizeFailed.\nFor example:
-                                if expanding a PVC for more capacity - this field
-                                can be one of the following states:\n\t- pvc.status.allocatedResourceStatus['storage']
-                                = \"ControllerResizeInProgress\"\n     - pvc.status.allocatedResourceStatus['storage']
-                                = \"ControllerResizeFailed\"\n     - pvc.status.allocatedResourceStatus['storage']
-                                = \"NodeResizePending\"\n     - pvc.status.allocatedResourceStatus['storage']
-                                = \"NodeResizeInProgress\"\n     - pvc.status.allocatedResourceStatus['storage']
-                                = \"NodeResizeFailed\"\nWhen this field is not set,
-                                it means that no resize operation is in progress for
-                                the given PVC.\n\nA controller that receives PVC update
-                                with previously unknown resourceName or ClaimResourceStatus\nshould
-                                ignore the update for the purpose it was designed.
-                                For example - a controller that\nonly is responsible
-                                for resizing capacity of the volume, should ignore
-                                PVC updates that change other valid\nresources associated
-                                with PVC.\n\nThis is an alpha field and requires enabling
-                                RecoverVolumeExpansionFailure feature."
-                              type: object
-                              x-kubernetes-map-type: granular
-                            allocatedResources:
-                              additionalProperties:
-                                anyOf:
-                                - type: integer
-                                - type: string
-                                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
-                                x-kubernetes-int-or-string: true
-                              description: "allocatedResources tracks the resources
-                                allocated to a PVC including its capacity.\nKey names
-                                follow standard Kubernetes label syntax. Valid values
-                                are either:\n\t* Un-prefixed keys:\n\t\t- storage
-                                - the capacity of the volume.\n\t* Custom resources
-                                must use implementation-defined prefixed names such
-                                as \"example.com/my-custom-resource\"\nApart from
-                                above values - keys that are unprefixed or have kubernetes.io
-                                prefix are considered\nreserved and hence may not
-                                be used.\n\nCapacity reported here may be larger than
-                                the actual capacity when a volume expansion operation\nis
-                                requested.\nFor storage quota, the larger value from
-                                allocatedResources and PVC.spec.resources is used.\nIf
-                                allocatedResources is not set, PVC.spec.resources
-                                alone is used for quota calculation.\nIf a volume
-                                expansion capacity request is lowered, allocatedResources
-                                is only\nlowered if there are no expansion operations
-                                in progress and if the actual volume capacity\nis
-                                equal or lower than the requested capacity.\n\nA controller
-                                that receives PVC update with previously unknown resourceName\nshould
-                                ignore the update for the purpose it was designed.
-                                For example - a controller that\nonly is responsible
-                                for resizing capacity of the volume, should ignore
-                                PVC updates that change other valid\nresources associated
-                                with PVC.\n\nThis is an alpha field and requires enabling
-                                RecoverVolumeExpansionFailure feature."
-                              type: object
-                            capacity:
-                              additionalProperties:
-                                anyOf:
-                                - type: integer
-                                - type: string
-                                pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
-                                x-kubernetes-int-or-string: true
-                              description: capacity represents the actual resources
-                                of the underlying volume.
-                              type: object
-                            conditions:
-                              description: |-
-                                conditions is the current Condition of persistent volume claim. If underlying persistent volume is being
-                                resized then the Condition will be set to 'Resizing'.
-                              items:
-                                description: PersistentVolumeClaimCondition contains
-                                  details about state of pvc
-                                properties:
-                                  lastProbeTime:
-                                    description: lastProbeTime is the time we probed
-                                      the condition.
-                                    format: date-time
-                                    type: string
-                                  lastTransitionTime:
-                                    description: lastTransitionTime is the time the
-                                      condition transitioned from one status to another.
-                                    format: date-time
-                                    type: string
-                                  message:
-                                    description: message is the human-readable message
-                                      indicating details about last transition.
-                                    type: string
-                                  reason:
-                                    description: |-
-                                      reason is a unique, this should be a short, machine understandable string that gives the reason
-                                      for condition's last transition. If it reports "Resizing" that means the underlying
-                                      persistent volume is being resized.
-                                    type: string
-                                  status:
-                                    description: |-
-                                      Status is the status of the condition.
-                                      Can be True, False, Unknown.
-                                      More info: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/persistent-volume-claim-v1/#:~:text=state%20of%20pvc-,conditions.status,-(string)%2C%20required
-                                    type: string
-                                  type:
-                                    description: |-
-                                      Type is the type of the condition.
-                                      More info: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/persistent-volume-claim-v1/#:~:text=set%20to%20%27ResizeStarted%27.-,PersistentVolumeClaimCondition,-contains%20details%20about
-                                    type: string
-                                required:
-                                - status
-                                - type
-                                type: object
-                              type: array
-                              x-kubernetes-list-map-keys:
-                              - type
-                              x-kubernetes-list-type: map
-                            currentVolumeAttributesClassName:
-                              description: |-
-                                currentVolumeAttributesClassName is the current name of the VolumeAttributesClass the PVC is using.
-                                When unset, there is no VolumeAttributeClass applied to this PersistentVolumeClaim
-                                This is a beta field and requires enabling VolumeAttributesClass feature (off by default).
-                              type: string
-                            modifyVolumeStatus:
-                              description: |-
-                                ModifyVolumeStatus represents the status object of ControllerModifyVolume operation.
-                                When this is unset, there is no ModifyVolume operation being attempted.
-                                This is a beta field and requires enabling VolumeAttributesClass feature (off by default).
-                              properties:
-                                status:
-                                  description: "status is the status of the ControllerModifyVolume
-                                    operation. It can be in any of following states:\n
-                                    - Pending\n   Pending indicates that the PersistentVolumeClaim
-                                    cannot be modified due to unmet requirements,
-                                    such as\n   the specified VolumeAttributesClass
-                                    not existing.\n - InProgress\n   InProgress indicates
-                                    that the volume is being modified.\n - Infeasible\n
-                                    \ Infeasible indicates that the request has been
-                                    rejected as invalid by the CSI driver. To\n\t
-                                    \ resolve the error, a valid VolumeAttributesClass
-                                    needs to be specified.\nNote: New statuses can
-                                    be added in the future. Consumers should check
-                                    for unknown statuses and fail appropriately."
-                                  type: string
-                                targetVolumeAttributesClassName:
-                                  description: targetVolumeAttributesClassName is
-                                    the name of the VolumeAttributesClass the PVC
-                                    currently being reconciled
-                                  type: string
-                              required:
-                              - status
-                              type: object
-                            phase:
-                              description: phase represents the current phase of PersistentVolumeClaim.
-                              type: string
-                          type: object
                       type: object
+                      x-kubernetes-preserve-unknown-fields: true
                     type: array
                   configMaps:
                     description: |-
diff --git a/config/crd/patches/operator.victoriametrics.com_vlclusters.yaml b/config/crd/patches/operator.victoriametrics.com_vlclusters.yaml
new file mode 100644
index 00000000..18ace6be
--- /dev/null
+++ b/config/crd/patches/operator.victoriametrics.com_vlclusters.yaml
@@ -0,0 +1,217 @@
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/requestsLoadBalancer/properties/spec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/requestsLoadBalancer/properties/spec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/storage/properties/volumeClaimTemplate/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/storage/properties/volumeClaimTemplate/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/affinity/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/affinity/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/claimTemplates/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/claimTemplates/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/containers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/containers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/dnsConfig/items
+  value:
+    x-kubernetes-preserve-unknown-fields: true
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/extraEnvs/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/extraEnvs/items/properties/valueFrom
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/initContainers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/initContainers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/topologySpreadConstraints/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/topologySpreadConstraints/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/serviceSpec/properties/spec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/serviceSpec/properties/spec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/volumes/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/volumes/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/startupProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/startupProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/readinessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/readinessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/livenessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/livenessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/securityContext/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/securityContext/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/serviceScrapeSpec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlstorage/properties/serviceScrapeSpec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/affinity/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/affinity/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/containers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/containers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/hpa/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/hpa/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/dnsConfig/items
+  value:
+    x-kubernetes-preserve-unknown-fields: true
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/extraEnvs/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/extraEnvs/items/properties/valueFrom
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/initContainers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/initContainers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/topologySpreadConstraints/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/topologySpreadConstraints/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/serviceSpec/properties/spec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/serviceSpec/properties/spec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/volumes/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/volumes/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/startupProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/startupProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/readinessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/readinessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/livenessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/livenessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/securityContext/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/securityContext/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/serviceScrapeSpec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlselect/properties/serviceScrapeSpec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/affinity/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/affinity/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/containers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/containers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/hpa/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/hpa/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/dnsConfig/items
+  value:
+    x-kubernetes-preserve-unknown-fields: true
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/extraEnvs/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/extraEnvs/items/properties/valueFrom
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/initContainers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/initContainers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/topologySpreadConstraints/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/topologySpreadConstraints/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/serviceSpec/properties/spec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/serviceSpec/properties/spec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/volumes/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/volumes/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/startupProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/startupProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/readinessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/readinessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/livenessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/livenessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/securityContext/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/securityContext/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/serviceScrapeSpec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vlinsert/properties/serviceScrapeSpec/properties
diff --git a/config/crd/patches/operator.victoriametrics.com_vmclusters.yaml b/config/crd/patches/operator.victoriametrics.com_vmclusters.yaml
index 8694eb56..1f6c01dd 100644
--- a/config/crd/patches/operator.victoriametrics.com_vmclusters.yaml
+++ b/config/crd/patches/operator.victoriametrics.com_vmclusters.yaml
@@ -14,8 +14,10 @@
 - op: remove
   path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vmstorage/properties/affinity/properties
 - op: add
-  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vmstorage/properties/claimTemplates/items/properties/metadata/x-kubernetes-preserve-unknown-fields
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vmstorage/properties/claimTemplates/items/x-kubernetes-preserve-unknown-fields
   value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vmstorage/properties/claimTemplates/items/properties
 - op: add
   path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vmstorage/properties/containers/items/x-kubernetes-preserve-unknown-fields
   value: true
@@ -86,8 +88,10 @@
 - op: remove
   path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vmselect/properties/affinity/properties
 - op: add
-  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vmselect/properties/claimTemplates/items/properties/metadata/x-kubernetes-preserve-unknown-fields
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vmselect/properties/claimTemplates/items/properties/x-kubernetes-preserve-unknown-fields
   value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vmselect/properties/claimTemplates/items/properties
 - op: add
   path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/vmselect/properties/containers/items/x-kubernetes-preserve-unknown-fields
   value: true
diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml
index 8927d4ba..177d7066 100644
--- a/config/rbac/kustomization.yaml
+++ b/config/rbac/kustomization.yaml
@@ -13,6 +13,8 @@ resources:
 # default, aiding admins in cluster management. Those roles are
 # not used by the Project itself. You can comment the following lines
 # if you do not want those helpers be installed with your Project.
+# - operator_vlcluster_editor_role.yaml
+# - operator_vlcluster_viewer_role.yaml
 # - operator_vlsingle_editor_role.yaml
 # - operator_vlsingle_viewer_role.yaml
 # - operator_vlogs_editor_role.yaml
diff --git a/config/rbac/operator_vlcluster_editor_role.yaml b/config/rbac/operator_vlcluster_editor_role.yaml
new file mode 100644
index 00000000..19d55482
--- /dev/null
+++ b/config/rbac/operator_vlcluster_editor_role.yaml
@@ -0,0 +1,27 @@
+# permissions for end users to edit vlclusters.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/name: victoriametrics-operator
+    app.kubernetes.io/managed-by: kustomize
+  name: operator-vlcluster-editor-role
+rules:
+- apiGroups:
+  - operator.victoriametrics.com
+  resources:
+  - vlclusters
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - operator.victoriametrics.com
+  resources:
+  - vlclusters/status
+  verbs:
+  - get
diff --git a/config/rbac/operator_vlcluster_viewer_role.yaml b/config/rbac/operator_vlcluster_viewer_role.yaml
new file mode 100644
index 00000000..a984a574
--- /dev/null
+++ b/config/rbac/operator_vlcluster_viewer_role.yaml
@@ -0,0 +1,23 @@
+# permissions for end users to view vlclusters.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/name: victoriametrics-operator
+    app.kubernetes.io/managed-by: kustomize
+  name: operator-vlcluster-viewer-role
+rules:
+- apiGroups:
+  - operator.victoriametrics.com
+  resources:
+  - vlclusters
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - operator.victoriametrics.com
+  resources:
+  - vlclusters/status
+  verbs:
+  - get
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index c4d1d06c..d69a985a 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -136,6 +136,9 @@ rules:
   - vlsingles
   - vlsingles/finalizers
   - vlsingles/status
+  - vlclusters
+  - vlclusters/finalizers
+  - vlclusters/status
   - vmagents
   - vmagents/finalizers
   - vmagents/status
diff --git a/config/samples/operator_v1_vlcluster.yaml b/config/samples/operator_v1_vlcluster.yaml
new file mode 100644
index 00000000..4e327e8a
--- /dev/null
+++ b/config/samples/operator_v1_vlcluster.yaml
@@ -0,0 +1,18 @@
+apiVersion: operator.victoriametrics.com/v1
+kind: VLCluster
+metadata:
+  labels:
+    app.kubernetes.io/name: victoriametrics-operator
+    app.kubernetes.io/managed-by: kustomize
+  name: vlcluster-sample
+spec:
+  vlinsert:
+    replicaCount: 1
+  vlselect:
+    replicaCount: 1
+  vlstorage:
+    retentionPeriod: "1y"
+    replicaCount: 2
+  requestsLoadBalancer:
+    enabled: true
+    spec: {}
diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml
index 85d36975..de4b6d33 100644
--- a/config/webhook/manifests.yaml
+++ b/config/webhook/manifests.yaml
@@ -24,6 +24,26 @@ webhooks:
     resources:
     - vlsingle
   sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: system
+      path: /validate-operator-victoriametrics-com-v1-vlcluster
+  failurePolicy: Fail
+  name: vvlcluster.kb.io
+  rules:
+  - apiGroups:
+    - operator.victoriametrics.com
+    apiVersions:
+    - v1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - vlcluster
+  sideEffects: None
 - admissionReviewVersions:
   - v1
   clientConfig:
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 32bf5b34..96372506 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -20,6 +20,7 @@ To perform migration to the `VLSingle` please follow [this docs](https://docs.vi
 
 * FEATURE: [config-reloader](https://github.com/VictoriaMetrics/operator/tree/master/cmd/config-reloader): exclude hidden directories from watch. It should prevent possible errors with hidden symlinks, which may occur during dir walk. See [this commit](https://github.com/VictoriaMetrics/operator/commit/13155fb2786264ce99ba5e33640219ee6ad326bda) for details.
 * FEATURE: [operator](https://docs.victoriametrics.com/operator/api): introduce new resource `VLSingle`. It replaces deprecated `VLogs`. See [this issue](https://github.com/VictoriaMetrics/operator/issues/1339) for details.
+* FEATURE: [operator](https://docs.victoriametrics.com/operator/api): introduce new resource `VLCluster`. See [this issue](https://github.com/VictoriaMetrics/operator/issues/1314) and [this doc](https://docs.victoriametrics.com/operator/resources/vlcluster/) for details.
 * FEATURE: [operator](https://docs.victoriametrics.com/operator/api): manifests distributed via [GitHub release](https://github.com/VictoriaMetrics/operator/releases) artifacts now include the label `app.kubernetes.io/instance: default`, and the value of `app.kubernetes.io/name` has been changed to `victoria-metrics-operator`. See [#1364](https://github.com/VictoriaMetrics/operator/pull/1364) for details.
 * FEATURE: [operator](https://docs.victoriametrics.com/operator/api): add new field `proxyURL` to the `VMagent` `remoteWriteSpec`. See [this PR](https://github.com/VictoriaMetrics/operator/pull/1379) for details. Thanks to the @DanielLeone
 * FEATURE: [operator](https://docs.victoriametrics.com/operator): introduce new flags for leader election configuration - `leader-elect-lease-duration`, `leader-elect-renew-deadline`. See [this issue](https://github.com/VictoriaMetrics/operator/issues/1362) for details.
diff --git a/docs/api.md b/docs/api.md
index 36a65779..bdd811a9 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -26,10 +26,245 @@ tags:
 Package v1 contains API Schema definitions for the operator v1 API group
 
 ### Resource Types
+- [VLCluster](#vlcluster)
 - [VLSingle](#vlsingle)
 
 
 
+#### FieldsListString
+
+_Underlying type:_ _string_
+
+FieldsListString reprents list of json encoded strings
+["field"] or ["field1","field2"]
+
+Appears in: [SyslogTCPListener](#syslogtcplistener), [SyslogUDPListener](#syslogudplistener)
+
+
+
+#### SyslogServerSpec
+
+
+
+SyslogServerSpec defines syslog servers configuration
+
+Appears in: [VLInsert](#vlinsert)
+
+| Field | Description |
+| --- | --- |
+| tcpListeners<a href="#syslogserverspec-tcplisteners" id="syslogserverspec-tcplisteners">#</a><br/>_[SyslogTCPListener](#syslogtcplistener) array_ | _(Required)_<br/>TCPListeners defines syslog server TCP listener configuration |
+| udpListeners<a href="#syslogserverspec-udplisteners" id="syslogserverspec-udplisteners">#</a><br/>_[SyslogUDPListener](#syslogudplistener) array_ | _(Required)_<br/>UDPListeners defines syslog server UDP listener configuration |
+
+
+#### SyslogTCPListener
+
+
+
+SyslogTCPListener defines configuration for TCP syslog server listen
+
+Appears in: [SyslogServerSpec](#syslogserverspec)
+
+| Field | Description |
+| --- | --- |
+| compressMethod<a href="#syslogtcplistener-compressmethod" id="syslogtcplistener-compressmethod">#</a><br/>_string_ | _(Optional)_<br/>CompressMethod for syslog messages<br />see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#compression |
+| decolorizeFields<a href="#syslogtcplistener-decolorizefields" id="syslogtcplistener-decolorizefields">#</a><br/>_[FieldsListString](#fieldsliststring)_ | _(Optional)_<br/>DecolorizeFields to remove ANSI color codes across logs<br />see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#decolorizing-fields |
+| ingoreFields<a href="#syslogtcplistener-ingorefields" id="syslogtcplistener-ingorefields">#</a><br/>_[FieldsListString](#fieldsliststring)_ | _(Optional)_<br/>IgnoreFields to ignore at logs<br />see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#dropping-fields |
+| listenPort<a href="#syslogtcplistener-listenport" id="syslogtcplistener-listenport">#</a><br/>_integer_ | _(Required)_<br/>ListenPort defines listen port |
+| streamFields<a href="#syslogtcplistener-streamfields" id="syslogtcplistener-streamfields">#</a><br/>_[FieldsListString](#fieldsliststring)_ | _(Optional)_<br/>StreamFields to use as log stream labels<br />see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#stream-fields |
+| tenantID<a href="#syslogtcplistener-tenantid" id="syslogtcplistener-tenantid">#</a><br/>_string_ | _(Optional)_<br/>TenantID for logs ingested in form of accountID:projectID<br />see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#multiple-configs |
+| tlsConfig<a href="#syslogtcplistener-tlsconfig" id="syslogtcplistener-tlsconfig">#</a><br/>_[TLSServerConfig](#tlsserverconfig)_ | _(Optional)_<br/> |
+
+
+#### SyslogUDPListener
+
+
+
+SyslogUDPListener defines configuration for UDP syslog server listen
+
+Appears in: [SyslogServerSpec](#syslogserverspec)
+
+| Field | Description |
+| --- | --- |
+| compressMethod<a href="#syslogudplistener-compressmethod" id="syslogudplistener-compressmethod">#</a><br/>_string_ | _(Optional)_<br/>CompressMethod for syslog messages<br />see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#compression |
+| decolorizeFields<a href="#syslogudplistener-decolorizefields" id="syslogudplistener-decolorizefields">#</a><br/>_[FieldsListString](#fieldsliststring)_ | _(Optional)_<br/>DecolorizeFields to remove ANSI color codes across logs<br />see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#decolorizing-fields |
+| ingoreFields<a href="#syslogudplistener-ingorefields" id="syslogudplistener-ingorefields">#</a><br/>_[FieldsListString](#fieldsliststring)_ | _(Optional)_<br/>IgnoreFields to ignore at logs<br />see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#dropping-fields |
+| listenPort<a href="#syslogudplistener-listenport" id="syslogudplistener-listenport">#</a><br/>_integer_ | _(Required)_<br/>ListenPort defines listen port |
+| streamFields<a href="#syslogudplistener-streamfields" id="syslogudplistener-streamfields">#</a><br/>_[FieldsListString](#fieldsliststring)_ | _(Optional)_<br/>StreamFields to use as log stream labels<br />see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#stream-fields |
+| tenantID<a href="#syslogudplistener-tenantid" id="syslogudplistener-tenantid">#</a><br/>_string_ | _(Optional)_<br/>TenantID for logs ingested in form of accountID:projectID<br />see https://docs.victoriametrics.com/victorialogs/data-ingestion/syslog/#multiple-configs |
+
+
+#### TLSServerConfig
+
+
+
+TLSServerConfig defines VictoriaMetrics TLS configuration for the application's server
+
+Appears in: [SyslogTCPListener](#syslogtcplistener)
+
+| Field | Description |
+| --- | --- |
+| certFile<a href="#tlsserverconfig-certfile" id="tlsserverconfig-certfile">#</a><br/>_string_ | _(Optional)_<br/>CertFile defines path to the pre-mounted file with certificate<br />mutually exclusive with CertSecretRef |
+| certSecret<a href="#tlsserverconfig-certsecret" id="tlsserverconfig-certsecret">#</a><br/>_[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#secretkeyselector-v1-core)_ | _(Optional)_<br/>CertSecretRef defines reference for secret with certificate content under given key<br />mutually exclusive with CertFile |
+| keyFile<a href="#tlsserverconfig-keyfile" id="tlsserverconfig-keyfile">#</a><br/>_string_ | _(Optional)_<br/>KeyFile defines path to the pre-mounted file with certificate key<br />mutually exclusive with KeySecretRef |
+| keySecret<a href="#tlsserverconfig-keysecret" id="tlsserverconfig-keysecret">#</a><br/>_[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#secretkeyselector-v1-core)_ | _(Optional)_<br/>Key defines reference for secret with certificate key content under given key<br />mutually exclusive with KeyFile |
+
+
+#### VLCluster
+
+
+
+VLCluster is the Schema for the vlclusters API
+
+
+
+| Field | Description |
+| --- | --- |
+| apiVersion<br/>_string_ | (Required)<br/>`operator.victoriametrics.com/v1` |
+| kind<br/>_string_ | (Required)<br/>`VLCluster` |
+| metadata<a href="#vlcluster-metadata" id="vlcluster-metadata">#</a><br/>_[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#objectmeta-v1-meta)_ | _(Required)_<br/>Refer to Kubernetes API documentation for fields of `metadata`. |
+| spec<a href="#vlcluster-spec" id="vlcluster-spec">#</a><br/>_[VLClusterSpec](#vlclusterspec)_ | _(Required)_<br/> |
+
+
+#### VLClusterSpec
+
+
+
+VLClusterSpec defines the desired state of VLCluster
+
+Appears in: [VLCluster](#vlcluster)
+
+| Field | Description |
+| --- | --- |
+| clusterDomainName<a href="#vlclusterspec-clusterdomainname" id="vlclusterspec-clusterdomainname">#</a><br/>_string_ | _(Optional)_<br/>ClusterDomainName defines domain name suffix for in-cluster dns addresses<br />aka .cluster.local<br />used by vlinsert and vlselect to build vlstorage address |
+| clusterVersion<a href="#vlclusterspec-clusterversion" id="vlclusterspec-clusterversion">#</a><br/>_string_ | _(Optional)_<br/>ClusterVersion defines default images tag for all components.<br />it can be overwritten with component specific image.tag value. |
+| imagePullSecrets<a href="#vlclusterspec-imagepullsecrets" id="vlclusterspec-imagepullsecrets">#</a><br/>_[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core) array_ | _(Optional)_<br/>ImagePullSecrets An optional list of references to secrets in the same namespace<br />to use for pulling images from registries<br />see https://kubernetes.io/docs/concepts/containers/images/#referring-to-an-imagepullsecrets-on-a-pod |
+| managedMetadata<a href="#vlclusterspec-managedmetadata" id="vlclusterspec-managedmetadata">#</a><br/>_[ManagedObjectsMetadata](#managedobjectsmetadata)_ | _(Required)_<br/>ManagedMetadata defines metadata that will be added to the all objects<br />created by operator for the given CustomResource |
+| paused<a href="#vlclusterspec-paused" id="vlclusterspec-paused">#</a><br/>_boolean_ | _(Optional)_<br/>Paused If set to true all actions on the underlying managed objects are not<br />going to be performed, except for delete actions. |
+| requestsLoadBalancer<a href="#vlclusterspec-requestsloadbalancer" id="vlclusterspec-requestsloadbalancer">#</a><br/>_[VMAuthLoadBalancer](#vmauthloadbalancer)_ | _(Required)_<br/>RequestsLoadBalancer configures load-balancing for vlinsert and vlselect requests.<br />It helps to evenly spread load across pods.<br />Usually it's not possible with Kubernetes TCP-based services. |
+| serviceAccountName<a href="#vlclusterspec-serviceaccountname" id="vlclusterspec-serviceaccountname">#</a><br/>_string_ | _(Optional)_<br/>ServiceAccountName is the name of the ServiceAccount to use to run the<br />VLSelect, VLInsert and VLStorage Pods. |
+| useStrictSecurity<a href="#vlclusterspec-usestrictsecurity" id="vlclusterspec-usestrictsecurity">#</a><br/>_boolean_ | _(Optional)_<br/>UseStrictSecurity enables strict security mode for component<br />it restricts disk writes access<br />uses non-root user out of the box<br />drops not needed security permissions |
+| vlinsert<a href="#vlclusterspec-vlinsert" id="vlclusterspec-vlinsert">#</a><br/>_[VLInsert](#vlinsert)_ | _(Required)_<br/> |
+| vlselect<a href="#vlclusterspec-vlselect" id="vlclusterspec-vlselect">#</a><br/>_[VLSelect](#vlselect)_ | _(Required)_<br/> |
+| vlstorage<a href="#vlclusterspec-vlstorage" id="vlclusterspec-vlstorage">#</a><br/>_[VLStorage](#vlstorage)_ | _(Required)_<br/> |
+
+
+
+
+#### VLInsert
+
+
+
+VLInsert defines vlinsert component configuration at victoria-logs cluster
+
+Appears in: [VLClusterSpec](#vlclusterspec)
+
+| Field | Description |
+| --- | --- |
+| affinity<a href="#vlinsert-affinity" id="vlinsert-affinity">#</a><br/>_[Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#affinity-v1-core)_ | _(Optional)_<br/>Affinity If specified, the pod's scheduling constraints. |
+| configMaps<a href="#vlinsert-configmaps" id="vlinsert-configmaps">#</a><br/>_string array_ | _(Optional)_<br/>ConfigMaps is a list of ConfigMaps in the same namespace as the Application<br />object, which shall be mounted into the Application container<br />at /etc/vm/configs/CONFIGMAP_NAME folder |
+| containers<a href="#vlinsert-containers" id="vlinsert-containers">#</a><br/>_[Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#container-v1-core) array_ | _(Optional)_<br/>Containers property allows to inject additions sidecars or to patch existing containers.<br />It can be useful for proxies, backup, etc. |
+| disableAutomountServiceAccountToken<a href="#vlinsert-disableautomountserviceaccounttoken" id="vlinsert-disableautomountserviceaccounttoken">#</a><br/>_boolean_ | _(Optional)_<br/>DisableAutomountServiceAccountToken whether to disable serviceAccount auto mount by Kubernetes (available from v0.54.0).<br />Operator will conditionally create volumes and volumeMounts for containers if it requires k8s API access.<br />For example, vmagent and vm-config-reloader requires k8s API access.<br />Operator creates volumes with name: "kube-api-access", which can be used as volumeMount for extraContainers if needed.<br />And also adds VolumeMounts at /var/run/secrets/kubernetes.io/serviceaccount. |
+| disableSelfServiceScrape<a href="#vlinsert-disableselfservicescrape" id="vlinsert-disableselfservicescrape">#</a><br/>_boolean_ | _(Optional)_<br/>DisableSelfServiceScrape controls creation of VMServiceScrape by operator<br />for the application.<br />Has priority over `VM_DISABLESELFSERVICESCRAPECREATION` operator env variable |
+| dnsConfig<a href="#vlinsert-dnsconfig" id="vlinsert-dnsconfig">#</a><br/>_[PodDNSConfig](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#poddnsconfig-v1-core)_ | _(Optional)_<br/>Specifies the DNS parameters of a pod.<br />Parameters specified here will be merged to the generated DNS<br />configuration based on DNSPolicy. |
+| dnsPolicy<a href="#vlinsert-dnspolicy" id="vlinsert-dnspolicy">#</a><br/>_[DNSPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#dnspolicy-v1-core)_ | _(Optional)_<br/>DNSPolicy sets DNS policy for the pod |
+| extraArgs<a href="#vlinsert-extraargs" id="vlinsert-extraargs">#</a><br/>_object (keys:string, values:string)_ | _(Optional)_<br/>ExtraArgs that will be passed to the application container<br />for example remoteWrite.tmpDataPath: /tmp |
+| extraEnvs<a href="#vlinsert-extraenvs" id="vlinsert-extraenvs">#</a><br/>_[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envvar-v1-core) array_ | _(Optional)_<br/>ExtraEnvs that will be passed to the application container |
+| extraEnvsFrom<a href="#vlinsert-extraenvsfrom" id="vlinsert-extraenvsfrom">#</a><br/>_[EnvFromSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envfromsource-v1-core) array_ | _(Optional)_<br/>ExtraEnvsFrom defines source of env variables for the application container<br />could either be secret or configmap |
+| hostAliases<a href="#vlinsert-hostaliases" id="vlinsert-hostaliases">#</a><br/>_[HostAlias](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#hostalias-v1-core) array_ | _(Optional)_<br/>HostAliases provides mapping for ip and hostname,<br />that would be propagated to pod,<br />cannot be used with HostNetwork. |
+| hostNetwork<a href="#vlinsert-hostnetwork" id="vlinsert-hostnetwork">#</a><br/>_boolean_ | _(Optional)_<br/>HostNetwork controls whether the pod may use the node network namespace |
+| host_aliases<a href="#vlinsert-host_aliases" id="vlinsert-host_aliases">#</a><br/>_[HostAlias](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#hostalias-v1-core) array_ | _(Optional)_<br/>HostAliasesUnderScore provides mapping for ip and hostname,<br />that would be propagated to pod,<br />cannot be used with HostNetwork.<br />Has Priority over hostAliases field |
+| hpa<a href="#vlinsert-hpa" id="vlinsert-hpa">#</a><br/>_[EmbeddedHPA](#embeddedhpa)_ | _(Optional)_<br/>Configures horizontal pod autoscaling. |
+| image<a href="#vlinsert-image" id="vlinsert-image">#</a><br/>_[Image](#image)_ | _(Optional)_<br/>Image - docker image settings<br />if no specified operator uses default version from operator config |
+| imagePullSecrets<a href="#vlinsert-imagepullsecrets" id="vlinsert-imagepullsecrets">#</a><br/>_[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core) array_ | _(Optional)_<br/>ImagePullSecrets An optional list of references to secrets in the same namespace<br />to use for pulling images from registries<br />see https://kubernetes.io/docs/concepts/containers/images/#referring-to-an-imagepullsecrets-on-a-pod |
+| initContainers<a href="#vlinsert-initcontainers" id="vlinsert-initcontainers">#</a><br/>_[Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#container-v1-core) array_ | _(Optional)_<br/>InitContainers allows adding initContainers to the pod definition.<br />Any errors during the execution of an initContainer will lead to a restart of the Pod.<br />More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ |
+| logFormat<a href="#vlinsert-logformat" id="vlinsert-logformat">#</a><br/>_string_ | _(Optional)_<br/>LogFormat for VLSelect to be configured with.<br />default or json |
+| logLevel<a href="#vlinsert-loglevel" id="vlinsert-loglevel">#</a><br/>_string_ | _(Optional)_<br/>LogLevel for VLSelect to be configured with. |
+| minReadySeconds<a href="#vlinsert-minreadyseconds" id="vlinsert-minreadyseconds">#</a><br/>_integer_ | _(Optional)_<br/>MinReadySeconds defines a minimum number of seconds to wait before starting update next pod<br />if previous in healthy state<br />Has no effect for VLogs and VMSingle |
+| nodeSelector<a href="#vlinsert-nodeselector" id="vlinsert-nodeselector">#</a><br/>_object (keys:string, values:string)_ | _(Optional)_<br/>NodeSelector Define which Nodes the Pods are scheduled on. |
+| paused<a href="#vlinsert-paused" id="vlinsert-paused">#</a><br/>_boolean_ | _(Optional)_<br/>Paused If set to true all actions on the underlying managed objects are not<br />going to be performed, except for delete actions. |
+| podDisruptionBudget<a href="#vlinsert-poddisruptionbudget" id="vlinsert-poddisruptionbudget">#</a><br/>_[EmbeddedPodDisruptionBudgetSpec](#embeddedpoddisruptionbudgetspec)_ | _(Optional)_<br/>PodDisruptionBudget created by operator |
+| podMetadata<a href="#vlinsert-podmetadata" id="vlinsert-podmetadata">#</a><br/>_[EmbeddedObjectMetadata](#embeddedobjectmetadata)_ | _(Required)_<br/>PodMetadata configures Labels and Annotations which are propagated to the VLSelect pods. |
+| port<a href="#vlinsert-port" id="vlinsert-port">#</a><br/>_string_ | _(Optional)_<br/>Port listen address |
+| priorityClassName<a href="#vlinsert-priorityclassname" id="vlinsert-priorityclassname">#</a><br/>_string_ | _(Optional)_<br/>PriorityClassName class assigned to the Pods |
+| readinessGates<a href="#vlinsert-readinessgates" id="vlinsert-readinessgates">#</a><br/>_[PodReadinessGate](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#podreadinessgate-v1-core) array_ | _(Required)_<br/>ReadinessGates defines pod readiness gates |
+| replicaCount<a href="#vlinsert-replicacount" id="vlinsert-replicacount">#</a><br/>_integer_ | _(Optional)_<br/>ReplicaCount is the expected size of the Application. |
+| resources<a href="#vlinsert-resources" id="vlinsert-resources">#</a><br/>_[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#resourcerequirements-v1-core)_ | _(Optional)_<br/>Resources container resource request and limits, https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/<br />if not defined default resources from operator config will be used |
+| revisionHistoryLimitCount<a href="#vlinsert-revisionhistorylimitcount" id="vlinsert-revisionhistorylimitcount">#</a><br/>_integer_ | _(Optional)_<br/>The number of old ReplicaSets to retain to allow rollback in deployment or<br />maximum number of revisions that will be maintained in the Deployment revision history.<br />Has no effect at StatefulSets<br />Defaults to 10. |
+| rollingUpdate<a href="#vlinsert-rollingupdate" id="vlinsert-rollingupdate">#</a><br/>_[RollingUpdateDeployment](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#rollingupdatedeployment-v1-apps)_ | _(Optional)_<br/>RollingUpdate - overrides deployment update params. |
+| runtimeClassName<a href="#vlinsert-runtimeclassname" id="vlinsert-runtimeclassname">#</a><br/>_string_ | _(Optional)_<br/>RuntimeClassName - defines runtime class for kubernetes pod.<br />https://kubernetes.io/docs/concepts/containers/runtime-class/ |
+| schedulerName<a href="#vlinsert-schedulername" id="vlinsert-schedulername">#</a><br/>_string_ | _(Optional)_<br/>SchedulerName - defines kubernetes scheduler name |
+| secrets<a href="#vlinsert-secrets" id="vlinsert-secrets">#</a><br/>_string array_ | _(Optional)_<br/>Secrets is a list of Secrets in the same namespace as the Application<br />object, which shall be mounted into the Application container<br />at /etc/vm/secrets/SECRET_NAME folder |
+| securityContext<a href="#vlinsert-securitycontext" id="vlinsert-securitycontext">#</a><br/>_[SecurityContext](#securitycontext)_ | _(Optional)_<br/>SecurityContext holds pod-level security attributes and common container settings.<br />This defaults to the default PodSecurityContext. |
+| serviceScrapeSpec<a href="#vlinsert-servicescrapespec" id="vlinsert-servicescrapespec">#</a><br/>_[VMServiceScrapeSpec](#vmservicescrapespec)_ | _(Optional)_<br/>ServiceScrapeSpec that will be added to vlselect VMServiceScrape spec |
+| serviceSpec<a href="#vlinsert-servicespec" id="vlinsert-servicespec">#</a><br/>_[AdditionalServiceSpec](#additionalservicespec)_ | _(Optional)_<br/>ServiceSpec that will be added to vlselect service spec |
+| syslogSpec<a href="#vlinsert-syslogspec" id="vlinsert-syslogspec">#</a><br/>_[SyslogServerSpec](#syslogserverspec)_ | _(Optional)_<br/>SyslogSpec defines syslog listener configuration |
+| terminationGracePeriodSeconds<a href="#vlinsert-terminationgraceperiodseconds" id="vlinsert-terminationgraceperiodseconds">#</a><br/>_integer_ | _(Optional)_<br/>TerminationGracePeriodSeconds period for container graceful termination |
+| tolerations<a href="#vlinsert-tolerations" id="vlinsert-tolerations">#</a><br/>_[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#toleration-v1-core) array_ | _(Optional)_<br/>Tolerations If specified, the pod's tolerations. |
+| topologySpreadConstraints<a href="#vlinsert-topologyspreadconstraints" id="vlinsert-topologyspreadconstraints">#</a><br/>_[TopologySpreadConstraint](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#topologyspreadconstraint-v1-core) array_ | _(Optional)_<br/>TopologySpreadConstraints embedded kubernetes pod configuration option,<br />controls how pods are spread across your cluster among failure-domains<br />such as regions, zones, nodes, and other user-defined topology domains<br />https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ |
+| updateStrategy<a href="#vlinsert-updatestrategy" id="vlinsert-updatestrategy">#</a><br/>_[DeploymentStrategyType](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#deploymentstrategytype-v1-apps)_ | _(Optional)_<br/>UpdateStrategy - overrides default update strategy. |
+| useDefaultResources<a href="#vlinsert-usedefaultresources" id="vlinsert-usedefaultresources">#</a><br/>_boolean_ | _(Optional)_<br/>UseDefaultResources controls resource settings<br />By default, operator sets built-in resource requirements |
+| useStrictSecurity<a href="#vlinsert-usestrictsecurity" id="vlinsert-usestrictsecurity">#</a><br/>_boolean_ | _(Optional)_<br/>UseStrictSecurity enables strict security mode for component<br />it restricts disk writes access<br />uses non-root user out of the box<br />drops not needed security permissions |
+| volumeMounts<a href="#vlinsert-volumemounts" id="vlinsert-volumemounts">#</a><br/>_[VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#volumemount-v1-core) array_ | _(Optional)_<br/>VolumeMounts allows configuration of additional VolumeMounts on the output Deployment/StatefulSet definition.<br />VolumeMounts specified will be appended to other VolumeMounts in the Application container |
+| volumes<a href="#vlinsert-volumes" id="vlinsert-volumes">#</a><br/>_[Volume](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#volume-v1-core) array_ | _(Required)_<br/>Volumes allows configuration of additional volumes on the output Deployment/StatefulSet definition.<br />Volumes specified will be appended to other volumes that are generated.<br />/ +optional |
+
+
+#### VLSelect
+
+
+
+VLSelect defines vlselect component configuration at victoria-logs cluster
+
+Appears in: [VLClusterSpec](#vlclusterspec)
+
+| Field | Description |
+| --- | --- |
+| affinity<a href="#vlselect-affinity" id="vlselect-affinity">#</a><br/>_[Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#affinity-v1-core)_ | _(Optional)_<br/>Affinity If specified, the pod's scheduling constraints. |
+| configMaps<a href="#vlselect-configmaps" id="vlselect-configmaps">#</a><br/>_string array_ | _(Optional)_<br/>ConfigMaps is a list of ConfigMaps in the same namespace as the Application<br />object, which shall be mounted into the Application container<br />at /etc/vm/configs/CONFIGMAP_NAME folder |
+| containers<a href="#vlselect-containers" id="vlselect-containers">#</a><br/>_[Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#container-v1-core) array_ | _(Optional)_<br/>Containers property allows to inject additions sidecars or to patch existing containers.<br />It can be useful for proxies, backup, etc. |
+| disableAutomountServiceAccountToken<a href="#vlselect-disableautomountserviceaccounttoken" id="vlselect-disableautomountserviceaccounttoken">#</a><br/>_boolean_ | _(Optional)_<br/>DisableAutomountServiceAccountToken whether to disable serviceAccount auto mount by Kubernetes (available from v0.54.0).<br />Operator will conditionally create volumes and volumeMounts for containers if it requires k8s API access.<br />For example, vmagent and vm-config-reloader requires k8s API access.<br />Operator creates volumes with name: "kube-api-access", which can be used as volumeMount for extraContainers if needed.<br />And also adds VolumeMounts at /var/run/secrets/kubernetes.io/serviceaccount. |
+| disableSelfServiceScrape<a href="#vlselect-disableselfservicescrape" id="vlselect-disableselfservicescrape">#</a><br/>_boolean_ | _(Optional)_<br/>DisableSelfServiceScrape controls creation of VMServiceScrape by operator<br />for the application.<br />Has priority over `VM_DISABLESELFSERVICESCRAPECREATION` operator env variable |
+| dnsConfig<a href="#vlselect-dnsconfig" id="vlselect-dnsconfig">#</a><br/>_[PodDNSConfig](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#poddnsconfig-v1-core)_ | _(Optional)_<br/>Specifies the DNS parameters of a pod.<br />Parameters specified here will be merged to the generated DNS<br />configuration based on DNSPolicy. |
+| dnsPolicy<a href="#vlselect-dnspolicy" id="vlselect-dnspolicy">#</a><br/>_[DNSPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#dnspolicy-v1-core)_ | _(Optional)_<br/>DNSPolicy sets DNS policy for the pod |
+| extraArgs<a href="#vlselect-extraargs" id="vlselect-extraargs">#</a><br/>_object (keys:string, values:string)_ | _(Optional)_<br/>ExtraArgs that will be passed to the application container<br />for example remoteWrite.tmpDataPath: /tmp |
+| extraEnvs<a href="#vlselect-extraenvs" id="vlselect-extraenvs">#</a><br/>_[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envvar-v1-core) array_ | _(Optional)_<br/>ExtraEnvs that will be passed to the application container |
+| extraEnvsFrom<a href="#vlselect-extraenvsfrom" id="vlselect-extraenvsfrom">#</a><br/>_[EnvFromSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envfromsource-v1-core) array_ | _(Optional)_<br/>ExtraEnvsFrom defines source of env variables for the application container<br />could either be secret or configmap |
+| hostAliases<a href="#vlselect-hostaliases" id="vlselect-hostaliases">#</a><br/>_[HostAlias](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#hostalias-v1-core) array_ | _(Optional)_<br/>HostAliases provides mapping for ip and hostname,<br />that would be propagated to pod,<br />cannot be used with HostNetwork. |
+| hostNetwork<a href="#vlselect-hostnetwork" id="vlselect-hostnetwork">#</a><br/>_boolean_ | _(Optional)_<br/>HostNetwork controls whether the pod may use the node network namespace |
+| host_aliases<a href="#vlselect-host_aliases" id="vlselect-host_aliases">#</a><br/>_[HostAlias](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#hostalias-v1-core) array_ | _(Optional)_<br/>HostAliasesUnderScore provides mapping for ip and hostname,<br />that would be propagated to pod,<br />cannot be used with HostNetwork.<br />Has Priority over hostAliases field |
+| hpa<a href="#vlselect-hpa" id="vlselect-hpa">#</a><br/>_[EmbeddedHPA](#embeddedhpa)_ | _(Optional)_<br/>Configures horizontal pod autoscaling. |
+| image<a href="#vlselect-image" id="vlselect-image">#</a><br/>_[Image](#image)_ | _(Optional)_<br/>Image - docker image settings<br />if no specified operator uses default version from operator config |
+| imagePullSecrets<a href="#vlselect-imagepullsecrets" id="vlselect-imagepullsecrets">#</a><br/>_[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core) array_ | _(Optional)_<br/>ImagePullSecrets An optional list of references to secrets in the same namespace<br />to use for pulling images from registries<br />see https://kubernetes.io/docs/concepts/containers/images/#referring-to-an-imagepullsecrets-on-a-pod |
+| initContainers<a href="#vlselect-initcontainers" id="vlselect-initcontainers">#</a><br/>_[Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#container-v1-core) array_ | _(Optional)_<br/>InitContainers allows adding initContainers to the pod definition.<br />Any errors during the execution of an initContainer will lead to a restart of the Pod.<br />More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ |
+| logFormat<a href="#vlselect-logformat" id="vlselect-logformat">#</a><br/>_string_ | _(Optional)_<br/>LogFormat for VLSelect to be configured with.<br />default or json |
+| logLevel<a href="#vlselect-loglevel" id="vlselect-loglevel">#</a><br/>_string_ | _(Optional)_<br/>LogLevel for VLSelect to be configured with. |
+| minReadySeconds<a href="#vlselect-minreadyseconds" id="vlselect-minreadyseconds">#</a><br/>_integer_ | _(Optional)_<br/>MinReadySeconds defines a minimum number of seconds to wait before starting update next pod<br />if previous in healthy state<br />Has no effect for VLogs and VMSingle |
+| nodeSelector<a href="#vlselect-nodeselector" id="vlselect-nodeselector">#</a><br/>_object (keys:string, values:string)_ | _(Optional)_<br/>NodeSelector Define which Nodes the Pods are scheduled on. |
+| paused<a href="#vlselect-paused" id="vlselect-paused">#</a><br/>_boolean_ | _(Optional)_<br/>Paused If set to true all actions on the underlying managed objects are not<br />going to be performed, except for delete actions. |
+| podDisruptionBudget<a href="#vlselect-poddisruptionbudget" id="vlselect-poddisruptionbudget">#</a><br/>_[EmbeddedPodDisruptionBudgetSpec](#embeddedpoddisruptionbudgetspec)_ | _(Optional)_<br/>PodDisruptionBudget created by operator |
+| podMetadata<a href="#vlselect-podmetadata" id="vlselect-podmetadata">#</a><br/>_[EmbeddedObjectMetadata](#embeddedobjectmetadata)_ | _(Required)_<br/>PodMetadata configures Labels and Annotations which are propagated to the VLSelect pods. |
+| port<a href="#vlselect-port" id="vlselect-port">#</a><br/>_string_ | _(Optional)_<br/>Port listen address |
+| priorityClassName<a href="#vlselect-priorityclassname" id="vlselect-priorityclassname">#</a><br/>_string_ | _(Optional)_<br/>PriorityClassName class assigned to the Pods |
+| readinessGates<a href="#vlselect-readinessgates" id="vlselect-readinessgates">#</a><br/>_[PodReadinessGate](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#podreadinessgate-v1-core) array_ | _(Required)_<br/>ReadinessGates defines pod readiness gates |
+| replicaCount<a href="#vlselect-replicacount" id="vlselect-replicacount">#</a><br/>_integer_ | _(Optional)_<br/>ReplicaCount is the expected size of the Application. |
+| resources<a href="#vlselect-resources" id="vlselect-resources">#</a><br/>_[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#resourcerequirements-v1-core)_ | _(Optional)_<br/>Resources container resource request and limits, https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/<br />if not defined default resources from operator config will be used |
+| revisionHistoryLimitCount<a href="#vlselect-revisionhistorylimitcount" id="vlselect-revisionhistorylimitcount">#</a><br/>_integer_ | _(Optional)_<br/>The number of old ReplicaSets to retain to allow rollback in deployment or<br />maximum number of revisions that will be maintained in the Deployment revision history.<br />Has no effect at StatefulSets<br />Defaults to 10. |
+| rollingUpdate<a href="#vlselect-rollingupdate" id="vlselect-rollingupdate">#</a><br/>_[RollingUpdateDeployment](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#rollingupdatedeployment-v1-apps)_ | _(Optional)_<br/>RollingUpdate - overrides deployment update params. |
+| runtimeClassName<a href="#vlselect-runtimeclassname" id="vlselect-runtimeclassname">#</a><br/>_string_ | _(Optional)_<br/>RuntimeClassName - defines runtime class for kubernetes pod.<br />https://kubernetes.io/docs/concepts/containers/runtime-class/ |
+| schedulerName<a href="#vlselect-schedulername" id="vlselect-schedulername">#</a><br/>_string_ | _(Optional)_<br/>SchedulerName - defines kubernetes scheduler name |
+| secrets<a href="#vlselect-secrets" id="vlselect-secrets">#</a><br/>_string array_ | _(Optional)_<br/>Secrets is a list of Secrets in the same namespace as the Application<br />object, which shall be mounted into the Application container<br />at /etc/vm/secrets/SECRET_NAME folder |
+| securityContext<a href="#vlselect-securitycontext" id="vlselect-securitycontext">#</a><br/>_[SecurityContext](#securitycontext)_ | _(Optional)_<br/>SecurityContext holds pod-level security attributes and common container settings.<br />This defaults to the default PodSecurityContext. |
+| serviceScrapeSpec<a href="#vlselect-servicescrapespec" id="vlselect-servicescrapespec">#</a><br/>_[VMServiceScrapeSpec](#vmservicescrapespec)_ | _(Optional)_<br/>ServiceScrapeSpec that will be added to vlselect VMServiceScrape spec |
+| serviceSpec<a href="#vlselect-servicespec" id="vlselect-servicespec">#</a><br/>_[AdditionalServiceSpec](#additionalservicespec)_ | _(Optional)_<br/>ServiceSpec that will be added to vlselect service spec |
+| terminationGracePeriodSeconds<a href="#vlselect-terminationgraceperiodseconds" id="vlselect-terminationgraceperiodseconds">#</a><br/>_integer_ | _(Optional)_<br/>TerminationGracePeriodSeconds period for container graceful termination |
+| tolerations<a href="#vlselect-tolerations" id="vlselect-tolerations">#</a><br/>_[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#toleration-v1-core) array_ | _(Optional)_<br/>Tolerations If specified, the pod's tolerations. |
+| topologySpreadConstraints<a href="#vlselect-topologyspreadconstraints" id="vlselect-topologyspreadconstraints">#</a><br/>_[TopologySpreadConstraint](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#topologyspreadconstraint-v1-core) array_ | _(Optional)_<br/>TopologySpreadConstraints embedded kubernetes pod configuration option,<br />controls how pods are spread across your cluster among failure-domains<br />such as regions, zones, nodes, and other user-defined topology domains<br />https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ |
+| updateStrategy<a href="#vlselect-updatestrategy" id="vlselect-updatestrategy">#</a><br/>_[DeploymentStrategyType](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#deploymentstrategytype-v1-apps)_ | _(Optional)_<br/>UpdateStrategy - overrides default update strategy. |
+| useDefaultResources<a href="#vlselect-usedefaultresources" id="vlselect-usedefaultresources">#</a><br/>_boolean_ | _(Optional)_<br/>UseDefaultResources controls resource settings<br />By default, operator sets built-in resource requirements |
+| useStrictSecurity<a href="#vlselect-usestrictsecurity" id="vlselect-usestrictsecurity">#</a><br/>_boolean_ | _(Optional)_<br/>UseStrictSecurity enables strict security mode for component<br />it restricts disk writes access<br />uses non-root user out of the box<br />drops not needed security permissions |
+| volumeMounts<a href="#vlselect-volumemounts" id="vlselect-volumemounts">#</a><br/>_[VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#volumemount-v1-core) array_ | _(Optional)_<br/>VolumeMounts allows configuration of additional VolumeMounts on the output Deployment/StatefulSet definition.<br />VolumeMounts specified will be appended to other VolumeMounts in the Application container |
+| volumes<a href="#vlselect-volumes" id="vlselect-volumes">#</a><br/>_[Volume](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#volume-v1-core) array_ | _(Required)_<br/>Volumes allows configuration of additional volumes on the output Deployment/StatefulSet definition.<br />Volumes specified will be appended to other volumes that are generated.<br />/ +optional |
+
+
 #### VLSingle
 
 
@@ -112,6 +347,71 @@ Appears in: [VLSingle](#vlsingle)
 
 
 
+#### VLStorage
+
+
+
+VLStorage defines vlstorage component configuration at victoria-logs cluster
+
+Appears in: [VLClusterSpec](#vlclusterspec)
+
+| Field | Description |
+| --- | --- |
+| affinity<a href="#vlstorage-affinity" id="vlstorage-affinity">#</a><br/>_[Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#affinity-v1-core)_ | _(Optional)_<br/>Affinity If specified, the pod's scheduling constraints. |
+| claimTemplates<a href="#vlstorage-claimtemplates" id="vlstorage-claimtemplates">#</a><br/>_[PersistentVolumeClaim](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#persistentvolumeclaim-v1-core) array_ | _(Required)_<br/>ClaimTemplates allows adding additional VolumeClaimTemplates for StatefulSet |
+| configMaps<a href="#vlstorage-configmaps" id="vlstorage-configmaps">#</a><br/>_string array_ | _(Optional)_<br/>ConfigMaps is a list of ConfigMaps in the same namespace as the Application<br />object, which shall be mounted into the Application container<br />at /etc/vm/configs/CONFIGMAP_NAME folder |
+| containers<a href="#vlstorage-containers" id="vlstorage-containers">#</a><br/>_[Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#container-v1-core) array_ | _(Optional)_<br/>Containers property allows to inject additions sidecars or to patch existing containers.<br />It can be useful for proxies, backup, etc. |
+| disableAutomountServiceAccountToken<a href="#vlstorage-disableautomountserviceaccounttoken" id="vlstorage-disableautomountserviceaccounttoken">#</a><br/>_boolean_ | _(Optional)_<br/>DisableAutomountServiceAccountToken whether to disable serviceAccount auto mount by Kubernetes (available from v0.54.0).<br />Operator will conditionally create volumes and volumeMounts for containers if it requires k8s API access.<br />For example, vmagent and vm-config-reloader requires k8s API access.<br />Operator creates volumes with name: "kube-api-access", which can be used as volumeMount for extraContainers if needed.<br />And also adds VolumeMounts at /var/run/secrets/kubernetes.io/serviceaccount. |
+| disableSelfServiceScrape<a href="#vlstorage-disableselfservicescrape" id="vlstorage-disableselfservicescrape">#</a><br/>_boolean_ | _(Optional)_<br/>DisableSelfServiceScrape controls creation of VMServiceScrape by operator<br />for the application.<br />Has priority over `VM_DISABLESELFSERVICESCRAPECREATION` operator env variable |
+| dnsConfig<a href="#vlstorage-dnsconfig" id="vlstorage-dnsconfig">#</a><br/>_[PodDNSConfig](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#poddnsconfig-v1-core)_ | _(Optional)_<br/>Specifies the DNS parameters of a pod.<br />Parameters specified here will be merged to the generated DNS<br />configuration based on DNSPolicy. |
+| dnsPolicy<a href="#vlstorage-dnspolicy" id="vlstorage-dnspolicy">#</a><br/>_[DNSPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#dnspolicy-v1-core)_ | _(Optional)_<br/>DNSPolicy sets DNS policy for the pod |
+| extraArgs<a href="#vlstorage-extraargs" id="vlstorage-extraargs">#</a><br/>_object (keys:string, values:string)_ | _(Optional)_<br/>ExtraArgs that will be passed to the application container<br />for example remoteWrite.tmpDataPath: /tmp |
+| extraEnvs<a href="#vlstorage-extraenvs" id="vlstorage-extraenvs">#</a><br/>_[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envvar-v1-core) array_ | _(Optional)_<br/>ExtraEnvs that will be passed to the application container |
+| extraEnvsFrom<a href="#vlstorage-extraenvsfrom" id="vlstorage-extraenvsfrom">#</a><br/>_[EnvFromSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envfromsource-v1-core) array_ | _(Optional)_<br/>ExtraEnvsFrom defines source of env variables for the application container<br />could either be secret or configmap |
+| futureRetention<a href="#vlstorage-futureretention" id="vlstorage-futureretention">#</a><br/>_string_ | _(Optional)_<br/>FutureRetention for the stored logs<br />Log entries with timestamps bigger than now+futureRetention are rejected during data ingestion; see https://docs.victoriametrics.com/victorialogs/#retention |
+| hostAliases<a href="#vlstorage-hostaliases" id="vlstorage-hostaliases">#</a><br/>_[HostAlias](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#hostalias-v1-core) array_ | _(Optional)_<br/>HostAliases provides mapping for ip and hostname,<br />that would be propagated to pod,<br />cannot be used with HostNetwork. |
+| hostNetwork<a href="#vlstorage-hostnetwork" id="vlstorage-hostnetwork">#</a><br/>_boolean_ | _(Optional)_<br/>HostNetwork controls whether the pod may use the node network namespace |
+| host_aliases<a href="#vlstorage-host_aliases" id="vlstorage-host_aliases">#</a><br/>_[HostAlias](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#hostalias-v1-core) array_ | _(Optional)_<br/>HostAliasesUnderScore provides mapping for ip and hostname,<br />that would be propagated to pod,<br />cannot be used with HostNetwork.<br />Has Priority over hostAliases field |
+| image<a href="#vlstorage-image" id="vlstorage-image">#</a><br/>_[Image](#image)_ | _(Optional)_<br/>Image - docker image settings<br />if no specified operator uses default version from operator config |
+| imagePullSecrets<a href="#vlstorage-imagepullsecrets" id="vlstorage-imagepullsecrets">#</a><br/>_[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core) array_ | _(Optional)_<br/>ImagePullSecrets An optional list of references to secrets in the same namespace<br />to use for pulling images from registries<br />see https://kubernetes.io/docs/concepts/containers/images/#referring-to-an-imagepullsecrets-on-a-pod |
+| initContainers<a href="#vlstorage-initcontainers" id="vlstorage-initcontainers">#</a><br/>_[Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#container-v1-core) array_ | _(Optional)_<br/>InitContainers allows adding initContainers to the pod definition.<br />Any errors during the execution of an initContainer will lead to a restart of the Pod.<br />More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ |
+| logFormat<a href="#vlstorage-logformat" id="vlstorage-logformat">#</a><br/>_string_ | _(Optional)_<br/>LogFormat for VLStorage to be configured with.<br />default or json |
+| logIngestedRows<a href="#vlstorage-logingestedrows" id="vlstorage-logingestedrows">#</a><br/>_boolean_ | _(Required)_<br/>Whether to log all the ingested log entries; this can be useful for debugging of data ingestion; see https://docs.victoriametrics.com/victorialogs/data-ingestion/ |
+| logLevel<a href="#vlstorage-loglevel" id="vlstorage-loglevel">#</a><br/>_string_ | _(Optional)_<br/>LogLevel for VLStorage to be configured with. |
+| logNewStreams<a href="#vlstorage-lognewstreams" id="vlstorage-lognewstreams">#</a><br/>_boolean_ | _(Required)_<br/>LogNewStreams Whether to log creation of new streams; this can be useful for debugging of high cardinality issues with log streams; see https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields |
+| maintenanceInsertNodeIDs<a href="#vlstorage-maintenanceinsertnodeids" id="vlstorage-maintenanceinsertnodeids">#</a><br/>_integer array_ | _(Optional)_<br/>MaintenanceInsertNodeIDs - excludes given node ids from insert requests routing, must contain pod suffixes - for pod-0, id will be 0 and etc.<br />lets say, you have pod-0, pod-1, pod-2, pod-3. to exclude pod-0 and pod-3 from insert routing, define nodeIDs: [0,3].<br />Useful at storage expanding, when you want to rebalance some data at cluster. |
+| maintenanceSelectNodeIDs<a href="#vlstorage-maintenanceselectnodeids" id="vlstorage-maintenanceselectnodeids">#</a><br/>_integer array_ | _(Required)_<br/>MaintenanceInsertNodeIDs - excludes given node ids from select requests routing, must contain pod suffixes - for pod-0, id will be 0 and etc. |
+| minReadySeconds<a href="#vlstorage-minreadyseconds" id="vlstorage-minreadyseconds">#</a><br/>_integer_ | _(Optional)_<br/>MinReadySeconds defines a minimum number of seconds to wait before starting update next pod<br />if previous in healthy state<br />Has no effect for VLogs and VMSingle |
+| nodeSelector<a href="#vlstorage-nodeselector" id="vlstorage-nodeselector">#</a><br/>_object (keys:string, values:string)_ | _(Optional)_<br/>NodeSelector Define which Nodes the Pods are scheduled on. |
+| paused<a href="#vlstorage-paused" id="vlstorage-paused">#</a><br/>_boolean_ | _(Optional)_<br/>Paused If set to true all actions on the underlying managed objects are not<br />going to be performed, except for delete actions. |
+| podDisruptionBudget<a href="#vlstorage-poddisruptionbudget" id="vlstorage-poddisruptionbudget">#</a><br/>_[EmbeddedPodDisruptionBudgetSpec](#embeddedpoddisruptionbudgetspec)_ | _(Optional)_<br/>PodDisruptionBudget created by operator |
+| podMetadata<a href="#vlstorage-podmetadata" id="vlstorage-podmetadata">#</a><br/>_[EmbeddedObjectMetadata](#embeddedobjectmetadata)_ | _(Required)_<br/>PodMetadata configures Labels and Annotations which are propagated to the VLStorage pods. |
+| port<a href="#vlstorage-port" id="vlstorage-port">#</a><br/>_string_ | _(Optional)_<br/>Port listen address |
+| priorityClassName<a href="#vlstorage-priorityclassname" id="vlstorage-priorityclassname">#</a><br/>_string_ | _(Optional)_<br/>PriorityClassName class assigned to the Pods |
+| readinessGates<a href="#vlstorage-readinessgates" id="vlstorage-readinessgates">#</a><br/>_[PodReadinessGate](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#podreadinessgate-v1-core) array_ | _(Required)_<br/>ReadinessGates defines pod readiness gates |
+| replicaCount<a href="#vlstorage-replicacount" id="vlstorage-replicacount">#</a><br/>_integer_ | _(Optional)_<br/>ReplicaCount is the expected size of the Application. |
+| resources<a href="#vlstorage-resources" id="vlstorage-resources">#</a><br/>_[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#resourcerequirements-v1-core)_ | _(Optional)_<br/>Resources container resource request and limits, https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/<br />if not defined default resources from operator config will be used |
+| retentionMaxDiskSpaceUsageBytes<a href="#vlstorage-retentionmaxdiskspaceusagebytes" id="vlstorage-retentionmaxdiskspaceusagebytes">#</a><br/>_[BytesString](#bytesstring)_ | _(Optional)_<br/>RetentionMaxDiskSpaceUsageBytes for the stored logs<br />VictoriaLogs keeps at least two last days of data in order to guarantee that the logs for the last day can be returned in queries.<br />This means that the total disk space usage may exceed the -retention.maxDiskSpaceUsageBytes,<br />if the size of the last two days of data exceeds the -retention.maxDiskSpaceUsageBytes.<br />https://docs.victoriametrics.com/victorialogs/#retention-by-disk-space-usage |
+| retentionPeriod<a href="#vlstorage-retentionperiod" id="vlstorage-retentionperiod">#</a><br/>_string_ | _(Optional)_<br/>RetentionPeriod for the stored logs<br />https://docs.victoriametrics.com/victorialogs/#retention |
+| revisionHistoryLimitCount<a href="#vlstorage-revisionhistorylimitcount" id="vlstorage-revisionhistorylimitcount">#</a><br/>_integer_ | _(Optional)_<br/>The number of old ReplicaSets to retain to allow rollback in deployment or<br />maximum number of revisions that will be maintained in the Deployment revision history.<br />Has no effect at StatefulSets<br />Defaults to 10. |
+| rollingUpdateStrategy<a href="#vlstorage-rollingupdatestrategy" id="vlstorage-rollingupdatestrategy">#</a><br/>_[StatefulSetUpdateStrategyType](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#statefulsetupdatestrategytype-v1-apps)_ | _(Optional)_<br/>RollingUpdateStrategy defines strategy for application updates<br />Default is OnDelete, in this case operator handles update process<br />Can be changed for RollingUpdate |
+| runtimeClassName<a href="#vlstorage-runtimeclassname" id="vlstorage-runtimeclassname">#</a><br/>_string_ | _(Optional)_<br/>RuntimeClassName - defines runtime class for kubernetes pod.<br />https://kubernetes.io/docs/concepts/containers/runtime-class/ |
+| schedulerName<a href="#vlstorage-schedulername" id="vlstorage-schedulername">#</a><br/>_string_ | _(Optional)_<br/>SchedulerName - defines kubernetes scheduler name |
+| secrets<a href="#vlstorage-secrets" id="vlstorage-secrets">#</a><br/>_string array_ | _(Optional)_<br/>Secrets is a list of Secrets in the same namespace as the Application<br />object, which shall be mounted into the Application container<br />at /etc/vm/secrets/SECRET_NAME folder |
+| securityContext<a href="#vlstorage-securitycontext" id="vlstorage-securitycontext">#</a><br/>_[SecurityContext](#securitycontext)_ | _(Optional)_<br/>SecurityContext holds pod-level security attributes and common container settings.<br />This defaults to the default PodSecurityContext. |
+| serviceScrapeSpec<a href="#vlstorage-servicescrapespec" id="vlstorage-servicescrapespec">#</a><br/>_[VMServiceScrapeSpec](#vmservicescrapespec)_ | _(Optional)_<br/>ServiceScrapeSpec that will be added to vlselect VMServiceScrape spec |
+| serviceSpec<a href="#vlstorage-servicespec" id="vlstorage-servicespec">#</a><br/>_[AdditionalServiceSpec](#additionalservicespec)_ | _(Optional)_<br/>ServiceSpec that will be added to vlselect service spec |
+| storage<a href="#vlstorage-storage" id="vlstorage-storage">#</a><br/>_[StorageSpec](#storagespec)_ | _(Optional)_<br/>Storage configures persistent volume for VLStorage |
+| storageDataPath<a href="#vlstorage-storagedatapath" id="vlstorage-storagedatapath">#</a><br/>_string_ | _(Optional)_<br/>StorageDataPath - path to storage data |
+| terminationGracePeriodSeconds<a href="#vlstorage-terminationgraceperiodseconds" id="vlstorage-terminationgraceperiodseconds">#</a><br/>_integer_ | _(Optional)_<br/>TerminationGracePeriodSeconds period for container graceful termination |
+| tolerations<a href="#vlstorage-tolerations" id="vlstorage-tolerations">#</a><br/>_[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#toleration-v1-core) array_ | _(Optional)_<br/>Tolerations If specified, the pod's tolerations. |
+| topologySpreadConstraints<a href="#vlstorage-topologyspreadconstraints" id="vlstorage-topologyspreadconstraints">#</a><br/>_[TopologySpreadConstraint](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#topologyspreadconstraint-v1-core) array_ | _(Optional)_<br/>TopologySpreadConstraints embedded kubernetes pod configuration option,<br />controls how pods are spread across your cluster among failure-domains<br />such as regions, zones, nodes, and other user-defined topology domains<br />https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ |
+| useDefaultResources<a href="#vlstorage-usedefaultresources" id="vlstorage-usedefaultresources">#</a><br/>_boolean_ | _(Optional)_<br/>UseDefaultResources controls resource settings<br />By default, operator sets built-in resource requirements |
+| useStrictSecurity<a href="#vlstorage-usestrictsecurity" id="vlstorage-usestrictsecurity">#</a><br/>_boolean_ | _(Optional)_<br/>UseStrictSecurity enables strict security mode for component<br />it restricts disk writes access<br />uses non-root user out of the box<br />drops not needed security permissions |
+| volumeMounts<a href="#vlstorage-volumemounts" id="vlstorage-volumemounts">#</a><br/>_[VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#volumemount-v1-core) array_ | _(Optional)_<br/>VolumeMounts allows configuration of additional VolumeMounts on the output Deployment/StatefulSet definition.<br />VolumeMounts specified will be appended to other VolumeMounts in the Application container |
+| volumes<a href="#vlstorage-volumes" id="vlstorage-volumes">#</a><br/>_[Volume](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#volume-v1-core) array_ | _(Required)_<br/>Volumes allows configuration of additional volumes on the output Deployment/StatefulSet definition.<br />Volumes specified will be appended to other volumes that are generated.<br />/ +optional |
+
+
 
 ## operator.victoriametrics.com/v1beta1
 
@@ -165,7 +465,7 @@ labels,selector, ports.
 if metadata.name is not defined, service will have format {{CRD_TYPE}}-{{CRD_NAME}}-additional-service.
 if UseAsDefault is set to true, changes applied to the main service without additional service creation
 
-Appears in: [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
 
 | Field | Description |
 | --- | --- |
@@ -322,7 +622,7 @@ _Underlying type:_ _string_
 BytesString represents bytes value defined directly as integer
 or as a string with suffix - kb,mb,gb,tb,KiB,MiB,GiB,TiB
 
-Appears in: [VLSingleSpec](#vlsinglespec), [VMAgentRemoteWriteSettings](#vmagentremotewritesettings), [VMAgentRemoteWriteSpec](#vmagentremotewritespec)
+Appears in: [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VMAgentRemoteWriteSettings](#vmagentremotewritesettings), [VMAgentRemoteWriteSpec](#vmagentremotewritespec)
 
 
 
@@ -366,7 +666,7 @@ Appears in: [TLSClientConfig](#tlsclientconfig), [TLSServerConfig](#tlsservercon
 CommonApplicationDeploymentParams defines common params
 for deployment and statefulset specifications
 
-Appears in: [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
 
 | Field | Description |
 | --- | --- |
@@ -426,7 +726,7 @@ Appears in: [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertma
 CommonDefaultableParams contains Application settings
 with known values populated from operator configuration
 
-Appears in: [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
 
 | Field | Description |
 | --- | --- |
@@ -444,7 +744,7 @@ Appears in: [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec
 
 Condition defines status condition of the resource
 
-Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [StatusMetadata](#statusmetadata), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus)
+Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [StatusMetadata](#statusmetadata), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus)
 
 | Field | Description |
 | --- | --- |
@@ -659,7 +959,7 @@ Appears in: [Receiver](#receiver)
 EmbeddedHPA embeds HorizontalPodAutoScaler spec v2.
 https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/horizontal-pod-autoscaler-v2/
 
-Appears in: [VMInsert](#vminsert), [VMSelect](#vmselect)
+Appears in: [VLInsert](#vlinsert), [VLSelect](#vlselect), [VMInsert](#vminsert), [VMSelect](#vmselect)
 
 | Field | Description |
 | --- | --- |
@@ -697,7 +997,7 @@ Appears in: [VMAuthSpec](#vmauthspec)
 EmbeddedObjectMetadata contains a subset of the fields included in k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta
 Only fields which are relevant to embedded resources are included.
 
-Appears in: [AdditionalServiceSpec](#additionalservicespec), [EmbeddedIngress](#embeddedingress), [EmbeddedPersistentVolumeClaim](#embeddedpersistentvolumeclaim), [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [AdditionalServiceSpec](#additionalservicespec), [EmbeddedIngress](#embeddedingress), [EmbeddedPersistentVolumeClaim](#embeddedpersistentvolumeclaim), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
 
 | Field | Description |
 | --- | --- |
@@ -727,7 +1027,7 @@ Appears in: [StorageSpec](#storagespec)
 
 
 
-Appears in: [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMStorage](#vmstorage)
+Appears in: [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLStorage](#vlstorage), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMStorage](#vmstorage)
 
 | Field | Description |
 | --- | --- |
@@ -744,7 +1044,7 @@ EmbeddedProbes - it allows to override some probe params.
 its not necessary to specify all options,
 operator will replace missing spec with default values.
 
-Appears in: [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
 
 | Field | Description |
 | --- | --- |
@@ -962,7 +1262,7 @@ Appears in: [VMScrapeConfigSpec](#vmscrapeconfigspec)
 
 Image defines docker image settings
 
-Appears in: [CommonDefaultableParams](#commondefaultableparams), [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMBackup](#vmbackup), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [CommonDefaultableParams](#commondefaultableparams), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMBackup](#vmbackup), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
 
 | Field | Description |
 | --- | --- |
@@ -1172,7 +1472,7 @@ Appears in: [Receiver](#receiver)
 
 ManagedObjectsMetadata contains Labels and Annotations
 
-Appears in: [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthSpec](#vmauthspec), [VMClusterSpec](#vmclusterspec), [VMSingleSpec](#vmsinglespec)
+Appears in: [VLClusterSpec](#vlclusterspec), [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthSpec](#vmauthspec), [VMClusterSpec](#vmclusterspec), [VMSingleSpec](#vmsinglespec)
 
 | Field | Description |
 | --- | --- |
@@ -1646,7 +1946,7 @@ Appears in: [OAuth2](#oauth2), [TLSConfig](#tlsconfig)
 SecurityContext extends PodSecurityContext with ContainerSecurityContext
 It allows to globally configure security params for pod and all containers
 
-Appears in: [CommonApplicationDeploymentParams](#commonapplicationdeploymentparams), [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [CommonApplicationDeploymentParams](#commonapplicationdeploymentparams), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
 
 
 
@@ -1818,7 +2118,7 @@ Appears in: [TargetRef](#targetref)
 
 StatusMetadata holds metadata of application update status
 
-Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus)
+Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus)
 
 | Field | Description |
 | --- | --- |
@@ -1835,7 +2135,7 @@ Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [VLSingleStatus](#vlsingl
 StorageSpec defines the configured storage for a group Prometheus servers.
 If neither `emptyDir` nor `volumeClaimTemplate` is specified, then by default an [EmptyDir](https://kubernetes.io/docs/concepts/storage/volumes/#emptydir) will be used.
 
-Appears in: [VMAgentSpec](#vmagentspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMSelect](#vmselect), [VMStorage](#vmstorage)
+Appears in: [VLStorage](#vlstorage), [VMAgentSpec](#vmagentspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMSelect](#vmselect), [VMStorage](#vmstorage)
 
 | Field | Description |
 | --- | --- |
@@ -2154,7 +2454,7 @@ _Underlying type:_ _string_
 
 UpdateStatus defines status for application
 
-Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [StatusMetadata](#statusmetadata), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus)
+Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [StatusMetadata](#statusmetadata), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus)
 
 
 
@@ -2295,6 +2595,7 @@ Appears in: [VMAgentSpec](#vmagentspec)
 | inlineUrlRelabelConfig<a href="#vmagentremotewritespec-inlineurlrelabelconfig" id="vmagentremotewritespec-inlineurlrelabelconfig">#</a><br/>_[RelabelConfig](#relabelconfig) array_ | _(Optional)_<br/>InlineUrlRelabelConfig defines relabeling config for remoteWriteURL, it can be defined at crd spec. |
 | maxDiskUsage<a href="#vmagentremotewritespec-maxdiskusage" id="vmagentremotewritespec-maxdiskusage">#</a><br/>_[BytesString](#bytesstring)_ | _(Optional)_<br/>MaxDiskUsage defines the maximum file-based buffer size in bytes for the given remoteWrite<br />It overrides global configuration defined at remoteWriteSettings.maxDiskUsagePerURL |
 | oauth2<a href="#vmagentremotewritespec-oauth2" id="vmagentremotewritespec-oauth2">#</a><br/>_[OAuth2](#oauth2)_ | _(Optional)_<br/>OAuth2 defines auth configuration |
+| proxyURL<a href="#vmagentremotewritespec-proxyurl" id="vmagentremotewritespec-proxyurl">#</a><br/>_string_ | _(Optional)_<br/>ProxyURL for -remoteWrite.url. Supported proxies: http, https, socks5. Example: socks5://proxy:1234 |
 | sendTimeout<a href="#vmagentremotewritespec-sendtimeout" id="vmagentremotewritespec-sendtimeout">#</a><br/>_string_ | _(Optional)_<br/>Timeout for sending a single block of data to -remoteWrite.url (default 1m0s) |
 | streamAggrConfig<a href="#vmagentremotewritespec-streamaggrconfig" id="vmagentremotewritespec-streamaggrconfig">#</a><br/>_[StreamAggrConfig](#streamaggrconfig)_ | _(Optional)_<br/>StreamAggrConfig defines stream aggregation configuration for VMAgent for -remoteWrite.url |
 | tlsConfig<a href="#vmagentremotewritespec-tlsconfig" id="vmagentremotewritespec-tlsconfig">#</a><br/>_[TLSConfig](#tlsconfig)_ | _(Optional)_<br/>TLSConfig describes tls configuration for remote write target |
@@ -2762,7 +3063,7 @@ VMAuth is the Schema for the vmauths API
 VMAuthLoadBalancer configures vmauth as a load balancer
 for the requests
 
-Appears in: [VMClusterSpec](#vmclusterspec)
+Appears in: [VLClusterSpec](#vlclusterspec), [VMClusterSpec](#vmclusterspec)
 
 | Field | Description |
 | --- | --- |
@@ -3494,7 +3795,7 @@ result config will scrape service endpoints
 
 VMServiceScrapeSpec defines the desired state of VMServiceScrape
 
-Appears in: [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMServiceScrape](#vmservicescrape), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMServiceScrape](#vmservicescrape), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
 
 | Field | Description |
 | --- | --- |
diff --git a/docs/env.md b/docs/env.md
index 362a299d..01b8b6af 100644
--- a/docs/env.md
+++ b/docs/env.md
@@ -112,6 +112,28 @@
 | VM_VMAUTHDEFAULT_RESOURCE_REQUEST_CPU: `50m` <a href="#variables-vm-vmauthdefault-resource-request-cpu" id="variables-vm-vmauthdefault-resource-request-cpu">#</a> |
 | VM_VMAUTHDEFAULT_CONFIGRELOADERCPU: `10m` <a href="#variables-vm-vmauthdefault-configreloadercpu" id="variables-vm-vmauthdefault-configreloadercpu">#</a><br>deprecated use VM_CONFIG_RELOADER_REQUEST_CPU instead |
 | VM_VMAUTHDEFAULT_CONFIGRELOADERMEMORY: `25Mi` <a href="#variables-vm-vmauthdefault-configreloadermemory" id="variables-vm-vmauthdefault-configreloadermemory">#</a><br>deprecated use VM_CONFIG_RELOADER_REQUEST_MEMORY instead |
+| VM_VLCLUSTERDEFAULT_USEDEFAULTRESOURCES: `true` <a href="#variables-vm-vlclusterdefault-usedefaultresources" id="variables-vm-vlclusterdefault-usedefaultresources">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSELECTDEFAULT_IMAGE: `victoriametrics/victoria-logs` <a href="#variables-vm-vlclusterdefault-vlselectdefault-image" id="variables-vm-vlclusterdefault-vlselectdefault-image">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSELECTDEFAULT_VERSION: `${VM_LOGS_VERSION}-victorialogs` <a href="#variables-vm-vlclusterdefault-vlselectdefault-version" id="variables-vm-vlclusterdefault-vlselectdefault-version">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSELECTDEFAULT_PORT: `9471` <a href="#variables-vm-vlclusterdefault-vlselectdefault-port" id="variables-vm-vlclusterdefault-vlselectdefault-port">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSELECTDEFAULT_RESOURCE_LIMIT_MEM: `1024Mi` <a href="#variables-vm-vlclusterdefault-vlselectdefault-resource-limit-mem" id="variables-vm-vlclusterdefault-vlselectdefault-resource-limit-mem">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSELECTDEFAULT_RESOURCE_LIMIT_CPU: `1000m` <a href="#variables-vm-vlclusterdefault-vlselectdefault-resource-limit-cpu" id="variables-vm-vlclusterdefault-vlselectdefault-resource-limit-cpu">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSELECTDEFAULT_RESOURCE_REQUEST_MEM: `256Mi` <a href="#variables-vm-vlclusterdefault-vlselectdefault-resource-request-mem" id="variables-vm-vlclusterdefault-vlselectdefault-resource-request-mem">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSELECTDEFAULT_RESOURCE_REQUEST_CPU: `100m` <a href="#variables-vm-vlclusterdefault-vlselectdefault-resource-request-cpu" id="variables-vm-vlclusterdefault-vlselectdefault-resource-request-cpu">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_IMAGE: `victoriametrics/victoria-logs` <a href="#variables-vm-vlclusterdefault-vlstoragedefault-image" id="variables-vm-vlclusterdefault-vlstoragedefault-image">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_VERSION: `${VM_LOGS_VERSION}-victorialogs` <a href="#variables-vm-vlclusterdefault-vlstoragedefault-version" id="variables-vm-vlclusterdefault-vlstoragedefault-version">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_PORT: `9491` <a href="#variables-vm-vlclusterdefault-vlstoragedefault-port" id="variables-vm-vlclusterdefault-vlstoragedefault-port">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_RESOURCE_LIMIT_MEM: `2048Mi` <a href="#variables-vm-vlclusterdefault-vlstoragedefault-resource-limit-mem" id="variables-vm-vlclusterdefault-vlstoragedefault-resource-limit-mem">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_RESOURCE_LIMIT_CPU: `1000m` <a href="#variables-vm-vlclusterdefault-vlstoragedefault-resource-limit-cpu" id="variables-vm-vlclusterdefault-vlstoragedefault-resource-limit-cpu">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_RESOURCE_REQUEST_MEM: `512Mi` <a href="#variables-vm-vlclusterdefault-vlstoragedefault-resource-request-mem" id="variables-vm-vlclusterdefault-vlstoragedefault-resource-request-mem">#</a> |
+| VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_RESOURCE_REQUEST_CPU: `200m` <a href="#variables-vm-vlclusterdefault-vlstoragedefault-resource-request-cpu" id="variables-vm-vlclusterdefault-vlstoragedefault-resource-request-cpu">#</a> |
+| VM_VLCLUSTERDEFAULT_VLINSERTDEFAULT_IMAGE: `victoriametrics/victoria-logs` <a href="#variables-vm-vlclusterdefault-vlinsertdefault-image" id="variables-vm-vlclusterdefault-vlinsertdefault-image">#</a> |
+| VM_VLCLUSTERDEFAULT_VLINSERTDEFAULT_VERSION: `${VM_LOGS_VERSION}-victorialogs` <a href="#variables-vm-vlclusterdefault-vlinsertdefault-version" id="variables-vm-vlclusterdefault-vlinsertdefault-version">#</a> |
+| VM_VLCLUSTERDEFAULT_VLINSERTDEFAULT_PORT: `9481` <a href="#variables-vm-vlclusterdefault-vlinsertdefault-port" id="variables-vm-vlclusterdefault-vlinsertdefault-port">#</a> |
+| VM_VLCLUSTERDEFAULT_VLINSERTDEFAULT_RESOURCE_LIMIT_MEM: `1024Mi` <a href="#variables-vm-vlclusterdefault-vlinsertdefault-resource-limit-mem" id="variables-vm-vlclusterdefault-vlinsertdefault-resource-limit-mem">#</a> |
+| VM_VLCLUSTERDEFAULT_VLINSERTDEFAULT_RESOURCE_LIMIT_CPU: `1000m` <a href="#variables-vm-vlclusterdefault-vlinsertdefault-resource-limit-cpu" id="variables-vm-vlclusterdefault-vlinsertdefault-resource-limit-cpu">#</a> |
+| VM_VLCLUSTERDEFAULT_VLINSERTDEFAULT_RESOURCE_REQUEST_MEM: `256Mi` <a href="#variables-vm-vlclusterdefault-vlinsertdefault-resource-request-mem" id="variables-vm-vlclusterdefault-vlinsertdefault-resource-request-mem">#</a> |
+| VM_VLCLUSTERDEFAULT_VLINSERTDEFAULT_RESOURCE_REQUEST_CPU: `100m` <a href="#variables-vm-vlclusterdefault-vlinsertdefault-resource-request-cpu" id="variables-vm-vlclusterdefault-vlinsertdefault-resource-request-cpu">#</a> |
 | VM_ENABLEDPROMETHEUSCONVERTER_PODMONITOR: `true` <a href="#variables-vm-enabledprometheusconverter-podmonitor" id="variables-vm-enabledprometheusconverter-podmonitor">#</a> |
 | VM_ENABLEDPROMETHEUSCONVERTER_SERVICESCRAPE: `true` <a href="#variables-vm-enabledprometheusconverter-servicescrape" id="variables-vm-enabledprometheusconverter-servicescrape">#</a> |
 | VM_ENABLEDPROMETHEUSCONVERTER_PROMETHEUSRULE: `true` <a href="#variables-vm-enabledprometheusconverter-prometheusrule" id="variables-vm-enabledprometheusconverter-prometheusrule">#</a> |
diff --git a/docs/flags.md b/docs/flags.md
index 9b439389..2640e653 100644
--- a/docs/flags.md
+++ b/docs/flags.md
@@ -31,11 +31,11 @@ Usage of bin/operator:
   -leader-elect-id string
     	Defines the name of the resource that leader election will use for holding the leader lock. (default "57410f0d.victoriametrics.com")
   -leader-elect-lease-duration duration
-      Defines the duration that non-leader candidates will wait to force acquire leadership. This is measured against time of last observed ack. (default 15s)
+    	Defines the duration that non-leader candidates will wait to force acquire leadership. This is measured against time of last observed ack. (default 15s)
   -leader-elect-namespace string
-      Defines optional namespace name in which the leader election resource will be created. By default, uses in-cluster namespace name.
+    	Defines optional namespace name in which the leader election resource will be created. By default, uses in-cluster namespace name.
   -leader-elect-renew-deadline duration
-      Defines the duration that the acting controlplane will retry refreshing leadership lock before giving up. (default 10s)
+    	Defines the duration that the acting controlplane will retry refreshing leadership lock before giving up. (default 10s)
   -loggerJSONFields string
     	Allows renaming fields in JSON formatted logsExample: "ts:timestamp,msg:message" renames "ts" to "timestamp" and "msg" to "message".Supported fields: ts, level, caller, msg
   -metrics-bind-address string
diff --git a/docs/resources/vlcluster.md b/docs/resources/vlcluster.md
new file mode 100644
index 00000000..b2165399
--- /dev/null
+++ b/docs/resources/vlcluster.md
@@ -0,0 +1,164 @@
+---
+weight: 21
+title: VLCluster
+menu:
+  docs:
+    identifier: operator-cr-vlcluster
+    parent: operator-cr
+    weight: 21
+aliases:
+  - /operator/resources/vlcluster/
+  - /operator/resources/vlcluster/index.html
+tags:
+  - kubernetes
+  - metrics
+---
+`VLCluster` represents database for storing logs.
+The `VLCluster` CRD declaratively defines a [VictoriaLogs cluster](https://docs.victoriametrics.com/victorialogs/cluster/)
+installation to run in a Kubernetes cluster.
+
+For each `VLCluster` resource, the Operator deploys a properly configured components `VLInsert`'s `Deployment`, `VLSelect`'s `Deployment` and `VLStorage`'s `StatefulSet` in the same namespace.
+The VLStorage `Pod`s are configured to mount an empty dir or `StorageSpec` for storing data.
+
+For each `VLCluster` resource components, the Operator adds `Service` and `VMServiceScrape` in the same namespace prefixed with name from `VLCluster.metadata.name`.
+
+## Specification
+
+You can see the full actual specification of the `VLCluster` resource in the **[API docs -> VLCluster](https://docs.victoriametrics.com/operator/api#vlcluster)**.
+
+If you can't find necessary field in the specification of the custom resource,
+see [Extra arguments section](./#extra-arguments).
+
+Also, you can check out the [examples](#examples) section.
+
+## Version management
+
+To set `VLCluster` version add `spec.clusterVersion` or `spec.COMPONENT.image.tag` name from [releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases)
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VLCluster
+metadata:
+  name: example
+spec:
+  ...
+  clusterVersion: v1.22.0-victoria-logs
+  vlstorage:
+    image:
+      repository: victoriametrics/victoria-logs
+      tag: v1.19.0-victoria-logs
+      pullPolicy: Always
+  # ...
+```
+
+Also, you can specify `imagePullSecrets` if you are pulling images from private repo:
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VLCluster
+metadata:
+  name: example
+spec:
+  imagePullSecrets:
+    - name: my-repo-secret
+# ...
+```
+
+## Resource management
+
+You can specify resources for each `VLCluster` resource components in the `spec` section of the `VLCluster` CRD.
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VLCluster
+metadata:
+  name: resources
+spec:
+    # ...
+    vlstorage:
+      resources:
+          requests:
+            memory: "64Mi"
+            cpu: "250m"
+          limits:
+            memory: "128Mi"
+            cpu: "500m"
+    # ...
+```
+
+If these parameters are not specified, then,
+by default all `VLCluster` pods have resource requests and limits from the default values of the following [operator parameters](https://docs.victoriametrics.com/operator/configuration):
+
+- `VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_RESOURCE_LIMIT_MEM:` - default memory limit for `VLCluster.vlstorage` pods,
+- `VLSTORAGEDEFAULT_RESOURCE_LIMIT_CPU` - default memory limit for `VLCluster.vlstorage` pods,
+- `VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_RESOURCE_REQUEST_MEM` - default memory limit for `VLCluster.vlstorage` pods,
+- `VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_RESOURCE_REQUEST_CPU` - default memory limit for `VLCluster.vlstorage` pods.
+
+These default parameters will be used if:
+
+- `VM_VLCLUSTERDEFAULT_USEDEFAULTRESOURCES` is set to `true` (default value),
+- `VLCluster` CR doesn't have `resources` field in `spec` section for component.
+
+Field `resources` in `VLSingle` spec have higher priority than operator parameters.
+
+If you set `VM_VLOGSDEFAULT_USEDEFAULTRESOURCES` to `false` and don't specify `resources` in `VLSingle` CRD,
+then `VLSingle` pods will be created without resource requests and limits.
+
+Also, you can specify requests without limits - in this case default values for limits will not be used.
+
+## Requests Load-Balancing
+
+ Operator provides enhanced load-balancing mechanism for `vlinsert` and `vlselect` clients. By default, operator uses built-in Kubernetes [service]() with `clusterIP` type for clients connection. It's good solution for short lived connections. But it acts poorly with long-lived TCP sessions and leads to the uneven resources utilization for `vlselect` and `vlinsert` components.
+
+ Operator allows to tweak Kubernetes TCP-based load-balancing with enabled [requestsLoadBalancer](https://docs.victoriametrics.com/operator/api/#vmclusterspec-requestsloadbalancer):
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VLCluster
+metadata:
+  name: with-balancer
+spec:
+  vlinsert:
+    replicaCount: 2
+  vlselect:
+    replicaCount: 2
+  vlstorage:
+    retentionPeriod: "1y"
+    replicaCount: 2
+  requestsLoadBalancer:
+    enabled: true
+    spec:
+     replicaCount: 2
+```
+
+ Operator will deploy `VMAuth` deployment with 2 replicas. And update vlinsert and vlselect services to point to `vmauth`.
+ In addition, operator will create 3 additional services with the following pattern:
+
+- vlinsertint-CLUSTER_NAME - needed for vlselect pod discovery
+- vlselectint-CLUSTER_NAME - needed for vlinsert pod discovery
+- vlclusterlb-CLUSTER_NAME - needed for metrics collection and exposing `vlselect` and `vlinsert` components via `VMAuth` balancer.
+
+The `requestsLoadBalancer` feature works transparently and is managed entirely by the `VLCluster` operator, 
+with no direct access to the underlying [VMAuth](https://docs.victoriametrics.com/vmauth/) configuration. 
+If you need more control over load balancing behavior, 
+or want to combine request routing with authentication or (m)TLS, 
+consider deploying a standalone [VMAuth](https://docs.victoriametrics.com/operator/resources/vmauth/) resource instead of enabling `requestsLoadBalancer`.
+
+
+## Examples
+
+```yaml
+
+apiVersion: operator.victoriametrics.com/v1
+kind: VLCluster
+metadata:
+  name: sample
+spec:
+  vlinsert:
+    replicaCount: 1
+  vlselect:
+    replicaCount: 1
+  vlstorage:
+    retentionPeriod: "1y"
+    replicaCount: 2
+```
diff --git a/docs/resources/vlsingle.md b/docs/resources/vlsingle.md
index e0fad5eb..9a49c43f 100644
--- a/docs/resources/vlsingle.md
+++ b/docs/resources/vlsingle.md
@@ -24,7 +24,7 @@ The VLSingle `Pod`s are configured to mount an empty dir or `PersistentVolumeCla
 Deployment update strategy set to [recreate](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#recreate-deployment).
 No more than one replica allowed.
 
-For each `VLSingle` resource, the Operator adds `Service` and `VMServiceScrape` in the same namespace prefixed with name from `VLogs.metadata.name`.
+For each `VLSingle` resource, the Operator adds `Service` and `VMServiceScrape` in the same namespace prefixed with name from `VLSingle.metadata.name`.
 
 ## Specification
 
@@ -75,7 +75,7 @@ spec:
 
 ## Resource management
 
-You can specify resources for each `VLSingle` resource in the `spec` section of the `VLogs` CRD.
+You can specify resources for each `VLSingle` resource in the `spec` section of the `VLSingle` CRD.
 
 ```yaml
 apiVersion: operator.victoriametrics.com/v1
diff --git a/internal/config/config.go b/internal/config/config.go
index 22f63c73..1ff87fb2 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -340,6 +340,55 @@ type BaseOperatorConf struct {
 		ConfigReloaderMemory string `default:"25Mi" env:"CONFIGRELOADERMEMORY"`
 	} `prefix:"VMAUTHDEFAULT_"`
 
+	VLClusterDefault struct {
+		UseDefaultResources bool `default:"true" env:"USEDEFAULTRESOURCES"`
+		VLSelectDefault     struct {
+			Image    string `default:"victoriametrics/victoria-logs"`
+			Version  string `env:",expand" default:"${VM_LOGS_VERSION}-victorialogs"`
+			Port     string `default:"9471"`
+			Resource struct {
+				Limit struct {
+					Mem string `default:"1024Mi"`
+					Cpu string `default:"1000m"`
+				} `prefix:"LIMIT_"`
+				Request struct {
+					Mem string `default:"256Mi"`
+					Cpu string `default:"100m"`
+				} `prefix:"REQUEST_"`
+			} `prefix:"RESOURCE_"`
+		} `prefix:"VLSELECTDEFAULT_"`
+		VLStorageDefault struct {
+			Image    string `default:"victoriametrics/victoria-logs"`
+			Version  string `env:",expand" default:"${VM_LOGS_VERSION}-victorialogs"`
+			Port     string `default:"9491"`
+			Resource struct {
+				Limit struct {
+					Mem string `default:"2048Mi"`
+					Cpu string `default:"1000m"`
+				} `prefix:"LIMIT_"`
+				Request struct {
+					Mem string `default:"512Mi"`
+					Cpu string `default:"200m"`
+				} `prefix:"REQUEST_"`
+			} `prefix:"RESOURCE_"`
+		} `prefix:"VLSTORAGEDEFAULT_"`
+		VLInsertDefault struct {
+			Image    string `default:"victoriametrics/victoria-logs"`
+			Version  string `env:",expand" default:"${VM_LOGS_VERSION}-victorialogs"`
+			Port     string `default:"9481"`
+			Resource struct {
+				Limit struct {
+					Mem string `default:"1024Mi"`
+					Cpu string `default:"1000m"`
+				} `prefix:"LIMIT_"`
+				Request struct {
+					Mem string `default:"256Mi"`
+					Cpu string `default:"100m"`
+				} `prefix:"REQUEST_"`
+			} `prefix:"RESOURCE_"`
+		} `prefix:"VLINSERTDEFAULT_"`
+	} `prefix:"VLCLUSTERDEFAULT_"`
+
 	EnabledPrometheusConverter struct {
 		PodMonitor         bool `default:"true" env:"PODMONITOR"`
 		ServiceScrape      bool `default:"true" env:"SERVICESCRAPE"`
diff --git a/internal/controller/operator/factory/build/defaults.go b/internal/controller/operator/factory/build/defaults.go
index 74883641..01f3f96b 100644
--- a/internal/controller/operator/factory/build/defaults.go
+++ b/internal/controller/operator/factory/build/defaults.go
@@ -31,6 +31,7 @@ func AddDefaults(scheme *runtime.Scheme) {
 	scheme.AddTypeDefaultingFunc(&vmv1beta1.VMCluster{}, addVMClusterDefaults)
 	scheme.AddTypeDefaultingFunc(&vmv1beta1.VLogs{}, addVlogsDefaults)
 	scheme.AddTypeDefaultingFunc(&vmv1.VLSingle{}, addVLSingleDefaults)
+	scheme.AddTypeDefaultingFunc(&vmv1.VLCluster{}, addVLClusterDefaults)
 	scheme.AddTypeDefaultingFunc(&vmv1beta1.VMServiceScrape{}, addVMServiceScrapeDefaults)
 }
 
@@ -602,3 +603,173 @@ func addVMServiceScrapeDefaults(objI any) {
 		cr.Spec.DiscoveryRole = "endpointslices"
 	}
 }
+
+const (
+	vlStorageDefaultDBPath = "vlstorage-data"
+)
+
+func addVLClusterDefaults(objI any) {
+	cr := objI.(*vmv1.VLCluster)
+	c := getCfg()
+
+	// cluster is tricky is has main strictSecurity and per app
+	useStrictSecurity := c.EnableStrictSecurity
+	if cr.Spec.UseStrictSecurity != nil {
+		useStrictSecurity = *cr.Spec.UseStrictSecurity
+	}
+	if cr.Spec.ClusterDomainName == "" {
+		cr.Spec.ClusterDomainName = c.ClusterDomainName
+	}
+
+	if cr.Spec.VLStorage != nil {
+		if cr.Spec.VLStorage.UseStrictSecurity == nil {
+			cr.Spec.VLStorage.UseStrictSecurity = &useStrictSecurity
+		}
+		if cr.Spec.VLStorage.DisableSelfServiceScrape == nil {
+			cr.Spec.VLStorage.DisableSelfServiceScrape = &c.DisableSelfServiceScrapeCreation
+		}
+		cr.Spec.VLStorage.ImagePullSecrets = append(cr.Spec.VLStorage.ImagePullSecrets, cr.Spec.ImagePullSecrets...)
+
+		if cr.Spec.VLStorage.Image.Repository == "" {
+			cr.Spec.VLStorage.Image.Repository = c.VLClusterDefault.VLStorageDefault.Image
+		}
+		cr.Spec.VLStorage.Image.Repository = formatContainerImage(c.ContainerRegistry, cr.Spec.VLStorage.Image.Repository)
+
+		if cr.Spec.VLStorage.Image.Tag == "" {
+			if cr.Spec.ClusterVersion != "" {
+				cr.Spec.VLStorage.Image.Tag = cr.Spec.ClusterVersion
+			} else {
+				cr.Spec.VLStorage.Image.Tag = c.VLClusterDefault.VLStorageDefault.Version
+			}
+		}
+
+		if cr.Spec.VLStorage.Port == "" {
+			cr.Spec.VLStorage.Port = c.VLClusterDefault.VLStorageDefault.Port
+		}
+
+		if cr.Spec.VLStorage.DNSPolicy == "" {
+			cr.Spec.VLStorage.DNSPolicy = corev1.DNSClusterFirst
+		}
+		if cr.Spec.VLStorage.SchedulerName == "" {
+			cr.Spec.VLStorage.SchedulerName = "default-scheduler"
+		}
+		if cr.Spec.VLStorage.Image.PullPolicy == "" {
+			cr.Spec.VLStorage.Image.PullPolicy = corev1.PullIfNotPresent
+		}
+		if cr.Spec.VLStorage.StorageDataPath == "" {
+			cr.Spec.VLStorage.StorageDataPath = vlStorageDefaultDBPath
+		}
+		if cr.Spec.VLStorage.UseDefaultResources == nil {
+			cr.Spec.VLStorage.UseDefaultResources = &c.VLClusterDefault.UseDefaultResources
+		}
+		cr.Spec.VLStorage.Resources = Resources(cr.Spec.VLStorage.Resources,
+			config.Resource(c.VLClusterDefault.VLStorageDefault.Resource),
+			*cr.Spec.VLStorage.UseDefaultResources,
+		)
+	}
+
+	if cr.Spec.VLInsert != nil {
+		if cr.Spec.VLInsert.UseStrictSecurity == nil {
+			cr.Spec.VLInsert.UseStrictSecurity = &useStrictSecurity
+		}
+		if cr.Spec.VLInsert.DisableSelfServiceScrape == nil {
+			cr.Spec.VLInsert.DisableSelfServiceScrape = &c.DisableSelfServiceScrapeCreation
+		}
+		cr.Spec.VLInsert.ImagePullSecrets = append(cr.Spec.VLInsert.ImagePullSecrets, cr.Spec.ImagePullSecrets...)
+
+		if cr.Spec.VLInsert.Image.Repository == "" {
+			cr.Spec.VLInsert.Image.Repository = c.VLClusterDefault.VLInsertDefault.Image
+		}
+		cr.Spec.VLInsert.Image.Repository = formatContainerImage(c.ContainerRegistry, cr.Spec.VLInsert.Image.Repository)
+		if cr.Spec.VLInsert.Image.Tag == "" {
+			if cr.Spec.ClusterVersion != "" {
+				cr.Spec.VLInsert.Image.Tag = cr.Spec.ClusterVersion
+			} else {
+				cr.Spec.VLInsert.Image.Tag = c.VLClusterDefault.VLInsertDefault.Version
+			}
+		}
+		if cr.Spec.VLInsert.Port == "" {
+			cr.Spec.VLInsert.Port = c.VLClusterDefault.VLInsertDefault.Port
+		}
+		if cr.Spec.VLInsert.UseDefaultResources == nil {
+			cr.Spec.VLInsert.UseDefaultResources = &c.VLClusterDefault.UseDefaultResources
+		}
+		cr.Spec.VLInsert.Resources = Resources(cr.Spec.VLInsert.Resources,
+			config.Resource(c.VLClusterDefault.VLInsertDefault.Resource),
+			*cr.Spec.VLInsert.UseDefaultResources,
+		)
+
+	}
+	if cr.Spec.VLSelect != nil {
+		if cr.Spec.VLSelect.UseStrictSecurity == nil {
+			cr.Spec.VLSelect.UseStrictSecurity = &useStrictSecurity
+		}
+		if cr.Spec.VLSelect.DisableSelfServiceScrape == nil {
+			cr.Spec.VLSelect.DisableSelfServiceScrape = &c.DisableSelfServiceScrapeCreation
+		}
+
+		cr.Spec.VLSelect.ImagePullSecrets = append(cr.Spec.VLSelect.ImagePullSecrets, cr.Spec.ImagePullSecrets...)
+
+		if cr.Spec.VLSelect.Image.Repository == "" {
+			cr.Spec.VLSelect.Image.Repository = c.VLClusterDefault.VLSelectDefault.Image
+		}
+		cr.Spec.VLSelect.Image.Repository = formatContainerImage(c.ContainerRegistry, cr.Spec.VLSelect.Image.Repository)
+		if cr.Spec.VLSelect.Image.Tag == "" {
+			if cr.Spec.ClusterVersion != "" {
+				cr.Spec.VLSelect.Image.Tag = cr.Spec.ClusterVersion
+			} else {
+				cr.Spec.VLSelect.Image.Tag = c.VLClusterDefault.VLSelectDefault.Version
+			}
+		}
+		if cr.Spec.VLSelect.Port == "" {
+			cr.Spec.VLSelect.Port = c.VLClusterDefault.VLSelectDefault.Port
+		}
+
+		if cr.Spec.VLSelect.DNSPolicy == "" {
+			cr.Spec.VLSelect.DNSPolicy = corev1.DNSClusterFirst
+		}
+		if cr.Spec.VLSelect.SchedulerName == "" {
+			cr.Spec.VLSelect.SchedulerName = "default-scheduler"
+		}
+		if cr.Spec.VLSelect.Image.PullPolicy == "" {
+			cr.Spec.VLSelect.Image.PullPolicy = corev1.PullIfNotPresent
+		}
+
+		if cr.Spec.VLSelect.UseDefaultResources == nil {
+			cr.Spec.VLSelect.UseDefaultResources = &c.VLClusterDefault.UseDefaultResources
+		}
+		cr.Spec.VLSelect.Resources = Resources(cr.Spec.VLSelect.Resources,
+			config.Resource(c.VLClusterDefault.VLSelectDefault.Resource),
+			*cr.Spec.VLSelect.UseDefaultResources,
+		)
+	}
+	if cr.Spec.RequestsLoadBalancer.Enabled {
+		if cr.Spec.RequestsLoadBalancer.Spec.UseStrictSecurity == nil {
+			cr.Spec.RequestsLoadBalancer.Spec.UseStrictSecurity = &useStrictSecurity
+		}
+		if cr.Spec.RequestsLoadBalancer.Spec.DisableSelfServiceScrape == nil {
+			cr.Spec.RequestsLoadBalancer.Spec.DisableSelfServiceScrape = &c.DisableSelfServiceScrapeCreation
+		}
+		cr.Spec.RequestsLoadBalancer.Spec.ImagePullSecrets = append(cr.Spec.RequestsLoadBalancer.Spec.ImagePullSecrets, cr.Spec.ImagePullSecrets...)
+		if cr.Spec.RequestsLoadBalancer.Spec.Image.Tag == "" {
+			cr.Spec.RequestsLoadBalancer.Spec.Image.Tag = cr.Spec.ClusterVersion
+		}
+		cv := config.ApplicationDefaults(c.VMAuthDefault)
+		addDefaultsToCommonParams(&cr.Spec.RequestsLoadBalancer.Spec.CommonDefaultableParams, &cv)
+		spec := &cr.Spec.RequestsLoadBalancer.Spec
+		if spec.EmbeddedProbes == nil {
+			spec.EmbeddedProbes = &vmv1beta1.EmbeddedProbes{}
+		}
+		if spec.StartupProbe == nil {
+			spec.StartupProbe = &corev1.Probe{
+				ProbeHandler: corev1.ProbeHandler{
+					HTTPGet: &corev1.HTTPGetAction{},
+				},
+			}
+		}
+		if spec.AdditionalServiceSpec != nil && !spec.AdditionalServiceSpec.UseAsDefault {
+			spec.AdditionalServiceSpec.UseAsDefault = true
+		}
+	}
+
+}
diff --git a/internal/controller/operator/factory/finalize/vlcluster.go b/internal/controller/operator/factory/finalize/vlcluster.go
new file mode 100644
index 00000000..c95704a3
--- /dev/null
+++ b/internal/controller/operator/factory/finalize/vlcluster.go
@@ -0,0 +1,209 @@
+package finalize
+
+import (
+	"context"
+	"fmt"
+
+	appsv1 "k8s.io/api/apps/v1"
+	v2 "k8s.io/api/autoscaling/v2"
+	v1 "k8s.io/api/core/v1"
+	policyv1 "k8s.io/api/policy/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+)
+
+func OnVClusterDelete(ctx context.Context, rclient client.Client, cr *vmv1.VLCluster) error {
+
+	if cr.Spec.VLInsert != nil {
+		if err := OnVLInsertDelete(ctx, rclient, cr, cr.Spec.VLInsert); err != nil {
+			return fmt.Errorf("cannot remove insert component objects: %w", err)
+		}
+	}
+
+	if cr.Spec.VLSelect != nil {
+		if err := OnVLSelectDelete(ctx, rclient, cr, cr.Spec.VLSelect); err != nil {
+			return fmt.Errorf("cannot remove select component objects: %w", err)
+		}
+	}
+	if cr.Spec.VLStorage != nil {
+		if err := OnVLStorageDelete(ctx, rclient, cr, cr.Spec.VLStorage); err != nil {
+			return fmt.Errorf("cannot remove storage component objects: %w", err)
+		}
+	}
+
+	if err := deleteSA(ctx, rclient, cr); err != nil {
+		return err
+	}
+	if cr.Spec.RequestsLoadBalancer.Enabled {
+		if err := OnVLClusterLoadBalancerDelete(ctx, rclient, cr); err != nil {
+			return fmt.Errorf("cannot delete cluster loadbalancer components: %w", err)
+		}
+	}
+	return removeFinalizeObjByName(ctx, rclient, cr, cr.Name, cr.Namespace)
+
+}
+
+// OnVLInsertDelete removes all objects related to vlinsert component
+func OnVLInsertDelete(ctx context.Context, rclient client.Client, cr *vmv1.VLCluster, obj *vmv1.VLInsert) error {
+	objMeta := metav1.ObjectMeta{
+		Namespace: cr.Namespace,
+		Name:      cr.GetVLInsertName(),
+	}
+	objsToRemove := []client.Object{
+		&appsv1.Deployment{ObjectMeta: objMeta},
+		&v1.Service{ObjectMeta: objMeta},
+	}
+	if obj.ServiceSpec != nil && !obj.ServiceSpec.UseAsDefault {
+		objsToRemove = append(objsToRemove, &v1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: cr.Namespace,
+				Name:      obj.ServiceSpec.NameOrDefault(cr.GetVLInsertName()),
+			},
+		})
+	}
+	if obj.PodDisruptionBudget != nil {
+		objsToRemove = append(objsToRemove, &policyv1.PodDisruptionBudget{ObjectMeta: objMeta})
+	}
+	if obj.HPA != nil {
+		objsToRemove = append(objsToRemove, &v2.HorizontalPodAutoscaler{ObjectMeta: objMeta})
+	}
+	if !ptr.Deref(obj.DisableSelfServiceScrape, false) {
+		objsToRemove = append(objsToRemove, &vmv1beta1.VMServiceScrape{ObjectMeta: objMeta})
+		objsToRemove = append(objsToRemove, &vmv1beta1.VMServiceScrape{ObjectMeta: metav1.ObjectMeta{Name: cr.GetVLInsertLBName(), Namespace: cr.Namespace}})
+	}
+
+	if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableInsertBalancing {
+		objsToRemove = append(objsToRemove, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: cr.GetVLInsertLBName(), Namespace: cr.Namespace}})
+	}
+	for _, objToRemove := range objsToRemove {
+		if err := SafeDeleteWithFinalizer(ctx, rclient, objToRemove); err != nil {
+			return fmt.Errorf("failed to remove object=%s: %w", objToRemove.GetObjectKind().GroupVersionKind(), err)
+		}
+	}
+	return nil
+}
+
+// OnVLInsertDelete removes all objects related to vlinsert component
+func OnVLSelectDelete(ctx context.Context, rclient client.Client, cr *vmv1.VLCluster, obj *vmv1.VLSelect) error {
+	objMeta := metav1.ObjectMeta{
+		Namespace: cr.Namespace,
+		Name:      cr.GetVLSelectName(),
+	}
+	objsToRemove := []client.Object{
+		&appsv1.StatefulSet{ObjectMeta: objMeta},
+		&v1.Service{ObjectMeta: objMeta},
+	}
+	if obj.ServiceSpec != nil && !obj.ServiceSpec.UseAsDefault {
+		objsToRemove = append(objsToRemove, &v1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: cr.Namespace,
+				Name:      obj.ServiceSpec.NameOrDefault(cr.GetVLSelectName()),
+			},
+		})
+	}
+	if obj.PodDisruptionBudget != nil {
+		objsToRemove = append(objsToRemove, &policyv1.PodDisruptionBudget{ObjectMeta: objMeta})
+	}
+	if obj.HPA != nil {
+		objsToRemove = append(objsToRemove, &v2.HorizontalPodAutoscaler{ObjectMeta: objMeta})
+	}
+	if !ptr.Deref(obj.DisableSelfServiceScrape, false) {
+		objsToRemove = append(objsToRemove, &vmv1beta1.VMServiceScrape{ObjectMeta: objMeta})
+		objsToRemove = append(objsToRemove, &vmv1beta1.VMServiceScrape{ObjectMeta: metav1.ObjectMeta{Name: cr.GetVLSelectLBName(), Namespace: cr.Namespace}})
+	}
+	if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableSelectBalancing {
+		objsToRemove = append(objsToRemove, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: cr.GetVLSelectLBName(), Namespace: cr.Namespace}})
+	}
+	for _, objToRemove := range objsToRemove {
+		if err := SafeDeleteWithFinalizer(ctx, rclient, objToRemove); err != nil {
+			return fmt.Errorf("failed to remove object=%s: %w", objToRemove.GetObjectKind().GroupVersionKind(), err)
+		}
+	}
+	return nil
+}
+
+// OnVLInsertDelete removes all objects related to vlinsert component
+func OnVLStorageDelete(ctx context.Context, rclient client.Client, cr *vmv1.VLCluster, obj *vmv1.VLStorage) error {
+	objMeta := metav1.ObjectMeta{
+		Namespace: cr.Namespace,
+		Name:      cr.GetVLStorageName(),
+	}
+	objsToRemove := []client.Object{
+		&appsv1.StatefulSet{ObjectMeta: objMeta},
+		&v1.Service{ObjectMeta: objMeta},
+	}
+	if obj.ServiceSpec != nil && !obj.ServiceSpec.UseAsDefault {
+		objsToRemove = append(objsToRemove, &v1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: cr.Namespace,
+				Name:      obj.ServiceSpec.NameOrDefault(cr.GetVLStorageName()),
+			},
+		})
+	}
+	if obj.PodDisruptionBudget != nil {
+		objsToRemove = append(objsToRemove, &policyv1.PodDisruptionBudget{ObjectMeta: objMeta})
+	}
+	if !ptr.Deref(obj.DisableSelfServiceScrape, false) {
+		objsToRemove = append(objsToRemove, &vmv1beta1.VMServiceScrape{ObjectMeta: objMeta})
+	}
+
+	for _, objToRemove := range objsToRemove {
+		if err := SafeDeleteWithFinalizer(ctx, rclient, objToRemove); err != nil {
+			return fmt.Errorf("failed to remove object=%s: %w", objToRemove.GetObjectKind().GroupVersionKind(), err)
+		}
+	}
+	return nil
+}
+
+// OnVLClusterLoadBalancerDelete removes vmauth loadbalancer components for vlcluster
+func OnVLClusterLoadBalancerDelete(ctx context.Context, rclient client.Client, cr *vmv1.VLCluster) error {
+	lbMeta := metav1.ObjectMeta{
+		Namespace: cr.Namespace,
+		Name:      cr.GetVMAuthLBName(),
+	}
+
+	objsToRemove := []client.Object{
+		&appsv1.Deployment{ObjectMeta: lbMeta},
+		&v1.Secret{ObjectMeta: lbMeta},
+		&v1.Service{ObjectMeta: lbMeta},
+	}
+	if !ptr.Deref(cr.Spec.RequestsLoadBalancer.Spec.DisableSelfServiceScrape, false) {
+		objsToRemove = append(objsToRemove, &vmv1beta1.VMServiceScrape{ObjectMeta: lbMeta})
+	}
+	if cr.Spec.RequestsLoadBalancer.Spec.PodDisruptionBudget != nil {
+		objsToRemove = append(objsToRemove, &policyv1.PodDisruptionBudget{ObjectMeta: lbMeta})
+	}
+
+	if cr.Spec.VLSelect != nil {
+		if !ptr.Deref(cr.Spec.VLSelect.DisableSelfServiceScrape, false) {
+			objsToRemove = append(objsToRemove, &vmv1beta1.VMServiceScrape{
+				ObjectMeta: metav1.ObjectMeta{Name: cr.GetVLSelectLBName(), Namespace: cr.Namespace}})
+		}
+		objsToRemove = append(objsToRemove, &v1.Service{ObjectMeta: metav1.ObjectMeta{
+			Name:      cr.GetVLSelectLBName(),
+			Namespace: cr.Namespace,
+		}})
+	}
+	if cr.Spec.VLInsert != nil {
+		if !ptr.Deref(cr.Spec.VLInsert.DisableSelfServiceScrape, false) {
+			objsToRemove = append(objsToRemove, &vmv1beta1.VMServiceScrape{
+				ObjectMeta: metav1.ObjectMeta{Name: cr.GetVLInsertLBName(), Namespace: cr.Namespace}})
+		}
+		objsToRemove = append(objsToRemove, &v1.Service{ObjectMeta: metav1.ObjectMeta{
+			Name:      cr.GetVLInsertLBName(),
+			Namespace: cr.Namespace,
+		}})
+	}
+
+	for _, objToRemove := range objsToRemove {
+		if err := SafeDeleteWithFinalizer(ctx, rclient, objToRemove); err != nil {
+			return fmt.Errorf("failed to remove lb object=%s: %w", objToRemove.GetObjectKind().GroupVersionKind(), err)
+		}
+	}
+
+	return nil
+}
diff --git a/internal/controller/operator/factory/vlcluster/helpers.go b/internal/controller/operator/factory/vlcluster/helpers.go
new file mode 100644
index 00000000..e7030fbb
--- /dev/null
+++ b/internal/controller/operator/factory/vlcluster/helpers.go
@@ -0,0 +1,122 @@
+package vlcluster
+
+import (
+	"fmt"
+
+	corev1 "k8s.io/api/core/v1"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+)
+
+const (
+	vmauthLBServiceProxyJobNameLabel = "operator.victoriametrics.com/vmauthlb-proxy-job-name"
+	vmauthLBServiceProxyTargetLabel  = "operator.victoriametrics.com/vmauthlb-proxy-name"
+	tlsServerConfigMountPath         = "/etc/vm/tls-server-secrets"
+)
+
+type optsBuilder struct {
+	*vmv1.VLCluster
+	prefixedName      string
+	finalLabels       map[string]string
+	selectorLabels    map[string]string
+	additionalService *vmv1beta1.AdditionalServiceSpec
+}
+
+// PrefixedName implements build.svcBuilderArgs interface
+func (csb *optsBuilder) PrefixedName() string {
+	return csb.prefixedName
+}
+
+// AllLabels implements build.svcBuilderArgs interface
+func (csb *optsBuilder) AllLabels() map[string]string {
+	return csb.finalLabels
+}
+
+// AnnotationsFiltered implements build.svcBuilderArgs interface
+func (csb *optsBuilder) AnnotationsFiltered() map[string]string {
+	return csb.FinalAnnotations()
+}
+
+// SelectorLabels implements build.svcBuilderArgs interface
+func (csb *optsBuilder) SelectorLabels() map[string]string {
+	return csb.selectorLabels
+}
+
+// GetAdditionalService implements build.svcBuilderArgs interface
+func (csb *optsBuilder) GetAdditionalService() *vmv1beta1.AdditionalServiceSpec {
+	return csb.additionalService
+}
+
+func newOptsBuilder(cr *vmv1.VLCluster, name string, selectorLabels map[string]string) *optsBuilder {
+	return &optsBuilder{
+		VLCluster:      cr,
+		prefixedName:   name,
+		finalLabels:    cr.FinalLabels(selectorLabels),
+		selectorLabels: selectorLabels,
+	}
+}
+
+func addTLSConfigToVolumes(dst []corev1.Volume, tlsC *vmv1.TLSServerConfig) []corev1.Volume {
+	if tlsC == nil {
+		return dst
+	}
+	addSecretVolume := func(sr *corev1.SecretKeySelector) {
+		name := fmt.Sprintf("secret-tls-%s", sr.Name)
+		for _, dst := range dst {
+			if dst.Name == name {
+				return
+			}
+		}
+		dst = append(dst, corev1.Volume{
+			Name: name,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: sr.Name,
+				},
+			},
+		})
+	}
+	switch {
+	case tlsC.CertFile != "":
+	case tlsC.CertSecret != nil:
+		addSecretVolume(tlsC.CertSecret)
+	}
+
+	switch {
+	case tlsC.KeyFile != "":
+	case tlsC.KeySecret != nil:
+		addSecretVolume(tlsC.KeySecret)
+	}
+	return dst
+}
+
+func addTLSConfigToVolumeMounts(dst []corev1.VolumeMount, tlsC *vmv1.TLSServerConfig) []corev1.VolumeMount {
+	if tlsC == nil {
+		return dst
+	}
+	addSecretVolume := func(sr *corev1.SecretKeySelector) {
+		name := fmt.Sprintf("secret-tls-%s", sr.Name)
+		for _, dst := range dst {
+			if dst.Name == name {
+				return
+			}
+		}
+		dst = append(dst, corev1.VolumeMount{
+			Name:      name,
+			MountPath: fmt.Sprintf("%s/%s", tlsServerConfigMountPath, sr.Name),
+		})
+	}
+	switch {
+	case tlsC.CertFile != "":
+	case tlsC.CertSecret != nil:
+		addSecretVolume(tlsC.CertSecret)
+	}
+
+	switch {
+	case tlsC.KeyFile != "":
+	case tlsC.KeySecret != nil:
+		addSecretVolume(tlsC.KeySecret)
+	}
+	return dst
+}
diff --git a/internal/controller/operator/factory/vlcluster/vlcluster.go b/internal/controller/operator/factory/vlcluster/vlcluster.go
new file mode 100644
index 00000000..29dddb19
--- /dev/null
+++ b/internal/controller/operator/factory/vlcluster/vlcluster.go
@@ -0,0 +1,254 @@
+package vlcluster
+
+import (
+	"context"
+	"fmt"
+
+	autoscalingv2 "k8s.io/api/autoscaling/v2"
+	corev1 "k8s.io/api/core/v1"
+	policyv1 "k8s.io/api/policy/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/build"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/finalize"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/reconcile"
+)
+
+// CreateOrUpdate syncs VLCluster object to the desired state
+func CreateOrUpdate(ctx context.Context, rclient client.Client, cr *vmv1.VLCluster) error {
+	var prevCR *vmv1.VLCluster
+	if cr.ParsedLastAppliedSpec != nil {
+		prevCR = cr.DeepCopy()
+		prevCR.Spec = *cr.ParsedLastAppliedSpec
+	}
+	if cr.IsOwnsServiceAccount() {
+		var prevSA *corev1.ServiceAccount
+		if prevCR != nil {
+			b := newOptsBuilder(prevCR, prevCR.PrefixedName(), prevCR.SelectorLabels())
+			prevSA = build.ServiceAccount(b)
+		}
+		b := newOptsBuilder(cr, cr.PrefixedName(), cr.SelectorLabels())
+		if err := reconcile.ServiceAccount(ctx, rclient, build.ServiceAccount(b), prevSA); err != nil {
+			return fmt.Errorf("failed create service account: %w", err)
+		}
+	}
+
+	// handle case for loadbalancing
+	if cr.Spec.RequestsLoadBalancer.Enabled && cr.Spec.VLStorage != nil {
+		if err := createOrUpdateVMAuthLB(ctx, rclient, cr, prevCR); err != nil {
+			return fmt.Errorf("cannot reconcile vmauthlb: %w", err)
+		}
+	}
+
+	if err := createOrUpdateVLStorage(ctx, rclient, cr, prevCR); err != nil {
+		return fmt.Errorf("cannot reconcile storage: %w", err)
+	}
+	if err := createOrUpdateVLSelect(ctx, rclient, cr, prevCR); err != nil {
+		return fmt.Errorf("cannot reconcile select: %w", err)
+	}
+
+	if err := createOrUpdateVLInsert(ctx, rclient, cr, prevCR); err != nil {
+		return fmt.Errorf("cannot reconcile insert: %w", err)
+	}
+
+	if err := deletePrevStateResources(ctx, rclient, cr, prevCR); err != nil {
+		return fmt.Errorf("failed to remove objects from previous cluster state: %w", err)
+	}
+	return nil
+}
+
+func deletePrevStateResources(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	if prevCR == nil {
+		// fast path
+		return nil
+	}
+	vmst := cr.Spec.VLStorage
+	vmse := cr.Spec.VLSelect
+	vmis := cr.Spec.VLInsert
+	prevSpec := prevCR.Spec
+	prevSt := prevSpec.VLStorage
+	prevSe := prevSpec.VLSelect
+	prevIs := prevSpec.VLInsert
+	if prevSt != nil {
+		if vmst == nil {
+			if err := finalize.OnVLStorageDelete(ctx, rclient, cr, prevSt); err != nil {
+				return fmt.Errorf("cannot remove storage from prev state: %w", err)
+			}
+		} else {
+			commonObjMeta := metav1.ObjectMeta{Namespace: cr.Namespace, Name: cr.GetVLStorageName()}
+			if vmst.PodDisruptionBudget == nil && prevSt.PodDisruptionBudget != nil {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &policyv1.PodDisruptionBudget{ObjectMeta: commonObjMeta}); err != nil {
+					return fmt.Errorf("cannot remove PDB from prev storage: %w", err)
+				}
+			}
+			if ptr.Deref(vmst.DisableSelfServiceScrape, false) && !ptr.Deref(prevSt.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{ObjectMeta: commonObjMeta}); err != nil {
+					return fmt.Errorf("cannot remove serviceScrape from prev storage: %w", err)
+				}
+			}
+			prevSvc, currSvc := prevSt.ServiceSpec, vmst.ServiceSpec
+			if err := reconcile.AdditionalServices(ctx, rclient, cr.GetVLStorageName(), cr.Namespace, prevSvc, currSvc); err != nil {
+				return fmt.Errorf("cannot remove storage additional service: %w", err)
+			}
+		}
+	}
+
+	if prevSe != nil {
+		if vmse == nil {
+			if err := finalize.OnVLSelectDelete(ctx, rclient, cr, prevSe); err != nil {
+				return fmt.Errorf("cannot remove select from prev state: %w", err)
+			}
+		} else {
+			commonObjMeta := metav1.ObjectMeta{
+				Namespace: cr.Namespace, Name: cr.GetVLSelectName()}
+			if vmse.PodDisruptionBudget == nil && prevSe.PodDisruptionBudget != nil {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &policyv1.PodDisruptionBudget{ObjectMeta: commonObjMeta}); err != nil {
+					return fmt.Errorf("cannot remove PDB from prev select: %w", err)
+				}
+			}
+			if vmse.HPA == nil && prevSe.HPA != nil {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &autoscalingv2.HorizontalPodAutoscaler{ObjectMeta: commonObjMeta}); err != nil {
+					return fmt.Errorf("cannot remove HPA from prev select: %w", err)
+				}
+			}
+			if ptr.Deref(vmse.DisableSelfServiceScrape, false) && !ptr.Deref(prevSe.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{ObjectMeta: commonObjMeta}); err != nil {
+					return fmt.Errorf("cannot remove serviceScrape from prev select: %w", err)
+				}
+			}
+			prevSvc, currSvc := prevSe.ServiceSpec, vmse.ServiceSpec
+			if err := reconcile.AdditionalServices(ctx, rclient, cr.GetVLSelectName(), cr.Namespace, prevSvc, currSvc); err != nil {
+				return fmt.Errorf("cannot remove select additional service: %w", err)
+			}
+		}
+		// transition to load-balancer state
+		// have to remove prev service scrape
+		if (!prevSpec.RequestsLoadBalancer.Enabled &&
+			cr.Spec.RequestsLoadBalancer.Enabled &&
+			!cr.Spec.RequestsLoadBalancer.DisableSelectBalancing) ||
+			// second case load balancer was enabled, but disabled for select component
+			(prevSpec.RequestsLoadBalancer.DisableInsertBalancing &&
+				!cr.Spec.RequestsLoadBalancer.DisableInsertBalancing) {
+			// remove service scrape because service was renamed
+			if !ptr.Deref(cr.Spec.VLSelect.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{
+					ObjectMeta: metav1.ObjectMeta{Name: cr.GetVLSelectName(), Namespace: cr.Namespace},
+				}); err != nil {
+					return fmt.Errorf("cannot delete vmservicescrape for non-lb select svc: %w", err)
+				}
+			}
+		}
+		// disabled loadbalancer only for component
+		// transit to the k8s service balancing mode
+		if prevSpec.RequestsLoadBalancer.Enabled &&
+			!prevSpec.RequestsLoadBalancer.DisableSelectBalancing &&
+			cr.Spec.RequestsLoadBalancer.DisableSelectBalancing {
+			if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &corev1.Service{ObjectMeta: metav1.ObjectMeta{
+				Name:      cr.GetVLSelectLBName(),
+				Namespace: cr.Namespace,
+			}}); err != nil {
+				return fmt.Errorf("cannot remove select lb service: %w", err)
+			}
+			if !ptr.Deref(cr.Spec.VLSelect.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{
+					ObjectMeta: metav1.ObjectMeta{Name: cr.GetVLSelectLBName(), Namespace: cr.Namespace},
+				}); err != nil {
+					return fmt.Errorf("cannot delete vmservicescrape for lb select svc: %w", err)
+				}
+			}
+		}
+	}
+
+	if prevIs != nil {
+		if vmis == nil {
+			if err := finalize.OnVLInsertDelete(ctx, rclient, cr, prevIs); err != nil {
+				return fmt.Errorf("cannot remove insert from prev state: %w", err)
+			}
+		} else {
+
+			commonObjMeta := metav1.ObjectMeta{Namespace: cr.Namespace, Name: cr.GetVLInsertName()}
+			if vmis.PodDisruptionBudget == nil && prevIs.PodDisruptionBudget != nil {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &policyv1.PodDisruptionBudget{ObjectMeta: commonObjMeta}); err != nil {
+					return fmt.Errorf("cannot remove PDB from prev insert: %w", err)
+				}
+			}
+			if vmis.HPA == nil && prevIs.HPA != nil {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &autoscalingv2.HorizontalPodAutoscaler{ObjectMeta: commonObjMeta}); err != nil {
+					return fmt.Errorf("cannot remove HPA from prev insert: %w", err)
+				}
+			}
+			if ptr.Deref(vmis.DisableSelfServiceScrape, false) && !ptr.Deref(prevIs.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{ObjectMeta: commonObjMeta}); err != nil {
+					return fmt.Errorf("cannot remove serviceScrape from prev insert: %w", err)
+				}
+			}
+			prevSvc, currSvc := prevIs.ServiceSpec, vmis.ServiceSpec
+			if err := reconcile.AdditionalServices(ctx, rclient, cr.GetVLInsertName(), cr.Namespace, prevSvc, currSvc); err != nil {
+				return fmt.Errorf("cannot remove insert additional service: %w", err)
+			}
+		}
+		// transition to load-balancer state
+		// have to remove prev service scrape
+		if (!prevSpec.RequestsLoadBalancer.Enabled &&
+			cr.Spec.RequestsLoadBalancer.Enabled &&
+			!cr.Spec.RequestsLoadBalancer.DisableInsertBalancing) ||
+			// second case load balancer was enabled, but disabled for insert component
+			(prevSpec.RequestsLoadBalancer.DisableInsertBalancing &&
+				!cr.Spec.RequestsLoadBalancer.DisableInsertBalancing) {
+			// remove service scrape because service was renamed
+			if !ptr.Deref(cr.Spec.VLInsert.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{
+					ObjectMeta: metav1.ObjectMeta{Name: cr.GetVLInsertName(), Namespace: cr.Namespace},
+				}); err != nil {
+					return fmt.Errorf("cannot delete vmservicescrape for non-lb insert svc: %w", err)
+				}
+			}
+		}
+		// disabled loadbalancer only for component
+		// transit to the k8s service balancing mode
+		if prevSpec.RequestsLoadBalancer.Enabled &&
+			!prevSpec.RequestsLoadBalancer.DisableInsertBalancing &&
+			cr.Spec.RequestsLoadBalancer.DisableInsertBalancing {
+			if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &corev1.Service{ObjectMeta: metav1.ObjectMeta{
+				Name:      cr.GetVLInsertLBName(),
+				Namespace: cr.Namespace,
+			}}); err != nil {
+				return fmt.Errorf("cannot remove insert lb service: %w", err)
+			}
+			if !ptr.Deref(cr.Spec.VLInsert.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{
+					ObjectMeta: metav1.ObjectMeta{Name: cr.GetVLInsertLBName(), Namespace: cr.Namespace},
+				}); err != nil {
+					return fmt.Errorf("cannot delete vmservicescrape for lb insert svc: %w", err)
+				}
+			}
+		}
+	}
+
+	if prevSpec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.Enabled {
+		if err := finalize.OnVLClusterLoadBalancerDelete(ctx, rclient, prevCR); err != nil {
+			return fmt.Errorf("failed to remove loadbalancer components enabled at prev state: %w", err)
+		}
+	}
+	if cr.Spec.RequestsLoadBalancer.Enabled {
+		// case for child objects
+		prevLBSpec := prevSpec.RequestsLoadBalancer.Spec
+		lbSpec := cr.Spec.RequestsLoadBalancer.Spec
+		if prevLBSpec.PodDisruptionBudget != nil && lbSpec.PodDisruptionBudget == nil {
+			if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &policyv1.PodDisruptionBudget{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      cr.GetVMAuthLBName(),
+					Namespace: cr.Namespace,
+				},
+			}); err != nil {
+				return fmt.Errorf("cannot delete PodDisruptionBudget for cluster lb: %w", err)
+			}
+		}
+	}
+
+	return nil
+}
diff --git a/internal/controller/operator/factory/vlcluster/vlinsert.go b/internal/controller/operator/factory/vlcluster/vlinsert.go
new file mode 100644
index 00000000..3c2446a6
--- /dev/null
+++ b/internal/controller/operator/factory/vlcluster/vlinsert.go
@@ -0,0 +1,504 @@
+package vlcluster
+
+import (
+	"context"
+	"fmt"
+	"path"
+	"sort"
+	"strings"
+
+	appsv1 "k8s.io/api/apps/v1"
+	autoscalingv2 "k8s.io/api/autoscaling/v2"
+	corev1 "k8s.io/api/core/v1"
+	policyv1 "k8s.io/api/policy/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/build"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/reconcile"
+)
+
+func createOrUpdateVLInsert(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	if cr.Spec.VLInsert == nil {
+		return nil
+	}
+
+	if err := createOrUpdatePodDisruptionBudgetForVLInsert(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	if err := createOrUpdateVLInsertDeployment(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	insertSvc, err := createOrUpdateVLInsertService(ctx, rclient, cr, prevCR)
+	if err != nil {
+		return err
+	}
+	if err := createOrUpdateVLInsertHPA(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	if !ptr.Deref(cr.Spec.VLInsert.DisableSelfServiceScrape, false) {
+		svs := build.VMServiceScrapeForServiceWithSpec(insertSvc, cr.Spec.VLInsert)
+		if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableInsertBalancing {
+			// for backward compatibility we must keep job label value
+			svs.Spec.JobLabel = vmauthLBServiceProxyJobNameLabel
+		}
+		err := reconcile.VMServiceScrapeForCRD(ctx, rclient, svs)
+		if err != nil {
+			return fmt.Errorf("cannot create VMServiceScrape for VLInsert: %w", err)
+		}
+	}
+
+	return nil
+}
+
+func createOrUpdatePodDisruptionBudgetForVLInsert(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	if cr.Spec.VLInsert.PodDisruptionBudget == nil {
+		return nil
+	}
+	b := newOptsBuilder(cr, cr.GetVLInsertName(), cr.VLInsertSelectorLabels())
+	pdb := build.PodDisruptionBudget(b, cr.Spec.VLInsert.PodDisruptionBudget)
+	var prevPDB *policyv1.PodDisruptionBudget
+	if prevCR != nil && prevCR.Spec.VLInsert.PodDisruptionBudget != nil {
+		prevB := newOptsBuilder(prevCR, prevCR.GetVLInsertName(), prevCR.VLInsertSelectorLabels())
+		prevPDB = build.PodDisruptionBudget(prevB, prevCR.Spec.VLInsert.PodDisruptionBudget)
+	}
+	return reconcile.PDB(ctx, rclient, pdb, prevPDB)
+}
+
+func createOrUpdateVLInsertDeployment(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	var prevDeploy *appsv1.Deployment
+
+	if prevCR != nil && prevCR.Spec.VLInsert != nil {
+		var err error
+		prevDeploy, err = buildVLInsertDeployment(prevCR)
+		if err != nil {
+			return fmt.Errorf("cannot generate prev deploy spec: %w", err)
+		}
+	}
+	newDeployment, err := buildVLInsertDeployment(cr)
+	if err != nil {
+		return err
+	}
+	return reconcile.Deployment(ctx, rclient, newDeployment, prevDeploy, cr.Spec.VLInsert.HPA != nil)
+}
+
+func buildVLInsertDeployment(cr *vmv1.VLCluster) (*appsv1.Deployment, error) {
+
+	podSpec, err := buildVLInsertPodSpec(cr)
+	if err != nil {
+		return nil, err
+	}
+
+	strategyType := appsv1.RollingUpdateDeploymentStrategyType
+	if cr.Spec.VLInsert.UpdateStrategy != nil {
+		strategyType = *cr.Spec.VLInsert.UpdateStrategy
+	}
+	stsSpec := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:            cr.GetVLInsertName(),
+			Namespace:       cr.Namespace,
+			Labels:          cr.FinalLabels(cr.VLInsertSelectorLabels()),
+			Annotations:     cr.FinalAnnotations(),
+			OwnerReferences: cr.AsOwner(),
+			Finalizers:      []string{vmv1beta1.FinalizerName},
+		},
+		Spec: appsv1.DeploymentSpec{
+			Replicas:             cr.Spec.VLInsert.ReplicaCount,
+			RevisionHistoryLimit: cr.Spec.VLInsert.RevisionHistoryLimitCount,
+			MinReadySeconds:      cr.Spec.VLInsert.MinReadySeconds,
+			Strategy: appsv1.DeploymentStrategy{
+				Type:          strategyType,
+				RollingUpdate: cr.Spec.VLInsert.RollingUpdate,
+			},
+			Selector: &metav1.LabelSelector{
+				MatchLabels: cr.VLInsertSelectorLabels(),
+			},
+			Template: *podSpec,
+		},
+	}
+	build.DeploymentAddCommonParams(stsSpec, ptr.Deref(cr.Spec.VLInsert.UseStrictSecurity, false), &cr.Spec.VLInsert.CommonApplicationDeploymentParams)
+	return stsSpec, nil
+}
+
+func buildVLInsertPodSpec(cr *vmv1.VLCluster) (*corev1.PodTemplateSpec, error) {
+	args := []string{
+		fmt.Sprintf("-httpListenAddr=:%s", cr.Spec.VLInsert.Port),
+		"-internalselect.disable=true",
+	}
+	if cr.Spec.VLInsert.LogLevel != "" {
+		args = append(args, fmt.Sprintf("-loggerLevel=%s", cr.Spec.VLInsert.LogLevel))
+	}
+	if cr.Spec.VLInsert.LogFormat != "" {
+		args = append(args, fmt.Sprintf("-loggerFormat=%s", cr.Spec.VLInsert.LogFormat))
+	}
+
+	if cr.Spec.VLStorage != nil && cr.Spec.VLStorage.ReplicaCount != nil {
+		storageArg := "-storageNode="
+		for _, i := range cr.AvailableStorageNodeIDs("insert") {
+			// TODO: introduce TLS webserver config for storage nodes
+			storageArg += build.PodDNSAddress(cr.GetVLStorageName(), i, cr.Namespace, cr.Spec.VLStorage.Port, cr.Spec.ClusterDomainName)
+		}
+		storageArg = strings.TrimSuffix(storageArg, ",")
+		args = append(args, storageArg)
+	}
+	if len(cr.Spec.VLInsert.ExtraEnvs) > 0 || len(cr.Spec.VLInsert.ExtraEnvsFrom) > 0 {
+		args = append(args, "-envflag.enable=true")
+	}
+
+	var envs []corev1.EnvVar
+
+	envs = append(envs, cr.Spec.VLInsert.ExtraEnvs...)
+
+	ports := []corev1.ContainerPort{
+		{
+			Name:          "http",
+			Protocol:      "TCP",
+			ContainerPort: intstr.Parse(cr.Spec.VLInsert.Port).IntVal,
+		},
+	}
+
+	volumes := make([]corev1.Volume, 0)
+	volumes = append(volumes, cr.Spec.VLInsert.Volumes...)
+
+	vmMounts := make([]corev1.VolumeMount, 0)
+	vmMounts = append(vmMounts, cr.Spec.VLInsert.VolumeMounts...)
+
+	if cr.Spec.VLInsert.SyslogSpec != nil && !cr.Spec.RequestsLoadBalancer.Enabled {
+		ports = addSyslogPortsTo(ports, cr.Spec.VLInsert.SyslogSpec)
+		args = buildSyslogArgs(args, cr.Spec.VLInsert.SyslogSpec)
+		for _, tcpC := range cr.Spec.VLInsert.SyslogSpec.TCPListeners {
+			volumes = addTLSConfigToVolumes(volumes, tcpC.TLSConfig)
+			vmMounts = addTLSConfigToVolumeMounts(vmMounts, tcpC.TLSConfig)
+		}
+	}
+
+	for _, s := range cr.Spec.VLInsert.Secrets {
+		volumes = append(volumes, corev1.Volume{
+			Name: k8stools.SanitizeVolumeName("secret-" + s),
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: s,
+				},
+			},
+		})
+		vmMounts = append(vmMounts, corev1.VolumeMount{
+			Name:      k8stools.SanitizeVolumeName("secret-" + s),
+			ReadOnly:  true,
+			MountPath: path.Join(vmv1beta1.SecretsDir, s),
+		})
+	}
+
+	for _, c := range cr.Spec.VLInsert.ConfigMaps {
+		volumes = append(volumes, corev1.Volume{
+			Name: k8stools.SanitizeVolumeName("configmap-" + c),
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: c,
+					},
+				},
+			},
+		})
+		vmMounts = append(vmMounts, corev1.VolumeMount{
+			Name:      k8stools.SanitizeVolumeName("configmap-" + c),
+			ReadOnly:  true,
+			MountPath: path.Join(vmv1beta1.ConfigMapsDir, c),
+		})
+	}
+
+	args = build.AddExtraArgsOverrideDefaults(args, cr.Spec.VLInsert.ExtraArgs, "-")
+	sort.Strings(args)
+
+	insertContainers := corev1.Container{
+		Name:                     "vlinsert",
+		Image:                    fmt.Sprintf("%s:%s", cr.Spec.VLInsert.Image.Repository, cr.Spec.VLInsert.Image.Tag),
+		ImagePullPolicy:          cr.Spec.VLInsert.Image.PullPolicy,
+		Ports:                    ports,
+		Args:                     args,
+		VolumeMounts:             vmMounts,
+		Resources:                cr.Spec.VLInsert.Resources,
+		Env:                      envs,
+		EnvFrom:                  cr.Spec.VLInsert.ExtraEnvsFrom,
+		TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
+	}
+
+	insertContainers = build.Probe(insertContainers, cr.Spec.VLInsert)
+	operatorContainers := []corev1.Container{insertContainers}
+
+	build.AddStrictSecuritySettingsToContainers(cr.Spec.VLInsert.SecurityContext, operatorContainers, ptr.Deref(cr.Spec.VLInsert.UseStrictSecurity, false))
+	containers, err := k8stools.MergePatchContainers(operatorContainers, cr.Spec.VLInsert.Containers)
+	if err != nil {
+		return nil, err
+	}
+
+	for i := range cr.Spec.VLInsert.TopologySpreadConstraints {
+		if cr.Spec.VLInsert.TopologySpreadConstraints[i].LabelSelector == nil {
+			cr.Spec.VLInsert.TopologySpreadConstraints[i].LabelSelector = &metav1.LabelSelector{
+				MatchLabels: cr.VLInsertSelectorLabels(),
+			}
+		}
+	}
+
+	podSpec := &corev1.PodTemplateSpec{
+		ObjectMeta: metav1.ObjectMeta{
+			Labels:      cr.VLInsertPodLabels(),
+			Annotations: cr.VLInsertPodAnnotations(),
+		},
+		Spec: corev1.PodSpec{
+			Volumes:            volumes,
+			InitContainers:     cr.Spec.VLInsert.InitContainers,
+			Containers:         containers,
+			ServiceAccountName: cr.GetServiceAccountName(),
+		},
+	}
+
+	return podSpec, nil
+}
+
+func createOrUpdateVLInsertHPA(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	if cr.Spec.VLInsert.HPA == nil {
+		return nil
+	}
+	targetRef := autoscalingv2.CrossVersionObjectReference{
+		Name:       cr.GetVLInsertName(),
+		Kind:       "Deployment",
+		APIVersion: "apps/v1",
+	}
+	t := newOptsBuilder(cr, cr.GetVLInsertName(), cr.VLInsertSelectorLabels())
+	newHPA := build.HPA(t, targetRef, cr.Spec.VLInsert.HPA)
+	var prevHPA *autoscalingv2.HorizontalPodAutoscaler
+	if prevCR != nil && prevCR.Spec.VLInsert.HPA != nil {
+		t = newOptsBuilder(prevCR, prevCR.GetVLInsertName(), prevCR.VLInsertSelectorLabels())
+		prevHPA = build.HPA(t, targetRef, prevCR.Spec.VLInsert.HPA)
+	}
+	return reconcile.HPA(ctx, rclient, newHPA, prevHPA)
+}
+
+func createOrUpdateVLInsertService(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) (*corev1.Service, error) {
+	newService := buildVLInsertService(cr)
+	var prevService, prevAdditionalService *corev1.Service
+	if prevCR != nil && prevCR.Spec.VLInsert != nil {
+		prevService = buildVLInsertService(prevCR)
+		prevAdditionalService = build.AdditionalServiceFromDefault(prevService, prevCR.Spec.VLInsert.ServiceSpec)
+	}
+	if err := cr.Spec.VLInsert.ServiceSpec.IsSomeAndThen(func(s *vmv1beta1.AdditionalServiceSpec) error {
+		additionalService := build.AdditionalServiceFromDefault(newService, s)
+		if additionalService.Name == newService.Name {
+			return fmt.Errorf("VLInsert additional service name: %q cannot be the same as crd.prefixedname: %q", additionalService.Name, newService.Name)
+		}
+		if err := reconcile.Service(ctx, rclient, additionalService, prevAdditionalService); err != nil {
+			return fmt.Errorf("cannot reconcile insert additional service: %w", err)
+		}
+		return nil
+	}); err != nil {
+		return nil, err
+	}
+
+	if err := reconcile.Service(ctx, rclient, newService, prevService); err != nil {
+		return nil, fmt.Errorf("cannot reconcile insert service: %w", err)
+	}
+
+	// create extra service for loadbalancing
+	if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableInsertBalancing {
+		var prevPort string
+		if prevCR != nil && prevCR.Spec.VLInsert != nil {
+			prevPort = prevCR.Spec.VLInsert.Port
+		}
+		if err := createOrUpdateLBProxyService(ctx, rclient, cr, prevCR, cr.GetVLInsertName(), cr.Spec.VLInsert.Port, prevPort, "vlinsert", cr.VMAuthLBSelectorLabels()); err != nil {
+			return nil, fmt.Errorf("cannot create lb svc for insert: %w", err)
+		}
+	}
+
+	return newService, nil
+}
+
+func buildVLInsertService(cr *vmv1.VLCluster) *corev1.Service {
+	t := &optsBuilder{
+		cr,
+		cr.GetVLInsertName(),
+		cr.FinalLabels(cr.VLInsertSelectorLabels()),
+		cr.VLInsertSelectorLabels(),
+		cr.Spec.VLInsert.ServiceSpec,
+	}
+
+	svc := build.Service(t, cr.Spec.VLInsert.Port, func(svc *corev1.Service) {
+		syslogSpec := cr.Spec.VLInsert.SyslogSpec
+		if syslogSpec == nil || cr.Spec.RequestsLoadBalancer.Enabled {
+			// fast path
+			return
+		}
+		addSyslogPortsToService(svc, syslogSpec)
+	})
+	if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableInsertBalancing {
+		svc.Name = cr.GetVLInsertLBName()
+		svc.Spec.ClusterIP = corev1.ClusterIPNone
+		svc.Spec.Type = corev1.ServiceTypeClusterIP
+		svc.Labels[vmauthLBServiceProxyJobNameLabel] = cr.GetVLInsertName()
+	}
+	return svc
+}
+
+func buildSyslogArgs(dst []string, syslogSpec *vmv1.SyslogServerSpec) []string {
+	if syslogSpec == nil {
+		return dst
+	}
+	type groupFlag struct {
+		isNotNull   bool
+		flagSetting string
+	}
+
+	tcpListenAddr := groupFlag{flagSetting: "-syslog.listenAddr.tcp="}
+	udpListenAddr := groupFlag{flagSetting: "-syslog.listenAddr.udp="}
+	tcpStreamFields := groupFlag{flagSetting: "-syslog.streamFields.tcp="}
+	udpStreamFileds := groupFlag{flagSetting: "-syslog.streamFields.udp="}
+	tcpIgnoreFields := groupFlag{flagSetting: "-syslog.ignoreFields.tcp="}
+	udpIgnoreFields := groupFlag{flagSetting: "-syslog.ignoreFields.udp="}
+	tcpDecolorizedFields := groupFlag{flagSetting: "-syslog.decolorizeFields.tcp="}
+	udpDecolorizedFields := groupFlag{flagSetting: "-syslog.decolorizeFields.udp="}
+	tcpTenantID := groupFlag{flagSetting: "-syslog.tenantID.tcp="}
+	udpTenantID := groupFlag{flagSetting: "-syslog.tenantID.udp="}
+	tcpCompress := groupFlag{flagSetting: "-syslog.compressMethod.tcp="}
+	udpCompress := groupFlag{flagSetting: "-syslog.compressMethod.udp="}
+
+	tlsEnabled := groupFlag{flagSetting: "-syslog.tls="}
+	tlsCertFile := groupFlag{flagSetting: "-syslog.tlsCertFile="}
+	tlsKeyFile := groupFlag{flagSetting: "-syslog.tlsKeyFile="}
+
+	var value string
+	addValue := func(flag *groupFlag, fieldValue string) {
+		value = ""
+		if len(fieldValue) > 0 {
+			flag.isNotNull = true
+			value = fieldValue
+		}
+		flag.flagSetting += fmt.Sprintf("'%s',", value)
+	}
+
+	for _, sTCP := range syslogSpec.TCPListeners {
+		tcpListenAddr.flagSetting += fmt.Sprintf(":%d,", sTCP.ListenPort)
+		tcpListenAddr.isNotNull = true
+		addValue(&tcpStreamFields, string(sTCP.StreamFields))
+		addValue(&tcpDecolorizedFields, string(sTCP.DecolorizeFields))
+		addValue(&tcpIgnoreFields, string(sTCP.IgnoreFields))
+		addValue(&tcpTenantID, sTCP.TenantID)
+		addValue(&tcpCompress, sTCP.CompressMethod)
+
+		value = ""
+		if sTCP.TLSConfig != nil {
+			tlsEnabled.isNotNull = true
+			value = "true"
+		}
+		tlsEnabled.flagSetting += fmt.Sprintf("%s,", value)
+
+		if sTCP.TLSConfig != nil {
+			tlsC := sTCP.TLSConfig
+			value = ""
+			switch {
+			case tlsC.CertFile != "":
+				value = tlsC.CertFile
+				tlsCertFile.isNotNull = true
+			case tlsC.CertSecret != nil:
+				tlsCertFile.isNotNull = true
+				value = fmt.Sprintf("%s/%s/%s", tlsServerConfigMountPath, tlsC.CertSecret.Name, tlsC.CertSecret.Key)
+			}
+			tlsCertFile.flagSetting += fmt.Sprintf("%s,", value)
+
+			value = ""
+			switch {
+			case tlsC.KeyFile != "":
+				value = tlsC.KeyFile
+				tlsKeyFile.isNotNull = true
+			case tlsC.KeySecret != nil:
+				value = fmt.Sprintf("%s/%s/%s", tlsServerConfigMountPath, tlsC.KeySecret.Name, tlsC.KeySecret.Key)
+				tlsKeyFile.isNotNull = true
+			}
+			tlsKeyFile.flagSetting += fmt.Sprintf("%s,", value)
+		}
+	}
+
+	for _, sUDP := range syslogSpec.UDPListeners {
+		udpListenAddr.flagSetting += fmt.Sprintf(":%d,", sUDP.ListenPort)
+		udpListenAddr.isNotNull = true
+		addValue(&udpStreamFileds, string(sUDP.StreamFields))
+		addValue(&udpIgnoreFields, string(sUDP.IgnoreFields))
+		addValue(&udpDecolorizedFields, string(sUDP.DecolorizeFields))
+		addValue(&udpTenantID, sUDP.TenantID)
+		addValue(&udpCompress, sUDP.CompressMethod)
+	}
+	flags := []groupFlag{
+		tcpListenAddr,
+		tcpStreamFields,
+		tcpIgnoreFields,
+		tcpDecolorizedFields,
+		tcpTenantID,
+		tcpCompress,
+		tlsEnabled,
+		tlsCertFile,
+		tlsKeyFile,
+
+		udpListenAddr,
+		udpStreamFileds,
+		udpIgnoreFields,
+		udpDecolorizedFields,
+		udpTenantID,
+		udpCompress,
+	}
+	for _, flagArg := range flags {
+		if flagArg.isNotNull {
+			dst = append(dst, strings.TrimSuffix(flagArg.flagSetting, ","))
+		}
+	}
+	return dst
+}
+
+func addSyslogPortsTo(dst []corev1.ContainerPort, syslogSpec *vmv1.SyslogServerSpec) []corev1.ContainerPort {
+	if syslogSpec == nil {
+		return dst
+	}
+	for idx, tcp := range syslogSpec.TCPListeners {
+		dst = append(dst, corev1.ContainerPort{
+			Name:          fmt.Sprintf("syslog-tcp-%d", idx),
+			Protocol:      corev1.ProtocolTCP,
+			ContainerPort: tcp.ListenPort,
+		})
+	}
+
+	for idx, udp := range syslogSpec.UDPListeners {
+		dst = append(dst, corev1.ContainerPort{
+			Name:          fmt.Sprintf("syslog-udp-%d", idx),
+			Protocol:      corev1.ProtocolUDP,
+			ContainerPort: udp.ListenPort,
+		},
+		)
+	}
+	return dst
+}
+
+func addSyslogPortsToService(svc *corev1.Service, syslogSpec *vmv1.SyslogServerSpec) {
+	if syslogSpec == nil {
+		return
+	}
+	for idx, tcp := range syslogSpec.TCPListeners {
+		svc.Spec.Ports = append(svc.Spec.Ports, corev1.ServicePort{
+			Name:       fmt.Sprintf("syslog-tcp-%d", idx),
+			Protocol:   corev1.ProtocolTCP,
+			Port:       tcp.ListenPort,
+			TargetPort: intstr.FromInt32(tcp.ListenPort),
+		})
+	}
+
+	for idx, udp := range syslogSpec.UDPListeners {
+		svc.Spec.Ports = append(svc.Spec.Ports, corev1.ServicePort{
+			Name:       fmt.Sprintf("syslog-udp-%d", idx),
+			Protocol:   corev1.ProtocolUDP,
+			Port:       udp.ListenPort,
+			TargetPort: intstr.FromInt32(udp.ListenPort),
+		},
+		)
+	}
+}
diff --git a/internal/controller/operator/factory/vlcluster/vlselect.go b/internal/controller/operator/factory/vlcluster/vlselect.go
new file mode 100644
index 00000000..e3f75b3e
--- /dev/null
+++ b/internal/controller/operator/factory/vlcluster/vlselect.go
@@ -0,0 +1,326 @@
+package vlcluster
+
+import (
+	"context"
+	"fmt"
+	"path"
+	"sort"
+	"strings"
+
+	appsv1 "k8s.io/api/apps/v1"
+	autoscalingv2 "k8s.io/api/autoscaling/v2"
+	corev1 "k8s.io/api/core/v1"
+	policyv1 "k8s.io/api/policy/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/build"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/reconcile"
+)
+
+func createOrUpdateVLSelect(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	if cr.Spec.VLSelect == nil {
+		return nil
+	}
+	b := newOptsBuilder(cr, cr.GetVLSelectName(), cr.VLSelectSelectorLabels())
+
+	if cr.Spec.VLSelect.PodDisruptionBudget != nil {
+		pdb := build.PodDisruptionBudget(b, cr.Spec.VLSelect.PodDisruptionBudget)
+		var prevPDB *policyv1.PodDisruptionBudget
+		if prevCR != nil && prevCR.Spec.VLSelect.PodDisruptionBudget != nil {
+			prevB := newOptsBuilder(prevCR, prevCR.GetVLSelectName(), prevCR.VLSelectSelectorLabels())
+			prevPDB = build.PodDisruptionBudget(prevB, prevCR.Spec.VLSelect.PodDisruptionBudget)
+		}
+		err := reconcile.PDB(ctx, rclient, pdb, prevPDB)
+		if err != nil {
+			return err
+		}
+	}
+	if err := createOrUpdateVLSelectHPA(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	selectSvc, err := createOrUpdateVLSelectService(ctx, rclient, cr, prevCR)
+	if err != nil {
+		return err
+	}
+	if !ptr.Deref(cr.Spec.VLSelect.DisableSelfServiceScrape, false) {
+		svs := build.VMServiceScrapeForServiceWithSpec(selectSvc, cr.Spec.VLSelect)
+		if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableSelectBalancing {
+			// for backward compatibility we must keep job label value
+			svs.Spec.JobLabel = vmauthLBServiceProxyJobNameLabel
+		}
+		err := reconcile.VMServiceScrapeForCRD(ctx, rclient, svs)
+		if err != nil {
+			return fmt.Errorf("cannot create VMServiceScrape for VLSelect: %w", err)
+		}
+	}
+	if err := createOrUpdateVLSelectSTS(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	return nil
+}
+
+func createOrUpdateVLSelectHPA(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	if cr.Spec.VLSelect.HPA == nil {
+		return nil
+	}
+	targetRef := autoscalingv2.CrossVersionObjectReference{
+		Name:       cr.GetVLSelectName(),
+		Kind:       "StatefulSet",
+		APIVersion: "apps/v1",
+	}
+	b := newOptsBuilder(cr, cr.GetVLSelectName(), cr.VLSelectSelectorLabels())
+	defaultHPA := build.HPA(b, targetRef, cr.Spec.VLSelect.HPA)
+	var prevHPA *autoscalingv2.HorizontalPodAutoscaler
+	if prevCR != nil && prevCR.Spec.VLSelect.HPA != nil {
+		b := newOptsBuilder(prevCR, prevCR.GetVLSelectName(), prevCR.VLSelectSelectorLabels())
+		prevHPA = build.HPA(b, targetRef, prevCR.Spec.VLSelect.HPA)
+	}
+
+	return reconcile.HPA(ctx, rclient, defaultHPA, prevHPA)
+
+}
+
+func createOrUpdateVLSelectService(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) (*corev1.Service, error) {
+
+	var prevService, prevAdditionalService *corev1.Service
+	if prevCR != nil && prevCR.Spec.VLSelect != nil {
+		prevService = buildVLSelectService(prevCR)
+		prevAdditionalService = build.AdditionalServiceFromDefault(prevService, prevCR.Spec.VLSelect.ServiceSpec)
+	}
+	svc := buildVLSelectService(cr)
+	if err := cr.Spec.VLSelect.ServiceSpec.IsSomeAndThen(func(s *vmv1beta1.AdditionalServiceSpec) error {
+		additionalService := build.AdditionalServiceFromDefault(svc, s)
+		if additionalService.Name == svc.Name {
+			return fmt.Errorf("VLSelect additional service name: %q cannot be the same as crd.prefixedname: %q", additionalService.Name, svc.Name)
+		}
+		if err := reconcile.Service(ctx, rclient, additionalService, prevAdditionalService); err != nil {
+			return fmt.Errorf("cannot reconcile service for select: %w", err)
+		}
+		return nil
+	}); err != nil {
+		return nil, err
+	}
+
+	if err := reconcile.Service(ctx, rclient, svc, prevService); err != nil {
+		return nil, fmt.Errorf("cannot reconcile select service: %w", err)
+	}
+	if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableSelectBalancing {
+		var prevPort string
+		if prevCR != nil && prevCR.Spec.VLSelect != nil {
+			prevPort = prevCR.Spec.VLSelect.Port
+		}
+		if err := createOrUpdateLBProxyService(ctx, rclient, cr, prevCR, cr.GetVLSelectName(), cr.Spec.VLSelect.Port, prevPort, "vlselect", cr.VMAuthLBSelectorLabels()); err != nil {
+			return nil, fmt.Errorf("cannot create lb svc for select: %w", err)
+		}
+	}
+	return svc, nil
+}
+
+func buildVLSelectService(cr *vmv1.VLCluster) *corev1.Service {
+	b := &optsBuilder{
+		cr,
+		cr.GetVLSelectName(),
+		cr.FinalLabels(cr.VLSelectSelectorLabels()),
+		cr.VLSelectSelectorLabels(),
+		cr.Spec.VLSelect.ServiceSpec,
+	}
+	svc := build.Service(b, cr.Spec.VLSelect.Port, func(svc *corev1.Service) {
+		svc.Spec.ClusterIP = "None"
+		svc.Spec.PublishNotReadyAddresses = true
+	})
+	if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableSelectBalancing {
+		svc.Name = cr.GetVLSelectLBName()
+		svc.Spec.ClusterIP = corev1.ClusterIPNone
+		svc.Spec.Type = corev1.ServiceTypeClusterIP
+		svc.Labels[vmauthLBServiceProxyJobNameLabel] = cr.GetVLSelectName()
+	}
+
+	return svc
+
+}
+
+func createOrUpdateVLSelectSTS(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	var prevDep *appsv1.Deployment
+	if prevCR != nil && prevCR.Spec.VLSelect != nil {
+		var err error
+		prevDep, err = buildVLSelectDeployment(prevCR)
+		if err != nil {
+			return fmt.Errorf("cannot build prev select spec: %w", err)
+		}
+	}
+	newDep, err := buildVLSelectDeployment(cr)
+	if err != nil {
+		return err
+	}
+
+	return reconcile.Deployment(ctx, rclient, newDep, prevDep, true)
+}
+
+func buildVLSelectDeployment(cr *vmv1.VLCluster) (*appsv1.Deployment, error) {
+	podSpec, err := buildVLSelectPodSpec(cr)
+	if err != nil {
+		return nil, err
+	}
+	strategyType := appsv1.RollingUpdateDeploymentStrategyType
+	if cr.Spec.VLInsert.UpdateStrategy != nil {
+		strategyType = *cr.Spec.VLSelect.UpdateStrategy
+	}
+	depSpec := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:            cr.GetVLSelectName(),
+			Namespace:       cr.Namespace,
+			Labels:          cr.FinalLabels(cr.VLSelectSelectorLabels()),
+			Annotations:     cr.FinalAnnotations(),
+			OwnerReferences: cr.AsOwner(),
+			Finalizers:      []string{vmv1beta1.FinalizerName},
+		},
+		Spec: appsv1.DeploymentSpec{
+			Replicas:             cr.Spec.VLInsert.ReplicaCount,
+			RevisionHistoryLimit: cr.Spec.VLInsert.RevisionHistoryLimitCount,
+			MinReadySeconds:      cr.Spec.VLInsert.MinReadySeconds,
+			Strategy: appsv1.DeploymentStrategy{
+				Type:          strategyType,
+				RollingUpdate: cr.Spec.VLInsert.RollingUpdate,
+			},
+			Selector: &metav1.LabelSelector{
+				MatchLabels: cr.VLSelectSelectorLabels(),
+			},
+			Template: *podSpec,
+		},
+	}
+	build.DeploymentAddCommonParams(depSpec, ptr.Deref(cr.Spec.VLSelect.UseStrictSecurity, false), &cr.Spec.VLSelect.CommonApplicationDeploymentParams)
+	return depSpec, nil
+}
+
+func buildVLSelectPodSpec(cr *vmv1.VLCluster) (*corev1.PodTemplateSpec, error) {
+	args := []string{
+		fmt.Sprintf("-httpListenAddr=:%s", cr.Spec.VLSelect.Port),
+		"-internalinsert.disable=true",
+	}
+	if cr.Spec.VLSelect.LogLevel != "" {
+		args = append(args, fmt.Sprintf("-loggerLevel=%s", cr.Spec.VLSelect.LogLevel))
+	}
+	if cr.Spec.VLSelect.LogFormat != "" {
+		args = append(args, fmt.Sprintf("-loggerFormat=%s", cr.Spec.VLSelect.LogFormat))
+	}
+
+	if cr.Spec.VLStorage != nil && cr.Spec.VLStorage.ReplicaCount != nil {
+		// TODO: check TLS
+		storageArg := "-storageNode="
+		for _, i := range cr.AvailableStorageNodeIDs("select") {
+			storageArg += build.PodDNSAddress(cr.GetVLStorageName(), i, cr.Namespace, cr.Spec.VLStorage.Port, cr.Spec.ClusterDomainName)
+		}
+		storageArg = strings.TrimSuffix(storageArg, ",")
+		args = append(args, storageArg)
+
+	}
+
+	if len(cr.Spec.VLSelect.ExtraEnvs) > 0 || len(cr.Spec.VLSelect.ExtraEnvsFrom) > 0 {
+		args = append(args, "-envflag.enable=true")
+	}
+
+	var envs []corev1.EnvVar
+	envs = append(envs, cr.Spec.VLSelect.ExtraEnvs...)
+
+	var ports []corev1.ContainerPort
+	ports = append(ports, corev1.ContainerPort{
+		Name:          "http",
+		Protocol:      "TCP",
+		ContainerPort: intstr.Parse(cr.Spec.VLSelect.Port).IntVal,
+	})
+
+	volumes := make([]corev1.Volume, 0)
+	volumes = append(volumes, cr.Spec.VLSelect.Volumes...)
+
+	vmMounts := make([]corev1.VolumeMount, 0)
+	vmMounts = append(vmMounts, cr.Spec.VLSelect.VolumeMounts...)
+
+	for _, s := range cr.Spec.VLSelect.Secrets {
+		volumes = append(volumes, corev1.Volume{
+			Name: k8stools.SanitizeVolumeName("secret-" + s),
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: s,
+				},
+			},
+		})
+		vmMounts = append(vmMounts, corev1.VolumeMount{
+			Name:      k8stools.SanitizeVolumeName("secret-" + s),
+			ReadOnly:  true,
+			MountPath: path.Join(vmv1beta1.SecretsDir, s),
+		})
+	}
+
+	for _, c := range cr.Spec.VLSelect.ConfigMaps {
+		volumes = append(volumes, corev1.Volume{
+			Name: k8stools.SanitizeVolumeName("configmap-" + c),
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: c,
+					},
+				},
+			},
+		})
+		vmMounts = append(vmMounts, corev1.VolumeMount{
+			Name:      k8stools.SanitizeVolumeName("configmap-" + c),
+			ReadOnly:  true,
+			MountPath: path.Join(vmv1beta1.ConfigMapsDir, c),
+		})
+	}
+
+	args = build.AddExtraArgsOverrideDefaults(args, cr.Spec.VLSelect.ExtraArgs, "-")
+	sort.Strings(args)
+	selectContainers := corev1.Container{
+		Name:                     "vlselect",
+		Image:                    fmt.Sprintf("%s:%s", cr.Spec.VLSelect.Image.Repository, cr.Spec.VLSelect.Image.Tag),
+		ImagePullPolicy:          cr.Spec.VLSelect.Image.PullPolicy,
+		Ports:                    ports,
+		Args:                     args,
+		VolumeMounts:             vmMounts,
+		Resources:                cr.Spec.VLSelect.Resources,
+		Env:                      envs,
+		EnvFrom:                  cr.Spec.VLSelect.ExtraEnvsFrom,
+		TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
+		TerminationMessagePath:   "/dev/termination-log",
+	}
+
+	selectContainers = build.Probe(selectContainers, cr.Spec.VLSelect)
+	operatorContainers := []corev1.Container{selectContainers}
+
+	build.AddStrictSecuritySettingsToContainers(cr.Spec.VLSelect.SecurityContext, operatorContainers, ptr.Deref(cr.Spec.VLSelect.UseStrictSecurity, false))
+	containers, err := k8stools.MergePatchContainers(operatorContainers, cr.Spec.VLSelect.Containers)
+	if err != nil {
+		return nil, err
+	}
+
+	for i := range cr.Spec.VLSelect.TopologySpreadConstraints {
+		if cr.Spec.VLSelect.TopologySpreadConstraints[i].LabelSelector == nil {
+			cr.Spec.VLSelect.TopologySpreadConstraints[i].LabelSelector = &metav1.LabelSelector{
+				MatchLabels: cr.VLSelectSelectorLabels(),
+			}
+		}
+	}
+
+	podSpec := &corev1.PodTemplateSpec{
+		ObjectMeta: metav1.ObjectMeta{
+			Labels:      cr.VLSelectPodLabels(),
+			Annotations: cr.VLSelectPodAnnotations(),
+		},
+		Spec: corev1.PodSpec{
+			Volumes:            volumes,
+			InitContainers:     cr.Spec.VLSelect.InitContainers,
+			Containers:         containers,
+			ServiceAccountName: cr.GetServiceAccountName(),
+			RestartPolicy:      "Always",
+		},
+	}
+
+	return podSpec, nil
+}
diff --git a/internal/controller/operator/factory/vlcluster/vlstorage.go b/internal/controller/operator/factory/vlcluster/vlstorage.go
new file mode 100644
index 00000000..f10bfd2e
--- /dev/null
+++ b/internal/controller/operator/factory/vlcluster/vlstorage.go
@@ -0,0 +1,311 @@
+package vlcluster
+
+import (
+	"context"
+	"fmt"
+	"path"
+	"sort"
+
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	policyv1 "k8s.io/api/policy/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/build"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/reconcile"
+)
+
+func createOrUpdateVLStorage(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	if cr.Spec.VLStorage == nil {
+		return nil
+	}
+	b := newOptsBuilder(cr, cr.GetVLStorageName(), cr.VLStorageSelectorLabels())
+
+	if cr.Spec.VLStorage.PodDisruptionBudget != nil {
+		pdb := build.PodDisruptionBudget(b, cr.Spec.VLStorage.PodDisruptionBudget)
+		var prevPDB *policyv1.PodDisruptionBudget
+		if prevCR != nil && prevCR.Spec.VLStorage.PodDisruptionBudget != nil {
+			prevB := newOptsBuilder(prevCR, prevCR.GetVLStorageName(), prevCR.VLStorageSelectorLabels())
+			prevPDB = build.PodDisruptionBudget(prevB, prevCR.Spec.VLStorage.PodDisruptionBudget)
+		}
+		err := reconcile.PDB(ctx, rclient, pdb, prevPDB)
+		if err != nil {
+			return err
+		}
+	}
+	if err := createOrUpdateVLStorageSTS(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+
+	storageSvc, err := createOrUpdateVLStorageService(ctx, rclient, cr, prevCR)
+	if err != nil {
+		return err
+	}
+	if !ptr.Deref(cr.Spec.VLStorage.DisableSelfServiceScrape, false) {
+		err := reconcile.VMServiceScrapeForCRD(ctx, rclient, build.VMServiceScrapeForServiceWithSpec(storageSvc, cr.Spec.VLStorage))
+		if err != nil {
+			return fmt.Errorf("cannot create VMServiceScrape for VLStorage: %w", err)
+		}
+	}
+
+	return nil
+}
+
+func createOrUpdateVLStorageService(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) (*corev1.Service, error) {
+	t := &optsBuilder{
+		cr,
+		cr.GetVLStorageName(),
+		cr.FinalLabels(cr.VLStorageSelectorLabels()),
+		cr.VLStorageSelectorLabels(),
+		cr.Spec.VLStorage.ServiceSpec,
+	}
+	var prevService, prevAdditionalService *corev1.Service
+	if prevCR != nil && prevCR.Spec.VLStorage != nil {
+		prevT := &optsBuilder{
+			prevCR,
+			prevCR.GetVLStorageName(),
+			prevCR.FinalLabels(prevCR.VLStorageSelectorLabels()),
+			prevCR.VLStorageSelectorLabels(),
+			prevCR.Spec.VLStorage.ServiceSpec,
+		}
+
+		prevService = build.Service(prevT, prevCR.Spec.VLStorage.Port, func(svc *corev1.Service) {
+			svc.Spec.ClusterIP = "None"
+			svc.Spec.PublishNotReadyAddresses = true
+		})
+		prevAdditionalService = build.AdditionalServiceFromDefault(prevService, prevCR.Spec.VLStorage.ServiceSpec)
+
+	}
+	newHeadless := build.Service(t, cr.Spec.VLStorage.Port, func(svc *corev1.Service) {
+		svc.Spec.ClusterIP = "None"
+		svc.Spec.PublishNotReadyAddresses = true
+	})
+
+	if err := cr.Spec.VLStorage.ServiceSpec.IsSomeAndThen(func(s *vmv1beta1.AdditionalServiceSpec) error {
+		additionalService := build.AdditionalServiceFromDefault(newHeadless, s)
+		if additionalService.Name == newHeadless.Name {
+			return fmt.Errorf("VLStorage additional service name: %q cannot be the same as crd.prefixedname: %q", additionalService.Name, newHeadless.Name)
+		}
+		if err := reconcile.Service(ctx, rclient, additionalService, prevAdditionalService); err != nil {
+			return fmt.Errorf("cannot reconcile storage additional service: %w", err)
+		}
+		return nil
+	}); err != nil {
+		return nil, err
+	}
+
+	if err := reconcile.Service(ctx, rclient, newHeadless, prevService); err != nil {
+		return nil, fmt.Errorf("cannot reconcile storage service: %w", err)
+	}
+	return newHeadless, nil
+}
+
+func createOrUpdateVLStorageSTS(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	var prevSts *appsv1.StatefulSet
+
+	if prevCR != nil && prevCR.Spec.VLStorage != nil {
+		var err error
+		prevSts, err = buildVLStorageSTSSpec(prevCR)
+		if err != nil {
+			return fmt.Errorf("cannot build prev storage spec: %w", err)
+		}
+	}
+	newSts, err := buildVLStorageSTSSpec(cr)
+	if err != nil {
+		return err
+	}
+
+	stsOpts := reconcile.STSOptions{
+		HasClaim:       len(newSts.Spec.VolumeClaimTemplates) > 0,
+		SelectorLabels: cr.VLStorageSelectorLabels,
+	}
+	return reconcile.HandleSTSUpdate(ctx, rclient, stsOpts, newSts, prevSts)
+}
+
+func buildVLStorageSTSSpec(cr *vmv1.VLCluster) (*appsv1.StatefulSet, error) {
+
+	podSpec, err := buildVLStoragePodSpec(cr)
+	if err != nil {
+		return nil, err
+	}
+
+	stsSpec := &appsv1.StatefulSet{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:            cr.GetVLStorageName(),
+			Namespace:       cr.Namespace,
+			Labels:          cr.FinalLabels(cr.VLStorageSelectorLabels()),
+			Annotations:     cr.FinalAnnotations(),
+			OwnerReferences: cr.AsOwner(),
+			Finalizers:      []string{vmv1beta1.FinalizerName},
+		},
+		Spec: appsv1.StatefulSetSpec{
+			Selector: &metav1.LabelSelector{
+				MatchLabels: cr.VLStorageSelectorLabels(),
+			},
+			UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
+				Type: cr.Spec.VLStorage.RollingUpdateStrategy,
+			},
+			Template:    *podSpec,
+			ServiceName: cr.GetVLStorageName(),
+		},
+	}
+	build.StatefulSetAddCommonParams(stsSpec, ptr.Deref(cr.Spec.VLStorage.UseStrictSecurity, false), &cr.Spec.VLStorage.CommonApplicationDeploymentParams)
+	storageSpec := cr.Spec.VLStorage.Storage
+	storageSpec.IntoSTSVolume(cr.Spec.VLStorage.GetStorageVolumeName(), &stsSpec.Spec)
+	stsSpec.Spec.VolumeClaimTemplates = append(stsSpec.Spec.VolumeClaimTemplates, cr.Spec.VLStorage.ClaimTemplates...)
+
+	return stsSpec, nil
+}
+
+func buildVLStoragePodSpec(cr *vmv1.VLCluster) (*corev1.PodTemplateSpec, error) {
+	args := []string{
+		fmt.Sprintf("-httpListenAddr=:%s", cr.Spec.VLStorage.Port),
+		fmt.Sprintf("-storageDataPath=%s", cr.Spec.VLStorage.StorageDataPath),
+	}
+
+	if cr.Spec.VLStorage.RetentionPeriod != "" {
+		args = append(args, fmt.Sprintf("-retentionPeriod=%s", cr.Spec.VLStorage.RetentionPeriod))
+	}
+	if cr.Spec.VLStorage.FutureRetention != "" {
+		args = append(args, fmt.Sprintf("-futureRetention=%s", cr.Spec.VLStorage.RetentionPeriod))
+	}
+	if cr.Spec.VLStorage.RetentionMaxDiskSpaceUsageBytes != "" {
+		args = append(args, fmt.Sprintf("-retention.maxDiskSpaceUsageBytes=%s", cr.Spec.VLStorage.RetentionPeriod))
+	}
+	if cr.Spec.VLStorage.LogNewStreams {
+		args = append(args, "-logNewStreams")
+	}
+	if cr.Spec.VLStorage.LogIngestedRows {
+		args = append(args, "-logIngestedRows")
+	}
+
+	if cr.Spec.VLStorage.LogLevel != "" {
+		args = append(args, fmt.Sprintf("-loggerLevel=%s", cr.Spec.VLStorage.LogLevel))
+	}
+	if cr.Spec.VLStorage.LogFormat != "" {
+		args = append(args, fmt.Sprintf("-loggerFormat=%s", cr.Spec.VLStorage.LogFormat))
+	}
+
+	if len(cr.Spec.VLStorage.ExtraEnvs) > 0 || len(cr.Spec.VLStorage.ExtraEnvsFrom) > 0 {
+		args = append(args, "-envflag.enable=true")
+	}
+
+	var envs []corev1.EnvVar
+
+	envs = append(envs, cr.Spec.VLStorage.ExtraEnvs...)
+
+	ports := []corev1.ContainerPort{
+		{
+			Name:          "http",
+			Protocol:      "TCP",
+			ContainerPort: intstr.Parse(cr.Spec.VLStorage.Port).IntVal,
+		},
+	}
+	volumes := make([]corev1.Volume, 0)
+	vmMounts := make([]corev1.VolumeMount, 0)
+
+	volumes = append(volumes, cr.Spec.VLStorage.Volumes...)
+	vmMounts = append(vmMounts, corev1.VolumeMount{
+		Name:      cr.Spec.VLStorage.GetStorageVolumeName(),
+		MountPath: cr.Spec.VLStorage.StorageDataPath,
+	})
+
+	vmMounts = append(vmMounts, cr.Spec.VLStorage.VolumeMounts...)
+
+	for _, s := range cr.Spec.VLStorage.Secrets {
+		volumes = append(volumes, corev1.Volume{
+			Name: k8stools.SanitizeVolumeName("secret-" + s),
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: s,
+				},
+			},
+		})
+		vmMounts = append(vmMounts, corev1.VolumeMount{
+			Name:      k8stools.SanitizeVolumeName("secret-" + s),
+			ReadOnly:  true,
+			MountPath: path.Join(vmv1beta1.SecretsDir, s),
+		})
+	}
+
+	for _, c := range cr.Spec.VLStorage.ConfigMaps {
+		volumes = append(volumes, corev1.Volume{
+			Name: k8stools.SanitizeVolumeName("configmap-" + c),
+			VolumeSource: corev1.VolumeSource{
+				ConfigMap: &corev1.ConfigMapVolumeSource{
+					LocalObjectReference: corev1.LocalObjectReference{
+						Name: c,
+					},
+				},
+			},
+		})
+		vmMounts = append(vmMounts, corev1.VolumeMount{
+			Name:      k8stools.SanitizeVolumeName("configmap-" + c),
+			ReadOnly:  true,
+			MountPath: path.Join(vmv1beta1.ConfigMapsDir, c),
+		})
+	}
+
+	args = build.AddExtraArgsOverrideDefaults(args, cr.Spec.VLStorage.ExtraArgs, "-")
+	sort.Strings(args)
+	vmstorageContainer := corev1.Container{
+		Name:                     "vlstorage",
+		Image:                    fmt.Sprintf("%s:%s", cr.Spec.VLStorage.Image.Repository, cr.Spec.VLStorage.Image.Tag),
+		ImagePullPolicy:          cr.Spec.VLStorage.Image.PullPolicy,
+		Ports:                    ports,
+		Args:                     args,
+		VolumeMounts:             vmMounts,
+		Resources:                cr.Spec.VLStorage.Resources,
+		Env:                      envs,
+		EnvFrom:                  cr.Spec.VLStorage.ExtraEnvsFrom,
+		TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
+		TerminationMessagePath:   "/dev/termination-log",
+	}
+
+	vmstorageContainer = build.Probe(vmstorageContainer, cr.Spec.VLStorage)
+
+	storageContainers := []corev1.Container{vmstorageContainer}
+	var initContainers []corev1.Container
+
+	useStrictSecurity := ptr.Deref(cr.Spec.VLStorage.UseStrictSecurity, false)
+	build.AddStrictSecuritySettingsToContainers(cr.Spec.VLStorage.SecurityContext, initContainers, useStrictSecurity)
+	ic, err := k8stools.MergePatchContainers(initContainers, cr.Spec.VLStorage.InitContainers)
+	if err != nil {
+		return nil, fmt.Errorf("cannot patch storage init containers: %w", err)
+	}
+
+	build.AddStrictSecuritySettingsToContainers(cr.Spec.VLStorage.SecurityContext, storageContainers, useStrictSecurity)
+	containers, err := k8stools.MergePatchContainers(storageContainers, cr.Spec.VLStorage.Containers)
+	if err != nil {
+		return nil, fmt.Errorf("cannot patch storage containers: %w", err)
+	}
+
+	for i := range cr.Spec.VLStorage.TopologySpreadConstraints {
+		if cr.Spec.VLStorage.TopologySpreadConstraints[i].LabelSelector == nil {
+			cr.Spec.VLStorage.TopologySpreadConstraints[i].LabelSelector = &metav1.LabelSelector{
+				MatchLabels: cr.VLStorageSelectorLabels(),
+			}
+		}
+	}
+
+	vmStoragePodSpec := &corev1.PodTemplateSpec{
+		ObjectMeta: metav1.ObjectMeta{
+			Labels:      cr.VLStoragePodLabels(),
+			Annotations: cr.VLStoragePodAnnotations(),
+		},
+		Spec: corev1.PodSpec{
+			Volumes:            volumes,
+			InitContainers:     ic,
+			Containers:         containers,
+			ServiceAccountName: cr.GetServiceAccountName(),
+		},
+	}
+
+	return vmStoragePodSpec, nil
+}
diff --git a/internal/controller/operator/factory/vlcluster/vmauth_lb.go b/internal/controller/operator/factory/vlcluster/vmauth_lb.go
new file mode 100644
index 00000000..79cff930
--- /dev/null
+++ b/internal/controller/operator/factory/vlcluster/vmauth_lb.go
@@ -0,0 +1,302 @@
+package vlcluster
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	policyv1 "k8s.io/api/policy/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/build"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/reconcile"
+)
+
+func createOrUpdateVMAuthLB(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+
+	var prevSecretMeta *metav1.ObjectMeta
+	if prevCR != nil {
+		prevSecretMeta = ptr.To(buildLBConfigSecretMeta(prevCR))
+	}
+	if err := reconcile.Secret(ctx, rclient, buildVMauthLBSecret(cr), prevSecretMeta); err != nil {
+		return fmt.Errorf("cannot reconcile vmauth lb secret: %w", err)
+	}
+	lbDep, err := buildVMauthLBDeployment(cr)
+	if err != nil {
+		return fmt.Errorf("cannot build deployment for vmauth loadbalancing: %w", err)
+	}
+	var prevLB *appsv1.Deployment
+	if prevCR != nil && prevCR.Spec.RequestsLoadBalancer.Enabled {
+		prevLB, err = buildVMauthLBDeployment(prevCR)
+		if err != nil {
+			return fmt.Errorf("cannot build prev deployment for vmauth loadbalancing: %w", err)
+		}
+	}
+	if err := reconcile.Deployment(ctx, rclient, lbDep, prevLB, false); err != nil {
+		return fmt.Errorf("cannot reconcile vmauth lb deployment: %w", err)
+	}
+	if err := createOrUpdateVMAuthLBService(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	if cr.Spec.RequestsLoadBalancer.Spec.PodDisruptionBudget != nil {
+		if err := createOrUpdatePodDisruptionBudgetForVMAuthLB(ctx, rclient, cr, prevCR); err != nil {
+			return fmt.Errorf("cannot create or update PodDisruptionBudget for vmauth lb: %w", err)
+		}
+	}
+	return nil
+}
+
+func buildLBConfigSecretMeta(cr *vmv1.VLCluster) metav1.ObjectMeta {
+	return metav1.ObjectMeta{
+		Namespace:       cr.Namespace,
+		Name:            cr.GetVMAuthLBName(),
+		Labels:          cr.FinalLabels(cr.VMAuthLBSelectorLabels()),
+		Annotations:     cr.FinalAnnotations(),
+		OwnerReferences: cr.AsOwner(),
+	}
+}
+
+func buildVMauthLBSecret(cr *vmv1.VLCluster) *corev1.Secret {
+	targetHostSuffix := fmt.Sprintf("%s.svc", cr.Namespace)
+	if cr.Spec.ClusterDomainName != "" {
+		targetHostSuffix += fmt.Sprintf(".%s", cr.Spec.ClusterDomainName)
+	}
+	insertPort := "9481"
+	selectPort := "9471"
+	insertProto := "http"
+	selectProto := "http"
+	if cr.Spec.VLSelect != nil {
+		selectPort = cr.Spec.VLSelect.Port
+		if v, ok := cr.Spec.VLSelect.ExtraArgs["tls"]; ok && v == "true" {
+			selectProto = "https"
+		}
+	}
+	if cr.Spec.VLInsert != nil {
+		insertPort = cr.Spec.VLInsert.Port
+		if v, ok := cr.Spec.VLInsert.ExtraArgs["tls"]; ok && v == "true" {
+			selectProto = "https"
+		}
+	}
+	insertURL := fmt.Sprintf("%s://%s.%s:%s",
+		insertProto, cr.GetVLInsertLBName(), targetHostSuffix, insertPort)
+	selectURL := fmt.Sprintf("%s://%s.%s:%s",
+		selectProto, cr.GetVLSelectLBName(), targetHostSuffix, selectPort)
+
+	lbScrt := &corev1.Secret{
+		ObjectMeta: buildLBConfigSecretMeta(cr),
+		// TODO: add backend auth
+		StringData: map[string]string{"config.yaml": fmt.Sprintf(`
+unauthorized_user:
+  url_map:
+  - src_paths:
+    - "/insert/.*"
+    - "/internal/insert"
+    url_prefix: "%s"
+    discover_backend_ips: true
+  - src_paths:
+    - ".*"
+    url_prefix: "%s"
+    discover_backend_ips: true
+      `, insertURL,
+			selectURL,
+		)},
+	}
+	return lbScrt
+}
+
+func buildVMauthLBDeployment(cr *vmv1.VLCluster) (*appsv1.Deployment, error) {
+	spec := cr.Spec.RequestsLoadBalancer.Spec
+	const configMountName = "vmauth-lb-config"
+	volumes := []corev1.Volume{
+		{
+			Name: configMountName,
+			VolumeSource: corev1.VolumeSource{
+				Secret: &corev1.SecretVolumeSource{
+					SecretName: cr.GetVMAuthLBName(),
+				},
+			},
+		},
+	}
+	volumes = append(volumes, spec.Volumes...)
+	vmounts := []corev1.VolumeMount{
+		{
+			MountPath: "/opt/vmauth-config/",
+			Name:      configMountName,
+		},
+	}
+	vmounts = append(vmounts, spec.VolumeMounts...)
+
+	args := []string{
+		"-auth.config=/opt/vmauth-config/config.yaml",
+		"-configCheckInterval=30s",
+	}
+	if spec.LogLevel != "" {
+		args = append(args, fmt.Sprintf("-loggerLevel=%s", spec.LogLevel))
+
+	}
+	if spec.LogFormat != "" {
+		args = append(args, fmt.Sprintf("-loggerFormat=%s", spec.LogFormat))
+	}
+
+	args = append(args, fmt.Sprintf("-httpListenAddr=:%s", spec.Port))
+	if len(spec.ExtraEnvs) > 0 || len(spec.ExtraEnvsFrom) > 0 {
+		args = append(args, "-envflag.enable=true")
+	}
+
+	args = build.AddExtraArgsOverrideDefaults(args, spec.ExtraArgs, "-")
+	vmauthLBCnt := corev1.Container{
+		Name: "vmauth",
+		Ports: []corev1.ContainerPort{
+			{
+				Protocol:      corev1.ProtocolTCP,
+				Name:          "http",
+				ContainerPort: intstr.Parse(spec.Port).IntVal,
+			},
+		},
+		Args:            args,
+		Env:             spec.ExtraEnvs,
+		EnvFrom:         spec.ExtraEnvsFrom,
+		Resources:       spec.Resources,
+		Image:           fmt.Sprintf("%s:%s", spec.Image.Repository, spec.Image.Tag),
+		ImagePullPolicy: spec.Image.PullPolicy,
+		VolumeMounts:    vmounts,
+	}
+	vmauthLBCnt = build.Probe(vmauthLBCnt, &spec)
+	containers := []corev1.Container{
+		vmauthLBCnt,
+	}
+	var err error
+
+	build.AddStrictSecuritySettingsToContainers(spec.SecurityContext, containers, ptr.Deref(spec.UseStrictSecurity, false))
+	containers, err = k8stools.MergePatchContainers(containers, spec.Containers)
+	if err != nil {
+		return nil, fmt.Errorf("cannot patch containers: %w", err)
+	}
+	lbDep := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace:       cr.Namespace,
+			Name:            cr.GetVMAuthLBName(),
+			Labels:          cr.FinalLabels(cr.VMAuthLBSelectorLabels()),
+			Annotations:     cr.FinalAnnotations(),
+			OwnerReferences: cr.AsOwner(),
+		},
+		Spec: appsv1.DeploymentSpec{
+			Selector: &metav1.LabelSelector{
+				MatchLabels: cr.VMAuthLBSelectorLabels(),
+			},
+			Template: corev1.PodTemplateSpec{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels:      cr.VMAuthLBPodLabels(),
+					Annotations: cr.VMAuthLBPodAnnotations(),
+				},
+				Spec: corev1.PodSpec{
+					Volumes:            volumes,
+					InitContainers:     spec.InitContainers,
+					Containers:         containers,
+					ServiceAccountName: cr.GetServiceAccountName(),
+				},
+			},
+		},
+	}
+	build.DeploymentAddCommonParams(lbDep, ptr.Deref(cr.Spec.RequestsLoadBalancer.Spec.UseStrictSecurity, false), &spec.CommonApplicationDeploymentParams)
+
+	return lbDep, nil
+
+}
+
+func createOrUpdateVMAuthLBService(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+	lbls := cr.VMAuthLBSelectorLabels()
+
+	// add proxy label directly to the service.labels
+	// it'll be used below for vmservicescrape matcher
+	// otherwise, it could lead to the multiple targets discovery
+	// for the single pod
+	// it's caused by multiple services pointed to the same pods
+	fls := cr.FinalLabels(lbls)
+	fls = labels.Merge(fls, map[string]string{vmauthLBServiceProxyTargetLabel: "vmauth"})
+	t := &optsBuilder{
+		VLCluster:         cr,
+		prefixedName:      cr.GetVMAuthLBName(),
+		finalLabels:       fls,
+		selectorLabels:    lbls,
+		additionalService: cr.Spec.RequestsLoadBalancer.Spec.AdditionalServiceSpec,
+	}
+	svc := build.Service(t, cr.Spec.RequestsLoadBalancer.Spec.Port, nil)
+
+	var prevSvc *corev1.Service
+	if prevCR != nil && prevCR.Spec.RequestsLoadBalancer.Enabled {
+		t.VLCluster = prevCR
+		t.additionalService = prevCR.Spec.RequestsLoadBalancer.Spec.AdditionalServiceSpec
+		prevSvc = build.Service(t, prevCR.Spec.RequestsLoadBalancer.Spec.Port, nil)
+	}
+
+	if err := reconcile.Service(ctx, rclient, svc, prevSvc); err != nil {
+		return fmt.Errorf("cannot reconcile vmauthlb service: %w", err)
+	}
+	svs := build.VMServiceScrapeForServiceWithSpec(svc, &cr.Spec.RequestsLoadBalancer.Spec)
+	svs.Spec.Selector.MatchLabels[vmauthLBServiceProxyTargetLabel] = "vmauth"
+	if err := reconcile.VMServiceScrapeForCRD(ctx, rclient, svs); err != nil {
+		return fmt.Errorf("cannot reconcile vmauthlb vmservicescrape: %w", err)
+	}
+	return nil
+}
+
+func createOrUpdatePodDisruptionBudgetForVMAuthLB(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster) error {
+
+	t := newOptsBuilder(cr, cr.GetVMAuthLBName(), cr.VMAuthLBSelectorLabels())
+	pdb := build.PodDisruptionBudget(t, cr.Spec.RequestsLoadBalancer.Spec.PodDisruptionBudget)
+	var prevPDB *policyv1.PodDisruptionBudget
+	if prevCR != nil && prevCR.Spec.RequestsLoadBalancer.Spec.PodDisruptionBudget != nil {
+		t = newOptsBuilder(prevCR, prevCR.GetVMAuthLBName(), prevCR.VMAuthLBSelectorLabels())
+		prevPDB = build.PodDisruptionBudget(t, prevCR.Spec.RequestsLoadBalancer.Spec.PodDisruptionBudget)
+	}
+	return reconcile.PDB(ctx, rclient, pdb, prevPDB)
+}
+
+// createOrUpdateLBProxyService builds vlinsert and vlselect external services to expose vlcluster components for access by vmauth
+func createOrUpdateLBProxyService(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VLCluster, svcName, port, prevPort, targetName string, svcSelectorLabels map[string]string) error {
+
+	// TODO: check
+	fls := cr.FinalLabels(svcSelectorLabels)
+	fls = labels.Merge(fls, map[string]string{vmauthLBServiceProxyTargetLabel: targetName})
+	t := &optsBuilder{
+		cr,
+		svcName,
+		fls,
+		cr.VMAuthLBSelectorLabels(),
+		nil,
+	}
+
+	svc := build.Service(t, cr.Spec.RequestsLoadBalancer.Spec.Port, func(svc *corev1.Service) {
+		svc.Spec.Ports[0].Port = intstr.Parse(port).IntVal
+	})
+
+	var prevSvc *corev1.Service
+	if prevCR != nil {
+		fls := prevCR.FinalLabels(svcSelectorLabels)
+		fls = labels.Merge(fls, map[string]string{vmauthLBServiceProxyTargetLabel: targetName})
+		t := &optsBuilder{
+			prevCR,
+			svcName,
+			fls,
+			prevCR.VMAuthLBSelectorLabels(),
+			nil,
+		}
+
+		prevSvc = build.Service(t, prevCR.Spec.RequestsLoadBalancer.Spec.Port, func(svc *corev1.Service) {
+			svc.Spec.Ports[0].Port = intstr.Parse(prevPort).IntVal
+		})
+	}
+
+	if err := reconcile.Service(ctx, rclient, svc, prevSvc); err != nil {
+		return fmt.Errorf("cannot reconcile lb service: %w", err)
+	}
+	return nil
+}
diff --git a/internal/controller/operator/objects_stat.go b/internal/controller/operator/objects_stat.go
index b652d311..9a690d56 100644
--- a/internal/controller/operator/objects_stat.go
+++ b/internal/controller/operator/objects_stat.go
@@ -46,7 +46,7 @@ func newCollector() *objectCollector {
 		objectsByController: map[string]map[string]struct{}{},
 	}
 	registeredObjects := []string{
-		"vmagent", "vmalert", "vmsingle", "vmcluster", "vmalertmanager", "vmauth", "vlogs", "vlsingle",
+		"vmagent", "vmalert", "vmsingle", "vmcluster", "vmalertmanager", "vmauth", "vlogs", "vlsingle", "vlcluster",
 		"vmalertmanagerconfig", "vmrule", "vmuser", "vmservicescrape", "vmstaticscrape", "vmnodescrape", "vmpodscrape", "vmprobescrape", "vmscrapeconfig",
 	}
 	for _, controller := range registeredObjects {
diff --git a/internal/controller/operator/vlcluster_controller.go b/internal/controller/operator/vlcluster_controller.go
new file mode 100644
index 00000000..91ef2c32
--- /dev/null
+++ b/internal/controller/operator/vlcluster_controller.go
@@ -0,0 +1,107 @@
+/*
+
+
+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 operator
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/go-logr/logr"
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	operatorv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	"github.com/VictoriaMetrics/operator/internal/config"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/finalize"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/logger"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/vlcluster"
+)
+
+// VLClusterReconciler reconciles a VLCluster object
+type VLClusterReconciler struct {
+	client.Client
+	Log          logr.Logger
+	OriginScheme *runtime.Scheme
+	BaseConf     *config.BaseOperatorConf
+}
+
+// Init implements crdController interface
+func (r *VLClusterReconciler) Init(rclient client.Client, l logr.Logger, sc *runtime.Scheme, cf *config.BaseOperatorConf) {
+	r.Client = rclient
+	r.Log = l.WithName("controller.VLCluster")
+	r.OriginScheme = sc
+	r.BaseConf = cf
+}
+
+// Reconcile is part of the main kubernetes reconciliation loop
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vlclusters,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vlclusters/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vlclusters/finalizers,verbs=update
+func (r *VLClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
+	reqLogger := r.Log.WithValues("vlcluster", req.Name, "namespace", req.Namespace)
+	ctx = logger.AddToContext(ctx, reqLogger)
+	instance := &operatorv1.VLCluster{}
+
+	defer func() {
+		result, err = handleReconcileErr(ctx, r.Client, instance, result, err)
+	}()
+
+	if err := r.Get(ctx, req.NamespacedName, instance); err != nil {
+		return result, &getError{err, "vlcluster", req}
+	}
+
+	RegisterObjectStat(instance, "vlcluster")
+	if !instance.DeletionTimestamp.IsZero() {
+		if err := finalize.OnVClusterDelete(ctx, r.Client, instance); err != nil {
+			return result, err
+		}
+		return
+	}
+	if instance.Spec.ParsingError != "" {
+		return result, &parsingError{instance.Spec.ParsingError, "vlsingle"}
+	}
+	if err := finalize.AddFinalizer(ctx, r.Client, instance); err != nil {
+		return result, err
+	}
+	r.Client.Scheme().Default(instance)
+
+	result, err = reconcileAndTrackStatus(ctx, r.Client, instance.DeepCopy(), func() (ctrl.Result, error) {
+		if err = vlcluster.CreateOrUpdate(ctx, r, instance); err != nil {
+			return result, fmt.Errorf("failed create or update vlsingle: %w", err)
+		}
+
+		return result, nil
+	})
+
+	result.RequeueAfter = r.BaseConf.ResyncAfterDuration()
+
+	return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *VLClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
+	return ctrl.NewControllerManagedBy(mgr).
+		For(&operatorv1.VLCluster{}).
+		Owns(&appsv1.Deployment{}).
+		Owns(&appsv1.StatefulSet{}).
+		Owns(&corev1.Service{}).
+		WithOptions(getDefaultOptions()).
+		Complete(r)
+}
diff --git a/internal/manager/manager.go b/internal/manager/manager.go
index 6747ea83..4e072862 100644
--- a/internal/manager/manager.go
+++ b/internal/manager/manager.go
@@ -24,6 +24,7 @@ import (
 	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
 	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/logger"
 	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/reconcile"
+	webhookv1 "github.com/VictoriaMetrics/operator/internal/webhook/operator/v1"
 	webhookv1beta1 "github.com/VictoriaMetrics/operator/internal/webhook/operator/v1beta1"
 	"github.com/go-logr/logr"
 	promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
@@ -355,7 +356,8 @@ func addWebhooks(mgr ctrl.Manager) error {
 		webhookv1beta1.SetupVMSingleWebhookWithManager,
 		webhookv1beta1.SetupVMClusterWebhookWithManager,
 		webhookv1beta1.SetupVLogsWebhookWithManager,
-		webhookv1beta1.SetupVLSingleWebhookWithManager,
+		webhookv1.SetupVLSingleWebhookWithManager,
+		webhookv1.SetupVLClusterWebhookWithManager,
 		webhookv1beta1.SetupVMAlertmanagerWebhookWithManager,
 		webhookv1beta1.SetupVMAlertmanagerConfigWebhookWithManager,
 		webhookv1beta1.SetupVMAuthWebhookWithManager,
@@ -444,6 +446,7 @@ var controllersByName = map[string]crdController{
 	"VMSingle":             &vmcontroller.VMSingleReconciler{},
 	"VLogs":                &vmcontroller.VLogsReconciler{},
 	"VLSingle":             &vmcontroller.VLSingleReconciler{},
+	"VLCuster":             &vmcontroller.VLClusterReconciler{},
 	"VMAlertmanager":       &vmcontroller.VMAlertmanagerReconciler{},
 	"VMAlert":              &vmcontroller.VMAlertReconciler{},
 	"VMUser":               &vmcontroller.VMUserReconciler{},
diff --git a/internal/webhook/operator/v1/vlcluster_webhook.go b/internal/webhook/operator/v1/vlcluster_webhook.go
new file mode 100644
index 00000000..03e00393
--- /dev/null
+++ b/internal/webhook/operator/v1/vlcluster_webhook.go
@@ -0,0 +1,77 @@
+/*
+
+
+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 v1
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+)
+
+// SetupVLClusterWebhookWithManager will setup the manager to manage the webhooks
+func SetupVLClusterWebhookWithManager(mgr ctrl.Manager) error {
+	return ctrl.NewWebhookManagedBy(mgr).
+		For(&vmv1.VLSingle{}).
+		WithValidator(&VLSingleCustomValidator{}).
+		Complete()
+}
+
+// +kubebuilder:webhook:path=/validate-operator-victoriametrics-com-v1-vlcluster,mutating=false,failurePolicy=fail,sideEffects=None,groups=operator.victoriametrics.com,resources=vlcluster,verbs=create;update,versions=v1,name=vvlsingles-v1.kb.io,admissionReviewVersions=v1
+type VLClusterCustomValidator struct{}
+
+var _ admission.CustomValidator = (*VLClusterCustomValidator)(nil)
+
+// ValidateCreate implements admission.CustomValidator so a webhook will be registered for the type
+func (*VLClusterCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
+	r, ok := obj.(*vmv1.VLCluster)
+	if !ok {
+		return nil, fmt.Errorf("BUG: unexpected type: %T", obj)
+	}
+	if r.Spec.ParsingError != "" {
+		return nil, errors.New(r.Spec.ParsingError)
+	}
+	if err := r.Validate(); err != nil {
+		return nil, err
+	}
+	return nil, nil
+}
+
+// ValidateUpdate implements admission.CustomValidator so a webhook will be registered for the type
+func (*VLClusterCustomValidator) ValidateUpdate(_ context.Context, _, newObj runtime.Object) (admission.Warnings, error) {
+	r, ok := newObj.(*vmv1.VLCluster)
+	if !ok {
+		return nil, fmt.Errorf("BUG: unexpected type: %T", newObj)
+	}
+	if r.Spec.ParsingError != "" {
+		return nil, errors.New(r.Spec.ParsingError)
+	}
+	if err := r.Validate(); err != nil {
+		return nil, err
+	}
+	return nil, nil
+}
+
+// ValidateDelete implements admission.CustomValidator so a webhook will be registered for the type
+func (*VLClusterCustomValidator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
+	return nil, nil
+}
diff --git a/internal/webhook/operator/v1beta1/vlsingle_webhook.go b/internal/webhook/operator/v1/vlsingle_webhook.go
similarity index 93%
rename from internal/webhook/operator/v1beta1/vlsingle_webhook.go
rename to internal/webhook/operator/v1/vlsingle_webhook.go
index ac77e802..2cba5e76 100644
--- a/internal/webhook/operator/v1beta1/vlsingle_webhook.go
+++ b/internal/webhook/operator/v1/vlsingle_webhook.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1beta1
+package v1
 
 import (
 	"context"
@@ -36,10 +36,10 @@ func SetupVLSingleWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-operator-victoriametrics-com-v1-vlsingle,mutating=false,failurePolicy=fail,sideEffects=None,groups=operator.victoriametrics.com,resources=vlsingles,verbs=create;update,versions=v1beta1,name=vvlsingles-v1.kb.io,admissionReviewVersions=v1
+// +kubebuilder:webhook:path=/validate-operator-victoriametrics-com-v1-vlsingle,mutating=false,failurePolicy=fail,sideEffects=None,groups=operator.victoriametrics.com,resources=vlsingles,verbs=create;update,versions=v1,name=vvlsingles-v1.kb.io,admissionReviewVersions=v1
 type VLSingleCustomValidator struct{}
 
-var _ admission.CustomValidator = &VLSingleCustomValidator{}
+var _ admission.CustomValidator = (*VLSingleCustomValidator)(nil)
 
 // ValidateCreate implements admission.CustomValidator so a webhook will be registered for the type
 func (*VLSingleCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {



Test patch:
diff --git a/internal/controller/operator/factory/k8stools/test_helpers.go b/internal/controller/operator/factory/k8stools/test_helpers.go
index 58615315..d68cee77 100644
--- a/internal/controller/operator/factory/k8stools/test_helpers.go
+++ b/internal/controller/operator/factory/k8stools/test_helpers.go
@@ -39,6 +39,7 @@ func testGetScheme() *runtime.Scheme {
 		&vmv1beta1.VMClusterList{},
 		&vmv1beta1.VLogsList{},
 		&vmv1.VLSingleList{},
+		&vmv1.VLClusterList{},
 	)
 	s.AddKnownTypes(vmv1beta1.GroupVersion,
 		&vmv1beta1.VMPodScrape{},
@@ -59,6 +60,7 @@ func testGetScheme() *runtime.Scheme {
 		&vmv1beta1.VMCluster{},
 		&vmv1beta1.VLogs{},
 		&vmv1.VLSingle{},
+		&vmv1.VLCluster{},
 	)
 	return s
 }
@@ -88,6 +90,7 @@ func GetTestClientWithObjects(predefinedObjects []runtime.Object) client.Client
 			&vmv1beta1.VMStaticScrape{},
 			&vmv1beta1.VMNodeScrape{},
 			&vmv1.VLSingle{},
+			&vmv1.VLCluster{},
 		).
 		WithObjects(obj...).Build()
 	withStats := TestClientWithStatsTrack{
diff --git a/internal/controller/operator/factory/vlcluster/vlcluster_test.go b/internal/controller/operator/factory/vlcluster/vlcluster_test.go
new file mode 100644
index 00000000..b7fbd7ed
--- /dev/null
+++ b/internal/controller/operator/factory/vlcluster/vlcluster_test.go
@@ -0,0 +1,209 @@
+package vlcluster
+
+import (
+	"context"
+	"sync"
+	"testing"
+	"time"
+
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/build"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCreateOrUpdate(t *testing.T) {
+	f := func(cr *vmv1.VLCluster, validate func(ctx context.Context, rclient client.Client) error, predefinedObjects []runtime.Object) {
+		t.Helper()
+		fclient := k8stools.GetTestClientWithObjects(predefinedObjects)
+		build.AddDefaults(fclient.Scheme())
+		fclient.Scheme().Default(cr)
+		ctx, cancel := context.WithCancel(context.Background())
+		var wg sync.WaitGroup
+		defer func() {
+			cancel()
+			wg.Wait()
+		}()
+		eventuallyUpdateStatusToOk := func(cb func() error) {
+			wg.Add(1)
+			go func() {
+				defer wg.Done()
+				tc := time.NewTicker(time.Millisecond * 100)
+				for {
+					select {
+					case <-ctx.Done():
+						return
+					case <-tc.C:
+						if err := cb(); err != nil {
+							if errors.IsNotFound(err) {
+								continue
+							}
+							t.Errorf("callback error: %s", err)
+							return
+						}
+						return
+					}
+				}
+			}()
+		}
+		if cr.Spec.VLStorage != nil {
+			var vlst appsv1.StatefulSet
+			eventuallyUpdateStatusToOk(func() error {
+				if err := fclient.Get(ctx, types.NamespacedName{Name: cr.GetVLStorageName(), Namespace: cr.Namespace}, &vlst); err != nil {
+					return err
+				}
+				vlst.Status.ReadyReplicas = *cr.Spec.VLStorage.ReplicaCount
+				vlst.Status.UpdatedReplicas = *cr.Spec.VLStorage.ReplicaCount
+				if err := fclient.Status().Update(ctx, &vlst); err != nil {
+					return err
+				}
+
+				return nil
+			})
+		}
+		if cr.Spec.VLSelect != nil {
+			var vls appsv1.Deployment
+			eventuallyUpdateStatusToOk(func() error {
+				if err := fclient.Get(ctx, types.NamespacedName{Name: cr.GetVLSelectName(), Namespace: cr.Namespace}, &vls); err != nil {
+					return err
+				}
+				vls.Status.Conditions = append(vls.Status.Conditions, appsv1.DeploymentCondition{
+					Type:   appsv1.DeploymentProgressing,
+					Reason: "NewReplicaSetAvailable",
+					Status: "True",
+				})
+				vls.Status.UpdatedReplicas = *vls.Spec.Replicas
+				vls.Status.AvailableReplicas = vls.Status.UpdatedReplicas
+				if err := fclient.Status().Update(ctx, &vls); err != nil {
+					return err
+				}
+
+				return nil
+			})
+
+		}
+		if cr.Spec.VLInsert != nil {
+			var vli appsv1.Deployment
+			eventuallyUpdateStatusToOk(func() error {
+				if err := fclient.Get(ctx, types.NamespacedName{Name: cr.GetVLInsertName(), Namespace: cr.Namespace}, &vli); err != nil {
+					return err
+				}
+				vli.Status.Conditions = append(vli.Status.Conditions, appsv1.DeploymentCondition{
+					Type:   appsv1.DeploymentProgressing,
+					Reason: "NewReplicaSetAvailable",
+					Status: "True",
+				})
+				vli.Status.UpdatedReplicas = *vli.Spec.Replicas
+				vli.Status.AvailableReplicas = vli.Status.UpdatedReplicas
+				if err := fclient.Status().Update(ctx, &vli); err != nil {
+					return err
+				}
+				return nil
+			})
+
+		}
+		if cr.Spec.RequestsLoadBalancer.Enabled {
+			var vmauthLB appsv1.Deployment
+			eventuallyUpdateStatusToOk(func() error {
+				if err := fclient.Get(ctx, types.NamespacedName{Name: cr.GetVMAuthLBName(), Namespace: cr.Namespace}, &vmauthLB); err != nil {
+					return err
+				}
+				vmauthLB.Status.Conditions = append(vmauthLB.Status.Conditions, appsv1.DeploymentCondition{
+					Type:   appsv1.DeploymentProgressing,
+					Reason: "NewReplicaSetAvailable",
+					Status: "True",
+				})
+				if err := fclient.Status().Update(ctx, &vmauthLB); err != nil {
+					return err
+				}
+
+				return nil
+			})
+		}
+		if err := CreateOrUpdate(ctx, fclient, cr); err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if validate != nil {
+			if err := validate(ctx, fclient); err != nil {
+				t.Fatalf("unexpected validation error: %s", err)
+			}
+		}
+	}
+	cr := &vmv1.VLCluster{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "base",
+			Namespace: "default",
+			Labels: map[string]string{
+				"only-main-object-label": "value",
+			},
+			Annotations: map[string]string{
+				"only-main-object-annotation": "value",
+			},
+		},
+		Spec: vmv1.VLClusterSpec{
+			VLInsert: &vmv1.VLInsert{
+				CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+					ReplicaCount: ptr.To(int32(1)),
+				},
+			},
+			VLStorage: &vmv1.VLStorage{
+				CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+					ReplicaCount: ptr.To(int32(1)),
+				},
+			},
+			VLSelect: &vmv1.VLSelect{
+				CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+					ReplicaCount: ptr.To(int32(1)),
+				},
+			},
+		},
+	}
+	validate := func(ctx context.Context, rclient client.Client) error {
+		// ensure SA created
+		var sa corev1.ServiceAccount
+		assert.Nil(t, rclient.Get(ctx, types.NamespacedName{Name: cr.GetServiceAccountName(), Namespace: cr.Namespace}, &sa))
+		assert.Nil(t, sa.Annotations)
+		assert.Equal(t, sa.Labels, cr.FinalLabels(cr.SelectorLabels()))
+
+		// check insert
+		var dep appsv1.Deployment
+		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.Nil(t, dep.Annotations)
+		assert.Equal(t, dep.Labels, cr.FinalLabels(cr.VLInsertSelectorLabels()))
+
+		// check select
+		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.Nil(t, dep.Annotations)
+		assert.Equal(t, dep.Labels, cr.FinalLabels(cr.VLSelectSelectorLabels()))
+
+		// check storage
+		var sts appsv1.StatefulSet
+		assert.Nil(t, rclient.Get(ctx, types.NamespacedName{Name: cr.GetVLStorageName(), Namespace: cr.Namespace}, &sts))
+		assert.Len(t, sts.Spec.Template.Spec.Containers, 1)
+		cnt = sts.Spec.Template.Spec.Containers[0]
+		assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:9491", "-storageDataPath=vlstorage-data"})
+		assert.Nil(t, sts.Annotations)
+		assert.Equal(t, sts.Labels, cr.FinalLabels(cr.VLStorageSelectorLabels()))
+
+		return nil
+	}
+
+	// base cluster
+	f(cr, validate, nil)
+}
diff --git a/internal/controller/operator/factory/vlcluster/vlinsert_test.go b/internal/controller/operator/factory/vlcluster/vlinsert_test.go
new file mode 100644
index 00000000..71fd7727
--- /dev/null
+++ b/internal/controller/operator/factory/vlcluster/vlinsert_test.go
@@ -0,0 +1,135 @@
+package vlcluster
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+)
+
+func TestBuildSyslogArgs(t *testing.T) {
+	f := func(src *vmv1.SyslogServerSpec, expectedArgs []string) {
+		t.Helper()
+		got := buildSyslogArgs(nil, src)
+		assert.Equal(t, expectedArgs, got, "unexpected syslog args")
+	}
+
+	// multiple tcp listeners
+	spec := vmv1.SyslogServerSpec{
+		TCPListeners: []*vmv1.SyslogTCPListener{
+			{
+				ListenPort: 3001,
+			},
+			{
+				ListenPort:   3002,
+				StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`),
+			},
+		},
+	}
+	expected := []string{
+		"-syslog.listenAddr.tcp=:3001,:3002",
+		`-syslog.streamFields.tcp='','["msg_1","msg_2"]'`,
+	}
+	f(&spec, expected)
+
+	// multiple udp listeners
+	spec = vmv1.SyslogServerSpec{
+		UDPListeners: []*vmv1.SyslogUDPListener{
+			{
+				ListenPort:   3001,
+				IgnoreFields: vmv1.FieldsListString(`["ignore_1"]`),
+			},
+			{
+				ListenPort:   3002,
+				StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`),
+			},
+			{
+				ListenPort: 3005,
+			},
+		},
+	}
+	expected = []string{
+		"-syslog.listenAddr.udp=:3001,:3002,:3005",
+		`-syslog.streamFields.udp='','["msg_1","msg_2"]',''`,
+		`-syslog.ignoreFields.udp='["ignore_1"]','',''`,
+	}
+	f(&spec, expected)
+
+	// mixed udp and tcp
+	// multiple udp listeners
+	spec = vmv1.SyslogServerSpec{
+		TCPListeners: []*vmv1.SyslogTCPListener{
+			{
+				ListenPort: 3001,
+			},
+			{
+				ListenPort:   3002,
+				StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`),
+			},
+		},
+		UDPListeners: []*vmv1.SyslogUDPListener{
+			{
+				ListenPort:   3001,
+				IgnoreFields: vmv1.FieldsListString(`["ignore_1"]`),
+			},
+			{
+				ListenPort:   3002,
+				StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`),
+			},
+			{
+				ListenPort: 3005,
+			},
+		},
+	}
+	expected = []string{
+		"-syslog.listenAddr.tcp=:3001,:3002",
+		`-syslog.streamFields.tcp='','["msg_1","msg_2"]'`,
+		"-syslog.listenAddr.udp=:3001,:3002,:3005",
+		`-syslog.streamFields.udp='','["msg_1","msg_2"]',''`,
+		`-syslog.ignoreFields.udp='["ignore_1"]','',''`,
+	}
+	f(&spec, expected)
+
+	// with tls
+	spec = vmv1.SyslogServerSpec{
+		TCPListeners: []*vmv1.SyslogTCPListener{
+			{
+				ListenPort: 3001,
+				TenantID:   "10:25",
+			},
+			{
+				ListenPort:   3002,
+				StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`),
+				TLSConfig: &vmv1.TLSServerConfig{
+					CertSecret: &corev1.SecretKeySelector{
+						Key: "CERT",
+						LocalObjectReference: corev1.LocalObjectReference{
+							Name: "tls",
+						},
+					},
+					KeyFile: "/etc/vm/secrets/tls/key",
+				},
+			},
+		},
+		UDPListeners: []*vmv1.SyslogUDPListener{
+			{
+				ListenPort:     3001,
+				CompressMethod: "zstd",
+			},
+		},
+	}
+	expected = []string{
+		"-syslog.listenAddr.tcp=:3001,:3002",
+		`-syslog.streamFields.tcp='','["msg_1","msg_2"]'`,
+		`-syslog.tenantID.tcp='10:25',''`,
+		"-syslog.tls=,true",
+		"-syslog.tlsCertFile=/etc/vm/tls-server-secrets/tls/CERT",
+		"-syslog.tlsKeyFile=/etc/vm/secrets/tls/key",
+		"-syslog.listenAddr.udp=:3001",
+		"-syslog.compressMethod.udp='zstd'",
+	}
+	f(&spec, expected)
+
+}
diff --git a/internal/controller/operator/vlcluster_controller_test.go b/internal/controller/operator/vlcluster_controller_test.go
new file mode 100644
index 00000000..a99e145e
--- /dev/null
+++ b/internal/controller/operator/vlcluster_controller_test.go
@@ -0,0 +1,84 @@
+/*
+
+
+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 operator
+
+import (
+	"context"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	operatorv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+)
+
+var _ = Describe("VLCluster Controller", func() {
+	Context("When reconciling a resource", func() {
+		const resourceName = "test-resource"
+
+		ctx := context.Background()
+
+		typeNamespacedName := types.NamespacedName{
+			Name:      resourceName,
+			Namespace: "default", // TODO(user):Modify as needed
+		}
+		vlcluster := &operatorv1.VLCluster{}
+
+		BeforeEach(func() {
+			By("creating the custom resource for the Kind VLCluster")
+			err := k8sClient.Get(ctx, typeNamespacedName, vlcluster)
+			if err != nil && errors.IsNotFound(err) {
+				resource := &operatorv1.VLCluster{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      resourceName,
+						Namespace: "default",
+					},
+					// TODO(user): Specify other spec details if needed.
+				}
+				Expect(k8sClient.Create(ctx, resource)).To(Succeed())
+			}
+		})
+
+		AfterEach(func() {
+			// TODO(user): Cleanup logic after each test, like removing the resource instance.
+			resource := &operatorv1.VLCluster{}
+			err := k8sClient.Get(ctx, typeNamespacedName, resource)
+			Expect(err).NotTo(HaveOccurred())
+
+			By("Cleanup the specific resource instance VLCluster")
+			Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
+		})
+		It("should successfully reconcile the resource", func() {
+			By("Reconciling the created resource")
+			controllerReconciler := &VLClusterReconciler{
+				Client:       k8sClient,
+				OriginScheme: k8sClient.Scheme(),
+			}
+
+			_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
+				NamespacedName: typeNamespacedName,
+			})
+			Expect(err).NotTo(HaveOccurred())
+			// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
+			// Example: If you expect a certain status condition after reconciliation, verify it here.
+		})
+	})
+})
diff --git a/test/e2e/utils_test.go b/test/e2e/utils_test.go
index e5a4913f..cd9bb277 100644
--- a/test/e2e/utils_test.go
+++ b/test/e2e/utils_test.go
@@ -102,7 +102,7 @@ func expectHTTPRequestToSucceed(ctx context.Context, object httpRequestCRDObject
 set -e 
 set -o pipefail
 set -x
-response_code=$(curl --write-out %%{http_code} -X %s %s -d "%s" -o /tmp/curl_log --connect-timeout 5 --max-time 6 --silent --show-error 2>>/tmp/curl_log)
+response_code=$(curl --write-out %%{http_code} -X %s '%s' -d '%s' -o /tmp/curl_log --connect-timeout 5 --max-time 6 --silent --show-error 2>>/tmp/curl_log)
 if [[ "$response_code" -ne %d ]] ; then
   echo "unexpected status code: $response_code" | tee >> /tmp/curl_log
   cat /tmp/curl_log | tr '\n' ' ' | tee > /dev/termination-log
diff --git a/test/e2e/vlcluster_test.go b/test/e2e/vlcluster_test.go
new file mode 100644
index 00000000..2f21ec20
--- /dev/null
+++ b/test/e2e/vlcluster_test.go
@@ -0,0 +1,286 @@
+package e2e
+
+import (
+	"context"
+	"fmt"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	vmv1beta "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/finalize"
+)
+
+//nolint:dupl,lll
+var _ = Describe("test vlsingle Controller", func() {
+
+	Context("e2e vlcluster", func() {
+		var ctx context.Context
+		namespace := "default"
+		namespacedName := types.NamespacedName{
+			Namespace: namespace,
+		}
+		BeforeEach(func() {
+			ctx = context.Background()
+		})
+		AfterEach(func() {
+			Expect(finalize.SafeDelete(ctx, k8sClient, &vmv1.VLCluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      namespacedName.Name,
+					Namespace: namespacedName.Namespace,
+				},
+			})).To(Succeed())
+			Eventually(func() error {
+				return k8sClient.Get(ctx, namespacedName, &vmv1.VLCluster{})
+			}, eventualDeletionTimeout).Should(MatchError(errors.IsNotFound, "IsNotFound"))
+		})
+		baseVLCluster := &vmv1.VLCluster{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: namespace,
+			},
+			Spec: vmv1.VLClusterSpec{
+				VLInsert: &vmv1.VLInsert{},
+				VLSelect: &vmv1.VLSelect{},
+				VLStorage: &vmv1.VLStorage{
+					RetentionPeriod: "1",
+					CommonApplicationDeploymentParams: vmv1beta.CommonApplicationDeploymentParams{
+						ReplicaCount: ptr.To[int32](1),
+					},
+				},
+			},
+		}
+		type testStep struct {
+			setup  func(*vmv1.VLCluster)
+			modify func(*vmv1.VLCluster)
+			verify func(*vmv1.VLCluster)
+		}
+
+		DescribeTable("should peform update steps",
+			func(name string, initCR *vmv1.VLCluster, steps ...testStep) {
+				initCR.Name = name
+				initCR.Namespace = namespace
+				namespacedName.Name = name
+				// setup test
+				Expect(k8sClient.Create(ctx, initCR)).To(Succeed())
+				Eventually(func() error {
+					return expectObjectStatusOperational(ctx, k8sClient, &vmv1.VLCluster{}, namespacedName)
+				}, eventualDeploymentAppReadyTimeout).Should(Succeed())
+
+				for _, step := range steps {
+					if step.setup != nil {
+						step.setup(initCR)
+					}
+					// perform update
+					Eventually(func() error {
+						var toUpdate vmv1.VLCluster
+						Expect(k8sClient.Get(ctx, namespacedName, &toUpdate)).To(Succeed())
+						step.modify(&toUpdate)
+						return k8sClient.Update(ctx, &toUpdate)
+					}, eventualExpandingTimeout).Should(Succeed())
+					Eventually(func() error {
+						return expectObjectStatusOperational(ctx, k8sClient, &vmv1.VLCluster{}, namespacedName)
+					}, eventualDeploymentAppReadyTimeout).Should(Succeed())
+
+					var updated vmv1.VLCluster
+					Expect(k8sClient.Get(ctx, namespacedName, &updated)).To(Succeed())
+
+					// verify results
+					step.verify(&updated)
+				}
+			},
+			Entry("add and remove annotations with strict security", "manage-annotations",
+				baseVLCluster.DeepCopy(),
+				testStep{
+					modify: func(cr *vmv1.VLCluster) {
+						cr.Spec.ManagedMetadata = &vmv1beta.ManagedObjectsMetadata{
+							Annotations: map[string]string{
+								"added-annotation": "some-value",
+							},
+						}
+						cr.Spec.UseStrictSecurity = ptr.To(true)
+					},
+					verify: func(cr *vmv1.VLCluster) {
+						nsss := []types.NamespacedName{
+							{Namespace: namespace, Name: cr.GetVLStorageName()},
+						}
+						expectedAnnotations := map[string]string{"added-annotation": "some-value"}
+						for _, nss := range nsss {
+							assertAnnotationsOnObjects(ctx, nss, []client.Object{&appsv1.StatefulSet{}, &corev1.Service{}}, expectedAnnotations)
+						}
+						for _, nss := range nsss {
+							sts := &appsv1.StatefulSet{}
+							Expect(k8sClient.Get(ctx, nss, sts)).To(Succeed())
+							assertStrictSecurity(sts.Spec.Template.Spec)
+						}
+						nsss = []types.NamespacedName{
+							{Namespace: namespace, Name: cr.GetVLInsertName()},
+							{Namespace: namespace, Name: cr.GetVLSelectName()},
+						}
+						for _, nss := range nsss {
+							sts := &appsv1.Deployment{}
+							Expect(k8sClient.Get(ctx, nss, sts)).To(Succeed())
+							assertStrictSecurity(sts.Spec.Template.Spec)
+						}
+					},
+				},
+				testStep{
+					modify: func(cr *vmv1.VLCluster) {
+						delete(cr.Spec.ManagedMetadata.Annotations, "added-annotation")
+					},
+					verify: func(cr *vmv1.VLCluster) {
+						nsss := []types.NamespacedName{
+							{Namespace: namespace, Name: cr.GetVLStorageName()},
+						}
+						expectedAnnotations := map[string]string{"added-annotation": ""}
+						for _, nss := range nsss {
+							assertAnnotationsOnObjects(ctx, nss, []client.Object{&appsv1.StatefulSet{}, &corev1.Service{}}, expectedAnnotations)
+						}
+						nsss = []types.NamespacedName{
+							{Namespace: namespace, Name: cr.GetVLInsertName()},
+							{Namespace: namespace, Name: cr.GetVLSelectName()},
+						}
+						for _, nss := range nsss {
+							assertAnnotationsOnObjects(ctx, nss, []client.Object{&appsv1.Deployment{}, &corev1.Service{}}, expectedAnnotations)
+						}
+
+					},
+				},
+			),
+			Entry("vlcluster with requests lb", "requests-lb",
+				baseVLCluster.DeepCopy(),
+				testStep{
+					modify: func(cr *vmv1.VLCluster) {
+						cr.Spec.RequestsLoadBalancer.Enabled = true
+					},
+					verify: func(cr *vmv1.VLCluster) {
+
+						var dep appsv1.Deployment
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetVMAuthLBName(), Namespace: namespace}, &dep)).To(Succeed())
+
+						var svc corev1.Service
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetVLSelectName(), Namespace: namespace}, &svc)).To(Succeed())
+						Expect(svc.Spec.Selector).To(Equal(cr.VMAuthLBSelectorLabels()))
+
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetVLInsertName(), Namespace: namespace}, &svc)).To(Succeed())
+						Expect(svc.Spec.Selector).To(Equal(cr.VMAuthLBSelectorLabels()))
+
+						expectHTTPRequestToSucceed(ctx, cr, httpRequestOpts{
+							dstURL: fmt.Sprintf("http://%s.%s.svc:9481/insert/jsonline?_stream_fields=stream&_time_field=date&_msg_field=log.message", cr.GetVLInsertName(), namespace),
+							payload: `{\"log\": {\"level\": \"info\", \"message\": \"hello world\" }, \"date\": \"0\", \"stream\": \"stream1\" }
+{ \"log\": { \"level\": \"info\", \"message\": \"hello world\" }, \"date\": \"0\", \"stream\": \"stream2\" }
+              `,
+							expectedCode: 200,
+							method:       "POST",
+						})
+						expectHTTPRequestToSucceed(ctx, cr, httpRequestOpts{
+							dstURL:       fmt.Sprintf("http://%s.%s.svc:9471/select/logsql/query?query=*", cr.GetVLSelectName(), namespace),
+							payload:      ``,
+							expectedCode: 200,
+						})
+					},
+				},
+				testStep{
+					modify: func(cr *vmv1.VLCluster) {
+						cr.Spec.RequestsLoadBalancer.Enabled = false
+					},
+					verify: func(cr *vmv1.VLCluster) {
+						var dep appsv1.Deployment
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetVMAuthLBName(), Namespace: namespace}, &dep)).To(MatchError(errors.IsNotFound, "isNotFound"))
+
+						var svc corev1.Service
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetVLSelectName(), Namespace: namespace}, &svc)).To(Succeed())
+						Expect(svc.Spec.Selector).To(Equal(cr.VLSelectSelectorLabels()))
+
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetVLInsertName(), Namespace: namespace}, &svc)).To(Succeed())
+						Expect(svc.Spec.Selector).To(Equal(cr.VLInsertSelectorLabels()))
+
+						expectHTTPRequestToSucceed(ctx, cr, httpRequestOpts{
+							dstURL: fmt.Sprintf("http://%s.%s.svc:9481/insert/jsonline?_stream_fields=stream&_time_field=date&_msg_field=log.message", cr.GetVLInsertName(), namespace),
+							payload: `{\"log\": {\"level\": \"info\", \"message\": \"hello world\" }, \"date\": \"0\", \"stream\": \"stream1\" }
+{ \"log\": { \"level\": \"info\", \"message\": \"hello world\" }, \"date\": \"0\", \"stream\": \"stream2\" }
+              `,
+							expectedCode: 200,
+							method:       "POST",
+						})
+						expectHTTPRequestToSucceed(ctx, cr, httpRequestOpts{
+							dstURL:       fmt.Sprintf("http://%s.%s.svc:9471/select/logsql/query?query=*", cr.GetVLSelectName(), namespace),
+							payload:      ``,
+							expectedCode: 200,
+						})
+					},
+				},
+			),
+
+			Entry("with syslog tls", "syslog-tls",
+				baseVLCluster.DeepCopy(),
+				testStep{
+					modify: func(cr *vmv1.VLCluster) {
+						tlsSecret := corev1.Secret{
+							ObjectMeta: metav1.ObjectMeta{
+								Name:      "syslog-tls",
+								Namespace: namespace,
+							},
+							StringData: map[string]string{
+								"TLS_CERT": tlsCert,
+								"TLS_KEY":  tlsKey,
+							},
+						}
+						Expect(k8sClient.Create(ctx, &tlsSecret)).To(Succeed())
+						DeferCleanup(func(ctx SpecContext) {
+							Expect(k8sClient.Delete(ctx, &tlsSecret)).To(Succeed())
+						})
+						cr.Spec.VLInsert.SyslogSpec = &vmv1.SyslogServerSpec{
+							TCPListeners: []*vmv1.SyslogTCPListener{
+								{
+									ListenPort:   9500,
+									StreamFields: vmv1.FieldsListString(`["stream", "log.message"]`),
+									TLSConfig: &vmv1.TLSServerConfig{
+										CertSecret: &corev1.SecretKeySelector{
+											LocalObjectReference: corev1.LocalObjectReference{
+												Name: tlsSecret.Name,
+											},
+											Key: "TLS_CERT",
+										},
+										KeySecret: &corev1.SecretKeySelector{
+											LocalObjectReference: corev1.LocalObjectReference{
+												Name: tlsSecret.Name,
+											},
+											Key: "TLS_KEY",
+										},
+									},
+								},
+							},
+							UDPListeners: []*vmv1.SyslogUDPListener{
+								{
+									ListenPort:   9500,
+									StreamFields: vmv1.FieldsListString(`["stream", "log.message"]`),
+								},
+							},
+						}
+					},
+					verify: func(cr *vmv1.VLCluster) {
+						var svc corev1.Service
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: cr.GetVLInsertName()}, &svc)).To(Succeed())
+						Expect(svc.Spec.Ports).To(HaveLen(3))
+
+						var dep appsv1.Deployment
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: cr.GetVLInsertName()}, &dep)).To(Succeed())
+						Expect(dep.Spec.Template.Spec.Volumes).To(HaveLen(1))
+						Expect(dep.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(1))
+					},
+				},
+			),
+		)
+	},
+	)
+
+})