Repository: VictoriaMetrics/operator

Issue:
Add Victoriatraces Custom Resources
It would be nice if there were CRDs for deploying Victoriatraces as a cluster, single node, and to be able to create a VMUser for Victoriatraces

- [ ] VTSingle
- [ ] VTInsert
- [ ] VTStorage
- [ ] VTSelect
- [ ] VMUser targetRef

Discussion:
@f41gh7: The feature was included into [v0.63.0](https://docs.victoriametrics.com/operator/changelog/#v0630) release

Solution patch:
diff --git a/PROJECT b/PROJECT
index e76e0cde..cbdbea24 100644
--- a/PROJECT
+++ b/PROJECT
@@ -249,4 +249,28 @@ resources:
   webhooks:
     validation: true
     webhookVersion: v1
+- api:
+    crdVersion: v1
+    namespaced: true
+  controller: true
+  domain: victoriametrics.com
+  group: operator
+  kind: VTSingle
+  path: github.com/VictoriaMetrics/operator/api/operator/v1
+  version: v1
+  webhooks:
+    validation: true
+    webhookVersion: v1
+- api:
+    crdVersion: v1
+    namespaced: true
+  controller: true
+  domain: victoriametrics.com
+  group: operator
+  kind: VTCluster
+  path: github.com/VictoriaMetrics/operator/api/operator/v1
+  version: v1
+  webhooks:
+    validation: true
+    webhookVersion: v1
 version: "3"
diff --git a/api/client/informers/externalversions/generic.go b/api/client/informers/externalversions/generic.go
index 52bcd45a..e43340fa 100644
--- a/api/client/informers/externalversions/generic.go
+++ b/api/client/informers/externalversions/generic.go
@@ -61,6 +61,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1().VLSingles().Informer()}, nil
 	case v1.SchemeGroupVersion.WithResource("vmanomalies"):
 		return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1().VMAnomalies().Informer()}, nil
+	case v1.SchemeGroupVersion.WithResource("vtclusters"):
+		return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1().VTClusters().Informer()}, nil
+	case v1.SchemeGroupVersion.WithResource("vtsingles"):
+		return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1().VTSingles().Informer()}, nil
 
 		// Group=operator, Version=v1beta1
 	case v1beta1.SchemeGroupVersion.WithResource("vlogs"):
diff --git a/api/client/informers/externalversions/operator/v1/interface.go b/api/client/informers/externalversions/operator/v1/interface.go
index 195f0142..1c1d1f0f 100644
--- a/api/client/informers/externalversions/operator/v1/interface.go
+++ b/api/client/informers/externalversions/operator/v1/interface.go
@@ -31,6 +31,10 @@ type Interface interface {
 	VLSingles() VLSingleInformer
 	// VMAnomalies returns a VMAnomalyInformer.
 	VMAnomalies() VMAnomalyInformer
+	// VTClusters returns a VTClusterInformer.
+	VTClusters() VTClusterInformer
+	// VTSingles returns a VTSingleInformer.
+	VTSingles() VTSingleInformer
 }
 
 type version struct {
@@ -63,3 +67,13 @@ func (v *version) VLSingles() VLSingleInformer {
 func (v *version) VMAnomalies() VMAnomalyInformer {
 	return &vMAnomalyInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
 }
+
+// VTClusters returns a VTClusterInformer.
+func (v *version) VTClusters() VTClusterInformer {
+	return &vTClusterInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+}
+
+// VTSingles returns a VTSingleInformer.
+func (v *version) VTSingles() VTSingleInformer {
+	return &vTSingleInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+}
diff --git a/api/client/informers/externalversions/operator/v1/vtcluster.go b/api/client/informers/externalversions/operator/v1/vtcluster.go
new file mode 100644
index 00000000..ca87ce0e
--- /dev/null
+++ b/api/client/informers/externalversions/operator/v1/vtcluster.go
@@ -0,0 +1,89 @@
+/*
+
+
+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.
+*/
+// Code generated by informer-gen-v0.32. DO NOT EDIT.
+
+package v1
+
+import (
+	context "context"
+	time "time"
+
+	internalinterfaces "github.com/VictoriaMetrics/operator/api/client/informers/externalversions/internalinterfaces"
+	operatorv1 "github.com/VictoriaMetrics/operator/api/client/listers/operator/v1"
+	versioned "github.com/VictoriaMetrics/operator/api/client/versioned"
+	apioperatorv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+	watch "k8s.io/apimachinery/pkg/watch"
+	cache "k8s.io/client-go/tools/cache"
+)
+
+// VTClusterInformer provides access to a shared informer and lister for
+// VTClusters.
+type VTClusterInformer interface {
+	Informer() cache.SharedIndexInformer
+	Lister() operatorv1.VTClusterLister
+}
+
+type vTClusterInformer struct {
+	factory          internalinterfaces.SharedInformerFactory
+	tweakListOptions internalinterfaces.TweakListOptionsFunc
+	namespace        string
+}
+
+// NewVTClusterInformer constructs a new informer for VTCluster type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewVTClusterInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+	return NewFilteredVTClusterInformer(client, namespace, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredVTClusterInformer constructs a new informer for VTCluster type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewFilteredVTClusterInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+	return cache.NewSharedIndexInformer(
+		&cache.ListWatch{
+			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.OperatorV1().VTClusters(namespace).List(context.TODO(), options)
+			},
+			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.OperatorV1().VTClusters(namespace).Watch(context.TODO(), options)
+			},
+		},
+		&apioperatorv1.VTCluster{},
+		resyncPeriod,
+		indexers,
+	)
+}
+
+func (f *vTClusterInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+	return NewFilteredVTClusterInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *vTClusterInformer) Informer() cache.SharedIndexInformer {
+	return f.factory.InformerFor(&apioperatorv1.VTCluster{}, f.defaultInformer)
+}
+
+func (f *vTClusterInformer) Lister() operatorv1.VTClusterLister {
+	return operatorv1.NewVTClusterLister(f.Informer().GetIndexer())
+}
diff --git a/api/client/informers/externalversions/operator/v1/vtsingle.go b/api/client/informers/externalversions/operator/v1/vtsingle.go
new file mode 100644
index 00000000..db2bd617
--- /dev/null
+++ b/api/client/informers/externalversions/operator/v1/vtsingle.go
@@ -0,0 +1,89 @@
+/*
+
+
+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.
+*/
+// Code generated by informer-gen-v0.32. DO NOT EDIT.
+
+package v1
+
+import (
+	context "context"
+	time "time"
+
+	internalinterfaces "github.com/VictoriaMetrics/operator/api/client/informers/externalversions/internalinterfaces"
+	operatorv1 "github.com/VictoriaMetrics/operator/api/client/listers/operator/v1"
+	versioned "github.com/VictoriaMetrics/operator/api/client/versioned"
+	apioperatorv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+	watch "k8s.io/apimachinery/pkg/watch"
+	cache "k8s.io/client-go/tools/cache"
+)
+
+// VTSingleInformer provides access to a shared informer and lister for
+// VTSingles.
+type VTSingleInformer interface {
+	Informer() cache.SharedIndexInformer
+	Lister() operatorv1.VTSingleLister
+}
+
+type vTSingleInformer struct {
+	factory          internalinterfaces.SharedInformerFactory
+	tweakListOptions internalinterfaces.TweakListOptionsFunc
+	namespace        string
+}
+
+// NewVTSingleInformer constructs a new informer for VTSingle type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewVTSingleInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+	return NewFilteredVTSingleInformer(client, namespace, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredVTSingleInformer constructs a new informer for VTSingle type.
+// Always prefer using an informer factory to get a shared informer instead of getting an independent
+// one. This reduces memory footprint and number of connections to the server.
+func NewFilteredVTSingleInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
+	return cache.NewSharedIndexInformer(
+		&cache.ListWatch{
+			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.OperatorV1().VTSingles(namespace).List(context.TODO(), options)
+			},
+			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
+				if tweakListOptions != nil {
+					tweakListOptions(&options)
+				}
+				return client.OperatorV1().VTSingles(namespace).Watch(context.TODO(), options)
+			},
+		},
+		&apioperatorv1.VTSingle{},
+		resyncPeriod,
+		indexers,
+	)
+}
+
+func (f *vTSingleInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+	return NewFilteredVTSingleInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *vTSingleInformer) Informer() cache.SharedIndexInformer {
+	return f.factory.InformerFor(&apioperatorv1.VTSingle{}, f.defaultInformer)
+}
+
+func (f *vTSingleInformer) Lister() operatorv1.VTSingleLister {
+	return operatorv1.NewVTSingleLister(f.Informer().GetIndexer())
+}
diff --git a/api/client/listers/operator/v1/expansion_generated.go b/api/client/listers/operator/v1/expansion_generated.go
index 29eb3d2c..bc5f1c4a 100644
--- a/api/client/listers/operator/v1/expansion_generated.go
+++ b/api/client/listers/operator/v1/expansion_generated.go
@@ -48,3 +48,19 @@ type VMAnomalyListerExpansion interface{}
 // VMAnomalyNamespaceListerExpansion allows custom methods to be added to
 // VMAnomalyNamespaceLister.
 type VMAnomalyNamespaceListerExpansion interface{}
+
+// VTClusterListerExpansion allows custom methods to be added to
+// VTClusterLister.
+type VTClusterListerExpansion interface{}
+
+// VTClusterNamespaceListerExpansion allows custom methods to be added to
+// VTClusterNamespaceLister.
+type VTClusterNamespaceListerExpansion interface{}
+
+// VTSingleListerExpansion allows custom methods to be added to
+// VTSingleLister.
+type VTSingleListerExpansion interface{}
+
+// VTSingleNamespaceListerExpansion allows custom methods to be added to
+// VTSingleNamespaceLister.
+type VTSingleNamespaceListerExpansion interface{}
diff --git a/api/client/listers/operator/v1/vtcluster.go b/api/client/listers/operator/v1/vtcluster.go
new file mode 100644
index 00000000..19c0ee7f
--- /dev/null
+++ b/api/client/listers/operator/v1/vtcluster.go
@@ -0,0 +1,69 @@
+/*
+
+
+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.
+*/
+// Code generated by lister-gen-v0.32. DO NOT EDIT.
+
+package v1
+
+import (
+	operatorv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	labels "k8s.io/apimachinery/pkg/labels"
+	listers "k8s.io/client-go/listers"
+	cache "k8s.io/client-go/tools/cache"
+)
+
+// VTClusterLister helps list VTClusters.
+// All objects returned here must be treated as read-only.
+type VTClusterLister interface {
+	// List lists all VTClusters in the indexer.
+	// Objects returned here must be treated as read-only.
+	List(selector labels.Selector) (ret []*operatorv1.VTCluster, err error)
+	// VTClusters returns an object that can list and get VTClusters.
+	VTClusters(namespace string) VTClusterNamespaceLister
+	VTClusterListerExpansion
+}
+
+// vTClusterLister implements the VTClusterLister interface.
+type vTClusterLister struct {
+	listers.ResourceIndexer[*operatorv1.VTCluster]
+}
+
+// NewVTClusterLister returns a new VTClusterLister.
+func NewVTClusterLister(indexer cache.Indexer) VTClusterLister {
+	return &vTClusterLister{listers.New[*operatorv1.VTCluster](indexer, operatorv1.Resource("vtcluster"))}
+}
+
+// VTClusters returns an object that can list and get VTClusters.
+func (s *vTClusterLister) VTClusters(namespace string) VTClusterNamespaceLister {
+	return vTClusterNamespaceLister{listers.NewNamespaced[*operatorv1.VTCluster](s.ResourceIndexer, namespace)}
+}
+
+// VTClusterNamespaceLister helps list and get VTClusters.
+// All objects returned here must be treated as read-only.
+type VTClusterNamespaceLister interface {
+	// List lists all VTClusters in the indexer for a given namespace.
+	// Objects returned here must be treated as read-only.
+	List(selector labels.Selector) (ret []*operatorv1.VTCluster, err error)
+	// Get retrieves the VTCluster from the indexer for a given namespace and name.
+	// Objects returned here must be treated as read-only.
+	Get(name string) (*operatorv1.VTCluster, error)
+	VTClusterNamespaceListerExpansion
+}
+
+// vTClusterNamespaceLister implements the VTClusterNamespaceLister
+// interface.
+type vTClusterNamespaceLister struct {
+	listers.ResourceIndexer[*operatorv1.VTCluster]
+}
diff --git a/api/client/listers/operator/v1/vtsingle.go b/api/client/listers/operator/v1/vtsingle.go
new file mode 100644
index 00000000..7a211c19
--- /dev/null
+++ b/api/client/listers/operator/v1/vtsingle.go
@@ -0,0 +1,69 @@
+/*
+
+
+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.
+*/
+// Code generated by lister-gen-v0.32. DO NOT EDIT.
+
+package v1
+
+import (
+	operatorv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	labels "k8s.io/apimachinery/pkg/labels"
+	listers "k8s.io/client-go/listers"
+	cache "k8s.io/client-go/tools/cache"
+)
+
+// VTSingleLister helps list VTSingles.
+// All objects returned here must be treated as read-only.
+type VTSingleLister interface {
+	// List lists all VTSingles in the indexer.
+	// Objects returned here must be treated as read-only.
+	List(selector labels.Selector) (ret []*operatorv1.VTSingle, err error)
+	// VTSingles returns an object that can list and get VTSingles.
+	VTSingles(namespace string) VTSingleNamespaceLister
+	VTSingleListerExpansion
+}
+
+// vTSingleLister implements the VTSingleLister interface.
+type vTSingleLister struct {
+	listers.ResourceIndexer[*operatorv1.VTSingle]
+}
+
+// NewVTSingleLister returns a new VTSingleLister.
+func NewVTSingleLister(indexer cache.Indexer) VTSingleLister {
+	return &vTSingleLister{listers.New[*operatorv1.VTSingle](indexer, operatorv1.Resource("vtsingle"))}
+}
+
+// VTSingles returns an object that can list and get VTSingles.
+func (s *vTSingleLister) VTSingles(namespace string) VTSingleNamespaceLister {
+	return vTSingleNamespaceLister{listers.NewNamespaced[*operatorv1.VTSingle](s.ResourceIndexer, namespace)}
+}
+
+// VTSingleNamespaceLister helps list and get VTSingles.
+// All objects returned here must be treated as read-only.
+type VTSingleNamespaceLister interface {
+	// List lists all VTSingles in the indexer for a given namespace.
+	// Objects returned here must be treated as read-only.
+	List(selector labels.Selector) (ret []*operatorv1.VTSingle, err error)
+	// Get retrieves the VTSingle from the indexer for a given namespace and name.
+	// Objects returned here must be treated as read-only.
+	Get(name string) (*operatorv1.VTSingle, error)
+	VTSingleNamespaceListerExpansion
+}
+
+// vTSingleNamespaceLister implements the VTSingleNamespaceLister
+// interface.
+type vTSingleNamespaceLister struct {
+	listers.ResourceIndexer[*operatorv1.VTSingle]
+}
diff --git a/api/client/versioned/typed/operator/v1/fake/fake_operator_client.go b/api/client/versioned/typed/operator/v1/fake/fake_operator_client.go
index 19a30030..8ae011b3 100644
--- a/api/client/versioned/typed/operator/v1/fake/fake_operator_client.go
+++ b/api/client/versioned/typed/operator/v1/fake/fake_operator_client.go
@@ -43,6 +43,14 @@ func (c *FakeOperatorV1) VMAnomalies(namespace string) v1.VMAnomalyInterface {
 	return newFakeVMAnomalies(c, namespace)
 }
 
+func (c *FakeOperatorV1) VTClusters(namespace string) v1.VTClusterInterface {
+	return newFakeVTClusters(c, namespace)
+}
+
+func (c *FakeOperatorV1) VTSingles(namespace string) v1.VTSingleInterface {
+	return newFakeVTSingles(c, namespace)
+}
+
 // RESTClient returns a RESTClient that is used to communicate
 // with API server by this client implementation.
 func (c *FakeOperatorV1) RESTClient() rest.Interface {
diff --git a/api/client/versioned/typed/operator/v1/fake/fake_vtcluster.go b/api/client/versioned/typed/operator/v1/fake/fake_vtcluster.go
new file mode 100644
index 00000000..3c527719
--- /dev/null
+++ b/api/client/versioned/typed/operator/v1/fake/fake_vtcluster.go
@@ -0,0 +1,47 @@
+/*
+
+
+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.
+*/
+// Code generated by client-gen-v0.32. DO NOT EDIT.
+
+package fake
+
+import (
+	operatorv1 "github.com/VictoriaMetrics/operator/api/client/versioned/typed/operator/v1"
+	v1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	gentype "k8s.io/client-go/gentype"
+)
+
+// fakeVTClusters implements VTClusterInterface
+type fakeVTClusters struct {
+	*gentype.FakeClientWithList[*v1.VTCluster, *v1.VTClusterList]
+	Fake *FakeOperatorV1
+}
+
+func newFakeVTClusters(fake *FakeOperatorV1, namespace string) operatorv1.VTClusterInterface {
+	return &fakeVTClusters{
+		gentype.NewFakeClientWithList[*v1.VTCluster, *v1.VTClusterList](
+			fake.Fake,
+			namespace,
+			v1.SchemeGroupVersion.WithResource("vtclusters"),
+			v1.SchemeGroupVersion.WithKind("VTCluster"),
+			func() *v1.VTCluster { return &v1.VTCluster{} },
+			func() *v1.VTClusterList { return &v1.VTClusterList{} },
+			func(dst, src *v1.VTClusterList) { dst.ListMeta = src.ListMeta },
+			func(list *v1.VTClusterList) []*v1.VTCluster { return gentype.ToPointerSlice(list.Items) },
+			func(list *v1.VTClusterList, items []*v1.VTCluster) { list.Items = gentype.FromPointerSlice(items) },
+		),
+		fake,
+	}
+}
diff --git a/api/client/versioned/typed/operator/v1/fake/fake_vtsingle.go b/api/client/versioned/typed/operator/v1/fake/fake_vtsingle.go
new file mode 100644
index 00000000..1a082531
--- /dev/null
+++ b/api/client/versioned/typed/operator/v1/fake/fake_vtsingle.go
@@ -0,0 +1,47 @@
+/*
+
+
+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.
+*/
+// Code generated by client-gen-v0.32. DO NOT EDIT.
+
+package fake
+
+import (
+	operatorv1 "github.com/VictoriaMetrics/operator/api/client/versioned/typed/operator/v1"
+	v1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	gentype "k8s.io/client-go/gentype"
+)
+
+// fakeVTSingles implements VTSingleInterface
+type fakeVTSingles struct {
+	*gentype.FakeClientWithList[*v1.VTSingle, *v1.VTSingleList]
+	Fake *FakeOperatorV1
+}
+
+func newFakeVTSingles(fake *FakeOperatorV1, namespace string) operatorv1.VTSingleInterface {
+	return &fakeVTSingles{
+		gentype.NewFakeClientWithList[*v1.VTSingle, *v1.VTSingleList](
+			fake.Fake,
+			namespace,
+			v1.SchemeGroupVersion.WithResource("vtsingles"),
+			v1.SchemeGroupVersion.WithKind("VTSingle"),
+			func() *v1.VTSingle { return &v1.VTSingle{} },
+			func() *v1.VTSingleList { return &v1.VTSingleList{} },
+			func(dst, src *v1.VTSingleList) { dst.ListMeta = src.ListMeta },
+			func(list *v1.VTSingleList) []*v1.VTSingle { return gentype.ToPointerSlice(list.Items) },
+			func(list *v1.VTSingleList, items []*v1.VTSingle) { list.Items = gentype.FromPointerSlice(items) },
+		),
+		fake,
+	}
+}
diff --git a/api/client/versioned/typed/operator/v1/generated_expansion.go b/api/client/versioned/typed/operator/v1/generated_expansion.go
index c1917fec..cadf4707 100644
--- a/api/client/versioned/typed/operator/v1/generated_expansion.go
+++ b/api/client/versioned/typed/operator/v1/generated_expansion.go
@@ -24,3 +24,7 @@ type VLClusterExpansion interface{}
 type VLSingleExpansion interface{}
 
 type VMAnomalyExpansion interface{}
+
+type VTClusterExpansion interface{}
+
+type VTSingleExpansion interface{}
diff --git a/api/client/versioned/typed/operator/v1/operator_client.go b/api/client/versioned/typed/operator/v1/operator_client.go
index ebc3688a..ff04e630 100644
--- a/api/client/versioned/typed/operator/v1/operator_client.go
+++ b/api/client/versioned/typed/operator/v1/operator_client.go
@@ -31,6 +31,8 @@ type OperatorV1Interface interface {
 	VLClustersGetter
 	VLSinglesGetter
 	VMAnomaliesGetter
+	VTClustersGetter
+	VTSinglesGetter
 }
 
 // OperatorV1Client is used to interact with features provided by the operator group.
@@ -54,6 +56,14 @@ func (c *OperatorV1Client) VMAnomalies(namespace string) VMAnomalyInterface {
 	return newVMAnomalies(c, namespace)
 }
 
+func (c *OperatorV1Client) VTClusters(namespace string) VTClusterInterface {
+	return newVTClusters(c, namespace)
+}
+
+func (c *OperatorV1Client) VTSingles(namespace string) VTSingleInterface {
+	return newVTSingles(c, namespace)
+}
+
 // NewForConfig creates a new OperatorV1Client for the given config.
 // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
 // where httpClient was generated with rest.HTTPClientFor(c).
diff --git a/api/client/versioned/typed/operator/v1/vtcluster.go b/api/client/versioned/typed/operator/v1/vtcluster.go
new file mode 100644
index 00000000..718896f7
--- /dev/null
+++ b/api/client/versioned/typed/operator/v1/vtcluster.go
@@ -0,0 +1,69 @@
+/*
+
+
+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.
+*/
+// Code generated by client-gen-v0.32. DO NOT EDIT.
+
+package v1
+
+import (
+	context "context"
+
+	scheme "github.com/VictoriaMetrics/operator/api/client/versioned/scheme"
+	operatorv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	gentype "k8s.io/client-go/gentype"
+)
+
+// VTClustersGetter has a method to return a VTClusterInterface.
+// A group's client should implement this interface.
+type VTClustersGetter interface {
+	VTClusters(namespace string) VTClusterInterface
+}
+
+// VTClusterInterface has methods to work with VTCluster resources.
+type VTClusterInterface interface {
+	Create(ctx context.Context, vTCluster *operatorv1.VTCluster, opts metav1.CreateOptions) (*operatorv1.VTCluster, error)
+	Update(ctx context.Context, vTCluster *operatorv1.VTCluster, opts metav1.UpdateOptions) (*operatorv1.VTCluster, error)
+	// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+	UpdateStatus(ctx context.Context, vTCluster *operatorv1.VTCluster, opts metav1.UpdateOptions) (*operatorv1.VTCluster, error)
+	Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
+	DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
+	Get(ctx context.Context, name string, opts metav1.GetOptions) (*operatorv1.VTCluster, error)
+	List(ctx context.Context, opts metav1.ListOptions) (*operatorv1.VTClusterList, error)
+	Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
+	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *operatorv1.VTCluster, err error)
+	VTClusterExpansion
+}
+
+// vTClusters implements VTClusterInterface
+type vTClusters struct {
+	*gentype.ClientWithList[*operatorv1.VTCluster, *operatorv1.VTClusterList]
+}
+
+// newVTClusters returns a VTClusters
+func newVTClusters(c *OperatorV1Client, namespace string) *vTClusters {
+	return &vTClusters{
+		gentype.NewClientWithList[*operatorv1.VTCluster, *operatorv1.VTClusterList](
+			"vtclusters",
+			c.RESTClient(),
+			scheme.ParameterCodec,
+			namespace,
+			func() *operatorv1.VTCluster { return &operatorv1.VTCluster{} },
+			func() *operatorv1.VTClusterList { return &operatorv1.VTClusterList{} },
+		),
+	}
+}
diff --git a/api/client/versioned/typed/operator/v1/vtsingle.go b/api/client/versioned/typed/operator/v1/vtsingle.go
new file mode 100644
index 00000000..c4bb5c66
--- /dev/null
+++ b/api/client/versioned/typed/operator/v1/vtsingle.go
@@ -0,0 +1,69 @@
+/*
+
+
+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.
+*/
+// Code generated by client-gen-v0.32. DO NOT EDIT.
+
+package v1
+
+import (
+	context "context"
+
+	scheme "github.com/VictoriaMetrics/operator/api/client/versioned/scheme"
+	operatorv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	types "k8s.io/apimachinery/pkg/types"
+	watch "k8s.io/apimachinery/pkg/watch"
+	gentype "k8s.io/client-go/gentype"
+)
+
+// VTSinglesGetter has a method to return a VTSingleInterface.
+// A group's client should implement this interface.
+type VTSinglesGetter interface {
+	VTSingles(namespace string) VTSingleInterface
+}
+
+// VTSingleInterface has methods to work with VTSingle resources.
+type VTSingleInterface interface {
+	Create(ctx context.Context, vTSingle *operatorv1.VTSingle, opts metav1.CreateOptions) (*operatorv1.VTSingle, error)
+	Update(ctx context.Context, vTSingle *operatorv1.VTSingle, opts metav1.UpdateOptions) (*operatorv1.VTSingle, error)
+	// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+	UpdateStatus(ctx context.Context, vTSingle *operatorv1.VTSingle, opts metav1.UpdateOptions) (*operatorv1.VTSingle, error)
+	Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
+	DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
+	Get(ctx context.Context, name string, opts metav1.GetOptions) (*operatorv1.VTSingle, error)
+	List(ctx context.Context, opts metav1.ListOptions) (*operatorv1.VTSingleList, error)
+	Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
+	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *operatorv1.VTSingle, err error)
+	VTSingleExpansion
+}
+
+// vTSingles implements VTSingleInterface
+type vTSingles struct {
+	*gentype.ClientWithList[*operatorv1.VTSingle, *operatorv1.VTSingleList]
+}
+
+// newVTSingles returns a VTSingles
+func newVTSingles(c *OperatorV1Client, namespace string) *vTSingles {
+	return &vTSingles{
+		gentype.NewClientWithList[*operatorv1.VTSingle, *operatorv1.VTSingleList](
+			"vtsingles",
+			c.RESTClient(),
+			scheme.ParameterCodec,
+			namespace,
+			func() *operatorv1.VTSingle { return &operatorv1.VTSingle{} },
+			func() *operatorv1.VTSingleList { return &operatorv1.VTSingleList{} },
+		),
+	}
+}
diff --git a/api/operator/v1/vlcluster_types.go b/api/operator/v1/vlcluster_types.go
index 9f017b5b..41a912d6 100644
--- a/api/operator/v1/vlcluster_types.go
+++ b/api/operator/v1/vlcluster_types.go
@@ -619,6 +619,7 @@ func (cr *VLCluster) GetVLStorageName() string {
 	return prefixedName(cr.Name, "vlstorage")
 }
 
+//nolint:dupl,lll
 func (cr *VLCluster) Validate() error {
 	if vmv1beta1.MustSkipCRValidation(cr) {
 		return nil
@@ -746,11 +747,11 @@ func (cr *VLCluster) AvailableStorageNodeIDs(requestsType string) []int32 {
 	return result
 }
 
-var globalClusterLabels = map[string]string{"app.kubernetes.io/part-of": "vlcluster"}
+var globalVLClusterLabels = 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)
+	baseLabels := labels.Merge(globalVLClusterLabels, selectorLabels)
 	if cr.Spec.ManagedMetadata == nil {
 		// fast path
 		return baseLabels
diff --git a/api/operator/v1/vtcluster_types.go b/api/operator/v1/vtcluster_types.go
new file mode 100644
index 00000000..f7bc3606
--- /dev/null
+++ b/api/operator/v1/vtcluster_types.go
@@ -0,0 +1,828 @@
+/*
+
+
+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"
+)
+
+// VTClusterSpec defines the desired state of VTCluster
+type VTClusterSpec 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
+	// VTSelect, VTInsert and VTStorage 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 vtinsert and vtselect to build vtstorage 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"`
+
+	Insert  *VTInsert  `json:"insert,omitempty"`
+	Select  *VTSelect  `json:"select,omitempty"`
+	Storage *VTStorage `json:"storage,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 vtinsert and vtselect 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 *VTClusterSpec) UnmarshalJSON(src []byte) error {
+	type pcr VTClusterSpec
+	if err := json.Unmarshal(src, (*pcr)(cr)); err != nil {
+		cr.ParsingError = fmt.Sprintf("cannot parse vtcluster spec: %s, err: %s", string(src), err)
+		return nil
+	}
+	return nil
+}
+
+// VTClusterStatus defines the observed state of VTCluster
+type VTClusterStatus struct {
+	vmv1beta1.StatusMetadata `json:",inline"`
+}
+
+// GetStatusMetadata returns metadata for object status
+func (cr *VTClusterStatus) GetStatusMetadata() *vmv1beta1.StatusMetadata {
+	return &cr.StatusMetadata
+}
+
+// VTInsert defines vtinsert component configuration at victoria-traces cluster
+type VTInsert struct {
+	// PodMetadata configures Labels and Annotations which are propagated to the VTSelect pods.
+	PodMetadata *vmv1beta1.EmbeddedObjectMetadata `json:"podMetadata,omitempty"`
+	// LogFormat for VTSelect to be configured with.
+	// default or json
+	// +optional
+	// +kubebuilder:validation:Enum=default;json
+	LogFormat string `json:"logFormat,omitempty"`
+	// LogLevel for VTSelect to be configured with.
+	// +optional
+	// +kubebuilder:validation:Enum=INFO;WARN;ERROR;FATAL;PANIC
+	LogLevel string `json:"logLevel,omitempty"`
+
+	// ServiceSpec that will be added to vtselect service spec
+	// +optional
+	ServiceSpec *vmv1beta1.AdditionalServiceSpec `json:"serviceSpec,omitempty"`
+	// ServiceScrapeSpec that will be added to vtselect 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"`
+}
+
+// Probe implements build.probeCRD interface
+func (cr *VTInsert) Probe() *vmv1beta1.EmbeddedProbes {
+	return cr.EmbeddedProbes
+}
+
+// ProbePath implements build.probeCRD interface
+func (cr *VTInsert) ProbePath() string {
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, healthPath)
+}
+
+// ProbeScheme implements build.probeCRD interface
+func (cr *VTInsert) ProbeScheme() string {
+	return strings.ToUpper(vmv1beta1.HTTPProtoFromFlags(cr.ExtraArgs))
+}
+
+// ProbePort implements build.probeCRD interface
+func (cr *VTInsert) ProbePort() string {
+	return cr.Port
+}
+
+// ProbeNeedLiveness implements build.probeCRD interface
+func (*VTInsert) ProbeNeedLiveness() bool {
+	return true
+}
+
+// GetMetricPath returns prefixed path for metric requests
+func (cr *VTInsert) GetMetricPath() string {
+	if cr == nil {
+		return healthPath
+	}
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, metricPath)
+}
+
+// ExtraArgs returns additionally configured command-line arguments
+func (cr *VTInsert) GetExtraArgs() map[string]string {
+	return cr.ExtraArgs
+}
+
+// ServiceScrape returns overrides for serviceScrape builder
+func (cr *VTInsert) GetServiceScrape() *vmv1beta1.VMServiceScrapeSpec {
+	return cr.ServiceScrapeSpec
+}
+
+// GetAdditionalService returns AdditionalServiceSpec settings
+func (cr *VTInsert) GetAdditionalService() *vmv1beta1.AdditionalServiceSpec {
+	return cr.ServiceSpec
+}
+
+// VTSelect defines vtselect component configuration at victoria-traces cluster
+type VTSelect struct {
+	// PodMetadata configures Labels and Annotations which are propagated to the VTSelect pods.
+	PodMetadata *vmv1beta1.EmbeddedObjectMetadata `json:"podMetadata,omitempty"`
+	// LogFormat for VTSelect to be configured with.
+	// default or json
+	// +optional
+	// +kubebuilder:validation:Enum=default;json
+	LogFormat string `json:"logFormat,omitempty"`
+	// LogLevel for VTSelect to be configured with.
+	// +optional
+	// +kubebuilder:validation:Enum=INFO;WARN;ERROR;FATAL;PANIC
+	LogLevel string `json:"logLevel,omitempty"`
+
+	// ServiceSpec that will be added to vtselect service spec
+	// +optional
+	ServiceSpec *vmv1beta1.AdditionalServiceSpec `json:"serviceSpec,omitempty"`
+	// ServiceScrapeSpec that will be added to vtselect 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 *VTSelect) GetMetricPath() string {
+	if cr == nil {
+		return healthPath
+	}
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, metricPath)
+}
+
+// ExtraArgs returns additionally configured command-line arguments
+func (cr *VTSelect) GetExtraArgs() map[string]string {
+	return cr.ExtraArgs
+}
+
+// ServiceScrape returns overrides for serviceScrape builder
+func (cr *VTSelect) GetServiceScrape() *vmv1beta1.VMServiceScrapeSpec {
+	return cr.ServiceScrapeSpec
+}
+
+// Probe implements build.probeCRD interface
+func (cr *VTSelect) Probe() *vmv1beta1.EmbeddedProbes {
+	return cr.EmbeddedProbes
+}
+
+// ProbePath implements build.probeCRD interface
+func (cr *VTSelect) ProbePath() string {
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, healthPath)
+}
+
+// ProbeScheme implements build.probeCRD interface
+func (cr *VTSelect) ProbeScheme() string {
+	return strings.ToUpper(vmv1beta1.HTTPProtoFromFlags(cr.ExtraArgs))
+}
+
+// ProbePort implements build.probeCRD interface
+func (cr *VTSelect) ProbePort() string {
+	return cr.Port
+}
+
+// ProbeNeedLiveness implements build.probeCRD interface
+func (*VTSelect) ProbeNeedLiveness() bool {
+	return true
+}
+
+// GetAdditionalService returns AdditionalServiceSpec settings
+func (cr *VTSelect) GetAdditionalService() *vmv1beta1.AdditionalServiceSpec {
+	return cr.ServiceSpec
+}
+
+// VTStorage defines vtstorage component configuration at victoria-traces cluster
+type VTStorage struct {
+	// RetentionPeriod for the stored traces
+	// https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces
+	// +optional
+	// +kubebuilder:validation:Pattern:="^[0-9]+(h|d|w|y)?$"
+	RetentionPeriod string `json:"retentionPeriod,omitempty"`
+	// RetentionMaxDiskSpaceUsageBytes for the stored traces
+	// VictoriaTraces keeps at least two last days of data in order to guarantee that the traces 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/victoriatraces/#configure-and-run-victoriatraces
+	// +optional
+	RetentionMaxDiskSpaceUsageBytes vmv1beta1.BytesString `json:"retentionMaxDiskSpaceUsageBytes,omitempty"`
+	// FutureRetention for the stored traces
+	// Log entries with timestamps bigger than now+futureRetention are rejected during data ingestion
+	// see https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces
+	// +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/victoriatraces/#configure-and-run-victoriatraces
+	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/victoriatraces/#configure-and-run-victoriatraces
+	LogIngestedRows bool `json:"logIngestedRows,omitempty"`
+
+	// PodMetadata configures Labels and Annotations which are propagated to the VTStorage pods.
+	PodMetadata *vmv1beta1.EmbeddedObjectMetadata `json:"podMetadata,omitempty"`
+	// LogFormat for VTStorage to be configured with.
+	// default or json
+	// +optional
+	// +kubebuilder:validation:Enum=default;json
+	LogFormat string `json:"logFormat,omitempty"`
+	// LogLevel for VTStorage to be configured with.
+	// +optional
+	// +kubebuilder:validation:Enum=INFO;WARN;ERROR;FATAL;PANIC
+	LogLevel string `json:"logLevel,omitempty"`
+
+	// ServiceSpec that will be added to vtselect service spec
+	// +optional
+	ServiceSpec *vmv1beta1.AdditionalServiceSpec `json:"serviceSpec,omitempty"`
+	// ServiceScrapeSpec that will be added to vtselect 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 VTStorage
+	// +optional
+	Storage *vmv1beta1.StorageSpec `json:"storage,omitempty"`
+	// PersistentVolumeClaimRetentionPolicy allows configuration of PVC retention policy
+	// +optional
+	PersistentVolumeClaimRetentionPolicy *appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy `json:"persistentVolumeClaimRetentionPolicy,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"`
+
+	// RollingUpdateStrategyBehavior defines customized behavior for rolling updates.
+	// It applies if the RollingUpdateStrategy is set to OnDelete, which is the default.
+	// +optional
+	RollingUpdateStrategyBehavior *vmv1beta1.StatefulSetUpdateStrategyBehavior `json:"rollingUpdateStrategyBehavior,omitempty"`
+}
+
+// GetStorageVolumeName returns formatted name for vtstorage volume
+func (cr *VTStorage) GetStorageVolumeName() string {
+	if cr.Storage != nil && cr.Storage.VolumeClaimTemplate.Name != "" {
+		return cr.Storage.VolumeClaimTemplate.Name
+	}
+	return "vtstorage-db"
+}
+
+// GetMetricPath returns prefixed path for metric requests
+func (cr *VTStorage) GetMetricPath() string {
+	if cr == nil {
+		return healthPath
+	}
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, metricPath)
+}
+
+// ExtraArgs returns additionally configured command-line arguments
+func (cr *VTStorage) GetExtraArgs() map[string]string {
+	return cr.ExtraArgs
+}
+
+// ServiceScrape returns overrides for serviceScrape builder
+func (cr *VTStorage) GetServiceScrape() *vmv1beta1.VMServiceScrapeSpec {
+	return cr.ServiceScrapeSpec
+}
+
+// Probe implements build.probeCRD interface
+func (cr *VTStorage) Probe() *vmv1beta1.EmbeddedProbes {
+	return cr.EmbeddedProbes
+}
+
+// ProbePath implements build.probeCRD interface
+func (cr *VTStorage) ProbePath() string {
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.ExtraArgs, healthPath)
+}
+
+// ProbeScheme implements build.probeCRD interface
+func (cr *VTStorage) ProbeScheme() string {
+	return strings.ToUpper(vmv1beta1.HTTPProtoFromFlags(cr.ExtraArgs))
+}
+
+// ProbePort implements build.probeCRD interface
+func (cr *VTStorage) ProbePort() string {
+	return cr.Port
+}
+
+// ProbeNeedLiveness implements build.probeCRD interface
+func (*VTStorage) ProbeNeedLiveness() bool {
+	return false
+}
+
+// GetAdditionalService returns AdditionalServiceSpec settings
+func (cr *VTStorage) GetAdditionalService() *vmv1beta1.AdditionalServiceSpec {
+	return cr.ServiceSpec
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+
+// VTCluster is fast, cost-effective and scalable traces database.
+// +kubebuilder:printcolumn:name="Insert Count",type="string",JSONPath=".spec.vtinsert.replicaCount",description="replicas of VTInsert"
+// +kubebuilder:printcolumn:name="Storage Count",type="string",JSONPath=".spec.vtstorage.replicaCount",description="replicas of VTStorage"
+// +kubebuilder:printcolumn:name="Select Count",type="string",JSONPath=".spec.vtselect.replicaCount",description="replicas of VTSelect"
+// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
+// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.updateStatus",description="Current status of cluster"
+// +genclient
+type VTCluster struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   VTClusterSpec   `json:"spec,omitempty"`
+	Status VTClusterStatus `json:"status,omitempty"`
+	// ParsedLastAppliedSpec contains last-applied configuration spec
+	ParsedLastAppliedSpec *VTClusterSpec `json:"-" yaml:"-"`
+}
+
+// SetLastSpec implements objectWithLastAppliedState interface
+func (cr *VTCluster) SetLastSpec(prevSpec VTClusterSpec) {
+	cr.ParsedLastAppliedSpec = &prevSpec
+}
+
+// UnmarshalJSON implements json.Unmarshaler interface
+func (cr *VTCluster) UnmarshalJSON(src []byte) error {
+	type pcr VTCluster
+	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 *VTCluster) GetStatus() *VTClusterStatus {
+	return &cr.Status
+}
+
+// DefaultStatusFields implements reconcile.ObjectWithDeepCopyAndStatus interface
+func (cr *VTCluster) DefaultStatusFields(vs *VTClusterStatus) {
+}
+
+// AsOwner returns owner references with current object as owner
+func (cr *VTCluster) 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 *VTCluster) VMAuthLBSelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vtclusterlb-vmauth-balancer",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// VMAuthLBPodLabels returns pod labels for vtclusterlb-vmauth-balancer cluster component
+func (cr *VTCluster) 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 *VTCluster) 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 *VTCluster) GetVMAuthLBName() string {
+	return fmt.Sprintf("vtclusterlb-%s", cr.Name)
+}
+
+// GetVTSelectLBName returns headless proxy service name for select component
+func (cr *VTCluster) GetVTSelectLBName() string {
+	return prefixedName(cr.Name, "vtselectint")
+}
+
+// GetVTInsertLBName returns headless proxy service name for insert component
+func (cr *VTCluster) GetVTInsertLBName() string {
+	return prefixedName(cr.Name, "vtinsertint")
+}
+
+// GetVTInsertName returns insert component name
+func (cr *VTCluster) GetVTInsertName() string {
+	return prefixedName(cr.Name, "vtinsert")
+}
+
+// GetLVSelectName returns select component name
+func (cr *VTCluster) GetVTSelectName() string {
+	return prefixedName(cr.Name, "vtselect")
+}
+
+// GetVTStorageName returns select component name
+func (cr *VTCluster) GetVTStorageName() string {
+	return prefixedName(cr.Name, "vtstorage")
+}
+
+//nolint:dupl,lll
+func (cr *VTCluster) Validate() error {
+	if vmv1beta1.MustSkipCRValidation(cr) {
+		return nil
+	}
+	if cr.Spec.Select != nil {
+		vms := cr.Spec.Select
+		if vms.ServiceSpec != nil && vms.ServiceSpec.Name == cr.GetVTSelectName() {
+			return fmt.Errorf(".serviceSpec.Name cannot be equal to prefixed name=%q", cr.GetVTSelectName())
+		}
+		if vms.HPA != nil {
+			if err := vms.HPA.Validate(); err != nil {
+				return err
+			}
+		}
+	}
+	if cr.Spec.Insert != nil {
+		vti := cr.Spec.Insert
+		if vti.ServiceSpec != nil && vti.ServiceSpec.Name == cr.GetVTInsertName() {
+			return fmt.Errorf(".serviceSpec.Name cannot be equal to prefixed name=%q", cr.GetVTInsertName())
+		}
+		if vti.HPA != nil {
+			if err := vti.HPA.Validate(); err != nil {
+				return err
+			}
+		}
+	}
+	if cr.Spec.Storage != nil {
+		vts := cr.Spec.Storage
+		if vts.ServiceSpec != nil && vts.ServiceSpec.Name == cr.GetVTStorageName() {
+			return fmt.Errorf(".serviceSpec.Name cannot be equal to prefixed name=%q", cr.GetVTStorageName())
+		}
+	}
+	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
+}
+
+// VTSelectSelectorLabels returns selector labels for select cluster component
+func (cr *VTCluster) VTSelectSelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vtselect",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// VTSelectPodLabels returns pod labels for select cluster component
+func (cr *VTCluster) VTSelectPodLabels() map[string]string {
+	selectorLabels := cr.VTSelectSelectorLabels()
+	if cr.Spec.Select == nil || cr.Spec.Select.PodMetadata == nil {
+		return selectorLabels
+	}
+	return labels.Merge(cr.Spec.Select.PodMetadata.Labels, selectorLabels)
+}
+
+// VTInsertSelectorLabels returns selector labels for insert cluster component
+func (cr *VTCluster) VTInsertSelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vtinsert",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// VTInsertPodLabels returns pod labels for vtinsert cluster component
+func (cr *VTCluster) VTInsertPodLabels() map[string]string {
+	selectorLabels := cr.VTInsertSelectorLabels()
+	if cr.Spec.Insert == nil || cr.Spec.Insert.PodMetadata == nil {
+		return selectorLabels
+	}
+	return labels.Merge(cr.Spec.Insert.PodMetadata.Labels, selectorLabels)
+}
+
+// VTStorageSelectorLabels  returns pod labels for vtstorage cluster component
+func (cr *VTCluster) VTStorageSelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vtstorage",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// VTStoragePodLabels returns pod labels for the vmstorage cluster component
+func (cr *VTCluster) VTStoragePodLabels() map[string]string {
+	selectorLabels := cr.VTStorageSelectorLabels()
+	if cr.Spec.Storage == nil || cr.Spec.Storage.PodMetadata == nil {
+		return selectorLabels
+	}
+	return labels.Merge(cr.Spec.Storage.PodMetadata.Labels, selectorLabels)
+}
+
+// AvailableStorageNodeIDs returns ids of the storage nodes for the provided component
+func (cr *VTCluster) AvailableStorageNodeIDs(requestsType string) []int32 {
+	var result []int32
+	if cr.Spec.Storage == nil || cr.Spec.Storage.ReplicaCount == nil {
+		return result
+	}
+	maintenanceNodes := make(map[int32]struct{})
+	switch requestsType {
+	case "select":
+		for _, i := range cr.Spec.Storage.MaintenanceSelectNodeIDs {
+			maintenanceNodes[i] = struct{}{}
+		}
+	case "insert":
+		for _, i := range cr.Spec.Storage.MaintenanceInsertNodeIDs {
+			maintenanceNodes[i] = struct{}{}
+		}
+	default:
+		panic("BUG unsupported requestsType: " + requestsType)
+	}
+	for i := int32(0); i < *cr.Spec.Storage.ReplicaCount; i++ {
+		if _, ok := maintenanceNodes[i]; ok {
+			continue
+		}
+		result = append(result, i)
+	}
+	return result
+}
+
+var globalVTClusterLabels = map[string]string{"app.kubernetes.io/part-of": "vtcluster"}
+
+// FinalLabels adds cluster labels to the base labels and filters by prefix if needed
+func (cr *VTCluster) FinalLabels(selectorLabels map[string]string) map[string]string {
+	baseLabels := labels.Merge(globalVTClusterLabels, selectorLabels)
+	if cr.Spec.ManagedMetadata == nil {
+		// fast path
+		return baseLabels
+	}
+	return labels.Merge(cr.Spec.ManagedMetadata.Labels, baseLabels)
+}
+
+// VTSelectPodAnnotations returns pod annotations for select cluster component
+func (cr *VTCluster) VTSelectPodAnnotations() map[string]string {
+	if cr.Spec.Select == nil || cr.Spec.Select.PodMetadata == nil {
+		return make(map[string]string)
+	}
+	return cr.Spec.Select.PodMetadata.Annotations
+}
+
+// VTInsertPodAnnotations returns pod annotations for insert cluster component
+func (cr *VTCluster) VTInsertPodAnnotations() map[string]string {
+	if cr.Spec.Insert == nil || cr.Spec.Insert.PodMetadata == nil {
+		return make(map[string]string)
+	}
+	return cr.Spec.Insert.PodMetadata.Annotations
+}
+
+// VTStoragePodAnnotations returns pod annotations for storage cluster component
+func (cr *VTCluster) VTStoragePodAnnotations() map[string]string {
+	if cr.Spec.Storage == nil || cr.Spec.Storage.PodMetadata == nil {
+		return make(map[string]string)
+	}
+	return cr.Spec.Storage.PodMetadata.Annotations
+}
+
+// FinalAnnotations returns global annotations to be applied by objects generate for vtcluster
+func (cr *VTCluster) 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 *VTCluster) AnnotationsFiltered() map[string]string {
+	return cr.FinalAnnotations()
+}
+
+// LastAppliedSpecAsPatch return last applied cluster spec as patch annotation
+func (cr *VTCluster) 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 *VTCluster) HasSpecChanges() (bool, error) {
+	return vmv1beta1.HasStateChanges(cr.ObjectMeta, cr.Spec)
+}
+
+func (cr *VTCluster) Paused() bool {
+	return cr.Spec.Paused
+}
+
+// GetServiceAccountName returns service account name for all vtcluster components
+func (cr *VTCluster) GetServiceAccountName() string {
+	if cr.Spec.ServiceAccountName == "" {
+		return cr.PrefixedName()
+	}
+	return cr.Spec.ServiceAccountName
+}
+
+func (cr *VTCluster) IsOwnsServiceAccount() bool {
+	return cr.Spec.ServiceAccountName == ""
+}
+
+// PrefixedName format name of the component with hard-coded prefix
+func (cr *VTCluster) PrefixedName() string {
+	return fmt.Sprintf("vtcluster-%s", cr.Name)
+}
+
+// SelectorLabels defines labels for objects generated used by all cluster components
+func (cr *VTCluster) SelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vtcluster",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// AsURL implements stub for interface.
+func (cr *VTCluster) AsURL() string {
+	return "unknown"
+}
+
+// SelectURL returns url to access VTSelect component
+func (cr *VTCluster) SelectURL() string {
+	if cr.Spec.Select == nil {
+		return ""
+	}
+	port := cr.Spec.Select.Port
+	if port == "" {
+		port = "10471"
+	}
+	if cr.Spec.Select.ServiceSpec != nil && cr.Spec.Select.ServiceSpec.UseAsDefault {
+		for _, svcPort := range cr.Spec.Select.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.Select.ExtraArgs), cr.GetVTSelectName(), cr.Namespace, port)
+}
+
+// InsertURL returns url to access VTInsert component
+func (cr *VTCluster) InsertURL() string {
+	if cr.Spec.Insert == nil {
+		return ""
+	}
+	port := cr.Spec.Insert.Port
+	if port == "" {
+		port = "10481"
+	}
+	if cr.Spec.Insert.ServiceSpec != nil && cr.Spec.Insert.ServiceSpec.UseAsDefault {
+		for _, svcPort := range cr.Spec.Insert.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.Insert.ExtraArgs), cr.GetVTInsertName(), cr.Namespace, port)
+}
+
+// StorageURL returns url to access VTStorage component
+func (cr *VTCluster) StorageURL() string {
+	if cr.Spec.Storage == nil {
+		return ""
+	}
+	port := cr.Spec.Storage.Port
+	if port == "" {
+		port = "10491"
+	}
+	if cr.Spec.Storage.ServiceSpec != nil && cr.Spec.Storage.ServiceSpec.UseAsDefault {
+		for _, svcPort := range cr.Spec.Storage.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.Storage.ExtraArgs), cr.GetVTStorageName(), cr.Namespace, port)
+}
+
+// +kubebuilder:object:root=true
+
+// VTClusterList contains a list of VTCluster
+type VTClusterList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []VTCluster `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&VTCluster{}, &VTClusterList{})
+}
diff --git a/api/operator/v1/vtsingle_types.go b/api/operator/v1/vtsingle_types.go
new file mode 100644
index 00000000..57699622
--- /dev/null
+++ b/api/operator/v1/vtsingle_types.go
@@ -0,0 +1,360 @@
+/*
+
+
+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"
+
+	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"
+)
+
+// VTSingleSpec defines the desired state of VTSingle
+// +k8s:openapi-gen=true
+type VTSingleSpec struct {
+	// ParsingError contents error with context if operator was failed to parse json object from kubernetes api server
+	ParsingError string `json:"-" yaml:"-"`
+
+	// PodMetadata configures Labels and Annotations which are propagated to the VTSingle pods.
+	// +optional
+	PodMetadata *vmv1beta1.EmbeddedObjectMetadata `json:"podMetadata,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"`
+
+	vmv1beta1.CommonDefaultableParams           `json:",inline,omitempty"`
+	vmv1beta1.CommonApplicationDeploymentParams `json:",inline,omitempty"`
+
+	// LogLevel for VictoriaTraces to be configured with.
+	// +optional
+	// +kubebuilder:validation:Enum=INFO;WARN;ERROR;FATAL;PANIC
+	LogLevel string `json:"logLevel,omitempty"`
+	// LogFormat for VTSingle to be configured with.
+	// +optional
+	// +kubebuilder:validation:Enum=default;json
+	LogFormat string `json:"logFormat,omitempty"`
+	// StorageDataPath disables spec.storage option and overrides arg for victoria-traces binary --storageDataPath,
+	// its users responsibility to mount proper device into given path.
+	// +optional
+	StorageDataPath string `json:"storageDataPath,omitempty"`
+	// Storage is the definition of how storage will be used by the VTSingle
+	// by default it`s empty dir
+	// +optional
+	Storage *corev1.PersistentVolumeClaimSpec `json:"storage,omitempty"`
+	// StorageMeta defines annotations and labels attached to PVC for given vtsingle CR
+	// +optional
+	StorageMetadata vmv1beta1.EmbeddedObjectMetadata `json:"storageMetadata,omitempty"`
+	// RetentionPeriod for the stored traces
+	// https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces
+	// +optional
+	// +kubebuilder:validation:Pattern:="^[0-9]+(h|d|w|y)?$"
+	RetentionPeriod string `json:"retentionPeriod,omitempty"`
+	// RetentionMaxDiskSpaceUsageBytes for the stored traces
+	// VictoriaTraces keeps at least two last days of data in order to guarantee that the traces 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/victoriatraces/#configure-and-run-victoriatraces
+	// +optional
+	RetentionMaxDiskSpaceUsageBytes vmv1beta1.BytesString `json:"retentionMaxDiskSpaceUsageBytes,omitempty"`
+	// FutureRetention for the stored traces
+	// Log entries with timestamps bigger than now+futureRetention are rejected during data ingestion;
+	// see https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces
+	// +optional
+	// +kubebuilder:validation:Pattern:="^[0-9]+(h|d|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/victoriatraces/#configure-and-run-victoriatraces
+	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/victoriatraces/#configure-and-run-victoriatraces
+	LogIngestedRows bool `json:"logIngestedRows,omitempty"`
+	// ServiceSpec that will be added to vtsingle service spec
+	// +optional
+	ServiceSpec *vmv1beta1.AdditionalServiceSpec `json:"serviceSpec,omitempty"`
+	// ServiceScrapeSpec that will be added to vtsingle VMServiceScrape spec
+	// +optional
+	ServiceScrapeSpec *vmv1beta1.VMServiceScrapeSpec `json:"serviceScrapeSpec,omitempty"`
+	// LivenessProbe that will be added to VTSingle pod
+	*vmv1beta1.EmbeddedProbes `json:",inline"`
+
+	// ServiceAccountName is the name of the ServiceAccount to use to run the pods
+	// +optional
+	ServiceAccountName string `json:"serviceAccountName,omitempty"`
+}
+
+// VTSingleStatus defines the observed state of VTSingle
+type VTSingleStatus struct {
+	vmv1beta1.StatusMetadata `json:",inline"`
+}
+
+// GetStatusMetadata returns metadata for object status
+func (cr *VTSingleStatus) GetStatusMetadata() *vmv1beta1.StatusMetadata {
+	return &cr.StatusMetadata
+}
+
+// VTSingle is fast, cost-effective and scalable traces database.
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +operator-sdk:gen-csv:customresourcedefinitions.displayName="VTSingle App"
+// +operator-sdk:gen-csv:customresourcedefinitions.resources="Deployment,apps"
+// +operator-sdk:gen-csv:customresourcedefinitions.resources="Service,v1"
+// +operator-sdk:gen-csv:customresourcedefinitions.resources="Secret,v1"
+// +genclient
+// +k8s:openapi-gen=true
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:path=vtsingles,scope=Namespaced
+// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status",description="Current status of traces instance update process"
+// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
+// VTSingle is the Schema for the API
+type VTSingle struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec VTSingleSpec `json:"spec,omitempty"`
+	// ParsedLastAppliedSpec contains last-applied configuration spec
+	ParsedLastAppliedSpec *VTSingleSpec `json:"-" yaml:"-"`
+
+	Status VTSingleStatus `json:"status,omitempty"`
+}
+
+// GetStatus implements reconcile.ObjectWithDeepCopyAndStatus interface
+func (cr *VTSingle) GetStatus() *VTSingleStatus {
+	return &cr.Status
+}
+
+// DefaultStatusFields implements reconcile.ObjectWithDeepCopyAndStatus interface
+func (cr *VTSingle) DefaultStatusFields(vs *VTSingleStatus) {
+}
+
+// +kubebuilder:object:root=true
+
+// VTSingleList contains a list of VTSingle
+type VTSingleList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []VTSingle `json:"items"`
+}
+
+func (r *VTSingle) PodAnnotations() map[string]string {
+	annotations := map[string]string{}
+	if r.Spec.PodMetadata != nil {
+		for annotation, value := range r.Spec.PodMetadata.Annotations {
+			annotations[annotation] = value
+		}
+	}
+	return annotations
+}
+
+// AsOwner returns owner references with current object as owner
+func (r *VTSingle) AsOwner() []metav1.OwnerReference {
+	return []metav1.OwnerReference{
+		{
+			APIVersion:         r.APIVersion,
+			Kind:               r.Kind,
+			Name:               r.Name,
+			UID:                r.UID,
+			Controller:         ptr.To(true),
+			BlockOwnerDeletion: ptr.To(true),
+		},
+	}
+}
+
+// SetLastSpec implements objectWithLastAppliedState interface
+func (cr *VTSingle) SetLastSpec(prevSpec VTSingleSpec) {
+	cr.ParsedLastAppliedSpec = &prevSpec
+}
+
+// UnmarshalJSON implements json.Unmarshaler interface
+func (cr *VTSingle) UnmarshalJSON(src []byte) error {
+	type pcr VTSingle
+	if err := json.Unmarshal(src, (*pcr)(cr)); err != nil {
+		return err
+	}
+	if err := vmv1beta1.ParseLastAppliedStateTo(cr); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// UnmarshalJSON implements json.Unmarshaler interface
+func (cr *VTSingleSpec) UnmarshalJSON(src []byte) error {
+	type pcr VTSingleSpec
+	if err := json.Unmarshal(src, (*pcr)(cr)); err != nil {
+		cr.ParsingError = fmt.Sprintf("cannot parse vtsingle spec: %s, err: %s", string(src), err)
+		return nil
+	}
+	return nil
+}
+
+// Probe implements build.probeCRD interface
+func (cr *VTSingle) Probe() *vmv1beta1.EmbeddedProbes {
+	return cr.Spec.EmbeddedProbes
+}
+
+// ProbePath implements build.probeCRD interface
+func (cr *VTSingle) ProbePath() string {
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.Spec.ExtraArgs, healthPath)
+}
+
+// ProbeScheme implements build.probeCRD interface
+func (cr *VTSingle) ProbeScheme() string {
+	return strings.ToUpper(vmv1beta1.HTTPProtoFromFlags(cr.Spec.ExtraArgs))
+}
+
+// ProbePort implements build.probeCRD interface
+func (cr *VTSingle) ProbePort() string {
+	return cr.Spec.Port
+}
+
+// ProbeNeedLiveness implements build.probeCRD interface
+func (cr *VTSingle) ProbeNeedLiveness() bool {
+	return false
+}
+
+// AnnotationsFiltered returns global annotations to be applied for created objects
+func (cr *VTSingle) AnnotationsFiltered() map[string]string {
+	if cr.Spec.ManagedMetadata == nil {
+		return nil
+	}
+	dst := make(map[string]string, len(cr.Spec.ManagedMetadata.Annotations))
+	for k, v := range cr.Spec.ManagedMetadata.Annotations {
+		dst[k] = v
+	}
+	return dst
+}
+
+// SelectorLabels returns unique labels for object
+func (cr *VTSingle) SelectorLabels() map[string]string {
+	return map[string]string{
+		"app.kubernetes.io/name":      "vtsingle",
+		"app.kubernetes.io/instance":  cr.Name,
+		"app.kubernetes.io/component": "monitoring",
+		"managed-by":                  "vm-operator",
+	}
+}
+
+// PodLabels returns labels attached to the podMetadata
+func (cr *VTSingle) PodLabels() map[string]string {
+	lbls := cr.SelectorLabels()
+	if cr.Spec.PodMetadata == nil {
+		return lbls
+	}
+	return labels.Merge(cr.Spec.PodMetadata.Labels, lbls)
+}
+
+// AllLabels returns combination of selector and managed labels
+func (cr *VTSingle) AllLabels() map[string]string {
+	selectorLabels := cr.SelectorLabels()
+	// fast path
+	if cr.Spec.ManagedMetadata == nil {
+		return selectorLabels
+	}
+
+	return labels.Merge(selectorLabels, cr.Spec.ManagedMetadata.Labels)
+}
+
+// PrefixedName format name of the component with hard-coded prefix
+func (cr *VTSingle) PrefixedName() string {
+	return fmt.Sprintf("vtsingle-%s", cr.Name)
+}
+
+// GetMetricPath returns prefixed path for metric requests
+func (cr *VTSingle) GetMetricPath() string {
+	return vmv1beta1.BuildPathWithPrefixFlag(cr.Spec.ExtraArgs, metricPath)
+}
+
+// Validate checks if spec is correct
+func (cr *VTSingle) Validate() error {
+	if vmv1beta1.MustSkipCRValidation(cr) {
+		return nil
+	}
+	if cr.Spec.ServiceSpec != nil && cr.Spec.ServiceSpec.Name == cr.PrefixedName() {
+		return fmt.Errorf("spec.serviceSpec.Name cannot be equal to prefixed name=%q", cr.PrefixedName())
+	}
+	return nil
+}
+
+// GetExtraArgs returns additionally configured command-line arguments
+func (cr *VTSingle) GetExtraArgs() map[string]string {
+	return cr.Spec.ExtraArgs
+}
+
+// GetServiceScrape returns overrides for serviceScrape builder
+func (cr *VTSingle) GetServiceScrape() *vmv1beta1.VMServiceScrapeSpec {
+	return cr.Spec.ServiceScrapeSpec
+}
+
+// GetServiceAccountName returns service account name for components
+func (cr *VTSingle) GetServiceAccountName() string {
+	if cr.Spec.ServiceAccountName == "" {
+		return cr.PrefixedName()
+	}
+	return cr.Spec.ServiceAccountName
+}
+
+// IsOwnsServiceAccount checks if ServiceAccountName is set explicitly
+func (cr *VTSingle) IsOwnsServiceAccount() bool {
+	return cr.Spec.ServiceAccountName == ""
+}
+
+// AsURL returns URL for components access
+func (cr *VTSingle) AsURL() string {
+	port := cr.Spec.Port
+	if port == "" {
+		port = "10428"
+	}
+	if cr.Spec.ServiceSpec != nil && cr.Spec.ServiceSpec.UseAsDefault {
+		for _, svcPort := range cr.Spec.ServiceSpec.Spec.Ports {
+			if svcPort.Name == "http" {
+				port = fmt.Sprintf("%d", svcPort.Port)
+				break
+			}
+		}
+	}
+	return fmt.Sprintf("%s://%s.%s.svc:%s", vmv1beta1.HTTPProtoFromFlags(cr.Spec.ExtraArgs), cr.PrefixedName(), cr.Namespace, port)
+}
+
+// LastAppliedSpecAsPatch return last applied vtsingle spec as patch annotation
+func (cr *VTSingle) LastAppliedSpecAsPatch() (client.Patch, error) {
+	return vmv1beta1.LastAppliedChangesAsPatch(cr.ObjectMeta, cr.Spec)
+}
+
+// HasSpecChanges compares vtsingle spec with last applied vtsingle spec stored in annotation
+func (cr *VTSingle) HasSpecChanges() (bool, error) {
+	return vmv1beta1.HasStateChanges(cr.ObjectMeta, cr.Spec)
+}
+
+func (cr *VTSingle) Paused() bool {
+	return cr.Spec.Paused
+}
+
+// GetAdditionalService returns AdditionalServiceSpec settings
+func (cr *VTSingle) GetAdditionalService() *vmv1beta1.AdditionalServiceSpec {
+	return cr.Spec.ServiceSpec
+}
+
+func init() {
+	SchemeBuilder.Register(&VTSingle{}, &VTSingleList{})
+}
diff --git a/api/operator/v1/zz_generated.deepcopy.go b/api/operator/v1/zz_generated.deepcopy.go
index 4c9a7faa..f77ff8f3 100644
--- a/api/operator/v1/zz_generated.deepcopy.go
+++ b/api/operator/v1/zz_generated.deepcopy.go
@@ -1225,3 +1225,445 @@ func (in *VMAnomalyWritersSpec) DeepCopy() *VMAnomalyWritersSpec {
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VTCluster) DeepCopyInto(out *VTCluster) {
+	*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(VTClusterSpec)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VTCluster.
+func (in *VTCluster) DeepCopy() *VTCluster {
+	if in == nil {
+		return nil
+	}
+	out := new(VTCluster)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *VTCluster) 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 *VTClusterList) DeepCopyInto(out *VTClusterList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]VTCluster, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VTClusterList.
+func (in *VTClusterList) DeepCopy() *VTClusterList {
+	if in == nil {
+		return nil
+	}
+	out := new(VTClusterList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *VTClusterList) 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 *VTClusterSpec) DeepCopyInto(out *VTClusterSpec) {
+	*out = *in
+	if in.ImagePullSecrets != nil {
+		in, out := &in.ImagePullSecrets, &out.ImagePullSecrets
+		*out = make([]corev1.LocalObjectReference, len(*in))
+		copy(*out, *in)
+	}
+	if in.Insert != nil {
+		in, out := &in.Insert, &out.Insert
+		*out = new(VTInsert)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Select != nil {
+		in, out := &in.Select, &out.Select
+		*out = new(VTSelect)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Storage != nil {
+		in, out := &in.Storage, &out.Storage
+		*out = new(VTStorage)
+		(*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 VTClusterSpec.
+func (in *VTClusterSpec) DeepCopy() *VTClusterSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(VTClusterSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VTClusterStatus) DeepCopyInto(out *VTClusterStatus) {
+	*out = *in
+	in.StatusMetadata.DeepCopyInto(&out.StatusMetadata)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VTClusterStatus.
+func (in *VTClusterStatus) DeepCopy() *VTClusterStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(VTClusterStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VTInsert) DeepCopyInto(out *VTInsert) {
+	*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 VTInsert.
+func (in *VTInsert) DeepCopy() *VTInsert {
+	if in == nil {
+		return nil
+	}
+	out := new(VTInsert)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VTSelect) DeepCopyInto(out *VTSelect) {
+	*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 VTSelect.
+func (in *VTSelect) DeepCopy() *VTSelect {
+	if in == nil {
+		return nil
+	}
+	out := new(VTSelect)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VTSingle) DeepCopyInto(out *VTSingle) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	if in.ParsedLastAppliedSpec != nil {
+		in, out := &in.ParsedLastAppliedSpec, &out.ParsedLastAppliedSpec
+		*out = new(VTSingleSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VTSingle.
+func (in *VTSingle) DeepCopy() *VTSingle {
+	if in == nil {
+		return nil
+	}
+	out := new(VTSingle)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *VTSingle) 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 *VTSingleList) DeepCopyInto(out *VTSingleList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]VTSingle, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VTSingleList.
+func (in *VTSingleList) DeepCopy() *VTSingleList {
+	if in == nil {
+		return nil
+	}
+	out := new(VTSingleList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *VTSingleList) 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 *VTSingleSpec) DeepCopyInto(out *VTSingleSpec) {
+	*out = *in
+	if in.PodMetadata != nil {
+		in, out := &in.PodMetadata, &out.PodMetadata
+		*out = new(v1beta1.EmbeddedObjectMetadata)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ManagedMetadata != nil {
+		in, out := &in.ManagedMetadata, &out.ManagedMetadata
+		*out = new(v1beta1.ManagedObjectsMetadata)
+		(*in).DeepCopyInto(*out)
+	}
+	in.CommonDefaultableParams.DeepCopyInto(&out.CommonDefaultableParams)
+	in.CommonApplicationDeploymentParams.DeepCopyInto(&out.CommonApplicationDeploymentParams)
+	if in.Storage != nil {
+		in, out := &in.Storage, &out.Storage
+		*out = new(corev1.PersistentVolumeClaimSpec)
+		(*in).DeepCopyInto(*out)
+	}
+	in.StorageMetadata.DeepCopyInto(&out.StorageMetadata)
+	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.EmbeddedProbes != nil {
+		in, out := &in.EmbeddedProbes, &out.EmbeddedProbes
+		*out = new(v1beta1.EmbeddedProbes)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VTSingleSpec.
+func (in *VTSingleSpec) DeepCopy() *VTSingleSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(VTSingleSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VTSingleStatus) DeepCopyInto(out *VTSingleStatus) {
+	*out = *in
+	in.StatusMetadata.DeepCopyInto(&out.StatusMetadata)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VTSingleStatus.
+func (in *VTSingleStatus) DeepCopy() *VTSingleStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(VTSingleStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VTStorage) DeepCopyInto(out *VTStorage) {
+	*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.PersistentVolumeClaimRetentionPolicy != nil {
+		in, out := &in.PersistentVolumeClaimRetentionPolicy, &out.PersistentVolumeClaimRetentionPolicy
+		*out = new(appsv1.StatefulSetPersistentVolumeClaimRetentionPolicy)
+		**out = **in
+	}
+	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)
+	if in.RollingUpdateStrategyBehavior != nil {
+		in, out := &in.RollingUpdateStrategyBehavior, &out.RollingUpdateStrategyBehavior
+		*out = new(v1beta1.StatefulSetUpdateStrategyBehavior)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VTStorage.
+func (in *VTStorage) DeepCopy() *VTStorage {
+	if in == nil {
+		return nil
+	}
+	out := new(VTStorage)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/api/operator/v1beta1/vmcluster_types.go b/api/operator/v1beta1/vmcluster_types.go
index 51c4353d..536c0258 100644
--- a/api/operator/v1beta1/vmcluster_types.go
+++ b/api/operator/v1beta1/vmcluster_types.go
@@ -921,7 +921,8 @@ func (cr *VMCluster) AsURL() string {
 	return "unknown"
 }
 
-func (cr *VMCluster) VMSelectURL() string {
+// SelectURL returns url to access VMSelect component
+func (cr *VMCluster) SelectURL() string {
 	if cr.Spec.VMSelect == nil {
 		return ""
 	}
@@ -939,7 +940,8 @@ func (cr *VMCluster) VMSelectURL() string {
 	return fmt.Sprintf("%s://%s.%s.svc:%s", HTTPProtoFromFlags(cr.Spec.VMSelect.ExtraArgs), cr.GetVMSelectName(), cr.Namespace, port)
 }
 
-func (cr *VMCluster) VMInsertURL() string {
+// InsertURL returns url to access VMInsert component
+func (cr *VMCluster) InsertURL() string {
 	if cr.Spec.VMInsert == nil {
 		return ""
 	}
@@ -957,7 +959,8 @@ func (cr *VMCluster) VMInsertURL() string {
 	return fmt.Sprintf("%s://%s.%s.svc:%s", HTTPProtoFromFlags(cr.Spec.VMInsert.ExtraArgs), cr.GetVMInsertName(), cr.Namespace, port)
 }
 
-func (cr *VMCluster) VMStorageURL() string {
+// StorageURL returns url to access VMStorage component
+func (cr *VMCluster) StorageURL() string {
 	if cr.Spec.VMStorage == nil {
 		return ""
 	}
diff --git a/api/operator/v1beta1/vmuser_types.go b/api/operator/v1beta1/vmuser_types.go
index 85e15f06..9c1d23bf 100644
--- a/api/operator/v1beta1/vmuser_types.go
+++ b/api/operator/v1beta1/vmuser_types.go
@@ -88,8 +88,8 @@ type VMUserIPFilters struct {
 // CRDRef describe CRD target reference.
 type CRDRef struct {
 	// Kind one of:
-	// VMAgent,VMAlert, VMSingle, VMCluster/vmselect, VMCluster/vmstorage,VMCluster/vminsert,VMAlertManager, VLSingle, VLCluster/vlinsert, VLCluster/vlselect, VLCluster/vlstorage and VLAgent
-	// +kubebuilder:validation:Enum=VMAgent;VMAlert;VMSingle;VLogs;VMAlertManager;VMAlertmanager;VMCluster/vmselect;VMCluster/vmstorage;VMCluster/vminsert;VLSingle;VLCluster/vlinsert;VLCluster/vlselect;VLCluster/vlstorage;VLAgent
+	// VMAgent,VMAlert, VMSingle, VMCluster/vmselect, VMCluster/vmstorage,VMCluster/vminsert,VMAlertManager, VLSingle, VLCluster/vlinsert, VLCluster/vlselect, VLCluster/vlstorage, VTSingle, VTCluster/vtinsert, VTCluster/vtselect, VTCluster/vtstorage and VLAgent
+	// +kubebuilder:validation:Enum=VMAgent;VMAlert;VMSingle;VLogs;VMAlertManager;VMAlertmanager;VMCluster/vmselect;VMCluster/vmstorage;VMCluster/vminsert;VLSingle;VLCluster/vlinsert;VLCluster/vlselect;VLCluster/vlstorage;VLAgent;VTCluster/vtinsert;VTCluster/vtselect;VTCluster/vtstorage;VTSingle
 	Kind string `json:"kind"`
 	// Name target CRD object name
 	Name string `json:"name"`
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index efed72b6..f85fd86c 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -21,6 +21,8 @@ resources:
 - bases/operator.victoriametrics.com_vlogs.yaml
 - bases/operator.victoriametrics.com_vlsingles.yaml
 - bases/operator.victoriametrics.com_vlclusters.yaml
+- bases/operator.victoriametrics.com_vtsingles.yaml
+- bases/operator.victoriametrics.com_vtclusters.yaml
 - bases/operator.victoriametrics.com_vmanomalies.yaml
 patches:
 # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
@@ -69,6 +71,14 @@ patches:
   target:
     kind: CustomResourceDefinition
     name: vlclusters.operator.victoriametrics.com
+- path: patches/operator.victoriametrics.com_vtsingles.yaml
+  target:
+    kind: CustomResourceDefinition
+    name: vtsingles.operator.victoriametrics.com
+- path: patches/operator.victoriametrics.com_vtclusters.yaml
+  target:
+    kind: CustomResourceDefinition
+    name: vtclusters.operator.victoriametrics.com
 - path: patches/operator.victoriametrics.com_vlagents.yaml
   target:
     kind: CustomResourceDefinition
diff --git a/config/crd/overlay/crd.yaml b/config/crd/overlay/crd.yaml
index b5abde0e..3cec3aff 100644
--- a/config/crd/overlay/crd.yaml
+++ b/config/crd/overlay/crd.yaml
@@ -38953,7 +38953,7 @@ spec:
                         kind:
                           description: |-
                             Kind one of:
-                            VMAgent,VMAlert, VMSingle, VMCluster/vmselect, VMCluster/vmstorage,VMCluster/vminsert,VMAlertManager, VLSingle, VLCluster/vlinsert, VLCluster/vlselect, VLCluster/vlstorage and VLAgent
+                            VMAgent,VMAlert, VMSingle, VMCluster/vmselect, VMCluster/vmstorage,VMCluster/vminsert,VMAlertManager, VLSingle, VLCluster/vlinsert, VLCluster/vlselect, VLCluster/vlstorage, VTSingle, VTCluster/vtinsert, VTCluster/vtselect, VTCluster/vtstorage and VLAgent
                           enum:
                           - VMAgent
                           - VMAlert
@@ -38969,6 +38969,10 @@ spec:
                           - VLCluster/vlselect
                           - VLCluster/vlstorage
                           - VLAgent
+                          - VTCluster/vtinsert
+                          - VTCluster/vtselect
+                          - VTCluster/vtstorage
+                          - VTSingle
                           type: string
                         name:
                           description: Name target CRD object name
@@ -39397,3 +39401,3710 @@ spec:
     storage: true
     subresources:
       status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.18.0
+  name: vtclusters.operator.victoriametrics.com
+spec:
+  group: operator.victoriametrics.com
+  names:
+    kind: VTCluster
+    listKind: VTClusterList
+    plural: vtclusters
+    singular: vtcluster
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: replicas of VTInsert
+      jsonPath: .spec.vtinsert.replicaCount
+      name: Insert Count
+      type: string
+    - description: replicas of VTStorage
+      jsonPath: .spec.vtstorage.replicaCount
+      name: Storage Count
+      type: string
+    - description: replicas of VTSelect
+      jsonPath: .spec.vtselect.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: VTCluster is fast, cost-effective and scalable traces database.
+        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: VTClusterSpec defines the desired state of VTCluster
+            properties:
+              clusterDomainName:
+                description: |-
+                  ClusterDomainName defines domain name suffix for in-cluster dns addresses
+                  aka .cluster.local
+                  used by vtinsert and vtselect to build vtstorage 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
+              insert:
+                description: VTInsert defines vtinsert component configuration at
+                  victoria-traces 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 VTSelect to be configured with.
+                      default or json
+                    enum:
+                    - default
+                    - json
+                    type: string
+                  logLevel:
+                    description: LogLevel for VTSelect 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 VTSelect 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 vtselect
+                      VMServiceScrape spec
+                    required:
+                    - endpoints
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  serviceSpec:
+                    description: ServiceSpec that will be added to vtselect 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
+              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 vtinsert and vtselect 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
+              select:
+                description: VTSelect defines vtselect component configuration at
+                  victoria-traces 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 VTSelect to be configured with.
+                      default or json
+                    enum:
+                    - default
+                    - json
+                    type: string
+                  logLevel:
+                    description: LogLevel for VTSelect 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 VTSelect 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 vtselect
+                      VMServiceScrape spec
+                    required:
+                    - endpoints
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  serviceSpec:
+                    description: ServiceSpec that will be added to vtselect 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
+              serviceAccountName:
+                description: |-
+                  ServiceAccountName is the name of the ServiceAccount to use to run the
+                  VTSelect, VTInsert and VTStorage Pods.
+                type: string
+              storage:
+                description: VTStorage defines vtstorage component configuration at
+                  victoria-traces 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 traces
+                      Log entries with timestamps bigger than now+futureRetention are rejected during data ingestion
+                      see https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces
+                    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 VTStorage 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/victoriatraces/#configure-and-run-victoriatraces
+                    type: boolean
+                  logLevel:
+                    description: LogLevel for VTStorage 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/victoriatraces/#configure-and-run-victoriatraces
+                    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
+                  persistentVolumeClaimRetentionPolicy:
+                    description: PersistentVolumeClaimRetentionPolicy allows configuration
+                      of PVC retention policy
+                    properties:
+                      whenDeleted:
+                        description: |-
+                          WhenDeleted specifies what happens to PVCs created from StatefulSet
+                          VolumeClaimTemplates when the StatefulSet is deleted. The default policy
+                          of `Retain` causes PVCs to not be affected by StatefulSet deletion. The
+                          `Delete` policy causes those PVCs to be deleted.
+                        type: string
+                      whenScaled:
+                        description: |-
+                          WhenScaled specifies what happens to PVCs created from StatefulSet
+                          VolumeClaimTemplates when the StatefulSet is scaled down. The default
+                          policy of `Retain` causes PVCs to not be affected by a scaledown. The
+                          `Delete` policy causes the associated PVCs for any excess pods above
+                          the replica count to be deleted.
+                        type: string
+                    type: object
+                  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 VTStorage 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 traces
+                      VictoriaTraces keeps at least two last days of data in order to guarantee that the traces 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/victoriatraces/#configure-and-run-victoriatraces
+                    type: string
+                  retentionPeriod:
+                    description: |-
+                      RetentionPeriod for the stored traces
+                      https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces
+                    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
+                  rollingUpdateStrategyBehavior:
+                    description: |-
+                      RollingUpdateStrategyBehavior defines customized behavior for rolling updates.
+                      It applies if the RollingUpdateStrategy is set to OnDelete, which is the default.
+                    properties:
+                      maxUnavailable:
+                        anyOf:
+                        - type: integer
+                        - type: string
+                        description: |-
+                          MaxUnavailable defines the maximum number of pods that can be unavailable during the update.
+                          It can be specified as an absolute number (e.g. 2) or a percentage of the total pods (e.g. "50%").
+                          For example, if set to 100%, all pods will be upgraded at once, minimizing downtime when needed.
+                        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 vtselect
+                      VMServiceScrape spec
+                    required:
+                    - endpoints
+                    type: object
+                    x-kubernetes-preserve-unknown-fields: true
+                  serviceSpec:
+                    description: ServiceSpec that will be added to vtselect 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 VTStorage
+                    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 StatefulSets/Deployments.
+                        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
+              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
+            type: object
+          status:
+            description: VTClusterStatus defines the observed state of VTCluster
+            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.18.0
+  name: vtsingles.operator.victoriametrics.com
+spec:
+  group: operator.victoriametrics.com
+  names:
+    kind: VTSingle
+    listKind: VTSingleList
+    plural: vtsingles
+    singular: vtsingle
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: Current status of traces instance update process
+      jsonPath: .status.status
+      name: Status
+      type: string
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v1
+    schema:
+      openAPIV3Schema:
+        description: |-
+          VTSingle is fast, cost-effective and scalable traces database.
+          VTSingle is the Schema for the 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: VTSingleSpec defines the desired state of VTSingle
+            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
+              futureRetention:
+                description: |-
+                  FutureRetention for the stored traces
+                  Log entries with timestamps bigger than now+futureRetention are rejected during data ingestion;
+                  see https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces
+                pattern: ^[0-9]+(h|d|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 VTSingle to be configured with.
+                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/victoriatraces/#configure-and-run-victoriatraces
+                type: boolean
+              logLevel:
+                description: LogLevel for VictoriaTraces 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/victoriatraces/#configure-and-run-victoriatraces
+                type: boolean
+              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
+              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
+              podMetadata:
+                description: PodMetadata configures Labels and Annotations which are
+                  propagated to the VTSingle 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 traces
+                  VictoriaTraces keeps at least two last days of data in order to guarantee that the traces 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/victoriatraces/#configure-and-run-victoriatraces
+                type: string
+              retentionPeriod:
+                description: |-
+                  RetentionPeriod for the stored traces
+                  https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces
+                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
+              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
+              serviceAccountName:
+                description: ServiceAccountName is the name of the ServiceAccount
+                  to use to run the pods
+                type: string
+              serviceScrapeSpec:
+                description: ServiceScrapeSpec that will be added to vtsingle VMServiceScrape
+                  spec
+                required:
+                - endpoints
+                type: object
+                x-kubernetes-preserve-unknown-fields: true
+              serviceSpec:
+                description: ServiceSpec that will be added to vtsingle 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 is the definition of how storage will be used by the VTSingle
+                  by default it`s empty dir
+                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
+              storageDataPath:
+                description: |-
+                  StorageDataPath disables spec.storage option and overrides arg for victoria-traces binary --storageDataPath,
+                  its users responsibility to mount proper device into given path.
+                type: string
+              storageMetadata:
+                description: StorageMeta defines annotations and labels attached to
+                  PVC for given vtsingle CR
+                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
+              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
+          status:
+            description: VTSingleStatus defines the observed state of VTSingle
+            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: {}
diff --git a/config/crd/patches/operator.victoriametrics.com_vtclusters.yaml b/config/crd/patches/operator.victoriametrics.com_vtclusters.yaml
new file mode 100644
index 00000000..9d8404fc
--- /dev/null
+++ b/config/crd/patches/operator.victoriametrics.com_vtclusters.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/storage/properties/storage/properties/volumeClaimTemplate/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/storage/properties/volumeClaimTemplate/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/affinity/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/affinity/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/claimTemplates/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/claimTemplates/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/containers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/containers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/dnsConfig/items
+  value:
+    x-kubernetes-preserve-unknown-fields: true
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/extraEnvs/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/extraEnvs/items/properties/valueFrom
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/initContainers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/initContainers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/topologySpreadConstraints/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/topologySpreadConstraints/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/serviceSpec/properties/spec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/serviceSpec/properties/spec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/volumes/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/volumes/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/startupProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/startupProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/readinessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/readinessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/livenessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/livenessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/securityContext/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/securityContext/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/serviceScrapeSpec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/storage/properties/serviceScrapeSpec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/affinity/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/affinity/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/containers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/containers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/hpa/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/hpa/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/dnsConfig/items
+  value:
+    x-kubernetes-preserve-unknown-fields: true
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/extraEnvs/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/extraEnvs/items/properties/valueFrom
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/initContainers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/initContainers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/topologySpreadConstraints/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/topologySpreadConstraints/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/serviceSpec/properties/spec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/serviceSpec/properties/spec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/volumes/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/volumes/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/startupProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/startupProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/readinessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/readinessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/livenessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/livenessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/securityContext/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/securityContext/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/serviceScrapeSpec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/select/properties/serviceScrapeSpec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/affinity/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/affinity/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/containers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/containers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/hpa/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/hpa/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/dnsConfig/items
+  value:
+    x-kubernetes-preserve-unknown-fields: true
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/extraEnvs/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/extraEnvs/items/properties/valueFrom
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/initContainers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/initContainers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/topologySpreadConstraints/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/topologySpreadConstraints/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/serviceSpec/properties/spec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/serviceSpec/properties/spec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/volumes/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/volumes/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/startupProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/startupProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/readinessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/readinessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/livenessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/livenessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/securityContext/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/securityContext/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/serviceScrapeSpec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/insert/properties/serviceScrapeSpec/properties
diff --git a/config/crd/patches/operator.victoriametrics.com_vtsingles.yaml b/config/crd/patches/operator.victoriametrics.com_vtsingles.yaml
new file mode 100644
index 00000000..ec25fa68
--- /dev/null
+++ b/config/crd/patches/operator.victoriametrics.com_vtsingles.yaml
@@ -0,0 +1,64 @@
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/affinity/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/affinity/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/containers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/containers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/dnsConfig/items
+  value:
+    x-kubernetes-preserve-unknown-fields: true
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/extraEnvs/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/extraEnvs/items/properties/valueFrom
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/initContainers/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/initContainers/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/topologySpreadConstraints/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/topologySpreadConstraints/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/serviceSpec/properties/spec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/serviceSpec/properties/spec/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/volumes/items/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/volumes/items/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/startupProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/startupProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/readinessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/readinessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/livenessProbe/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/livenessProbe/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/securityContext/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/securityContext/properties
+- op: add
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/serviceScrapeSpec/x-kubernetes-preserve-unknown-fields
+  value: true
+- op: remove
+  path: /spec/versions/0/schema/openAPIV3Schema/properties/spec/properties/serviceScrapeSpec/properties
diff --git a/config/examples/vtcluster.yaml b/config/examples/vtcluster.yaml
new file mode 100644
index 00000000..dba9297b
--- /dev/null
+++ b/config/examples/vtcluster.yaml
@@ -0,0 +1,24 @@
+apiVersion: operator.victoriametrics.com/v1
+kind: VTCluster
+metadata:
+  labels:
+    app.kubernetes.io/name: victoriametrics-operator
+    app.kubernetes.io/managed-by: kustomize
+  name: example
+spec:
+  insert:
+    replicaCount: 1
+  select:
+    replicaCount: 1
+  storage:
+    retentionPeriod: "1y"
+    replicaCount: 2
+    storage:
+      volumeClaimTemplate:
+        spec:
+          resources:
+            requests:
+              storage: 50Gi
+  requestsLoadBalancer:
+    enabled: true
+    spec: {}
diff --git a/config/examples/vtcluster_minimum_downtime.yaml b/config/examples/vtcluster_minimum_downtime.yaml
new file mode 100644
index 00000000..e7d9fd1d
--- /dev/null
+++ b/config/examples/vtcluster_minimum_downtime.yaml
@@ -0,0 +1,26 @@
+apiVersion: operator.victoriametrics.com/v1
+kind: VTCluster
+metadata:
+  labels:
+    app.kubernetes.io/name: victoriametrics-operator
+    app.kubernetes.io/managed-by: kustomize
+  name: example
+spec:
+  insert:
+    replicaCount: 1
+  select:
+    replicaCount: 1
+  storage:
+    rollingUpdateStrategyBehavior:
+      maxUnavailable: 100%
+    retentionPeriod: "1y"
+    replicaCount: 8
+    storage:
+      volumeClaimTemplate:
+        spec:
+          resources:
+            requests:
+              storage: 50Gi
+  requestsLoadBalancer:
+    enabled: true
+    spec: {}
diff --git a/config/examples/vtsingle.yaml b/config/examples/vtsingle.yaml
new file mode 100644
index 00000000..664bf695
--- /dev/null
+++ b/config/examples/vtsingle.yaml
@@ -0,0 +1,14 @@
+apiVersion: operator.victoriametrics.com/v1
+kind: VTSingle
+metadata:
+  labels:
+    app.kubernetes.io/name: victoriametrics-operator
+    app.kubernetes.io/managed-by: kustomize
+  name: sample
+spec:
+  retentionPeriod: 1y
+  retentionMaxDiskSpaceUsageBytes: 20GB
+  storage:
+    resources:
+      requests:
+        storage: 50Gi
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 0aeb131b..e53bee70 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -144,6 +144,12 @@ rules:
   - vlclusters
   - vlclusters/finalizers
   - vlclusters/status
+  - vtsingles
+  - vtsingles/finalizers
+  - vtsingles/status
+  - vtclusters
+  - vtclusters/finalizers
+  - vtclusters/status
   - vmagents
   - vmagents/finalizers
   - vmagents/status
diff --git a/config/samples/operator_v1_vtcluster.yaml b/config/samples/operator_v1_vtcluster.yaml
new file mode 100644
index 00000000..882bec70
--- /dev/null
+++ b/config/samples/operator_v1_vtcluster.yaml
@@ -0,0 +1,18 @@
+apiVersion: operator.victoriametrics.com/v1
+kind: VTCluster
+metadata:
+  labels:
+    app.kubernetes.io/name: victoriametrics-operator
+    app.kubernetes.io/managed-by: kustomize
+  name: test
+spec:
+  insert:
+    replicaCount: 1
+  select:
+    replicaCount: 1
+  storage:
+    retentionPeriod: "1y"
+    replicaCount: 2
+  requestsLoadBalancer:
+    enabled: true
+    spec: {}
diff --git a/config/samples/operator_v1_vtsingle.yaml b/config/samples/operator_v1_vtsingle.yaml
new file mode 100644
index 00000000..b1b85b56
--- /dev/null
+++ b/config/samples/operator_v1_vtsingle.yaml
@@ -0,0 +1,13 @@
+apiVersion: operator.victoriametrics.com/v1
+kind: VTSingle
+metadata:
+  labels:
+    app.kubernetes.io/name: victoriametrics-operator
+    app.kubernetes.io/managed-by: kustomize
+  name: dev
+spec:
+  retentionPeriod: 1y
+  storage:
+    resources:
+      requests:
+        storage: 50Gi
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index a9760425..5e8e7509 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -16,7 +16,8 @@ aliases:
 * Dependency: [vmoperator](https://docs.victoriametrics.com/operator/): Updated default versions for VM apps to [v1.125.0](https://docs.victoriametrics.com/victoriametrics/changelog/#v11250) version
 * Dependency: [vmoperator](https://docs.victoriametrics.com/operator/): Updated default versions for VL apps to [v1.33.0](https://docs.victoriametrics.com/victorialogs/changelog/#v1330).
 
-* FEATURE: [vmuser](https://docs.victoriametrics.com/operator/resources/vmuser/): add `VLSingle`, `VLAgent`,`VLCluster/vlinsert`,`VLCluster/vlselect` and `VLCluster/vlstorage` to the `targetRef.crd.kind`.
+* FEATURE: [vmuser](https://docs.victoriametrics.com/operator/resources/vmuser/): add `VLSingle`, `VLAgent`,`VLCluster/vlinsert`,`VLCluster/vlselect`, `VLCluster/vlstorage`, `VTSingle`, `VTCluster/vtinsert`,`VTCluster/vtselect`, `VTCluster/vtstorage` to the `targetRef.crd.kind`.
+* FEATURE: [operator](https://docs.victoriametrics.com/operator/api/): introduce new resources `VLSingle` and `VTCluster`. See [#1498](https://github.com/VictoriaMetrics/operator/issues/1498) for details.
 
 * BUGFIX: [vmoperator](https://docs.victoriametrics.com/operator/): properly reconcile `Service` on value of `loadbalancerClass` change. Previously it produced error `may not change once set`. See PR [#1522](https://github.com/VictoriaMetrics/operator/pull/1522) for details.
 * BUGFIX: [vmuser](https://docs.victoriametrics.com/operator/resources/vmuser/): simplify path based routing for  `src_paths` when using `VMCluster/vminsert` or `VMCluster/vmselect` as targetRefs. See this PR [#1519](https://github.com/VictoriaMetrics/operator/pull/1519) for details.
diff --git a/docs/api.md b/docs/api.md
index d4555ffe..8ff1a98a 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -30,6 +30,8 @@ Package v1 contains API Schema definitions for the operator v1 API group
 - [VLCluster](#vlcluster)
 - [VLSingle](#vlsingle)
 - [VMAnomaly](#vmanomaly)
+- [VTCluster](#vtcluster)
+- [VTSingle](#vtsingle)
 
 
 
@@ -795,6 +797,310 @@ Appears in: [VMAnomalySpec](#vmanomalyspec)
 | tlsConfig<a href="#vmanomalywritersspec-tlsconfig" id="vmanomalywritersspec-tlsconfig">#</a><br/>_[TLSConfig](#tlsconfig)_ | _(Required)_<br/>TLSConfig defines tls connection configuration |
 
 
+#### VTCluster
+
+
+
+VTCluster is fast, cost-effective and scalable traces database.
+
+
+
+| Field | Description |
+| --- | --- |
+| apiVersion<br/>_string_ | (Required)<br/>`operator.victoriametrics.com/v1` |
+| kind<br/>_string_ | (Required)<br/>`VTCluster` |
+| metadata<a href="#vtcluster-metadata" id="vtcluster-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="#vtcluster-spec" id="vtcluster-spec">#</a><br/>_[VTClusterSpec](#vtclusterspec)_ | _(Required)_<br/> |
+
+
+#### VTClusterSpec
+
+
+
+VTClusterSpec defines the desired state of VTCluster
+
+Appears in: [VTCluster](#vtcluster)
+
+| Field | Description |
+| --- | --- |
+| clusterDomainName<a href="#vtclusterspec-clusterdomainname" id="vtclusterspec-clusterdomainname">#</a><br/>_string_ | _(Optional)_<br/>ClusterDomainName defines domain name suffix for in-cluster dns addresses<br />aka .cluster.local<br />used by vtinsert and vtselect to build vtstorage address |
+| clusterVersion<a href="#vtclusterspec-clusterversion" id="vtclusterspec-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="#vtclusterspec-imagepullsecrets" id="vtclusterspec-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 |
+| insert<a href="#vtclusterspec-insert" id="vtclusterspec-insert">#</a><br/>_[VTInsert](#vtinsert)_ | _(Required)_<br/> |
+| managedMetadata<a href="#vtclusterspec-managedmetadata" id="vtclusterspec-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="#vtclusterspec-paused" id="vtclusterspec-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="#vtclusterspec-requestsloadbalancer" id="vtclusterspec-requestsloadbalancer">#</a><br/>_[VMAuthLoadBalancer](#vmauthloadbalancer)_ | _(Required)_<br/>RequestsLoadBalancer configures load-balancing for vtinsert and vtselect requests.<br />It helps to evenly spread load across pods.<br />Usually it's not possible with Kubernetes TCP-based services. |
+| select<a href="#vtclusterspec-select" id="vtclusterspec-select">#</a><br/>_[VTSelect](#vtselect)_ | _(Required)_<br/> |
+| serviceAccountName<a href="#vtclusterspec-serviceaccountname" id="vtclusterspec-serviceaccountname">#</a><br/>_string_ | _(Optional)_<br/>ServiceAccountName is the name of the ServiceAccount to use to run the<br />VTSelect, VTInsert and VTStorage Pods. |
+| storage<a href="#vtclusterspec-storage" id="vtclusterspec-storage">#</a><br/>_[VTStorage](#vtstorage)_ | _(Required)_<br/> |
+| useStrictSecurity<a href="#vtclusterspec-usestrictsecurity" id="vtclusterspec-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 |
+
+
+
+
+#### VTInsert
+
+
+
+VTInsert defines vtinsert component configuration at victoria-traces cluster
+
+Appears in: [VTClusterSpec](#vtclusterspec)
+
+| Field | Description |
+| --- | --- |
+| affinity<a href="#vtinsert-affinity" id="vtinsert-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="#vtinsert-configmaps" id="vtinsert-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="#vtinsert-containers" id="vtinsert-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="#vtinsert-disableautomountserviceaccounttoken" id="vtinsert-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="#vtinsert-disableselfservicescrape" id="vtinsert-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="#vtinsert-dnsconfig" id="vtinsert-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="#vtinsert-dnspolicy" id="vtinsert-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="#vtinsert-extraargs" id="vtinsert-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="#vtinsert-extraenvs" id="vtinsert-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="#vtinsert-extraenvsfrom" id="vtinsert-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="#vtinsert-hostaliases" id="vtinsert-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="#vtinsert-hostnetwork" id="vtinsert-hostnetwork">#</a><br/>_boolean_ | _(Optional)_<br/>HostNetwork controls whether the pod may use the node network namespace |
+| host_aliases<a href="#vtinsert-host_aliases" id="vtinsert-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="#vtinsert-hpa" id="vtinsert-hpa">#</a><br/>_[EmbeddedHPA](#embeddedhpa)_ | _(Optional)_<br/>Configures horizontal pod autoscaling. |
+| image<a href="#vtinsert-image" id="vtinsert-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="#vtinsert-imagepullsecrets" id="vtinsert-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="#vtinsert-initcontainers" id="vtinsert-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="#vtinsert-logformat" id="vtinsert-logformat">#</a><br/>_string_ | _(Optional)_<br/>LogFormat for VTSelect to be configured with.<br />default or json |
+| logLevel<a href="#vtinsert-loglevel" id="vtinsert-loglevel">#</a><br/>_string_ | _(Optional)_<br/>LogLevel for VTSelect to be configured with. |
+| minReadySeconds<a href="#vtinsert-minreadyseconds" id="vtinsert-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="#vtinsert-nodeselector" id="vtinsert-nodeselector">#</a><br/>_object (keys:string, values:string)_ | _(Optional)_<br/>NodeSelector Define which Nodes the Pods are scheduled on. |
+| paused<a href="#vtinsert-paused" id="vtinsert-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="#vtinsert-poddisruptionbudget" id="vtinsert-poddisruptionbudget">#</a><br/>_[EmbeddedPodDisruptionBudgetSpec](#embeddedpoddisruptionbudgetspec)_ | _(Optional)_<br/>PodDisruptionBudget created by operator |
+| podMetadata<a href="#vtinsert-podmetadata" id="vtinsert-podmetadata">#</a><br/>_[EmbeddedObjectMetadata](#embeddedobjectmetadata)_ | _(Required)_<br/>PodMetadata configures Labels and Annotations which are propagated to the VTSelect pods. |
+| port<a href="#vtinsert-port" id="vtinsert-port">#</a><br/>_string_ | _(Optional)_<br/>Port listen address |
+| priorityClassName<a href="#vtinsert-priorityclassname" id="vtinsert-priorityclassname">#</a><br/>_string_ | _(Optional)_<br/>PriorityClassName class assigned to the Pods |
+| readinessGates<a href="#vtinsert-readinessgates" id="vtinsert-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="#vtinsert-replicacount" id="vtinsert-replicacount">#</a><br/>_integer_ | _(Optional)_<br/>ReplicaCount is the expected size of the Application. |
+| resources<a href="#vtinsert-resources" id="vtinsert-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="#vtinsert-revisionhistorylimitcount" id="vtinsert-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="#vtinsert-rollingupdate" id="vtinsert-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="#vtinsert-runtimeclassname" id="vtinsert-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="#vtinsert-schedulername" id="vtinsert-schedulername">#</a><br/>_string_ | _(Optional)_<br/>SchedulerName - defines kubernetes scheduler name |
+| secrets<a href="#vtinsert-secrets" id="vtinsert-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="#vtinsert-securitycontext" id="vtinsert-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="#vtinsert-servicescrapespec" id="vtinsert-servicescrapespec">#</a><br/>_[VMServiceScrapeSpec](#vmservicescrapespec)_ | _(Optional)_<br/>ServiceScrapeSpec that will be added to vtselect VMServiceScrape spec |
+| serviceSpec<a href="#vtinsert-servicespec" id="vtinsert-servicespec">#</a><br/>_[AdditionalServiceSpec](#additionalservicespec)_ | _(Optional)_<br/>ServiceSpec that will be added to vtselect service spec |
+| terminationGracePeriodSeconds<a href="#vtinsert-terminationgraceperiodseconds" id="vtinsert-terminationgraceperiodseconds">#</a><br/>_integer_ | _(Optional)_<br/>TerminationGracePeriodSeconds period for container graceful termination |
+| tolerations<a href="#vtinsert-tolerations" id="vtinsert-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="#vtinsert-topologyspreadconstraints" id="vtinsert-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="#vtinsert-updatestrategy" id="vtinsert-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="#vtinsert-usedefaultresources" id="vtinsert-usedefaultresources">#</a><br/>_boolean_ | _(Optional)_<br/>UseDefaultResources controls resource settings<br />By default, operator sets built-in resource requirements |
+| useStrictSecurity<a href="#vtinsert-usestrictsecurity" id="vtinsert-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="#vtinsert-volumemounts" id="vtinsert-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="#vtinsert-volumes" id="vtinsert-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 |
+
+
+#### VTSelect
+
+
+
+VTSelect defines vtselect component configuration at victoria-traces cluster
+
+Appears in: [VTClusterSpec](#vtclusterspec)
+
+| Field | Description |
+| --- | --- |
+| affinity<a href="#vtselect-affinity" id="vtselect-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="#vtselect-configmaps" id="vtselect-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="#vtselect-containers" id="vtselect-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="#vtselect-disableautomountserviceaccounttoken" id="vtselect-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="#vtselect-disableselfservicescrape" id="vtselect-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="#vtselect-dnsconfig" id="vtselect-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="#vtselect-dnspolicy" id="vtselect-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="#vtselect-extraargs" id="vtselect-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="#vtselect-extraenvs" id="vtselect-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="#vtselect-extraenvsfrom" id="vtselect-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="#vtselect-hostaliases" id="vtselect-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="#vtselect-hostnetwork" id="vtselect-hostnetwork">#</a><br/>_boolean_ | _(Optional)_<br/>HostNetwork controls whether the pod may use the node network namespace |
+| host_aliases<a href="#vtselect-host_aliases" id="vtselect-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="#vtselect-hpa" id="vtselect-hpa">#</a><br/>_[EmbeddedHPA](#embeddedhpa)_ | _(Optional)_<br/>Configures horizontal pod autoscaling. |
+| image<a href="#vtselect-image" id="vtselect-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="#vtselect-imagepullsecrets" id="vtselect-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="#vtselect-initcontainers" id="vtselect-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="#vtselect-logformat" id="vtselect-logformat">#</a><br/>_string_ | _(Optional)_<br/>LogFormat for VTSelect to be configured with.<br />default or json |
+| logLevel<a href="#vtselect-loglevel" id="vtselect-loglevel">#</a><br/>_string_ | _(Optional)_<br/>LogLevel for VTSelect to be configured with. |
+| minReadySeconds<a href="#vtselect-minreadyseconds" id="vtselect-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="#vtselect-nodeselector" id="vtselect-nodeselector">#</a><br/>_object (keys:string, values:string)_ | _(Optional)_<br/>NodeSelector Define which Nodes the Pods are scheduled on. |
+| paused<a href="#vtselect-paused" id="vtselect-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="#vtselect-poddisruptionbudget" id="vtselect-poddisruptionbudget">#</a><br/>_[EmbeddedPodDisruptionBudgetSpec](#embeddedpoddisruptionbudgetspec)_ | _(Optional)_<br/>PodDisruptionBudget created by operator |
+| podMetadata<a href="#vtselect-podmetadata" id="vtselect-podmetadata">#</a><br/>_[EmbeddedObjectMetadata](#embeddedobjectmetadata)_ | _(Required)_<br/>PodMetadata configures Labels and Annotations which are propagated to the VTSelect pods. |
+| port<a href="#vtselect-port" id="vtselect-port">#</a><br/>_string_ | _(Optional)_<br/>Port listen address |
+| priorityClassName<a href="#vtselect-priorityclassname" id="vtselect-priorityclassname">#</a><br/>_string_ | _(Optional)_<br/>PriorityClassName class assigned to the Pods |
+| readinessGates<a href="#vtselect-readinessgates" id="vtselect-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="#vtselect-replicacount" id="vtselect-replicacount">#</a><br/>_integer_ | _(Optional)_<br/>ReplicaCount is the expected size of the Application. |
+| resources<a href="#vtselect-resources" id="vtselect-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="#vtselect-revisionhistorylimitcount" id="vtselect-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="#vtselect-rollingupdate" id="vtselect-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="#vtselect-runtimeclassname" id="vtselect-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="#vtselect-schedulername" id="vtselect-schedulername">#</a><br/>_string_ | _(Optional)_<br/>SchedulerName - defines kubernetes scheduler name |
+| secrets<a href="#vtselect-secrets" id="vtselect-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="#vtselect-securitycontext" id="vtselect-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="#vtselect-servicescrapespec" id="vtselect-servicescrapespec">#</a><br/>_[VMServiceScrapeSpec](#vmservicescrapespec)_ | _(Optional)_<br/>ServiceScrapeSpec that will be added to vtselect VMServiceScrape spec |
+| serviceSpec<a href="#vtselect-servicespec" id="vtselect-servicespec">#</a><br/>_[AdditionalServiceSpec](#additionalservicespec)_ | _(Optional)_<br/>ServiceSpec that will be added to vtselect service spec |
+| terminationGracePeriodSeconds<a href="#vtselect-terminationgraceperiodseconds" id="vtselect-terminationgraceperiodseconds">#</a><br/>_integer_ | _(Optional)_<br/>TerminationGracePeriodSeconds period for container graceful termination |
+| tolerations<a href="#vtselect-tolerations" id="vtselect-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="#vtselect-topologyspreadconstraints" id="vtselect-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="#vtselect-updatestrategy" id="vtselect-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="#vtselect-usedefaultresources" id="vtselect-usedefaultresources">#</a><br/>_boolean_ | _(Optional)_<br/>UseDefaultResources controls resource settings<br />By default, operator sets built-in resource requirements |
+| useStrictSecurity<a href="#vtselect-usestrictsecurity" id="vtselect-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="#vtselect-volumemounts" id="vtselect-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="#vtselect-volumes" id="vtselect-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 |
+
+
+#### VTSingle
+
+
+
+VTSingle is fast, cost-effective and scalable traces database.
+VTSingle is the Schema for the API
+
+
+
+| Field | Description |
+| --- | --- |
+| apiVersion<br/>_string_ | (Required)<br/>`operator.victoriametrics.com/v1` |
+| kind<br/>_string_ | (Required)<br/>`VTSingle` |
+| metadata<a href="#vtsingle-metadata" id="vtsingle-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="#vtsingle-spec" id="vtsingle-spec">#</a><br/>_[VTSingleSpec](#vtsinglespec)_ | _(Required)_<br/> |
+
+
+#### VTSingleSpec
+
+
+
+VTSingleSpec defines the desired state of VTSingle
+
+Appears in: [VTSingle](#vtsingle)
+
+| Field | Description |
+| --- | --- |
+| affinity<a href="#vtsinglespec-affinity" id="vtsinglespec-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="#vtsinglespec-configmaps" id="vtsinglespec-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="#vtsinglespec-containers" id="vtsinglespec-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="#vtsinglespec-disableautomountserviceaccounttoken" id="vtsinglespec-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="#vtsinglespec-disableselfservicescrape" id="vtsinglespec-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="#vtsinglespec-dnsconfig" id="vtsinglespec-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="#vtsinglespec-dnspolicy" id="vtsinglespec-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="#vtsinglespec-extraargs" id="vtsinglespec-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="#vtsinglespec-extraenvs" id="vtsinglespec-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="#vtsinglespec-extraenvsfrom" id="vtsinglespec-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="#vtsinglespec-futureretention" id="vtsinglespec-futureretention">#</a><br/>_string_ | _(Optional)_<br/>FutureRetention for the stored traces<br />Log entries with timestamps bigger than now+futureRetention are rejected during data ingestion;<br />see https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces |
+| hostAliases<a href="#vtsinglespec-hostaliases" id="vtsinglespec-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="#vtsinglespec-hostnetwork" id="vtsinglespec-hostnetwork">#</a><br/>_boolean_ | _(Optional)_<br/>HostNetwork controls whether the pod may use the node network namespace |
+| host_aliases<a href="#vtsinglespec-host_aliases" id="vtsinglespec-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="#vtsinglespec-image" id="vtsinglespec-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="#vtsinglespec-imagepullsecrets" id="vtsinglespec-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="#vtsinglespec-initcontainers" id="vtsinglespec-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="#vtsinglespec-logformat" id="vtsinglespec-logformat">#</a><br/>_string_ | _(Optional)_<br/>LogFormat for VTSingle to be configured with. |
+| logIngestedRows<a href="#vtsinglespec-logingestedrows" id="vtsinglespec-logingestedrows">#</a><br/>_boolean_ | _(Required)_<br/>Whether to log all the ingested log entries; this can be useful for debugging of data ingestion;<br />see https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces |
+| logLevel<a href="#vtsinglespec-loglevel" id="vtsinglespec-loglevel">#</a><br/>_string_ | _(Optional)_<br/>LogLevel for VictoriaTraces to be configured with. |
+| logNewStreams<a href="#vtsinglespec-lognewstreams" id="vtsinglespec-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;<br />see https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces |
+| managedMetadata<a href="#vtsinglespec-managedmetadata" id="vtsinglespec-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 |
+| minReadySeconds<a href="#vtsinglespec-minreadyseconds" id="vtsinglespec-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="#vtsinglespec-nodeselector" id="vtsinglespec-nodeselector">#</a><br/>_object (keys:string, values:string)_ | _(Optional)_<br/>NodeSelector Define which Nodes the Pods are scheduled on. |
+| paused<a href="#vtsinglespec-paused" id="vtsinglespec-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. |
+| podMetadata<a href="#vtsinglespec-podmetadata" id="vtsinglespec-podmetadata">#</a><br/>_[EmbeddedObjectMetadata](#embeddedobjectmetadata)_ | _(Optional)_<br/>PodMetadata configures Labels and Annotations which are propagated to the VTSingle pods. |
+| port<a href="#vtsinglespec-port" id="vtsinglespec-port">#</a><br/>_string_ | _(Optional)_<br/>Port listen address |
+| priorityClassName<a href="#vtsinglespec-priorityclassname" id="vtsinglespec-priorityclassname">#</a><br/>_string_ | _(Optional)_<br/>PriorityClassName class assigned to the Pods |
+| readinessGates<a href="#vtsinglespec-readinessgates" id="vtsinglespec-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="#vtsinglespec-replicacount" id="vtsinglespec-replicacount">#</a><br/>_integer_ | _(Optional)_<br/>ReplicaCount is the expected size of the Application. |
+| resources<a href="#vtsinglespec-resources" id="vtsinglespec-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="#vtsinglespec-retentionmaxdiskspaceusagebytes" id="vtsinglespec-retentionmaxdiskspaceusagebytes">#</a><br/>_[BytesString](#bytesstring)_ | _(Optional)_<br/>RetentionMaxDiskSpaceUsageBytes for the stored traces<br />VictoriaTraces keeps at least two last days of data in order to guarantee that the traces 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/victoriatraces/#configure-and-run-victoriatraces |
+| retentionPeriod<a href="#vtsinglespec-retentionperiod" id="vtsinglespec-retentionperiod">#</a><br/>_string_ | _(Optional)_<br/>RetentionPeriod for the stored traces<br />https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces |
+| revisionHistoryLimitCount<a href="#vtsinglespec-revisionhistorylimitcount" id="vtsinglespec-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. |
+| runtimeClassName<a href="#vtsinglespec-runtimeclassname" id="vtsinglespec-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="#vtsinglespec-schedulername" id="vtsinglespec-schedulername">#</a><br/>_string_ | _(Optional)_<br/>SchedulerName - defines kubernetes scheduler name |
+| secrets<a href="#vtsinglespec-secrets" id="vtsinglespec-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="#vtsinglespec-securitycontext" id="vtsinglespec-securitycontext">#</a><br/>_[SecurityContext](#securitycontext)_ | _(Optional)_<br/>SecurityContext holds pod-level security attributes and common container settings.<br />This defaults to the default PodSecurityContext. |
+| serviceAccountName<a href="#vtsinglespec-serviceaccountname" id="vtsinglespec-serviceaccountname">#</a><br/>_string_ | _(Optional)_<br/>ServiceAccountName is the name of the ServiceAccount to use to run the pods |
+| serviceScrapeSpec<a href="#vtsinglespec-servicescrapespec" id="vtsinglespec-servicescrapespec">#</a><br/>_[VMServiceScrapeSpec](#vmservicescrapespec)_ | _(Optional)_<br/>ServiceScrapeSpec that will be added to vtsingle VMServiceScrape spec |
+| serviceSpec<a href="#vtsinglespec-servicespec" id="vtsinglespec-servicespec">#</a><br/>_[AdditionalServiceSpec](#additionalservicespec)_ | _(Optional)_<br/>ServiceSpec that will be added to vtsingle service spec |
+| storage<a href="#vtsinglespec-storage" id="vtsinglespec-storage">#</a><br/>_[PersistentVolumeClaimSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#persistentvolumeclaimspec-v1-core)_ | _(Optional)_<br/>Storage is the definition of how storage will be used by the VTSingle<br />by default it`s empty dir |
+| storageDataPath<a href="#vtsinglespec-storagedatapath" id="vtsinglespec-storagedatapath">#</a><br/>_string_ | _(Optional)_<br/>StorageDataPath disables spec.storage option and overrides arg for victoria-traces binary --storageDataPath,<br />its users responsibility to mount proper device into given path. |
+| storageMetadata<a href="#vtsinglespec-storagemetadata" id="vtsinglespec-storagemetadata">#</a><br/>_[EmbeddedObjectMetadata](#embeddedobjectmetadata)_ | _(Optional)_<br/>StorageMeta defines annotations and labels attached to PVC for given vtsingle CR |
+| terminationGracePeriodSeconds<a href="#vtsinglespec-terminationgraceperiodseconds" id="vtsinglespec-terminationgraceperiodseconds">#</a><br/>_integer_ | _(Optional)_<br/>TerminationGracePeriodSeconds period for container graceful termination |
+| tolerations<a href="#vtsinglespec-tolerations" id="vtsinglespec-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="#vtsinglespec-topologyspreadconstraints" id="vtsinglespec-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="#vtsinglespec-usedefaultresources" id="vtsinglespec-usedefaultresources">#</a><br/>_boolean_ | _(Optional)_<br/>UseDefaultResources controls resource settings<br />By default, operator sets built-in resource requirements |
+| useStrictSecurity<a href="#vtsinglespec-usestrictsecurity" id="vtsinglespec-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="#vtsinglespec-volumemounts" id="vtsinglespec-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="#vtsinglespec-volumes" id="vtsinglespec-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 |
+
+
+
+
+#### VTStorage
+
+
+
+VTStorage defines vtstorage component configuration at victoria-traces cluster
+
+Appears in: [VTClusterSpec](#vtclusterspec)
+
+| Field | Description |
+| --- | --- |
+| affinity<a href="#vtstorage-affinity" id="vtstorage-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="#vtstorage-claimtemplates" id="vtstorage-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="#vtstorage-configmaps" id="vtstorage-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="#vtstorage-containers" id="vtstorage-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="#vtstorage-disableautomountserviceaccounttoken" id="vtstorage-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="#vtstorage-disableselfservicescrape" id="vtstorage-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="#vtstorage-dnsconfig" id="vtstorage-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="#vtstorage-dnspolicy" id="vtstorage-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="#vtstorage-extraargs" id="vtstorage-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="#vtstorage-extraenvs" id="vtstorage-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="#vtstorage-extraenvsfrom" id="vtstorage-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="#vtstorage-futureretention" id="vtstorage-futureretention">#</a><br/>_string_ | _(Optional)_<br/>FutureRetention for the stored traces<br />Log entries with timestamps bigger than now+futureRetention are rejected during data ingestion<br />see https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces |
+| hostAliases<a href="#vtstorage-hostaliases" id="vtstorage-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="#vtstorage-hostnetwork" id="vtstorage-hostnetwork">#</a><br/>_boolean_ | _(Optional)_<br/>HostNetwork controls whether the pod may use the node network namespace |
+| host_aliases<a href="#vtstorage-host_aliases" id="vtstorage-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="#vtstorage-image" id="vtstorage-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="#vtstorage-imagepullsecrets" id="vtstorage-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="#vtstorage-initcontainers" id="vtstorage-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="#vtstorage-logformat" id="vtstorage-logformat">#</a><br/>_string_ | _(Optional)_<br/>LogFormat for VTStorage to be configured with.<br />default or json |
+| logIngestedRows<a href="#vtstorage-logingestedrows" id="vtstorage-logingestedrows">#</a><br/>_boolean_ | _(Required)_<br/>Whether to log all the ingested log entries; this can be useful for debugging of data ingestion<br />see https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces |
+| logLevel<a href="#vtstorage-loglevel" id="vtstorage-loglevel">#</a><br/>_string_ | _(Optional)_<br/>LogLevel for VTStorage to be configured with. |
+| logNewStreams<a href="#vtstorage-lognewstreams" id="vtstorage-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<br />see https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces |
+| maintenanceInsertNodeIDs<a href="#vtstorage-maintenanceinsertnodeids" id="vtstorage-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="#vtstorage-maintenanceselectnodeids" id="vtstorage-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="#vtstorage-minreadyseconds" id="vtstorage-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="#vtstorage-nodeselector" id="vtstorage-nodeselector">#</a><br/>_object (keys:string, values:string)_ | _(Optional)_<br/>NodeSelector Define which Nodes the Pods are scheduled on. |
+| paused<a href="#vtstorage-paused" id="vtstorage-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. |
+| persistentVolumeClaimRetentionPolicy<a href="#vtstorage-persistentvolumeclaimretentionpolicy" id="vtstorage-persistentvolumeclaimretentionpolicy">#</a><br/>_[StatefulSetPersistentVolumeClaimRetentionPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#statefulsetpersistentvolumeclaimretentionpolicy-v1-apps)_ | _(Optional)_<br/>PersistentVolumeClaimRetentionPolicy allows configuration of PVC retention policy |
+| podDisruptionBudget<a href="#vtstorage-poddisruptionbudget" id="vtstorage-poddisruptionbudget">#</a><br/>_[EmbeddedPodDisruptionBudgetSpec](#embeddedpoddisruptionbudgetspec)_ | _(Optional)_<br/>PodDisruptionBudget created by operator |
+| podMetadata<a href="#vtstorage-podmetadata" id="vtstorage-podmetadata">#</a><br/>_[EmbeddedObjectMetadata](#embeddedobjectmetadata)_ | _(Required)_<br/>PodMetadata configures Labels and Annotations which are propagated to the VTStorage pods. |
+| port<a href="#vtstorage-port" id="vtstorage-port">#</a><br/>_string_ | _(Optional)_<br/>Port listen address |
+| priorityClassName<a href="#vtstorage-priorityclassname" id="vtstorage-priorityclassname">#</a><br/>_string_ | _(Optional)_<br/>PriorityClassName class assigned to the Pods |
+| readinessGates<a href="#vtstorage-readinessgates" id="vtstorage-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="#vtstorage-replicacount" id="vtstorage-replicacount">#</a><br/>_integer_ | _(Optional)_<br/>ReplicaCount is the expected size of the Application. |
+| resources<a href="#vtstorage-resources" id="vtstorage-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="#vtstorage-retentionmaxdiskspaceusagebytes" id="vtstorage-retentionmaxdiskspaceusagebytes">#</a><br/>_[BytesString](#bytesstring)_ | _(Optional)_<br/>RetentionMaxDiskSpaceUsageBytes for the stored traces<br />VictoriaTraces keeps at least two last days of data in order to guarantee that the traces 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/victoriatraces/#configure-and-run-victoriatraces |
+| retentionPeriod<a href="#vtstorage-retentionperiod" id="vtstorage-retentionperiod">#</a><br/>_string_ | _(Optional)_<br/>RetentionPeriod for the stored traces<br />https://docs.victoriametrics.com/victoriatraces/#configure-and-run-victoriatraces |
+| revisionHistoryLimitCount<a href="#vtstorage-revisionhistorylimitcount" id="vtstorage-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="#vtstorage-rollingupdatestrategy" id="vtstorage-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 |
+| rollingUpdateStrategyBehavior<a href="#vtstorage-rollingupdatestrategybehavior" id="vtstorage-rollingupdatestrategybehavior">#</a><br/>_[StatefulSetUpdateStrategyBehavior](#statefulsetupdatestrategybehavior)_ | _(Optional)_<br/>RollingUpdateStrategyBehavior defines customized behavior for rolling updates.<br />It applies if the RollingUpdateStrategy is set to OnDelete, which is the default. |
+| runtimeClassName<a href="#vtstorage-runtimeclassname" id="vtstorage-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="#vtstorage-schedulername" id="vtstorage-schedulername">#</a><br/>_string_ | _(Optional)_<br/>SchedulerName - defines kubernetes scheduler name |
+| secrets<a href="#vtstorage-secrets" id="vtstorage-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="#vtstorage-securitycontext" id="vtstorage-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="#vtstorage-servicescrapespec" id="vtstorage-servicescrapespec">#</a><br/>_[VMServiceScrapeSpec](#vmservicescrapespec)_ | _(Optional)_<br/>ServiceScrapeSpec that will be added to vtselect VMServiceScrape spec |
+| serviceSpec<a href="#vtstorage-servicespec" id="vtstorage-servicespec">#</a><br/>_[AdditionalServiceSpec](#additionalservicespec)_ | _(Optional)_<br/>ServiceSpec that will be added to vtselect service spec |
+| storage<a href="#vtstorage-storage" id="vtstorage-storage">#</a><br/>_[StorageSpec](#storagespec)_ | _(Optional)_<br/>Storage configures persistent volume for VTStorage |
+| storageDataPath<a href="#vtstorage-storagedatapath" id="vtstorage-storagedatapath">#</a><br/>_string_ | _(Optional)_<br/>StorageDataPath - path to storage data |
+| terminationGracePeriodSeconds<a href="#vtstorage-terminationgraceperiodseconds" id="vtstorage-terminationgraceperiodseconds">#</a><br/>_integer_ | _(Optional)_<br/>TerminationGracePeriodSeconds period for container graceful termination |
+| tolerations<a href="#vtstorage-tolerations" id="vtstorage-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="#vtstorage-topologyspreadconstraints" id="vtstorage-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="#vtstorage-usedefaultresources" id="vtstorage-usedefaultresources">#</a><br/>_boolean_ | _(Optional)_<br/>UseDefaultResources controls resource settings<br />By default, operator sets built-in resource requirements |
+| useStrictSecurity<a href="#vtstorage-usestrictsecurity" id="vtstorage-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="#vtstorage-volumemounts" id="vtstorage-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="#vtstorage-volumes" id="vtstorage-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
 
@@ -866,7 +1172,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: [VLAgentSpec](#vlagentspec), [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)
+Appears in: [VLAgentSpec](#vlagentspec), [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), [VTInsert](#vtinsert), [VTSelect](#vtselect), [VTSingleSpec](#vtsinglespec), [VTStorage](#vtstorage)
 
 | Field | Description |
 | --- | --- |
@@ -1023,7 +1329,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: [VLAgentRemoteWriteSettings](#vlagentremotewritesettings), [VLAgentRemoteWriteSpec](#vlagentremotewritespec), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VMAgentRemoteWriteSettings](#vmagentremotewritesettings), [VMAgentRemoteWriteSpec](#vmagentremotewritespec)
+Appears in: [VLAgentRemoteWriteSettings](#vlagentremotewritesettings), [VLAgentRemoteWriteSpec](#vlagentremotewritespec), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VMAgentRemoteWriteSettings](#vmagentremotewritesettings), [VMAgentRemoteWriteSpec](#vmagentremotewritespec), [VTSingleSpec](#vtsinglespec), [VTStorage](#vtstorage)
 
 
 
@@ -1039,7 +1345,7 @@ Appears in: [TargetRef](#targetref)
 
 | Field | Description |
 | --- | --- |
-| kind<a href="#crdref-kind" id="crdref-kind">#</a><br/>_string_ | _(Required)_<br/>Kind one of:<br />VMAgent,VMAlert, VMSingle, VMCluster/vmselect, VMCluster/vmstorage,VMCluster/vminsert,VMAlertManager, VLSingle, VLCluster/vlinsert, VLCluster/vlselect, VLCluster/vlstorage and VLAgent |
+| kind<a href="#crdref-kind" id="crdref-kind">#</a><br/>_string_ | _(Required)_<br/>Kind one of:<br />VMAgent,VMAlert, VMSingle, VMCluster/vmselect, VMCluster/vmstorage,VMCluster/vminsert,VMAlertManager, VLSingle, VLCluster/vlinsert, VLCluster/vlselect, VLCluster/vlstorage, VTSingle, VTCluster/vtinsert, VTCluster/vtselect, VTCluster/vtstorage and VLAgent |
 | name<a href="#crdref-name" id="crdref-name">#</a><br/>_string_ | _(Required)_<br/>Name target CRD object name |
 | namespace<a href="#crdref-namespace" id="crdref-namespace">#</a><br/>_string_ | _(Required)_<br/>Namespace target CRD object namespace. |
 
@@ -1067,7 +1373,7 @@ Appears in: [TLSClientConfig](#tlsclientconfig), [TLSServerConfig](#tlsservercon
 CommonApplicationDeploymentParams defines common params
 for deployment and statefulset specifications
 
-Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage), [VTInsert](#vtinsert), [VTSelect](#vtselect), [VTSingleSpec](#vtsinglespec), [VTStorage](#vtstorage)
 
 | Field | Description |
 | --- | --- |
@@ -1127,7 +1433,7 @@ Appears in: [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertma
 CommonDefaultableParams contains Application settings
 with known values populated from operator configuration
 
-Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage), [VTInsert](#vtinsert), [VTSelect](#vtselect), [VTSingleSpec](#vtsinglespec), [VTStorage](#vtstorage)
 
 | Field | Description |
 | --- | --- |
@@ -1145,7 +1451,7 @@ Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlse
 
 Condition defines status condition of the resource
 
-Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [StatusMetadata](#statusmetadata), [VLAgentStatus](#vlagentstatus), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAnomalyStatus](#vmanomalystatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus)
+Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [StatusMetadata](#statusmetadata), [VLAgentStatus](#vlagentstatus), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAnomalyStatus](#vmanomalystatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus), [VTClusterStatus](#vtclusterstatus), [VTSingleStatus](#vtsinglestatus)
 
 | Field | Description |
 | --- | --- |
@@ -1360,7 +1666,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: [VLInsert](#vlinsert), [VLSelect](#vlselect), [VMInsert](#vminsert), [VMSelect](#vmselect)
+Appears in: [VLInsert](#vlinsert), [VLSelect](#vlselect), [VMInsert](#vminsert), [VMSelect](#vmselect), [VTInsert](#vtinsert), [VTSelect](#vtselect)
 
 | Field | Description |
 | --- | --- |
@@ -1398,7 +1704,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), [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [AdditionalServiceSpec](#additionalservicespec), [EmbeddedIngress](#embeddedingress), [EmbeddedPersistentVolumeClaim](#embeddedpersistentvolumeclaim), [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage), [VTInsert](#vtinsert), [VTSelect](#vtselect), [VTSingleSpec](#vtsinglespec), [VTStorage](#vtstorage)
 
 | Field | Description |
 | --- | --- |
@@ -1428,7 +1734,7 @@ Appears in: [StorageSpec](#storagespec)
 
 
 
-Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLStorage](#vlstorage), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMStorage](#vmstorage)
+Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLStorage](#vlstorage), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMStorage](#vmstorage), [VTInsert](#vtinsert), [VTSelect](#vtselect), [VTStorage](#vtstorage)
 
 | Field | Description |
 | --- | --- |
@@ -1445,7 +1751,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: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage), [VTInsert](#vtinsert), [VTSelect](#vtselect), [VTSingleSpec](#vtsinglespec), [VTStorage](#vtstorage)
 
 | Field | Description |
 | --- | --- |
@@ -1663,7 +1969,7 @@ Appears in: [VMScrapeConfigSpec](#vmscrapeconfigspec)
 
 Image defines docker image settings
 
-Appears in: [CommonDefaultableParams](#commondefaultableparams), [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMBackup](#vmbackup), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [CommonDefaultableParams](#commondefaultableparams), [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMBackup](#vmbackup), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage), [VTInsert](#vtinsert), [VTSelect](#vtselect), [VTSingleSpec](#vtsinglespec), [VTStorage](#vtstorage)
 
 | Field | Description |
 | --- | --- |
@@ -1873,7 +2179,7 @@ Appears in: [Receiver](#receiver)
 
 ManagedObjectsMetadata contains Labels and Annotations
 
-Appears in: [VLAgentSpec](#vlagentspec), [VLClusterSpec](#vlclusterspec), [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthSpec](#vmauthspec), [VMClusterSpec](#vmclusterspec), [VMSingleSpec](#vmsinglespec)
+Appears in: [VLAgentSpec](#vlagentspec), [VLClusterSpec](#vlclusterspec), [VLSingleSpec](#vlsinglespec), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthSpec](#vmauthspec), [VMClusterSpec](#vmclusterspec), [VMSingleSpec](#vmsinglespec), [VTClusterSpec](#vtclusterspec), [VTSingleSpec](#vtsinglespec)
 
 | Field | Description |
 | --- | --- |
@@ -2347,7 +2653,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), [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [CommonApplicationDeploymentParams](#commonapplicationdeploymentparams), [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage), [VTInsert](#vtinsert), [VTSelect](#vtselect), [VTSingleSpec](#vtsinglespec), [VTStorage](#vtstorage)
 
 
 
@@ -2490,7 +2796,7 @@ Appears in: [Receiver](#receiver)
 
 StatefulSetUpdateStrategyBehavior customizes behavior for StatefulSet updates.
 
-Appears in: [VLStorage](#vlstorage), [VMSelect](#vmselect), [VMStorage](#vmstorage)
+Appears in: [VLStorage](#vlstorage), [VMSelect](#vmselect), [VMStorage](#vmstorage), [VTStorage](#vtstorage)
 
 | Field | Description |
 | --- | --- |
@@ -2532,7 +2838,7 @@ Appears in: [TargetRef](#targetref)
 
 StatusMetadata holds metadata of application update status
 
-Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [VLAgentStatus](#vlagentstatus), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAnomalyStatus](#vmanomalystatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus)
+Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [VLAgentStatus](#vlagentstatus), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAnomalyStatus](#vmanomalystatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus), [VTClusterStatus](#vtclusterstatus), [VTSingleStatus](#vtsinglestatus)
 
 | Field | Description |
 | --- | --- |
@@ -2549,7 +2855,7 @@ Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [VLAgentStatus](#vlagents
 StorageSpec defines the configured storage for a group VM 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: [VLAgentSpec](#vlagentspec), [VLStorage](#vlstorage), [VMAgentSpec](#vmagentspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMSelect](#vmselect), [VMStorage](#vmstorage)
+Appears in: [VLAgentSpec](#vlagentspec), [VLStorage](#vlstorage), [VMAgentSpec](#vmagentspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMSelect](#vmselect), [VMStorage](#vmstorage), [VTStorage](#vtstorage)
 
 | Field | Description |
 | --- | --- |
@@ -2868,7 +3174,7 @@ _Underlying type:_ _string_
 
 UpdateStatus defines status for application
 
-Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [StatusMetadata](#statusmetadata), [VLAgentStatus](#vlagentstatus), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAnomalyStatus](#vmanomalystatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus)
+Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [StatusMetadata](#statusmetadata), [VLAgentStatus](#vlagentstatus), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAnomalyStatus](#vmanomalystatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus), [VTClusterStatus](#vtclusterstatus), [VTSingleStatus](#vtsinglestatus)
 
 
 
@@ -3482,7 +3788,7 @@ VMAuth is the Schema for the vmauths API
 VMAuthLoadBalancer configures vmauth as a load balancer
 for the requests
 
-Appears in: [VLClusterSpec](#vlclusterspec), [VMClusterSpec](#vmclusterspec)
+Appears in: [VLClusterSpec](#vlclusterspec), [VMClusterSpec](#vmclusterspec), [VTClusterSpec](#vtclusterspec)
 
 | Field | Description |
 | --- | --- |
@@ -4216,7 +4522,7 @@ result config will scrape service endpoints
 
 VMServiceScrapeSpec defines the desired state of VMServiceScrape
 
-Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMServiceScrape](#vmservicescrape), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage)
+Appears in: [VLAgentSpec](#vlagentspec), [VLInsert](#vlinsert), [VLSelect](#vlselect), [VLSingleSpec](#vlsinglespec), [VLStorage](#vlstorage), [VLogsSpec](#vlogsspec), [VMAgentSpec](#vmagentspec), [VMAlertSpec](#vmalertspec), [VMAlertmanagerSpec](#vmalertmanagerspec), [VMAnomalySpec](#vmanomalyspec), [VMAuthLoadBalancerSpec](#vmauthloadbalancerspec), [VMAuthSpec](#vmauthspec), [VMInsert](#vminsert), [VMSelect](#vmselect), [VMServiceScrape](#vmservicescrape), [VMSingleSpec](#vmsinglespec), [VMStorage](#vmstorage), [VTInsert](#vtinsert), [VTSelect](#vtselect), [VTSingleSpec](#vtsinglespec), [VTStorage](#vtstorage)
 
 | Field | Description |
 | --- | --- |
diff --git a/docs/env.md b/docs/env.md
index 95fe15ca..05ae272f 100644
--- a/docs/env.md
+++ b/docs/env.md
@@ -35,6 +35,14 @@
 | VM_VLSINGLEDEFAULT_RESOURCE_LIMIT_CPU: `1200m` <a href="#variables-vm-vlsingledefault-resource-limit-cpu" id="variables-vm-vlsingledefault-resource-limit-cpu">#</a> |
 | VM_VLSINGLEDEFAULT_RESOURCE_REQUEST_MEM: `500Mi` <a href="#variables-vm-vlsingledefault-resource-request-mem" id="variables-vm-vlsingledefault-resource-request-mem">#</a> |
 | VM_VLSINGLEDEFAULT_RESOURCE_REQUEST_CPU: `150m` <a href="#variables-vm-vlsingledefault-resource-request-cpu" id="variables-vm-vlsingledefault-resource-request-cpu">#</a> |
+| VM_VTSINGLEDEFAULT_IMAGE: `victoriametrics/victoria-traces` <a href="#variables-vm-vtsingledefault-image" id="variables-vm-vtsingledefault-image">#</a> |
+| VM_VTSINGLEDEFAULT_VERSION: `${VM_TRACES_VERSION}` <a href="#variables-vm-vtsingledefault-version" id="variables-vm-vtsingledefault-version">#</a> |
+| VM_VTSINGLEDEFAULT_PORT: `10428` <a href="#variables-vm-vtsingledefault-port" id="variables-vm-vtsingledefault-port">#</a> |
+| VM_VTSINGLEDEFAULT_USEDEFAULTRESOURCES: `true` <a href="#variables-vm-vtsingledefault-usedefaultresources" id="variables-vm-vtsingledefault-usedefaultresources">#</a> |
+| VM_VTSINGLEDEFAULT_RESOURCE_LIMIT_MEM: `1500Mi` <a href="#variables-vm-vtsingledefault-resource-limit-mem" id="variables-vm-vtsingledefault-resource-limit-mem">#</a> |
+| VM_VTSINGLEDEFAULT_RESOURCE_LIMIT_CPU: `1200m` <a href="#variables-vm-vtsingledefault-resource-limit-cpu" id="variables-vm-vtsingledefault-resource-limit-cpu">#</a> |
+| VM_VTSINGLEDEFAULT_RESOURCE_REQUEST_MEM: `500Mi` <a href="#variables-vm-vtsingledefault-resource-request-mem" id="variables-vm-vtsingledefault-resource-request-mem">#</a> |
+| VM_VTSINGLEDEFAULT_RESOURCE_REQUEST_CPU: `150m` <a href="#variables-vm-vtsingledefault-resource-request-cpu" id="variables-vm-vtsingledefault-resource-request-cpu">#</a> |
 | VM_VMALERTDEFAULT_IMAGE: `victoriametrics/vmalert` <a href="#variables-vm-vmalertdefault-image" id="variables-vm-vmalertdefault-image">#</a> |
 | VM_VMALERTDEFAULT_VERSION: `${VM_METRICS_VERSION}` <a href="#variables-vm-vmalertdefault-version" id="variables-vm-vmalertdefault-version">#</a> |
 | VM_VMALERTDEFAULT_CONFIGRELOADIMAGE: `jimmidyson/configmap-reload:v0.3.0` <a href="#variables-vm-vmalertdefault-configreloadimage" id="variables-vm-vmalertdefault-configreloadimage">#</a> |
@@ -154,6 +162,28 @@
 | 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_VTCLUSTERDEFAULT_USEDEFAULTRESOURCES: `true` <a href="#variables-vm-vtclusterdefault-usedefaultresources" id="variables-vm-vtclusterdefault-usedefaultresources">#</a> |
+| VM_VTCLUSTERDEFAULT_SELECT_IMAGE: `victoriametrics/victoria-traces` <a href="#variables-vm-vtclusterdefault-select-image" id="variables-vm-vtclusterdefault-select-image">#</a> |
+| VM_VTCLUSTERDEFAULT_SELECT_VERSION: `${VM_TRACES_VERSION}` <a href="#variables-vm-vtclusterdefault-select-version" id="variables-vm-vtclusterdefault-select-version">#</a> |
+| VM_VTCLUSTERDEFAULT_SELECT_PORT: `10471` <a href="#variables-vm-vtclusterdefault-select-port" id="variables-vm-vtclusterdefault-select-port">#</a> |
+| VM_VTCLUSTERDEFAULT_SELECT_RESOURCE_LIMIT_MEM: `1024Mi` <a href="#variables-vm-vtclusterdefault-select-resource-limit-mem" id="variables-vm-vtclusterdefault-select-resource-limit-mem">#</a> |
+| VM_VTCLUSTERDEFAULT_SELECT_RESOURCE_LIMIT_CPU: `1000m` <a href="#variables-vm-vtclusterdefault-select-resource-limit-cpu" id="variables-vm-vtclusterdefault-select-resource-limit-cpu">#</a> |
+| VM_VTCLUSTERDEFAULT_SELECT_RESOURCE_REQUEST_MEM: `256Mi` <a href="#variables-vm-vtclusterdefault-select-resource-request-mem" id="variables-vm-vtclusterdefault-select-resource-request-mem">#</a> |
+| VM_VTCLUSTERDEFAULT_SELECT_RESOURCE_REQUEST_CPU: `100m` <a href="#variables-vm-vtclusterdefault-select-resource-request-cpu" id="variables-vm-vtclusterdefault-select-resource-request-cpu">#</a> |
+| VM_VTCLUSTERDEFAULT_STORAGE_IMAGE: `victoriametrics/victoria-traces` <a href="#variables-vm-vtclusterdefault-storage-image" id="variables-vm-vtclusterdefault-storage-image">#</a> |
+| VM_VTCLUSTERDEFAULT_STORAGE_VERSION: `${VM_TRACES_VERSION}` <a href="#variables-vm-vtclusterdefault-storage-version" id="variables-vm-vtclusterdefault-storage-version">#</a> |
+| VM_VTCLUSTERDEFAULT_STORAGE_PORT: `10491` <a href="#variables-vm-vtclusterdefault-storage-port" id="variables-vm-vtclusterdefault-storage-port">#</a> |
+| VM_VTCLUSTERDEFAULT_STORAGE_RESOURCE_LIMIT_MEM: `2048Mi` <a href="#variables-vm-vtclusterdefault-storage-resource-limit-mem" id="variables-vm-vtclusterdefault-storage-resource-limit-mem">#</a> |
+| VM_VTCLUSTERDEFAULT_STORAGE_RESOURCE_LIMIT_CPU: `1000m` <a href="#variables-vm-vtclusterdefault-storage-resource-limit-cpu" id="variables-vm-vtclusterdefault-storage-resource-limit-cpu">#</a> |
+| VM_VTCLUSTERDEFAULT_STORAGE_RESOURCE_REQUEST_MEM: `512Mi` <a href="#variables-vm-vtclusterdefault-storage-resource-request-mem" id="variables-vm-vtclusterdefault-storage-resource-request-mem">#</a> |
+| VM_VTCLUSTERDEFAULT_STORAGE_RESOURCE_REQUEST_CPU: `200m` <a href="#variables-vm-vtclusterdefault-storage-resource-request-cpu" id="variables-vm-vtclusterdefault-storage-resource-request-cpu">#</a> |
+| VM_VTCLUSTERDEFAULT_INSERT_IMAGE: `victoriametrics/victoria-traces` <a href="#variables-vm-vtclusterdefault-insert-image" id="variables-vm-vtclusterdefault-insert-image">#</a> |
+| VM_VTCLUSTERDEFAULT_INSERT_VERSION: `${VM_TRACES_VERSION}` <a href="#variables-vm-vtclusterdefault-insert-version" id="variables-vm-vtclusterdefault-insert-version">#</a> |
+| VM_VTCLUSTERDEFAULT_INSERT_PORT: `10481` <a href="#variables-vm-vtclusterdefault-insert-port" id="variables-vm-vtclusterdefault-insert-port">#</a> |
+| VM_VTCLUSTERDEFAULT_INSERT_RESOURCE_LIMIT_MEM: `1024Mi` <a href="#variables-vm-vtclusterdefault-insert-resource-limit-mem" id="variables-vm-vtclusterdefault-insert-resource-limit-mem">#</a> |
+| VM_VTCLUSTERDEFAULT_INSERT_RESOURCE_LIMIT_CPU: `1000m` <a href="#variables-vm-vtclusterdefault-insert-resource-limit-cpu" id="variables-vm-vtclusterdefault-insert-resource-limit-cpu">#</a> |
+| VM_VTCLUSTERDEFAULT_INSERT_RESOURCE_REQUEST_MEM: `256Mi` <a href="#variables-vm-vtclusterdefault-insert-resource-request-mem" id="variables-vm-vtclusterdefault-insert-resource-request-mem">#</a> |
+| VM_VTCLUSTERDEFAULT_INSERT_RESOURCE_REQUEST_CPU: `100m` <a href="#variables-vm-vtclusterdefault-insert-resource-request-cpu" id="variables-vm-vtclusterdefault-insert-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/resources/README.md b/docs/resources/README.md
index 80889814..76f21095 100644
--- a/docs/resources/README.md
+++ b/docs/resources/README.md
@@ -25,6 +25,11 @@ Metrics Operator introduces.
 - [VMSingle](https://docs.victoriametrics.com/operator/resources/vmsingle/)
 - [VMUser](https://docs.victoriametrics.com/operator/resources/vmuser/)
 - [VMScrapeConfig](https://docs.victoriametrics.com/operator/resources/vmscrapeconfig/)
+- [VLSingle](https://docs.victoriametrics.com/operator/resources/vlsingle/)
+- [VLAgent](https://docs.victoriametrics.com/operator/resources/vlagent/)
+- [VLCluster](https://docs.victoriametrics.com/operator/resources/vlcluster/)
+- [VTSingle](https://docs.victoriametrics.com/operator/resources/vtsingle/)
+- [VTCluster](https://docs.victoriametrics.com/operator/resources/vtcluster/)
 
 Here is the scheme of relations between the custom resources:
 
diff --git a/docs/resources/vlcluster.md b/docs/resources/vlcluster.md
index 777d1107..aef13612 100644
--- a/docs/resources/vlcluster.md
+++ b/docs/resources/vlcluster.md
@@ -42,11 +42,11 @@ metadata:
   name: example
 spec:
   ...
-  clusterVersion: v1.22.0-victoria-logs
+  clusterVersion: v1.26.0
   vlstorage:
     image:
       repository: victoriametrics/victoria-logs
-      tag: v1.19.0-victoria-logs
+      tag: v1.25.0
       pullPolicy: Always
   # ...
 ```
@@ -89,10 +89,10 @@ spec:
 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_LIMIT_MEM` - default memory limit for `VLCluster.vlstorage` pods,
+- `VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_RESOURCE_LIMIT_CPU` - default cpu 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.
+- `VM_VLCLUSTERDEFAULT_VLSTORAGEDEFAULT_RESOURCE_REQUEST_CPU` - default cpu limit for `VLCluster.vlstorage` pods.
 
 These default parameters will be used if:
 
@@ -101,7 +101,7 @@ These default parameters will be used if:
 
 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,
+If you set `VM_VLCLUSTEREDEFAULT_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.
diff --git a/docs/resources/vlsingle.md b/docs/resources/vlsingle.md
index f4089474..ad1b6bee 100644
--- a/docs/resources/vlsingle.md
+++ b/docs/resources/vlsingle.md
@@ -51,7 +51,7 @@ metadata:
 spec:
   image:
     repository: victoriametrics/victoria-logs
-    tag: v1.4.0
+    tag: v1.26.0
     pullPolicy: Always
   # ...
 ```
@@ -66,7 +66,7 @@ metadata:
 spec:
   image:
     repository: victoriametrics/victoria-logs
-    tag: v1.4.0
+    tag: v1.26.0
     pullPolicy: Always
   imagePullSecrets:
     - name: my-repo-secret
@@ -104,12 +104,12 @@ by default all `VLSingle` pods have resource requests and limits from the defaul
 
 These default parameters will be used if:
 
-- `VM_VLOGSDEFAULT_USEDEFAULTRESOURCES` is set to `true` (default value),
+- `VM_VLSINGLEDEFAULT_USEDEFAULTRESOURCES` is set to `true` (default value),
 - `VLSingle` CR doesn't have `resources` field in `spec` section.
 
 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,
+If you set `VM_VLSINGLEDEFAULT_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.
diff --git a/docs/resources/vtcluster.md b/docs/resources/vtcluster.md
new file mode 100644
index 00000000..c3242997
--- /dev/null
+++ b/docs/resources/vtcluster.md
@@ -0,0 +1,164 @@
+---
+weight: 22
+title: VTCluster
+menu:
+  docs:
+    identifier: operator-cr-vtcluster
+    parent: operator-cr
+    weight: 22
+aliases:
+  - /operator/resources/vtcluster/
+  - /operator/resources/vtcluster/index.html
+tags:
+  - kubernetes
+  - metrics
+---
+`VTCluster` represents database for storing traces {{% available_from "v0.62.0" "operator" %}}.
+The `VTCluster` CRD declaratively defines a [VictoriaTraces cluster](https://docs.victoriametrics.com/victoriatraces/)
+installation to run in a Kubernetes cluster.
+
+For each `VTCluster` resource, the Operator deploys a properly configured components `VTInsert`'s `Deployment`, `VTSelect`'s `Deployment` and `VTStorage`'s `StatefulSet` in the same namespace.
+The VTStorage `Pod`s are configured to mount an empty dir or `StorageSpec` for storing data.
+
+For each `VTCluster` resource components, the Operator adds `Service` and `VMServiceScrape` in the same namespace prefixed with name from `VTCluster.metadata.name`.
+
+## Specification
+
+You can see the full actual specification of the `VTCluster` resource in the **[API docs -> VTCluster](https://docs.victoriametrics.com/operator/api/#vtcluster)**.
+
+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 `VTCluster` 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: VTCluster
+metadata:
+  name: example
+spec:
+  ...
+  clusterVersion: v0.1.0
+  storage:
+    image:
+      repository: victoriametrics/victoria-traces
+      tag: v0.1.0
+      pullPolicy: Always
+  # ...
+```
+
+Also, you can specify `imagePullSecrets` if you are pulling images from private repo:
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VTCluster
+metadata:
+  name: example
+spec:
+  imagePullSecrets:
+    - name: my-repo-secret
+# ...
+```
+
+## Resource management
+
+You can specify resources for each `VTCluster` resource components in the `spec` section of the `VTCluster` CRD.
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VTCluster
+metadata:
+  name: resources
+spec:
+    # ...
+    storage:
+      resources:
+          requests:
+            memory: "64Mi"
+            cpu: "250m"
+          limits:
+            memory: "128Mi"
+            cpu: "500m"
+    # ...
+```
+
+If these parameters are not specified, then,
+by default all `VTCluster` pods have resource requests and limits from the default values of the following [operator parameters](https://docs.victoriametrics.com/operator/configuration):
+
+- `VM_VTCLUSTERDEFAULT_STORAGE_RESOURCE_LIMIT_MEM` - default memory limit for `VTCluster.storage` pods,
+- `VM_VTCLUSTERDEFAULT_STORAGE_RESOURCE_LIMIT_CPU` - default cpu limit for `VTCluster.vtstorage` pods,
+- `VM_VTCLUSTERDEFAULT_STORAGE_RESOURCE_REQUEST_MEM` - default memory limit for `VTCluster.storage` pods,
+- `VM_VTCLUSTERDEFAULT_STORAGE_RESOURCE_REQUEST_CPU` - default cpu limit for `VTCluster.storage` pods.
+
+These default parameters will be used if:
+
+- `VM_VTCLUSTERDEFAULT_USEDEFAULTRESOURCES` is set to `true` (default value),
+- `VTCluster` CR doesn't have `resources` field in `spec` section for component.
+
+Field `resources` in `VTSingle` spec have higher priority than operator parameters.
+
+If you set `VM_VTCLUSTERDEFAULT_USEDEFAULTRESOURCES` to `false` and don't specify `resources` in `VTSingle` CRD,
+then `VTSingle` 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 `insert` and `select` 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 `select` and `insert` 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: VTCluster
+metadata:
+  name: with-balancer
+spec:
+  insert:
+    replicaCount: 2
+  select:
+    replicaCount: 2
+  storage:
+    retentionPeriod: "1y"
+    replicaCount: 2
+  requestsLoadBalancer:
+    enabled: true
+    spec:
+     replicaCount: 2
+```
+
+ Operator will deploy `VMAuth` deployment with 2 replicas. And update insert and select services to point to `vmauth`.
+ In addition, operator will create 3 additional services with the following pattern:
+
+- vtinsertint-CLUSTER_NAME - needed for select pod discovery
+- vtselectint-CLUSTER_NAME - needed for insert pod discovery
+- vtclusterlb-CLUSTER_NAME - needed for metrics collection and exposing `select` and `insert` components via `VMAuth` balancer.
+
+The `requestsLoadBalancer` feature works transparently and is managed entirely by the `VTCluster` operator, 
+with no direct access to the underlying [VMAuth](https://docs.victoriametrics.com/victoriametrics/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: VTCluster
+metadata:
+  name: sample
+spec:
+  insert:
+    replicaCount: 1
+  select:
+    replicaCount: 1
+  storage:
+    retentionPeriod: "1y"
+    replicaCount: 2
+```
diff --git a/docs/resources/vtsingle.md b/docs/resources/vtsingle.md
new file mode 100644
index 00000000..2f0f1163
--- /dev/null
+++ b/docs/resources/vtsingle.md
@@ -0,0 +1,189 @@
+---
+weight: 20
+title: VTSingle
+menu:
+  docs:
+    identifier: operator-cr-vtsingle
+    parent: operator-cr
+    weight: 20
+aliases:
+  - /operator/resources/vtsingle/
+  - /operator/resources/vtsingle/index.html
+tags:
+  - kubernetes
+  - metrics
+---
+`VTSingle` represents database for storing traces.
+The `VTSingle` CRD declaratively defines a [single-node VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/)
+installation to run in a Kubernetes cluster.
+
+For each `VTSingle` resource, the Operator deploys a properly configured `Deployment` in the same namespace.
+The VTSingle `Pod`s are configured to mount an empty dir or `PersistentVolumeClaimSpec` for storing data.
+Deployment update strategy set to [recreate](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#recreate-deployment).
+No more than one replica allowed.
+
+For each `VTSingle` resource, the Operator adds `Service` and `VMServiceScrape` in the same namespace prefixed with name from `VTSingle.metadata.name`.
+
+## Specification
+
+You can see the full actual specification of the `VTSingle` resource in the **[API docs -> VTSingle](https://docs.victoriametrics.com/operator/api/#vtsingle)**.
+
+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.
+
+## High availability
+
+`VTSingle` doesn't support high availability. Consider using [`VTCluster`](https://docs.victoriametrics.com/operator/resources/vtcluster/) or multiple `VTSingle` resources.
+
+## Version management
+
+To set `VTSingle` version add `spec.image.tag` name from [releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases)
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VTSingle
+metadata:
+  name: example
+spec:
+  image:
+    repository: victoriametrics/victoria-traces
+    tag: v0.1.0
+    pullPolicy: Always
+  # ...
+```
+
+Also, you can specify `imagePullSecrets` if you are pulling images from private repo:
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VTSingle
+metadata:
+  name: example
+spec:
+  image:
+    repository: victoriametrics/victoria-traces
+    tag: v1.4.0
+    pullPolicy: Always
+  imagePullSecrets:
+    - name: my-repo-secret
+# ...
+```
+
+## Resource management
+
+You can specify resources for each `VTSingle` resource in the `spec` section of the `VTSingle` CRD.
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VTSingle
+metadata:
+  name: resources-example
+spec:
+    # ...
+    resources:
+        requests:
+          memory: "64Mi"
+          cpu: "250m"
+        limits:
+          memory: "128Mi"
+          cpu: "500m"
+    # ...
+```
+
+If these parameters are not specified, then,
+by default all `VTSingle` pods have resource requests and limits from the default values of the following [operator parameters](https://docs.victoriametrics.com/operator/configuration):
+
+- `VM_VTSINGLEDEFAULT_RESOURCE_LIMIT_MEM` - default memory limit for `VTSingle` pods,
+- `VM_VTSINGLEDEFAULT_RESOURCE_LIMIT_CPU` - default memory limit for `VTSingle` pods,
+- `VM_VTSINGLEDEFAULT_RESOURCE_REQUEST_MEM` - default memory limit for `VTSingle` pods,
+- `VM_VTSINGLEDEFAULT_RESOURCE_REQUEST_CPU` - default memory limit for `VTSingle` pods.
+
+These default parameters will be used if:
+
+- `VM_VTSINGLEDEFAULT_USEDEFAULTRESOURCES` is set to `true` (default value),
+- `VTSingle` CR doesn't have `resources` field in `spec` section.
+
+Field `resources` in `VTSingle` spec have higher priority than operator parameters.
+
+If you set `VM_VTSINGLEDEFAULT_USEDEFAULTRESOURCES` to `false` and don't specify `resources` in `VTSingle` CRD,
+then `VTSingle` 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.
+
+## Examples
+
+
+### VTSingle with resources set
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VTSingle
+metadata:
+  name: example
+spec:
+  retentionPeriod: "12"
+  storage:
+    resources:
+      requests:
+        storage: 50Gi
+  resources:
+    requests:
+      memory: 500Mi
+      cpu: 500m
+    limits:
+      memory: 10Gi
+      cpu: 5
+```
+
+### VTSingle with existing volume
+
+create PVC and bind it to existing PV `existing-pv-name`
+
+> `spec.storageClassName` should match a storage class name of PersistentVolume
+
+```yaml
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: example-pvc
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 10Gi
+  storageClassName: standard
+  volumeName: existing-pv-name
+```
+
+create VTSingle with PVC `example-pvc` mounted
+
+> `spec.replicaCount` should be set to `1` since existing volume can only be mounted once
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VTSingle
+metadata:
+  name: example
+spec:
+  replicaCount: 1
+  retentionPeriod: "12"
+  extraArgs:
+    storageDataPath: /vt-data
+  volumes:
+    - name: vtstorage
+      persistentVolumeClaim:
+        claimName: example-pvc
+  volumeMounts:
+    - name: vtstorage
+      mountPath: /vt-data
+  resources:
+    requests:
+      memory: 500Mi
+      cpu: 500m
+    limits:
+      memory: 10Gi
+      cpu: 5
+```
diff --git a/internal/config/config.go b/internal/config/config.go
index 631b0c9b..9ecca064 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -37,6 +37,7 @@ var (
 		"VM_METRICS_VERSION": "v1.125.0",
 		"VM_LOGS_VERSION":    "v1.33.0",
 		"VM_ANOMALY_VERSION": "v1.25.2",
+		"VM_TRACES_VERSION":  "v0.1.0",
 	}
 )
 
@@ -184,6 +185,26 @@ type BaseOperatorConf struct {
 		ConfigReloaderMemory string `env:"-"`
 	} `prefix:"VLSINGLEDEFAULT_"`
 
+	VTSingleDefault struct {
+		Image               string `default:"victoriametrics/victoria-traces"`
+		Version             string `env:",expand" default:"${VM_TRACES_VERSION}"`
+		ConfigReloadImage   string `env:"-"`
+		Port                string `default:"10428"`
+		UseDefaultResources bool   `default:"true" env:"USEDEFAULTRESOURCES"`
+		Resource            struct {
+			Limit struct {
+				Mem string `default:"1500Mi"`
+				Cpu string `default:"1200m"`
+			} `prefix:"LIMIT_"`
+			Request struct {
+				Mem string `default:"500Mi"`
+				Cpu string `default:"150m"`
+			} `prefix:"REQUEST_"`
+		} `prefix:"RESOURCE_"`
+		ConfigReloaderCPU    string `env:"-"`
+		ConfigReloaderMemory string `env:"-"`
+	} `prefix:"VTSINGLEDEFAULT_"`
+
 	VMAlertDefault struct {
 		Image               string `default:"victoriametrics/vmalert"`
 		Version             string `env:",expand" default:"${VM_METRICS_VERSION}"`
@@ -437,6 +458,55 @@ type BaseOperatorConf struct {
 		} `prefix:"VLINSERTDEFAULT_"`
 	} `prefix:"VLCLUSTERDEFAULT_"`
 
+	VTClusterDefault struct {
+		UseDefaultResources bool `default:"true" env:"USEDEFAULTRESOURCES"`
+		SelectDefault       struct {
+			Image    string `default:"victoriametrics/victoria-traces"`
+			Version  string `env:",expand" default:"${VM_TRACES_VERSION}"`
+			Port     string `default:"10471"`
+			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:"SELECT_"`
+		StorageDefault struct {
+			Image    string `default:"victoriametrics/victoria-traces"`
+			Version  string `env:",expand" default:"${VM_TRACES_VERSION}"`
+			Port     string `default:"10491"`
+			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:"STORAGE_"`
+		InsertDefault struct {
+			Image    string `default:"victoriametrics/victoria-traces"`
+			Version  string `env:",expand" default:"${VM_TRACES_VERSION}"`
+			Port     string `default:"10481"`
+			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:"INSERT_"`
+	} `prefix:"VTCLUSTERDEFAULT_"`
+
 	EnabledPrometheusConverter struct {
 		PodMonitor         bool `default:"true" env:"PODMONITOR"`
 		ServiceScrape      bool `default:"true" env:"SERVICESCRAPE"`
@@ -600,7 +670,6 @@ func (boc BaseOperatorConf) Validate() error {
 	if err := validateResource("vmstorage", Resource(boc.VMClusterDefault.VMStorageDefault.Resource)); err != nil {
 		return err
 	}
-
 	if err := validateResource("vmsingle", Resource(boc.VMSingleDefault.Resource)); err != nil {
 		return err
 	}
@@ -616,6 +685,30 @@ func (boc BaseOperatorConf) Validate() error {
 	if err := validateResource("vmanomaly", Resource(boc.VMAnomalyDefault.Resource)); err != nil {
 		return err
 	}
+	if err := validateResource("vlsingle", Resource(boc.VLSingleDefault.Resource)); err != nil {
+		return err
+	}
+	if err := validateResource("vlselect", Resource(boc.VLClusterDefault.VLSelectDefault.Resource)); err != nil {
+		return err
+	}
+	if err := validateResource("vlinsert", Resource(boc.VLClusterDefault.VLInsertDefault.Resource)); err != nil {
+		return err
+	}
+	if err := validateResource("vlstorage", Resource(boc.VLClusterDefault.VLStorageDefault.Resource)); err != nil {
+		return err
+	}
+	if err := validateResource("vtsingle", Resource(boc.VTSingleDefault.Resource)); err != nil {
+		return err
+	}
+	if err := validateResource("vtselect", Resource(boc.VTClusterDefault.SelectDefault.Resource)); err != nil {
+		return err
+	}
+	if err := validateResource("vtinsert", Resource(boc.VTClusterDefault.InsertDefault.Resource)); err != nil {
+		return err
+	}
+	if err := validateResource("vtstorage", Resource(boc.VTClusterDefault.StorageDefault.Resource)); err != nil {
+		return err
+	}
 
 	return nil
 }
diff --git a/internal/controller/operator/factory/build/defaults.go b/internal/controller/operator/factory/build/defaults.go
index 49414674..e1cf2074 100644
--- a/internal/controller/operator/factory/build/defaults.go
+++ b/internal/controller/operator/factory/build/defaults.go
@@ -33,6 +33,8 @@ func AddDefaults(scheme *runtime.Scheme) {
 	scheme.AddTypeDefaultingFunc(&vmv1.VLSingle{}, addVLSingleDefaults)
 	scheme.AddTypeDefaultingFunc(&vmv1.VLCluster{}, addVLClusterDefaults)
 	scheme.AddTypeDefaultingFunc(&vmv1.VLAgent{}, addVLAgentDefaults)
+	scheme.AddTypeDefaultingFunc(&vmv1.VTSingle{}, addVTSingleDefaults)
+	scheme.AddTypeDefaultingFunc(&vmv1.VTCluster{}, addVTClusterDefaults)
 	scheme.AddTypeDefaultingFunc(&vmv1.VMAnomaly{}, addVMAnomalyDefaults)
 	scheme.AddTypeDefaultingFunc(&vmv1beta1.VMServiceScrape{}, addVMServiceScrapeDefaults)
 }
@@ -292,6 +294,13 @@ func addVLSingleDefaults(objI any) {
 	addDefaultsToCommonParams(&cr.Spec.CommonDefaultableParams, nil, &cv)
 }
 
+func addVTSingleDefaults(objI any) {
+	cr := objI.(*vmv1.VTSingle)
+	c := getCfg()
+	cv := config.ApplicationDefaults(c.VTSingleDefault)
+	addDefaultsToCommonParams(&cr.Spec.CommonDefaultableParams, nil, &cv)
+}
+
 func addVMAlertmanagerDefaults(objI any) {
 	cr := objI.(*vmv1beta1.VMAlertmanager)
 	c := getCfg()
@@ -649,8 +658,174 @@ func addVMServiceScrapeDefaults(objI any) {
 
 const (
 	vlStorageDefaultDBPath = "/vlstorage-data"
+	vtStorageDefaultDBPath = "/vtstorage-data"
 )
 
+func addVTClusterDefaults(objI any) {
+	cr := objI.(*vmv1.VTCluster)
+	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.Storage != nil {
+		if cr.Spec.Storage.UseStrictSecurity == nil {
+			cr.Spec.Storage.UseStrictSecurity = &useStrictSecurity
+		}
+		if cr.Spec.Storage.DisableSelfServiceScrape == nil {
+			cr.Spec.Storage.DisableSelfServiceScrape = &c.DisableSelfServiceScrapeCreation
+		}
+		cr.Spec.Storage.ImagePullSecrets = append(cr.Spec.Storage.ImagePullSecrets, cr.Spec.ImagePullSecrets...)
+
+		if cr.Spec.Storage.Image.Repository == "" {
+			cr.Spec.Storage.Image.Repository = c.VTClusterDefault.StorageDefault.Image
+		}
+		cr.Spec.Storage.Image.Repository = formatContainerImage(c.ContainerRegistry, cr.Spec.Storage.Image.Repository)
+
+		if cr.Spec.Storage.Image.Tag == "" {
+			if cr.Spec.ClusterVersion != "" {
+				cr.Spec.Storage.Image.Tag = cr.Spec.ClusterVersion
+			} else {
+				cr.Spec.Storage.Image.Tag = c.VTClusterDefault.StorageDefault.Version
+			}
+		}
+
+		if cr.Spec.Storage.Port == "" {
+			cr.Spec.Storage.Port = c.VTClusterDefault.StorageDefault.Port
+		}
+
+		if cr.Spec.Storage.DNSPolicy == "" {
+			cr.Spec.Storage.DNSPolicy = corev1.DNSClusterFirst
+		}
+		if cr.Spec.Storage.SchedulerName == "" {
+			cr.Spec.Storage.SchedulerName = "default-scheduler"
+		}
+		if cr.Spec.Storage.Image.PullPolicy == "" {
+			cr.Spec.Storage.Image.PullPolicy = corev1.PullIfNotPresent
+		}
+		if cr.Spec.Storage.StorageDataPath == "" {
+			cr.Spec.Storage.StorageDataPath = vtStorageDefaultDBPath
+		}
+		if cr.Spec.Storage.UseDefaultResources == nil {
+			cr.Spec.Storage.UseDefaultResources = &c.VTClusterDefault.UseDefaultResources
+		}
+		cr.Spec.Storage.Resources = Resources(cr.Spec.Storage.Resources,
+			config.Resource(c.VTClusterDefault.StorageDefault.Resource),
+			*cr.Spec.Storage.UseDefaultResources,
+		)
+	}
+
+	if cr.Spec.Insert != nil {
+		if cr.Spec.Insert.UseStrictSecurity == nil {
+			cr.Spec.Insert.UseStrictSecurity = &useStrictSecurity
+		}
+		if cr.Spec.Insert.DisableSelfServiceScrape == nil {
+			cr.Spec.Insert.DisableSelfServiceScrape = &c.DisableSelfServiceScrapeCreation
+		}
+		cr.Spec.Insert.ImagePullSecrets = append(cr.Spec.Insert.ImagePullSecrets, cr.Spec.ImagePullSecrets...)
+
+		if cr.Spec.Insert.Image.Repository == "" {
+			cr.Spec.Insert.Image.Repository = c.VTClusterDefault.InsertDefault.Image
+		}
+		cr.Spec.Insert.Image.Repository = formatContainerImage(c.ContainerRegistry, cr.Spec.Insert.Image.Repository)
+		if cr.Spec.Insert.Image.Tag == "" {
+			if cr.Spec.ClusterVersion != "" {
+				cr.Spec.Insert.Image.Tag = cr.Spec.ClusterVersion
+			} else {
+				cr.Spec.Insert.Image.Tag = c.VTClusterDefault.InsertDefault.Version
+			}
+		}
+		if cr.Spec.Insert.Port == "" {
+			cr.Spec.Insert.Port = c.VTClusterDefault.InsertDefault.Port
+		}
+		if cr.Spec.Insert.UseDefaultResources == nil {
+			cr.Spec.Insert.UseDefaultResources = &c.VTClusterDefault.UseDefaultResources
+		}
+		cr.Spec.Insert.Resources = Resources(cr.Spec.Insert.Resources,
+			config.Resource(c.VTClusterDefault.InsertDefault.Resource),
+			*cr.Spec.Insert.UseDefaultResources,
+		)
+
+	}
+
+	if cr.Spec.Select != nil {
+		if cr.Spec.Select.UseStrictSecurity == nil {
+			cr.Spec.Select.UseStrictSecurity = &useStrictSecurity
+		}
+		if cr.Spec.Select.DisableSelfServiceScrape == nil {
+			cr.Spec.Select.DisableSelfServiceScrape = &c.DisableSelfServiceScrapeCreation
+		}
+
+		cr.Spec.Select.ImagePullSecrets = append(cr.Spec.Select.ImagePullSecrets, cr.Spec.ImagePullSecrets...)
+
+		if cr.Spec.Select.Image.Repository == "" {
+			cr.Spec.Select.Image.Repository = c.VTClusterDefault.SelectDefault.Image
+		}
+		cr.Spec.Select.Image.Repository = formatContainerImage(c.ContainerRegistry, cr.Spec.Select.Image.Repository)
+		if cr.Spec.Select.Image.Tag == "" {
+			if cr.Spec.ClusterVersion != "" {
+				cr.Spec.Select.Image.Tag = cr.Spec.ClusterVersion
+			} else {
+				cr.Spec.Select.Image.Tag = c.VTClusterDefault.SelectDefault.Version
+			}
+		}
+		if cr.Spec.Select.Port == "" {
+			cr.Spec.Select.Port = c.VTClusterDefault.SelectDefault.Port
+		}
+
+		if cr.Spec.Select.DNSPolicy == "" {
+			cr.Spec.Select.DNSPolicy = corev1.DNSClusterFirst
+		}
+		if cr.Spec.Select.SchedulerName == "" {
+			cr.Spec.Select.SchedulerName = "default-scheduler"
+		}
+		if cr.Spec.Select.Image.PullPolicy == "" {
+			cr.Spec.Select.Image.PullPolicy = corev1.PullIfNotPresent
+		}
+
+		if cr.Spec.Select.UseDefaultResources == nil {
+			cr.Spec.Select.UseDefaultResources = &c.VTClusterDefault.UseDefaultResources
+		}
+		cr.Spec.Select.Resources = Resources(cr.Spec.Select.Resources,
+			config.Resource(c.VTClusterDefault.SelectDefault.Resource),
+			*cr.Spec.Select.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...)
+
+		cv := config.ApplicationDefaults(c.VMAuthDefault)
+		addDefaultsToCommonParams(&cr.Spec.RequestsLoadBalancer.Spec.CommonDefaultableParams, nil, &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
+		}
+	}
+}
+
 func addVLClusterDefaults(objI any) {
 	cr := objI.(*vmv1.VLCluster)
 	c := getCfg()
diff --git a/internal/controller/operator/factory/finalize/vlcluster.go b/internal/controller/operator/factory/finalize/vlcluster.go
index 5f4ca582..73da3e3d 100644
--- a/internal/controller/operator/factory/finalize/vlcluster.go
+++ b/internal/controller/operator/factory/finalize/vlcluster.go
@@ -16,7 +16,7 @@ import (
 	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
 )
 
-func OnVClusterDelete(ctx context.Context, rclient client.Client, cr *vmv1.VLCluster) error {
+func OnVLClusterDelete(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 {
diff --git a/internal/controller/operator/factory/finalize/vtcluster.go b/internal/controller/operator/factory/finalize/vtcluster.go
new file mode 100644
index 00000000..a8ad3267
--- /dev/null
+++ b/internal/controller/operator/factory/finalize/vtcluster.go
@@ -0,0 +1,210 @@
+package finalize
+
+import (
+	"context"
+	"fmt"
+
+	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/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"
+)
+
+// OnVTClusterDelete removes all child objects and releases finalizers
+func OnVTClusterDelete(ctx context.Context, rclient client.Client, cr *vmv1.VTCluster) error {
+
+	if cr.Spec.Insert != nil {
+		if err := OnVTInsertDelete(ctx, rclient, cr, cr.Spec.Insert); err != nil {
+			return fmt.Errorf("cannot remove insert component objects: %w", err)
+		}
+	}
+
+	if cr.Spec.Select != nil {
+		if err := OnVTSelectDelete(ctx, rclient, cr, cr.Spec.Select); err != nil {
+			return fmt.Errorf("cannot remove select component objects: %w", err)
+		}
+	}
+	if cr.Spec.Storage != nil {
+		if err := OnVTStorageDelete(ctx, rclient, cr, cr.Spec.Storage); 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 := OnVTClusterLoadBalancerDelete(ctx, rclient, cr); err != nil {
+			return fmt.Errorf("cannot delete cluster loadbalancer components: %w", err)
+		}
+	}
+	return removeFinalizeObjByName(ctx, rclient, cr, cr.Name, cr.Namespace)
+
+}
+
+// OnVTInsertDelete removes all objects related to vtinsert component
+func OnVTInsertDelete(ctx context.Context, rclient client.Client, cr *vmv1.VTCluster, obj *vmv1.VTInsert) error {
+	objMeta := metav1.ObjectMeta{
+		Namespace: cr.Namespace,
+		Name:      cr.GetVTInsertName(),
+	}
+	objsToRemove := []client.Object{
+		&appsv1.Deployment{ObjectMeta: objMeta},
+		&corev1.Service{ObjectMeta: objMeta},
+	}
+	if obj.ServiceSpec != nil && !obj.ServiceSpec.UseAsDefault {
+		objsToRemove = append(objsToRemove, &corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: cr.Namespace,
+				Name:      obj.ServiceSpec.NameOrDefault(cr.GetVTInsertName()),
+			},
+		})
+	}
+	if obj.PodDisruptionBudget != nil {
+		objsToRemove = append(objsToRemove, &policyv1.PodDisruptionBudget{ObjectMeta: objMeta})
+	}
+	if obj.HPA != nil {
+		objsToRemove = append(objsToRemove, &autoscalingv2.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.GetVTInsertLBName(), Namespace: cr.Namespace}})
+	}
+
+	if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableInsertBalancing {
+		objsToRemove = append(objsToRemove, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: cr.GetVTInsertLBName(), 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
+}
+
+// OnVTInsertDelete removes all objects related to vtinsert component
+func OnVTSelectDelete(ctx context.Context, rclient client.Client, cr *vmv1.VTCluster, obj *vmv1.VTSelect) error {
+	objMeta := metav1.ObjectMeta{
+		Namespace: cr.Namespace,
+		Name:      cr.GetVTSelectName(),
+	}
+	objsToRemove := []client.Object{
+		&appsv1.Deployment{ObjectMeta: objMeta},
+		&corev1.Service{ObjectMeta: objMeta},
+	}
+	if obj.ServiceSpec != nil && !obj.ServiceSpec.UseAsDefault {
+		objsToRemove = append(objsToRemove, &corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: cr.Namespace,
+				Name:      obj.ServiceSpec.NameOrDefault(cr.GetVTSelectName()),
+			},
+		})
+	}
+	if obj.PodDisruptionBudget != nil {
+		objsToRemove = append(objsToRemove, &policyv1.PodDisruptionBudget{ObjectMeta: objMeta})
+	}
+	if obj.HPA != nil {
+		objsToRemove = append(objsToRemove, &autoscalingv2.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.GetVTSelectLBName(), Namespace: cr.Namespace}})
+	}
+	if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableSelectBalancing {
+		objsToRemove = append(objsToRemove, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: cr.GetVTSelectLBName(), 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
+}
+
+// OnVTInsertDelete removes all objects related to vtinsert component
+func OnVTStorageDelete(ctx context.Context, rclient client.Client, cr *vmv1.VTCluster, obj *vmv1.VTStorage) error {
+	objMeta := metav1.ObjectMeta{
+		Namespace: cr.Namespace,
+		Name:      cr.GetVTStorageName(),
+	}
+	objsToRemove := []client.Object{
+		&appsv1.StatefulSet{ObjectMeta: objMeta},
+		&corev1.Service{ObjectMeta: objMeta},
+	}
+	if obj.ServiceSpec != nil && !obj.ServiceSpec.UseAsDefault {
+		objsToRemove = append(objsToRemove, &corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: cr.Namespace,
+				Name:      obj.ServiceSpec.NameOrDefault(cr.GetVTStorageName()),
+			},
+		})
+	}
+	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
+}
+
+// OnVTClusterLoadBalancerDelete removes vmauth loadbalancer components for vtcluster
+func OnVTClusterLoadBalancerDelete(ctx context.Context, rclient client.Client, cr *vmv1.VTCluster) error {
+	lbMeta := metav1.ObjectMeta{
+		Namespace: cr.Namespace,
+		Name:      cr.GetVMAuthLBName(),
+	}
+
+	objsToRemove := []client.Object{
+		&appsv1.Deployment{ObjectMeta: lbMeta},
+		&corev1.Secret{ObjectMeta: lbMeta},
+		&corev1.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.Select != nil {
+		if !ptr.Deref(cr.Spec.Select.DisableSelfServiceScrape, false) {
+			objsToRemove = append(objsToRemove, &vmv1beta1.VMServiceScrape{
+				ObjectMeta: metav1.ObjectMeta{Name: cr.GetVTSelectLBName(), Namespace: cr.Namespace}})
+		}
+		objsToRemove = append(objsToRemove, &corev1.Service{ObjectMeta: metav1.ObjectMeta{
+			Name:      cr.GetVTSelectLBName(),
+			Namespace: cr.Namespace,
+		}})
+	}
+	if cr.Spec.Insert != nil {
+		if !ptr.Deref(cr.Spec.Insert.DisableSelfServiceScrape, false) {
+			objsToRemove = append(objsToRemove, &vmv1beta1.VMServiceScrape{
+				ObjectMeta: metav1.ObjectMeta{Name: cr.GetVTInsertLBName(), Namespace: cr.Namespace}})
+		}
+		objsToRemove = append(objsToRemove, &corev1.Service{ObjectMeta: metav1.ObjectMeta{
+			Name:      cr.GetVTInsertLBName(),
+			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/finalize/vtsingle.go b/internal/controller/operator/factory/finalize/vtsingle.go
new file mode 100644
index 00000000..b56d5069
--- /dev/null
+++ b/internal/controller/operator/factory/finalize/vtsingle.go
@@ -0,0 +1,38 @@
+package finalize
+
+import (
+	"context"
+
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+)
+
+// OnVTSingleDelete deletes all vtsingle related resources
+func OnVTSingleDelete(ctx context.Context, rclient client.Client, cr *vmv1.VTSingle) error {
+	// check deployment
+	if err := removeFinalizeObjByName(ctx, rclient, &appsv1.Deployment{}, cr.PrefixedName(), cr.Namespace); err != nil {
+		return err
+	}
+	// check service
+	if err := removeFinalizeObjByName(ctx, rclient, &corev1.Service{}, cr.PrefixedName(), cr.Namespace); err != nil {
+		return err
+	}
+	if cr.Spec.Storage != nil {
+		if err := removeFinalizeObjByName(ctx, rclient, &corev1.PersistentVolumeClaim{}, cr.PrefixedName(), cr.Namespace); err != nil {
+			return err
+		}
+	}
+	if cr.Spec.ServiceSpec != nil {
+		if err := removeFinalizeObjByName(ctx, rclient, &corev1.Service{}, cr.Spec.ServiceSpec.NameOrDefault(cr.PrefixedName()), cr.Namespace); err != nil {
+			return err
+		}
+	}
+	if err := deleteSA(ctx, rclient, cr); err != nil {
+		return err
+	}
+
+	return removeFinalizeObjByName(ctx, rclient, cr, cr.Name, cr.Namespace)
+}
diff --git a/internal/controller/operator/factory/vmauth/vmusers_config.go b/internal/controller/operator/factory/vmauth/vmusers_config.go
index 4387b434..4f772320 100644
--- a/internal/controller/operator/factory/vmauth/vmusers_config.go
+++ b/internal/controller/operator/factory/vmauth/vmusers_config.go
@@ -327,6 +327,10 @@ var crdNameToObject = map[string]objectWithURL{
 	"VLCluster/vlinsert":  newClusterWithURL("vlinsert"),
 	"VLCluster/vlstorage": newClusterWithURL("vlstorage"),
 	"VLAgent":             &vmv1.VLAgent{},
+	"VTSingle":            &vmv1.VTSingle{},
+	"VTCluster/vtselect":  newClusterWithURL("vtselect"),
+	"VTCluster/vtinsert":  newClusterWithURL("vtinsert"),
+	"VTCluster/vtstorage": newClusterWithURL("vtstorage"),
 }
 
 // helper interface to restore VMCluster type
@@ -336,13 +340,13 @@ type unwrapObject interface {
 
 var clusterComponentToURL = map[string]func(obj client.Object) string{
 	"vminsert": func(obj client.Object) string {
-		return obj.(*vmv1beta1.VMCluster).VMInsertURL()
+		return obj.(*vmv1beta1.VMCluster).InsertURL()
 	},
 	"vmselect": func(obj client.Object) string {
-		return obj.(*vmv1beta1.VMCluster).VMSelectURL()
+		return obj.(*vmv1beta1.VMCluster).SelectURL()
 	},
 	"vmstorage": func(obj client.Object) string {
-		return obj.(*vmv1beta1.VMCluster).VMStorageURL()
+		return obj.(*vmv1beta1.VMCluster).StorageURL()
 	},
 	"vlinsert": func(obj client.Object) string {
 		return obj.(*vmv1.VLCluster).InsertURL()
@@ -353,6 +357,15 @@ var clusterComponentToURL = map[string]func(obj client.Object) string{
 	"vlstorage": func(obj client.Object) string {
 		return obj.(*vmv1.VLCluster).StorageURL()
 	},
+	"vtinsert": func(obj client.Object) string {
+		return obj.(*vmv1.VTCluster).InsertURL()
+	},
+	"vtselect": func(obj client.Object) string {
+		return obj.(*vmv1.VTCluster).SelectURL()
+	},
+	"vtstorage": func(obj client.Object) string {
+		return obj.(*vmv1.VTCluster).StorageURL()
+	},
 }
 
 type clusterWithURL struct {
@@ -368,6 +381,8 @@ func newClusterWithURL(component string) *clusterWithURL {
 		clusterObj = &vmv1beta1.VMCluster{}
 	case strings.HasPrefix(component, "vl"):
 		clusterObj = &vmv1.VLCluster{}
+	case strings.HasPrefix(component, "vt"):
+		clusterObj = &vmv1.VTCluster{}
 	default:
 		panic(fmt.Sprintf("BUG: unexpected component name: %q", component))
 	}
@@ -724,6 +739,29 @@ func genURLMaps(userName string, refs []vmv1beta1.TargetRef, result yaml.MapSlic
 
 	}
 
+	appendPaths := func(paths []string, tr *vmv1beta1.TargetRef) []string {
+		if tr.CRD != nil {
+			switch tr.CRD.Kind {
+			case "VMCluster/vminsert":
+				if len(tr.TargetPathSuffix) == 0 {
+					return append(paths, "/insert/.*")
+				}
+				return addVMInsertPaths(paths)
+			case "VMCluster/vmselect":
+				if len(tr.TargetPathSuffix) == 0 {
+					return append(paths, "/select/.*", "/admin/.*")
+				}
+				return addVMSelectPaths(paths)
+			case "VLCluster/vlinsert", "VTCluster/vtinsert":
+				return append(paths, "/insert/.*")
+			case "VLCluster/vlselect", "VTCluster/vtselect":
+				return append(paths, "/select/.*")
+			}
+		}
+		return append(paths, "/.*")
+
+	}
+
 	for i := range refs {
 		var urlMap yaml.MapSlice
 		ref := refs[i]
@@ -740,22 +778,7 @@ func genURLMaps(userName string, refs []vmv1beta1.TargetRef, result yaml.MapSlic
 		case 0:
 			// special case for
 			// https://github.com/VictoriaMetrics/operator/issues/379
-			switch {
-			case len(refs) > 1 && ref.CRD != nil && ref.CRD.Kind == "VMCluster/vminsert":
-				if len(ref.TargetPathSuffix) == 0 {
-					paths = append(paths, "/insert/.*")
-				} else {
-					paths = addVMInsertPaths(paths)
-				}
-			case len(refs) > 1 && ref.CRD != nil && ref.CRD.Kind == "VMCluster/vmselect":
-				if len(ref.TargetPathSuffix) == 0 {
-					paths = append(paths, "/select/.*", "/admin/.*")
-				} else {
-					paths = addVMSelectPaths(paths)
-				}
-			default:
-				paths = append(paths, "/.*")
-			}
+			paths = appendPaths(paths, &ref)
 
 		case 1:
 			switch paths[0] {
diff --git a/internal/controller/operator/factory/vtcluster/cluster.go b/internal/controller/operator/factory/vtcluster/cluster.go
new file mode 100644
index 00000000..93b3be78
--- /dev/null
+++ b/internal/controller/operator/factory/vtcluster/cluster.go
@@ -0,0 +1,254 @@
+package vtcluster
+
+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 VTCluster object to the desired state
+func CreateOrUpdate(ctx context.Context, rclient client.Client, cr *vmv1.VTCluster) error {
+	var prevCR *vmv1.VTCluster
+	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.Storage != nil {
+		if err := createOrUpdateVMAuthLB(ctx, rclient, cr, prevCR); err != nil {
+			return fmt.Errorf("cannot reconcile vmauthlb: %w", err)
+		}
+	}
+
+	if err := createOrUpdateVTStorage(ctx, rclient, cr, prevCR); err != nil {
+		return fmt.Errorf("cannot reconcile storage: %w", err)
+	}
+	if err := createOrUpdateVTSelect(ctx, rclient, cr, prevCR); err != nil {
+		return fmt.Errorf("cannot reconcile select: %w", err)
+	}
+
+	if err := createOrUpdateVTInsert(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.VTCluster) error {
+	if prevCR == nil {
+		// fast path
+		return nil
+	}
+	vmst := cr.Spec.Storage
+	vmse := cr.Spec.Select
+	vmis := cr.Spec.Insert
+	prevSpec := prevCR.Spec
+	prevSt := prevSpec.Storage
+	prevSe := prevSpec.Select
+	prevIs := prevSpec.Insert
+	if prevSt != nil {
+		if vmst == nil {
+			if err := finalize.OnVTStorageDelete(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.GetVTStorageName()}
+			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.GetVTStorageName(), 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.OnVTSelectDelete(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.GetVTSelectName()}
+			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.GetVTSelectName(), 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.Select.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{
+					ObjectMeta: metav1.ObjectMeta{Name: cr.GetVTSelectName(), 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.GetVTSelectLBName(),
+				Namespace: cr.Namespace,
+			}}); err != nil {
+				return fmt.Errorf("cannot remove select lb service: %w", err)
+			}
+			if !ptr.Deref(cr.Spec.Select.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{
+					ObjectMeta: metav1.ObjectMeta{Name: cr.GetVTSelectLBName(), 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.OnVTInsertDelete(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.GetVTInsertName()}
+			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.GetVTInsertName(), 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.Insert.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{
+					ObjectMeta: metav1.ObjectMeta{Name: cr.GetVTInsertName(), 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.GetVTInsertLBName(),
+				Namespace: cr.Namespace,
+			}}); err != nil {
+				return fmt.Errorf("cannot remove insert lb service: %w", err)
+			}
+			if !ptr.Deref(cr.Spec.Insert.DisableSelfServiceScrape, false) {
+				if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{
+					ObjectMeta: metav1.ObjectMeta{Name: cr.GetVTInsertLBName(), 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.OnVTClusterLoadBalancerDelete(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/vtcluster/helpers.go b/internal/controller/operator/factory/vtcluster/helpers.go
new file mode 100644
index 00000000..f82bcd1a
--- /dev/null
+++ b/internal/controller/operator/factory/vtcluster/helpers.go
@@ -0,0 +1,54 @@
+package vtcluster
+
+import (
+	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.VTCluster
+	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.VTCluster, name string, selectorLabels map[string]string) *optsBuilder {
+	return &optsBuilder{
+		VTCluster:      cr,
+		prefixedName:   name,
+		finalLabels:    cr.FinalLabels(selectorLabels),
+		selectorLabels: selectorLabels,
+	}
+}
diff --git a/internal/controller/operator/factory/vtcluster/insert.go b/internal/controller/operator/factory/vtcluster/insert.go
new file mode 100644
index 00000000..65d91e8b
--- /dev/null
+++ b/internal/controller/operator/factory/vtcluster/insert.go
@@ -0,0 +1,328 @@
+package vtcluster
+
+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 createOrUpdateVTInsert(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) error {
+	if cr.Spec.Insert == nil {
+		return nil
+	}
+
+	if err := createOrUpdatePodDisruptionBudgetForVTInsert(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	if err := createOrUpdateVTInsertDeployment(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	insertSvc, err := createOrUpdateVTInsertService(ctx, rclient, cr, prevCR)
+	if err != nil {
+		return err
+	}
+	if err := createOrUpdateVTInsertHPA(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	if !ptr.Deref(cr.Spec.Insert.DisableSelfServiceScrape, false) {
+		svs := build.VMServiceScrapeForServiceWithSpec(insertSvc, cr.Spec.Insert)
+		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 VTInsert: %w", err)
+		}
+	}
+
+	return nil
+}
+
+func createOrUpdatePodDisruptionBudgetForVTInsert(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) error {
+	if cr.Spec.Insert.PodDisruptionBudget == nil {
+		return nil
+	}
+	b := newOptsBuilder(cr, cr.GetVTInsertName(), cr.VTInsertSelectorLabels())
+	pdb := build.PodDisruptionBudget(b, cr.Spec.Insert.PodDisruptionBudget)
+	var prevPDB *policyv1.PodDisruptionBudget
+	if prevCR != nil && prevCR.Spec.Insert.PodDisruptionBudget != nil {
+		prevB := newOptsBuilder(prevCR, prevCR.GetVTInsertName(), prevCR.VTInsertSelectorLabels())
+		prevPDB = build.PodDisruptionBudget(prevB, prevCR.Spec.Insert.PodDisruptionBudget)
+	}
+	return reconcile.PDB(ctx, rclient, pdb, prevPDB)
+}
+
+func createOrUpdateVTInsertDeployment(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) error {
+	var prevDeploy *appsv1.Deployment
+
+	if prevCR != nil && prevCR.Spec.Insert != nil {
+		var err error
+		prevDeploy, err = buildVTInsertDeployment(prevCR)
+		if err != nil {
+			return fmt.Errorf("cannot generate prev deploy spec: %w", err)
+		}
+	}
+	newDeployment, err := buildVTInsertDeployment(cr)
+	if err != nil {
+		return err
+	}
+	return reconcile.Deployment(ctx, rclient, newDeployment, prevDeploy, cr.Spec.Insert.HPA != nil)
+}
+
+func buildVTInsertDeployment(cr *vmv1.VTCluster) (*appsv1.Deployment, error) {
+
+	podSpec, err := buildVTInsertPodSpec(cr)
+	if err != nil {
+		return nil, err
+	}
+
+	strategyType := appsv1.RollingUpdateDeploymentStrategyType
+	if cr.Spec.Insert.UpdateStrategy != nil {
+		strategyType = *cr.Spec.Insert.UpdateStrategy
+	}
+	stsSpec := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:            cr.GetVTInsertName(),
+			Namespace:       cr.Namespace,
+			Labels:          cr.FinalLabels(cr.VTInsertSelectorLabels()),
+			Annotations:     cr.FinalAnnotations(),
+			OwnerReferences: cr.AsOwner(),
+			Finalizers:      []string{vmv1beta1.FinalizerName},
+		},
+		Spec: appsv1.DeploymentSpec{
+			Replicas:             cr.Spec.Insert.ReplicaCount,
+			RevisionHistoryLimit: cr.Spec.Insert.RevisionHistoryLimitCount,
+			MinReadySeconds:      cr.Spec.Insert.MinReadySeconds,
+			Strategy: appsv1.DeploymentStrategy{
+				Type:          strategyType,
+				RollingUpdate: cr.Spec.Insert.RollingUpdate,
+			},
+			Selector: &metav1.LabelSelector{
+				MatchLabels: cr.VTInsertSelectorLabels(),
+			},
+			Template: *podSpec,
+		},
+	}
+	build.DeploymentAddCommonParams(stsSpec, ptr.Deref(cr.Spec.Insert.UseStrictSecurity, false), &cr.Spec.Insert.CommonApplicationDeploymentParams)
+	return stsSpec, nil
+}
+
+func buildVTInsertPodSpec(cr *vmv1.VTCluster) (*corev1.PodTemplateSpec, error) {
+	args := []string{
+		fmt.Sprintf("-httpListenAddr=:%s", cr.Spec.Insert.Port),
+		"-internalselect.disable=true",
+	}
+	if cr.Spec.Insert.LogLevel != "" {
+		args = append(args, fmt.Sprintf("-loggerLevel=%s", cr.Spec.Insert.LogLevel))
+	}
+	if cr.Spec.Insert.LogFormat != "" {
+		args = append(args, fmt.Sprintf("-loggerFormat=%s", cr.Spec.Insert.LogFormat))
+	}
+
+	if cr.Spec.Storage != nil && cr.Spec.Storage.ReplicaCount != nil {
+		storageArg := "-storageNode="
+		for _, i := range cr.AvailableStorageNodeIDs("insert") {
+			// TODO: introduce TLS webserver config for storage nodes
+			storageArg += build.PodDNSAddress(cr.GetVTStorageName(), i, cr.Namespace, cr.Spec.Storage.Port, cr.Spec.ClusterDomainName)
+		}
+		storageArg = strings.TrimSuffix(storageArg, ",")
+		args = append(args, storageArg)
+	}
+	if len(cr.Spec.Insert.ExtraEnvs) > 0 || len(cr.Spec.Insert.ExtraEnvsFrom) > 0 {
+		args = append(args, "-envflag.enable=true")
+	}
+
+	var envs []corev1.EnvVar
+
+	envs = append(envs, cr.Spec.Insert.ExtraEnvs...)
+
+	ports := []corev1.ContainerPort{
+		{
+			Name:          "http",
+			Protocol:      "TCP",
+			ContainerPort: intstr.Parse(cr.Spec.Insert.Port).IntVal,
+		},
+	}
+
+	volumes := make([]corev1.Volume, 0)
+	volumes = append(volumes, cr.Spec.Insert.Volumes...)
+
+	vmMounts := make([]corev1.VolumeMount, 0)
+	vmMounts = append(vmMounts, cr.Spec.Insert.VolumeMounts...)
+
+	for _, s := range cr.Spec.Insert.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.Insert.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.Insert.ExtraArgs, "-")
+	sort.Strings(args)
+
+	insertContainers := corev1.Container{
+		Name:                     "vtinsert",
+		Image:                    fmt.Sprintf("%s:%s", cr.Spec.Insert.Image.Repository, cr.Spec.Insert.Image.Tag),
+		ImagePullPolicy:          cr.Spec.Insert.Image.PullPolicy,
+		Ports:                    ports,
+		Args:                     args,
+		VolumeMounts:             vmMounts,
+		Resources:                cr.Spec.Insert.Resources,
+		Env:                      envs,
+		EnvFrom:                  cr.Spec.Insert.ExtraEnvsFrom,
+		TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
+	}
+
+	insertContainers = build.Probe(insertContainers, cr.Spec.Insert)
+	operatorContainers := []corev1.Container{insertContainers}
+
+	build.AddStrictSecuritySettingsToContainers(cr.Spec.Insert.SecurityContext, operatorContainers, ptr.Deref(cr.Spec.Insert.UseStrictSecurity, false))
+	containers, err := k8stools.MergePatchContainers(operatorContainers, cr.Spec.Insert.Containers)
+	if err != nil {
+		return nil, err
+	}
+
+	for i := range cr.Spec.Insert.TopologySpreadConstraints {
+		if cr.Spec.Insert.TopologySpreadConstraints[i].LabelSelector == nil {
+			cr.Spec.Insert.TopologySpreadConstraints[i].LabelSelector = &metav1.LabelSelector{
+				MatchLabels: cr.VTInsertSelectorLabels(),
+			}
+		}
+	}
+
+	podSpec := &corev1.PodTemplateSpec{
+		ObjectMeta: metav1.ObjectMeta{
+			Labels:      cr.VTInsertPodLabels(),
+			Annotations: cr.VTInsertPodAnnotations(),
+		},
+		Spec: corev1.PodSpec{
+			Volumes:            volumes,
+			InitContainers:     cr.Spec.Insert.InitContainers,
+			Containers:         containers,
+			ServiceAccountName: cr.GetServiceAccountName(),
+		},
+	}
+
+	return podSpec, nil
+}
+
+func createOrUpdateVTInsertHPA(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) error {
+	if cr.Spec.Insert.HPA == nil {
+		return nil
+	}
+	targetRef := autoscalingv2.CrossVersionObjectReference{
+		Name:       cr.GetVTInsertName(),
+		Kind:       "Deployment",
+		APIVersion: "apps/v1",
+	}
+	t := newOptsBuilder(cr, cr.GetVTInsertName(), cr.VTInsertSelectorLabels())
+	newHPA := build.HPA(t, targetRef, cr.Spec.Insert.HPA)
+	var prevHPA *autoscalingv2.HorizontalPodAutoscaler
+	if prevCR != nil && prevCR.Spec.Insert.HPA != nil {
+		t = newOptsBuilder(prevCR, prevCR.GetVTInsertName(), prevCR.VTInsertSelectorLabels())
+		prevHPA = build.HPA(t, targetRef, prevCR.Spec.Insert.HPA)
+	}
+	return reconcile.HPA(ctx, rclient, newHPA, prevHPA)
+}
+
+func createOrUpdateVTInsertService(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) (*corev1.Service, error) {
+	newService := buildVTInsertService(cr)
+	var prevService, prevAdditionalService *corev1.Service
+	if prevCR != nil && prevCR.Spec.Insert != nil {
+		prevService = buildVTInsertService(prevCR)
+		prevAdditionalService = build.AdditionalServiceFromDefault(prevService, prevCR.Spec.Insert.ServiceSpec)
+	}
+	if err := cr.Spec.Insert.ServiceSpec.IsSomeAndThen(func(s *vmv1beta1.AdditionalServiceSpec) error {
+		additionalService := build.AdditionalServiceFromDefault(newService, s)
+		if additionalService.Name == newService.Name {
+			return fmt.Errorf("VTInsert 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.Insert != nil {
+			prevPort = prevCR.Spec.Insert.Port
+		}
+		if err := createOrUpdateLBProxyService(ctx, rclient, cr, prevCR, cr.GetVTInsertName(), cr.Spec.Insert.Port, prevPort, "vtinsert", cr.VMAuthLBSelectorLabels()); err != nil {
+			return nil, fmt.Errorf("cannot create lb svc for insert: %w", err)
+		}
+	}
+
+	return newService, nil
+}
+
+func buildVTInsertService(cr *vmv1.VTCluster) *corev1.Service {
+	t := &optsBuilder{
+		cr,
+		cr.GetVTInsertName(),
+		cr.FinalLabels(cr.VTInsertSelectorLabels()),
+		cr.VTInsertSelectorLabels(),
+		cr.Spec.Insert.ServiceSpec,
+	}
+
+	svc := build.Service(t, cr.Spec.Insert.Port, nil)
+	if cr.Spec.RequestsLoadBalancer.Enabled && !cr.Spec.RequestsLoadBalancer.DisableInsertBalancing {
+		svc.Name = cr.GetVTInsertLBName()
+		svc.Spec.ClusterIP = corev1.ClusterIPNone
+		svc.Spec.Type = corev1.ServiceTypeClusterIP
+		svc.Labels[vmauthLBServiceProxyJobNameLabel] = cr.GetVTInsertName()
+	}
+	return svc
+}
diff --git a/internal/controller/operator/factory/vtcluster/select.go b/internal/controller/operator/factory/vtcluster/select.go
new file mode 100644
index 00000000..f4eb7077
--- /dev/null
+++ b/internal/controller/operator/factory/vtcluster/select.go
@@ -0,0 +1,323 @@
+package vtcluster
+
+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 createOrUpdateVTSelect(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) error {
+	if cr.Spec.Select == nil {
+		return nil
+	}
+	b := newOptsBuilder(cr, cr.GetVTSelectName(), cr.VTSelectSelectorLabels())
+
+	if cr.Spec.Select.PodDisruptionBudget != nil {
+		pdb := build.PodDisruptionBudget(b, cr.Spec.Select.PodDisruptionBudget)
+		var prevPDB *policyv1.PodDisruptionBudget
+		if prevCR != nil && prevCR.Spec.Select.PodDisruptionBudget != nil {
+			prevB := newOptsBuilder(prevCR, prevCR.GetVTSelectName(), prevCR.VTSelectSelectorLabels())
+			prevPDB = build.PodDisruptionBudget(prevB, prevCR.Spec.Select.PodDisruptionBudget)
+		}
+		err := reconcile.PDB(ctx, rclient, pdb, prevPDB)
+		if err != nil {
+			return err
+		}
+	}
+	if err := createOrUpdateVTSelectHPA(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	selectSvc, err := createOrUpdateVTSelectService(ctx, rclient, cr, prevCR)
+	if err != nil {
+		return err
+	}
+	if !ptr.Deref(cr.Spec.Select.DisableSelfServiceScrape, false) {
+		svs := build.VMServiceScrapeForServiceWithSpec(selectSvc, cr.Spec.Select)
+		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 VTSelect: %w", err)
+		}
+	}
+	if err := createOrUpdateVTSelectDeployment(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+	return nil
+}
+
+func createOrUpdateVTSelectHPA(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) error {
+	if cr.Spec.Select.HPA == nil {
+		return nil
+	}
+	targetRef := autoscalingv2.CrossVersionObjectReference{
+		Name:       cr.GetVTSelectName(),
+		Kind:       "Deployment",
+		APIVersion: "apps/v1",
+	}
+	b := newOptsBuilder(cr, cr.GetVTSelectName(), cr.VTSelectSelectorLabels())
+	defaultHPA := build.HPA(b, targetRef, cr.Spec.Select.HPA)
+	var prevHPA *autoscalingv2.HorizontalPodAutoscaler
+	if prevCR != nil && prevCR.Spec.Select.HPA != nil {
+		b := newOptsBuilder(prevCR, prevCR.GetVTSelectName(), prevCR.VTSelectSelectorLabels())
+		prevHPA = build.HPA(b, targetRef, prevCR.Spec.Select.HPA)
+	}
+
+	return reconcile.HPA(ctx, rclient, defaultHPA, prevHPA)
+
+}
+
+func createOrUpdateVTSelectService(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) (*corev1.Service, error) {
+
+	var prevService, prevAdditionalService *corev1.Service
+	if prevCR != nil && prevCR.Spec.Select != nil {
+		prevService = buildVTSelectService(prevCR)
+		prevAdditionalService = build.AdditionalServiceFromDefault(prevService, prevCR.Spec.Select.ServiceSpec)
+	}
+	svc := buildVTSelectService(cr)
+	if err := cr.Spec.Select.ServiceSpec.IsSomeAndThen(func(s *vmv1beta1.AdditionalServiceSpec) error {
+		additionalService := build.AdditionalServiceFromDefault(svc, s)
+		if additionalService.Name == svc.Name {
+			return fmt.Errorf("VTSelect 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.Select != nil {
+			prevPort = prevCR.Spec.Select.Port
+		}
+		if err := createOrUpdateLBProxyService(ctx, rclient, cr, prevCR, cr.GetVTSelectName(), cr.Spec.Select.Port, prevPort, "vtselect", cr.VMAuthLBSelectorLabels()); err != nil {
+			return nil, fmt.Errorf("cannot create lb svc for select: %w", err)
+		}
+	}
+	return svc, nil
+}
+
+func buildVTSelectService(cr *vmv1.VTCluster) *corev1.Service {
+	b := &optsBuilder{
+		cr,
+		cr.GetVTSelectName(),
+		cr.FinalLabels(cr.VTSelectSelectorLabels()),
+		cr.VTSelectSelectorLabels(),
+		cr.Spec.Select.ServiceSpec,
+	}
+	svc := build.Service(b, cr.Spec.Select.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.GetVTSelectLBName()
+		svc.Spec.ClusterIP = corev1.ClusterIPNone
+		svc.Spec.Type = corev1.ServiceTypeClusterIP
+		svc.Labels[vmauthLBServiceProxyJobNameLabel] = cr.GetVTSelectName()
+	}
+
+	return svc
+
+}
+
+func createOrUpdateVTSelectDeployment(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) error {
+	var prevDep *appsv1.Deployment
+	if prevCR != nil && prevCR.Spec.Select != nil {
+		var err error
+		prevDep, err = buildVTSelectDeployment(prevCR)
+		if err != nil {
+			return fmt.Errorf("cannot build prev select spec: %w", err)
+		}
+	}
+	newDep, err := buildVTSelectDeployment(cr)
+	if err != nil {
+		return err
+	}
+
+	return reconcile.Deployment(ctx, rclient, newDep, prevDep, cr.Spec.Select.HPA != nil)
+}
+
+func buildVTSelectDeployment(cr *vmv1.VTCluster) (*appsv1.Deployment, error) {
+	podSpec, err := buildVTSelectPodSpec(cr)
+	if err != nil {
+		return nil, err
+	}
+	strategyType := appsv1.RollingUpdateDeploymentStrategyType
+	if cr.Spec.Select.UpdateStrategy != nil {
+		strategyType = *cr.Spec.Select.UpdateStrategy
+	}
+	depSpec := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:            cr.GetVTSelectName(),
+			Namespace:       cr.Namespace,
+			Labels:          cr.FinalLabels(cr.VTSelectSelectorLabels()),
+			Annotations:     cr.FinalAnnotations(),
+			OwnerReferences: cr.AsOwner(),
+			Finalizers:      []string{vmv1beta1.FinalizerName},
+		},
+		Spec: appsv1.DeploymentSpec{
+			Strategy: appsv1.DeploymentStrategy{
+				Type:          strategyType,
+				RollingUpdate: cr.Spec.Select.RollingUpdate,
+			},
+			Selector: &metav1.LabelSelector{
+				MatchLabels: cr.VTSelectSelectorLabels(),
+			},
+			Template: *podSpec,
+		},
+	}
+	build.DeploymentAddCommonParams(depSpec, ptr.Deref(cr.Spec.Select.UseStrictSecurity, false), &cr.Spec.Select.CommonApplicationDeploymentParams)
+	return depSpec, nil
+}
+
+func buildVTSelectPodSpec(cr *vmv1.VTCluster) (*corev1.PodTemplateSpec, error) {
+	args := []string{
+		fmt.Sprintf("-httpListenAddr=:%s", cr.Spec.Select.Port),
+		"-internalinsert.disable=true",
+	}
+	if cr.Spec.Select.LogLevel != "" {
+		args = append(args, fmt.Sprintf("-loggerLevel=%s", cr.Spec.Select.LogLevel))
+	}
+	if cr.Spec.Select.LogFormat != "" {
+		args = append(args, fmt.Sprintf("-loggerFormat=%s", cr.Spec.Select.LogFormat))
+	}
+
+	if cr.Spec.Storage != nil && cr.Spec.Storage.ReplicaCount != nil {
+		// TODO: check TLS
+		storageArg := "-storageNode="
+		for _, i := range cr.AvailableStorageNodeIDs("select") {
+			storageArg += build.PodDNSAddress(cr.GetVTStorageName(), i, cr.Namespace, cr.Spec.Storage.Port, cr.Spec.ClusterDomainName)
+		}
+		storageArg = strings.TrimSuffix(storageArg, ",")
+		args = append(args, storageArg)
+
+	}
+
+	if len(cr.Spec.Select.ExtraEnvs) > 0 || len(cr.Spec.Select.ExtraEnvsFrom) > 0 {
+		args = append(args, "-envflag.enable=true")
+	}
+
+	var envs []corev1.EnvVar
+	envs = append(envs, cr.Spec.Select.ExtraEnvs...)
+
+	var ports []corev1.ContainerPort
+	ports = append(ports, corev1.ContainerPort{
+		Name:          "http",
+		Protocol:      "TCP",
+		ContainerPort: intstr.Parse(cr.Spec.Select.Port).IntVal,
+	})
+
+	volumes := make([]corev1.Volume, 0)
+	volumes = append(volumes, cr.Spec.Select.Volumes...)
+
+	vmMounts := make([]corev1.VolumeMount, 0)
+	vmMounts = append(vmMounts, cr.Spec.Select.VolumeMounts...)
+
+	for _, s := range cr.Spec.Select.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.Select.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.Select.ExtraArgs, "-")
+	sort.Strings(args)
+	selectContainers := corev1.Container{
+		Name:                     "vtselect",
+		Image:                    fmt.Sprintf("%s:%s", cr.Spec.Select.Image.Repository, cr.Spec.Select.Image.Tag),
+		ImagePullPolicy:          cr.Spec.Select.Image.PullPolicy,
+		Ports:                    ports,
+		Args:                     args,
+		VolumeMounts:             vmMounts,
+		Resources:                cr.Spec.Select.Resources,
+		Env:                      envs,
+		EnvFrom:                  cr.Spec.Select.ExtraEnvsFrom,
+		TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
+		TerminationMessagePath:   "/dev/termination-log",
+	}
+
+	selectContainers = build.Probe(selectContainers, cr.Spec.Select)
+	operatorContainers := []corev1.Container{selectContainers}
+
+	build.AddStrictSecuritySettingsToContainers(cr.Spec.Select.SecurityContext, operatorContainers, ptr.Deref(cr.Spec.Select.UseStrictSecurity, false))
+	containers, err := k8stools.MergePatchContainers(operatorContainers, cr.Spec.Select.Containers)
+	if err != nil {
+		return nil, err
+	}
+
+	for i := range cr.Spec.Select.TopologySpreadConstraints {
+		if cr.Spec.Select.TopologySpreadConstraints[i].LabelSelector == nil {
+			cr.Spec.Select.TopologySpreadConstraints[i].LabelSelector = &metav1.LabelSelector{
+				MatchLabels: cr.VTSelectSelectorLabels(),
+			}
+		}
+	}
+
+	podSpec := &corev1.PodTemplateSpec{
+		ObjectMeta: metav1.ObjectMeta{
+			Labels:      cr.VTSelectPodLabels(),
+			Annotations: cr.VTSelectPodAnnotations(),
+		},
+		Spec: corev1.PodSpec{
+			Volumes:            volumes,
+			InitContainers:     cr.Spec.Select.InitContainers,
+			Containers:         containers,
+			ServiceAccountName: cr.GetServiceAccountName(),
+			RestartPolicy:      "Always",
+		},
+	}
+
+	return podSpec, nil
+}
diff --git a/internal/controller/operator/factory/vtcluster/storage.go b/internal/controller/operator/factory/vtcluster/storage.go
new file mode 100644
index 00000000..b4bc66ec
--- /dev/null
+++ b/internal/controller/operator/factory/vtcluster/storage.go
@@ -0,0 +1,315 @@
+package vtcluster
+
+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 createOrUpdateVTStorage(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) error {
+	if cr.Spec.Storage == nil {
+		return nil
+	}
+	b := newOptsBuilder(cr, cr.GetVTStorageName(), cr.VTStorageSelectorLabels())
+
+	if cr.Spec.Storage.PodDisruptionBudget != nil {
+		pdb := build.PodDisruptionBudget(b, cr.Spec.Storage.PodDisruptionBudget)
+		var prevPDB *policyv1.PodDisruptionBudget
+		if prevCR != nil && prevCR.Spec.Storage.PodDisruptionBudget != nil {
+			prevB := newOptsBuilder(prevCR, prevCR.GetVTStorageName(), prevCR.VTStorageSelectorLabels())
+			prevPDB = build.PodDisruptionBudget(prevB, prevCR.Spec.Storage.PodDisruptionBudget)
+		}
+		err := reconcile.PDB(ctx, rclient, pdb, prevPDB)
+		if err != nil {
+			return err
+		}
+	}
+	if err := createOrUpdateVTStorageSTS(ctx, rclient, cr, prevCR); err != nil {
+		return err
+	}
+
+	storageSvc, err := createOrUpdateVTStorageService(ctx, rclient, cr, prevCR)
+	if err != nil {
+		return err
+	}
+	if !ptr.Deref(cr.Spec.Storage.DisableSelfServiceScrape, false) {
+		err := reconcile.VMServiceScrapeForCRD(ctx, rclient, build.VMServiceScrapeForServiceWithSpec(storageSvc, cr.Spec.Storage))
+		if err != nil {
+			return fmt.Errorf("cannot create VMServiceScrape for VTStorage: %w", err)
+		}
+	}
+
+	return nil
+}
+
+func createOrUpdateVTStorageService(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) (*corev1.Service, error) {
+	t := &optsBuilder{
+		cr,
+		cr.GetVTStorageName(),
+		cr.FinalLabels(cr.VTStorageSelectorLabels()),
+		cr.VTStorageSelectorLabels(),
+		cr.Spec.Storage.ServiceSpec,
+	}
+	var prevService, prevAdditionalService *corev1.Service
+	if prevCR != nil && prevCR.Spec.Storage != nil {
+		prevT := &optsBuilder{
+			prevCR,
+			prevCR.GetVTStorageName(),
+			prevCR.FinalLabels(prevCR.VTStorageSelectorLabels()),
+			prevCR.VTStorageSelectorLabels(),
+			prevCR.Spec.Storage.ServiceSpec,
+		}
+
+		prevService = build.Service(prevT, prevCR.Spec.Storage.Port, func(svc *corev1.Service) {
+			svc.Spec.ClusterIP = "None"
+			svc.Spec.PublishNotReadyAddresses = true
+		})
+		prevAdditionalService = build.AdditionalServiceFromDefault(prevService, prevCR.Spec.Storage.ServiceSpec)
+
+	}
+	newHeadless := build.Service(t, cr.Spec.Storage.Port, func(svc *corev1.Service) {
+		svc.Spec.ClusterIP = "None"
+		svc.Spec.PublishNotReadyAddresses = true
+	})
+
+	if err := cr.Spec.Storage.ServiceSpec.IsSomeAndThen(func(s *vmv1beta1.AdditionalServiceSpec) error {
+		additionalService := build.AdditionalServiceFromDefault(newHeadless, s)
+		if additionalService.Name == newHeadless.Name {
+			return fmt.Errorf("VTStorage 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 createOrUpdateVTStorageSTS(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) error {
+	var prevSts *appsv1.StatefulSet
+
+	if prevCR != nil && prevCR.Spec.Storage != nil {
+		var err error
+		prevSts, err = buildVTStorageSTSSpec(prevCR)
+		if err != nil {
+			return fmt.Errorf("cannot build prev storage spec: %w", err)
+		}
+	}
+	newSts, err := buildVTStorageSTSSpec(cr)
+	if err != nil {
+		return err
+	}
+
+	stsOpts := reconcile.STSOptions{
+		HasClaim:       len(newSts.Spec.VolumeClaimTemplates) > 0,
+		SelectorLabels: cr.VTStorageSelectorLabels,
+		UpdateBehavior: cr.Spec.Storage.RollingUpdateStrategyBehavior,
+	}
+	return reconcile.HandleSTSUpdate(ctx, rclient, stsOpts, newSts, prevSts)
+}
+
+func buildVTStorageSTSSpec(cr *vmv1.VTCluster) (*appsv1.StatefulSet, error) {
+
+	podSpec, err := buildVTStoragePodSpec(cr)
+	if err != nil {
+		return nil, err
+	}
+
+	stsSpec := &appsv1.StatefulSet{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:            cr.GetVTStorageName(),
+			Namespace:       cr.Namespace,
+			Labels:          cr.FinalLabels(cr.VTStorageSelectorLabels()),
+			Annotations:     cr.FinalAnnotations(),
+			OwnerReferences: cr.AsOwner(),
+			Finalizers:      []string{vmv1beta1.FinalizerName},
+		},
+		Spec: appsv1.StatefulSetSpec{
+			Selector: &metav1.LabelSelector{
+				MatchLabels: cr.VTStorageSelectorLabels(),
+			},
+			UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
+				Type: cr.Spec.Storage.RollingUpdateStrategy,
+			},
+			Template:    *podSpec,
+			ServiceName: cr.GetVTStorageName(),
+		},
+	}
+	if cr.Spec.Storage.PersistentVolumeClaimRetentionPolicy != nil {
+		stsSpec.Spec.PersistentVolumeClaimRetentionPolicy = cr.Spec.Storage.PersistentVolumeClaimRetentionPolicy
+	}
+	build.StatefulSetAddCommonParams(stsSpec, ptr.Deref(cr.Spec.Storage.UseStrictSecurity, false), &cr.Spec.Storage.CommonApplicationDeploymentParams)
+	storageSpec := cr.Spec.Storage.Storage
+	storageSpec.IntoSTSVolume(cr.Spec.Storage.GetStorageVolumeName(), &stsSpec.Spec)
+	stsSpec.Spec.VolumeClaimTemplates = append(stsSpec.Spec.VolumeClaimTemplates, cr.Spec.Storage.ClaimTemplates...)
+
+	return stsSpec, nil
+}
+
+func buildVTStoragePodSpec(cr *vmv1.VTCluster) (*corev1.PodTemplateSpec, error) {
+	args := []string{
+		fmt.Sprintf("-httpListenAddr=:%s", cr.Spec.Storage.Port),
+		fmt.Sprintf("-storageDataPath=%s", cr.Spec.Storage.StorageDataPath),
+	}
+
+	if cr.Spec.Storage.RetentionPeriod != "" {
+		args = append(args, fmt.Sprintf("-retentionPeriod=%s", cr.Spec.Storage.RetentionPeriod))
+	}
+	if cr.Spec.Storage.FutureRetention != "" {
+		args = append(args, fmt.Sprintf("-futureRetention=%s", cr.Spec.Storage.FutureRetention))
+	}
+	if cr.Spec.Storage.RetentionMaxDiskSpaceUsageBytes != "" {
+		args = append(args, fmt.Sprintf("-retention.maxDiskSpaceUsageBytes=%s", cr.Spec.Storage.RetentionMaxDiskSpaceUsageBytes))
+	}
+	if cr.Spec.Storage.LogNewStreams {
+		args = append(args, "-logNewStreams")
+	}
+	if cr.Spec.Storage.LogIngestedRows {
+		args = append(args, "-logIngestedRows")
+	}
+
+	if cr.Spec.Storage.LogLevel != "" {
+		args = append(args, fmt.Sprintf("-loggerLevel=%s", cr.Spec.Storage.LogLevel))
+	}
+	if cr.Spec.Storage.LogFormat != "" {
+		args = append(args, fmt.Sprintf("-loggerFormat=%s", cr.Spec.Storage.LogFormat))
+	}
+
+	if len(cr.Spec.Storage.ExtraEnvs) > 0 || len(cr.Spec.Storage.ExtraEnvsFrom) > 0 {
+		args = append(args, "-envflag.enable=true")
+	}
+
+	var envs []corev1.EnvVar
+
+	envs = append(envs, cr.Spec.Storage.ExtraEnvs...)
+
+	ports := []corev1.ContainerPort{
+		{
+			Name:          "http",
+			Protocol:      "TCP",
+			ContainerPort: intstr.Parse(cr.Spec.Storage.Port).IntVal,
+		},
+	}
+	volumes := make([]corev1.Volume, 0)
+	vmMounts := make([]corev1.VolumeMount, 0)
+
+	volumes = append(volumes, cr.Spec.Storage.Volumes...)
+	vmMounts = append(vmMounts, corev1.VolumeMount{
+		Name:      cr.Spec.Storage.GetStorageVolumeName(),
+		MountPath: cr.Spec.Storage.StorageDataPath,
+	})
+
+	vmMounts = append(vmMounts, cr.Spec.Storage.VolumeMounts...)
+
+	for _, s := range cr.Spec.Storage.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.Storage.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.Storage.ExtraArgs, "-")
+	sort.Strings(args)
+	vmstorageContainer := corev1.Container{
+		Name:                     "vtstorage",
+		Image:                    fmt.Sprintf("%s:%s", cr.Spec.Storage.Image.Repository, cr.Spec.Storage.Image.Tag),
+		ImagePullPolicy:          cr.Spec.Storage.Image.PullPolicy,
+		Ports:                    ports,
+		Args:                     args,
+		VolumeMounts:             vmMounts,
+		Resources:                cr.Spec.Storage.Resources,
+		Env:                      envs,
+		EnvFrom:                  cr.Spec.Storage.ExtraEnvsFrom,
+		TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
+		TerminationMessagePath:   "/dev/termination-log",
+	}
+
+	vmstorageContainer = build.Probe(vmstorageContainer, cr.Spec.Storage)
+
+	storageContainers := []corev1.Container{vmstorageContainer}
+	var initContainers []corev1.Container
+
+	useStrictSecurity := ptr.Deref(cr.Spec.Storage.UseStrictSecurity, false)
+	build.AddStrictSecuritySettingsToContainers(cr.Spec.Storage.SecurityContext, initContainers, useStrictSecurity)
+	ic, err := k8stools.MergePatchContainers(initContainers, cr.Spec.Storage.InitContainers)
+	if err != nil {
+		return nil, fmt.Errorf("cannot patch storage init containers: %w", err)
+	}
+
+	build.AddStrictSecuritySettingsToContainers(cr.Spec.Storage.SecurityContext, storageContainers, useStrictSecurity)
+	containers, err := k8stools.MergePatchContainers(storageContainers, cr.Spec.Storage.Containers)
+	if err != nil {
+		return nil, fmt.Errorf("cannot patch storage containers: %w", err)
+	}
+
+	for i := range cr.Spec.Storage.TopologySpreadConstraints {
+		if cr.Spec.Storage.TopologySpreadConstraints[i].LabelSelector == nil {
+			cr.Spec.Storage.TopologySpreadConstraints[i].LabelSelector = &metav1.LabelSelector{
+				MatchLabels: cr.VTStorageSelectorLabels(),
+			}
+		}
+	}
+
+	vmStoragePodSpec := &corev1.PodTemplateSpec{
+		ObjectMeta: metav1.ObjectMeta{
+			Labels:      cr.VTStoragePodLabels(),
+			Annotations: cr.VTStoragePodAnnotations(),
+		},
+		Spec: corev1.PodSpec{
+			Volumes:            volumes,
+			InitContainers:     ic,
+			Containers:         containers,
+			ServiceAccountName: cr.GetServiceAccountName(),
+		},
+	}
+
+	return vmStoragePodSpec, nil
+}
diff --git a/internal/controller/operator/factory/vtcluster/vmauth_lb.go b/internal/controller/operator/factory/vtcluster/vmauth_lb.go
new file mode 100644
index 00000000..c7bbeca3
--- /dev/null
+++ b/internal/controller/operator/factory/vtcluster/vmauth_lb.go
@@ -0,0 +1,302 @@
+package vtcluster
+
+import (
+	"context"
+	"fmt"
+
+	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/k8stools"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/reconcile"
+)
+
+func createOrUpdateVMAuthLB(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster) 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.VTCluster) 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.VTCluster) *corev1.Secret {
+	targetHostSuffix := fmt.Sprintf("%s.svc", cr.Namespace)
+	if cr.Spec.ClusterDomainName != "" {
+		targetHostSuffix += fmt.Sprintf(".%s", cr.Spec.ClusterDomainName)
+	}
+	insertPort := "10481"
+	selectPort := "10471"
+	insertProto := "http"
+	selectProto := "http"
+	if cr.Spec.Select != nil {
+		selectPort = cr.Spec.Select.Port
+		if v, ok := cr.Spec.Select.ExtraArgs["tls"]; ok && v == "true" {
+			selectProto = "https"
+		}
+	}
+	if cr.Spec.Insert != nil {
+		insertPort = cr.Spec.Insert.Port
+		if v, ok := cr.Spec.Insert.ExtraArgs["tls"]; ok && v == "true" {
+			selectProto = "https"
+		}
+	}
+	insertURL := fmt.Sprintf("%s://%s.%s:%s",
+		insertProto, cr.GetVTInsertLBName(), targetHostSuffix, insertPort)
+	selectURL := fmt.Sprintf("%s://%s.%s:%s",
+		selectProto, cr.GetVTSelectLBName(), 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.VTCluster) (*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.VTCluster) 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{
+		VTCluster:         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.VTCluster = 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.VTCluster) 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 vtinsert and vtselect external services to expose vtcluster components for access by vmauth
+func createOrUpdateLBProxyService(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTCluster, 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/factory/vtsingle/vtsingle.go b/internal/controller/operator/factory/vtsingle/vtsingle.go
new file mode 100644
index 00000000..be750073
--- /dev/null
+++ b/internal/controller/operator/factory/vtsingle/vtsingle.go
@@ -0,0 +1,340 @@
+package vtsingle
+
+import (
+	"context"
+	"fmt"
+	"path"
+	"sort"
+
+	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/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/finalize"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/reconcile"
+)
+
+const (
+	vtsingleDataDir          = "/victoria-traces-data"
+	vtsingleDataVolumeName   = "data"
+	tlsServerConfigMountPath = "/etc/vm/tls-server-secrets"
+)
+
+func createOrUpdatePVC(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTSingle) error {
+	newPvc := newPVC(cr)
+	var prevPVC *corev1.PersistentVolumeClaim
+	if prevCR != nil && prevCR.Spec.Storage != nil {
+		prevPVC = newPVC(prevCR)
+	}
+	return reconcile.PersistentVolumeClaim(ctx, rclient, newPvc, prevPVC)
+}
+
+func newPVC(r *vmv1.VTSingle) *corev1.PersistentVolumeClaim {
+	pvcObject := &corev1.PersistentVolumeClaim{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:            r.PrefixedName(),
+			Namespace:       r.Namespace,
+			Labels:          labels.Merge(r.Spec.StorageMetadata.Labels, r.SelectorLabels()),
+			Annotations:     r.Spec.StorageMetadata.Annotations,
+			Finalizers:      []string{vmv1beta1.FinalizerName},
+			OwnerReferences: r.AsOwner(),
+		},
+		Spec: *r.Spec.Storage,
+	}
+	if len(pvcObject.Spec.AccessModes) == 0 {
+		pvcObject.Spec.AccessModes = []corev1.PersistentVolumeAccessMode{
+			corev1.ReadWriteOnce,
+		}
+	}
+
+	return pvcObject
+}
+
+// CreateOrUpdate performs an update for vtsingle resource
+func CreateOrUpdate(ctx context.Context, rclient client.Client, cr *vmv1.VTSingle) error {
+	var prevCR *vmv1.VTSingle
+	if cr.ParsedLastAppliedSpec != nil {
+		prevCR = cr.DeepCopy()
+		prevCR.Spec = *cr.ParsedLastAppliedSpec
+	}
+	if err := deletePrevStateResources(ctx, cr, rclient); err != nil {
+		return err
+	}
+	if cr.Spec.Storage != nil && cr.Spec.StorageDataPath == "" {
+		if err := createOrUpdatePVC(ctx, rclient, cr, prevCR); err != nil {
+			return err
+		}
+	}
+
+	if cr.IsOwnsServiceAccount() {
+		var prevSA *corev1.ServiceAccount
+		if prevCR != nil {
+			prevSA = build.ServiceAccount(prevCR)
+		}
+		if err := reconcile.ServiceAccount(ctx, rclient, build.ServiceAccount(cr), prevSA); err != nil {
+			return fmt.Errorf("failed create service account: %w", err)
+		}
+	}
+
+	svc, err := createOrUpdateService(ctx, rclient, cr, prevCR)
+	if err != nil {
+		return err
+	}
+
+	if !ptr.Deref(cr.Spec.DisableSelfServiceScrape, false) {
+		err := reconcile.VMServiceScrapeForCRD(ctx, rclient, build.VMServiceScrapeForServiceWithSpec(svc, cr))
+		if err != nil {
+			return fmt.Errorf("cannot create serviceScrape for vtsingle: %w", err)
+		}
+	}
+
+	var prevDeploy *appsv1.Deployment
+	if prevCR != nil {
+		prevDeploy, err = newDeployment(prevCR)
+		if err != nil {
+			return fmt.Errorf("cannot generate prev deploy spec: %w", err)
+		}
+	}
+
+	newDeploy, err := newDeployment(cr)
+	if err != nil {
+		return fmt.Errorf("cannot generate new deploy for vtsingle: %w", err)
+	}
+
+	return reconcile.Deployment(ctx, rclient, newDeploy, prevDeploy, false)
+}
+
+func newDeployment(r *vmv1.VTSingle) (*appsv1.Deployment, error) {
+	podSpec, err := makePodSpec(r)
+	if err != nil {
+		return nil, err
+	}
+
+	depSpec := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:            r.PrefixedName(),
+			Namespace:       r.Namespace,
+			Labels:          r.AllLabels(),
+			Annotations:     r.AnnotationsFiltered(),
+			OwnerReferences: r.AsOwner(),
+			Finalizers:      []string{vmv1beta1.FinalizerName},
+		},
+		Spec: appsv1.DeploymentSpec{
+			Replicas: r.Spec.ReplicaCount,
+			Selector: &metav1.LabelSelector{
+				MatchLabels: r.SelectorLabels(),
+			},
+			Strategy: appsv1.DeploymentStrategy{
+				// we use recreate, coz of volume claim
+				Type: appsv1.RecreateDeploymentStrategyType,
+			},
+			Template: *podSpec,
+		},
+	}
+	build.DeploymentAddCommonParams(depSpec, ptr.Deref(r.Spec.UseStrictSecurity, false), &r.Spec.CommonApplicationDeploymentParams)
+	return depSpec, nil
+}
+
+func makePodSpec(r *vmv1.VTSingle) (*corev1.PodTemplateSpec, error) {
+	var args []string
+
+	if len(r.Spec.RetentionPeriod) > 0 {
+		args = append(args, fmt.Sprintf("-retentionPeriod=%s", r.Spec.RetentionPeriod))
+	}
+	if len(r.Spec.RetentionMaxDiskSpaceUsageBytes) > 0 {
+		args = append(args, fmt.Sprintf("-retention.maxDiskSpaceUsageBytes=%s", r.Spec.RetentionMaxDiskSpaceUsageBytes))
+	}
+
+	// if customStorageDataPath is not empty, do not add pvc.
+	shouldAddPVC := r.Spec.StorageDataPath == ""
+
+	storagePath := vtsingleDataDir
+	if r.Spec.StorageDataPath != "" {
+		storagePath = r.Spec.StorageDataPath
+	}
+	args = append(args, fmt.Sprintf("-storageDataPath=%s", storagePath))
+	if r.Spec.LogLevel != "" {
+		args = append(args, fmt.Sprintf("-loggerLevel=%s", r.Spec.LogLevel))
+	}
+	if r.Spec.LogFormat != "" {
+		args = append(args, fmt.Sprintf("-loggerFormat=%s", r.Spec.LogFormat))
+	}
+	if len(r.Spec.FutureRetention) > 0 {
+		args = append(args, fmt.Sprintf("-futureRetention=%s", r.Spec.FutureRetention))
+	}
+	if r.Spec.LogNewStreams {
+		args = append(args, "-logNewStreams")
+	}
+	if r.Spec.LogIngestedRows {
+		args = append(args, "-logIngestedRows")
+	}
+	args = append(args, fmt.Sprintf("-httpListenAddr=:%s", r.Spec.Port))
+	if len(r.Spec.ExtraEnvs) > 0 || len(r.Spec.ExtraEnvsFrom) > 0 {
+		args = append(args, "-envflag.enable=true")
+	}
+
+	var envs []corev1.EnvVar
+	envs = append(envs, r.Spec.ExtraEnvs...)
+
+	var ports []corev1.ContainerPort
+	ports = append(ports, corev1.ContainerPort{Name: "http", Protocol: "TCP", ContainerPort: intstr.Parse(r.Spec.Port).IntVal})
+	volumes := []corev1.Volume{}
+
+	storageSpec := r.Spec.Storage
+
+	if storageSpec == nil {
+		volumes = append(volumes, corev1.Volume{
+			Name: vtsingleDataVolumeName,
+			VolumeSource: corev1.VolumeSource{
+				EmptyDir: &corev1.EmptyDirVolumeSource{},
+			},
+		})
+	} else if shouldAddPVC {
+		volumes = append(volumes, corev1.Volume{
+			Name: vtsingleDataVolumeName,
+			VolumeSource: corev1.VolumeSource{
+				PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
+					ClaimName: r.PrefixedName(),
+				},
+			},
+		})
+	}
+	volumes = append(volumes, r.Spec.Volumes...)
+	vmMounts := []corev1.VolumeMount{
+		{
+			Name:      vtsingleDataVolumeName,
+			MountPath: storagePath,
+		},
+	}
+
+	vmMounts = append(vmMounts, r.Spec.VolumeMounts...)
+
+	for _, s := range r.Spec.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 r.Spec.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, r.Spec.ExtraArgs, "-")
+	sort.Strings(args)
+	vtsingleContainer := corev1.Container{
+		Name:                     "vtsingle",
+		Image:                    fmt.Sprintf("%s:%s", r.Spec.Image.Repository, r.Spec.Image.Tag),
+		Ports:                    ports,
+		Args:                     args,
+		VolumeMounts:             vmMounts,
+		Resources:                r.Spec.Resources,
+		Env:                      envs,
+		EnvFrom:                  r.Spec.ExtraEnvsFrom,
+		TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
+		ImagePullPolicy:          r.Spec.Image.PullPolicy,
+	}
+
+	vtsingleContainer = build.Probe(vtsingleContainer, r)
+
+	operatorContainers := []corev1.Container{vtsingleContainer}
+
+	build.AddStrictSecuritySettingsToContainers(r.Spec.SecurityContext, operatorContainers, ptr.Deref(r.Spec.UseStrictSecurity, false))
+
+	containers, err := k8stools.MergePatchContainers(operatorContainers, r.Spec.Containers)
+	if err != nil {
+		return nil, err
+	}
+
+	vtsingleSpec := &corev1.PodTemplateSpec{
+		ObjectMeta: metav1.ObjectMeta{
+			Labels:      r.PodLabels(),
+			Annotations: r.PodAnnotations(),
+		},
+		Spec: corev1.PodSpec{
+			Volumes:            volumes,
+			InitContainers:     r.Spec.InitContainers,
+			Containers:         containers,
+			ServiceAccountName: r.GetServiceAccountName(),
+		},
+	}
+
+	return vtsingleSpec, nil
+}
+
+// createOrUpdateService creates service for vtsingle
+func createOrUpdateService(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VTSingle) (*corev1.Service, error) {
+	var prevService, prevAdditionalService *corev1.Service
+	if prevCR != nil {
+		prevService = build.Service(prevCR, prevCR.Spec.Port, nil)
+		prevAdditionalService = build.AdditionalServiceFromDefault(prevService, prevCR.Spec.ServiceSpec)
+	}
+
+	newService := build.Service(cr, cr.Spec.Port, nil)
+	if err := cr.Spec.ServiceSpec.IsSomeAndThen(func(s *vmv1beta1.AdditionalServiceSpec) error {
+		additionalService := build.AdditionalServiceFromDefault(newService, s)
+		if additionalService.Name == newService.Name {
+			return fmt.Errorf("vtsingle 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 additional service for vtsingle: %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 service for vtsingle: %w", err)
+	}
+	return newService, nil
+}
+
+func deletePrevStateResources(ctx context.Context, cr *vmv1.VTSingle, rclient client.Client) error {
+	if cr.ParsedLastAppliedSpec == nil {
+		return nil
+	}
+	prevSvc, currSvc := cr.ParsedLastAppliedSpec.ServiceSpec, cr.Spec.ServiceSpec
+	if err := reconcile.AdditionalServices(ctx, rclient, cr.PrefixedName(), cr.Namespace, prevSvc, currSvc); err != nil {
+		return fmt.Errorf("cannot remove additional service: %w", err)
+	}
+
+	objMeta := metav1.ObjectMeta{Name: cr.PrefixedName(), Namespace: cr.Namespace}
+	if ptr.Deref(cr.Spec.DisableSelfServiceScrape, false) && !ptr.Deref(cr.ParsedLastAppliedSpec.DisableSelfServiceScrape, false) {
+		if err := finalize.SafeDeleteWithFinalizer(ctx, rclient, &vmv1beta1.VMServiceScrape{ObjectMeta: objMeta}); err != nil {
+			return fmt.Errorf("cannot remove serviceScrape: %w", err)
+		}
+	}
+
+	return nil
+}
diff --git a/internal/controller/operator/objects_stat.go b/internal/controller/operator/objects_stat.go
index 7402a71d..71220601 100644
--- a/internal/controller/operator/objects_stat.go
+++ b/internal/controller/operator/objects_stat.go
@@ -49,6 +49,7 @@ func newCollector() *objectCollector {
 		"vmagent", "vmalert", "vmsingle", "vmcluster", "vmalertmanager", "vmauth", "vlogs", "vlsingle",
 		"vlcluster", "vmalertmanagerconfig", "vmrule", "vmuser", "vmservicescrape", "vmstaticscrape",
 		"vmnodescrape", "vmpodscrape", "vmprobescrape", "vmscrapeconfig", "vmanomaly", "vlagent",
+		"vtsingle", "vtcluster",
 	}
 	for _, controller := range registeredObjects {
 		oc.objectsByController[controller] = map[string]struct{}{}
diff --git a/internal/controller/operator/vlcluster_controller.go b/internal/controller/operator/vlcluster_controller.go
index a77df996..bc537580 100644
--- a/internal/controller/operator/vlcluster_controller.go
+++ b/internal/controller/operator/vlcluster_controller.go
@@ -69,13 +69,13 @@ func (r *VLClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 
 	RegisterObjectStat(instance, "vlcluster")
 	if !instance.DeletionTimestamp.IsZero() {
-		if err := finalize.OnVClusterDelete(ctx, r.Client, instance); err != nil {
+		if err := finalize.OnVLClusterDelete(ctx, r.Client, instance); err != nil {
 			return result, err
 		}
 		return
 	}
 	if instance.Spec.ParsingError != "" {
-		return result, &parsingError{instance.Spec.ParsingError, "vlsingle"}
+		return result, &parsingError{instance.Spec.ParsingError, "vlcluster"}
 	}
 	if err := finalize.AddFinalizer(ctx, r.Client, instance); err != nil {
 		return result, err
@@ -84,7 +84,7 @@ func (r *VLClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 
 	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, fmt.Errorf("failed create or update vlcluster: %w", err)
 		}
 
 		return result, nil
diff --git a/internal/controller/operator/vtcluster_controller.go b/internal/controller/operator/vtcluster_controller.go
new file mode 100644
index 00000000..e680ea75
--- /dev/null
+++ b/internal/controller/operator/vtcluster_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"
+
+	vmv1 "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/vtcluster"
+)
+
+// VTClusterReconciler reconciles a VTCluster object
+type VTClusterReconciler struct {
+	client.Client
+	Log          logr.Logger
+	OriginScheme *runtime.Scheme
+	BaseConf     *config.BaseOperatorConf
+}
+
+// Init implements crdController interface
+func (r *VTClusterReconciler) Init(rclient client.Client, l logr.Logger, sc *runtime.Scheme, cf *config.BaseOperatorConf) {
+	r.Client = rclient
+	r.Log = l.WithName("controller.VTCluster")
+	r.OriginScheme = sc
+	r.BaseConf = cf
+}
+
+// Reconcile is part of the main kubernetes reconciliation loop
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vtclusters,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vtclusters/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vtclusters/finalizers,verbs=update
+func (r *VTClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
+	l := r.Log.WithValues("vtcluster", req.Name, "namespace", req.Namespace)
+	ctx = logger.AddToContext(ctx, l)
+	instance := &vmv1.VTCluster{}
+
+	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, "vtcluster", req}
+	}
+
+	RegisterObjectStat(instance, "vtcluster")
+	if !instance.DeletionTimestamp.IsZero() {
+		if err := finalize.OnVTClusterDelete(ctx, r.Client, instance); err != nil {
+			return result, err
+		}
+		return
+	}
+	if instance.Spec.ParsingError != "" {
+		return result, &parsingError{instance.Spec.ParsingError, "vtcluster"}
+	}
+	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 = vtcluster.CreateOrUpdate(ctx, r, instance); err != nil {
+			return result, fmt.Errorf("failed create or update vtcluster: %w", err)
+		}
+
+		return result, nil
+	})
+
+	result.RequeueAfter = r.BaseConf.ResyncAfterDuration()
+
+	return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *VTClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
+	return ctrl.NewControllerManagedBy(mgr).
+		For(&vmv1.VTCluster{}).
+		Owns(&appsv1.Deployment{}).
+		Owns(&appsv1.StatefulSet{}).
+		Owns(&corev1.Service{}).
+		WithOptions(getDefaultOptions()).
+		Complete(r)
+}
diff --git a/internal/controller/operator/vtsingle_controller.go b/internal/controller/operator/vtsingle_controller.go
new file mode 100644
index 00000000..a76c3a92
--- /dev/null
+++ b/internal/controller/operator/vtsingle_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"
+
+	vmv1 "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/vtsingle"
+)
+
+// VTSingleReconciler reconciles a VTSingle object
+type VTSingleReconciler struct {
+	client.Client
+	Log          logr.Logger
+	OriginScheme *runtime.Scheme
+	BaseConf     *config.BaseOperatorConf
+}
+
+// Init implements crdController interface
+func (r *VTSingleReconciler) Init(rclient client.Client, l logr.Logger, sc *runtime.Scheme, cf *config.BaseOperatorConf) {
+	r.Client = rclient
+	r.Log = l.WithName("controller.VTSingle")
+	r.OriginScheme = sc
+	r.BaseConf = cf
+}
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vtsingles,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vtsingles/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vtsingles/finalizers,verbs=update
+func (r *VTSingleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
+	l := r.Log.WithValues("vtsingle", req.Name, "namespace", req.Namespace)
+	ctx = logger.AddToContext(ctx, l)
+	instance := &vmv1.VTSingle{}
+
+	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, "vtsingle", req}
+	}
+
+	RegisterObjectStat(instance, "vtsingle")
+	if !instance.DeletionTimestamp.IsZero() {
+		if err := finalize.OnVTSingleDelete(ctx, r.Client, instance); err != nil {
+			return result, err
+		}
+		return
+	}
+	if instance.Spec.ParsingError != "" {
+		return result, &parsingError{instance.Spec.ParsingError, "vtsingle"}
+	}
+	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 = vtsingle.CreateOrUpdate(ctx, r, instance); err != nil {
+			return result, fmt.Errorf("failed create or update vtsingle: %w", err)
+		}
+
+		return result, nil
+	})
+
+	result.RequeueAfter = r.BaseConf.ResyncAfterDuration()
+
+	return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *VTSingleReconciler) SetupWithManager(mgr ctrl.Manager) error {
+	return ctrl.NewControllerManagedBy(mgr).
+		For(&vmv1.VTSingle{}).
+		Owns(&appsv1.Deployment{}).
+		Owns(&corev1.Service{}).
+		WithOptions(getDefaultOptions()).
+		Complete(r)
+}
diff --git a/internal/manager/manager.go b/internal/manager/manager.go
index bfe8833b..633c94f6 100644
--- a/internal/manager/manager.go
+++ b/internal/manager/manager.go
@@ -371,6 +371,8 @@ func addWebhooks(mgr ctrl.Manager) error {
 		webhookv1.SetupVLAgentWebhookWithManager,
 		webhookv1.SetupVLSingleWebhookWithManager,
 		webhookv1.SetupVLClusterWebhookWithManager,
+		webhookv1.SetupVTSingleWebhookWithManager,
+		webhookv1.SetupVTClusterWebhookWithManager,
 		webhookv1beta1.SetupVMAlertmanagerWebhookWithManager,
 		webhookv1beta1.SetupVMAlertmanagerConfigWebhookWithManager,
 		webhookv1beta1.SetupVMAuthWebhookWithManager,
@@ -461,7 +463,9 @@ var controllersByName = map[string]crdController{
 	"VLAgent":              &vmcontroller.VLAgentReconciler{},
 	"VLogs":                &vmcontroller.VLogsReconciler{},
 	"VLSingle":             &vmcontroller.VLSingleReconciler{},
-	"VLCuster":             &vmcontroller.VLClusterReconciler{},
+	"VLCluster":            &vmcontroller.VLClusterReconciler{},
+	"VTSingle":             &vmcontroller.VTSingleReconciler{},
+	"VTCluster":            &vmcontroller.VTClusterReconciler{},
 	"VMAlertmanager":       &vmcontroller.VMAlertmanagerReconciler{},
 	"VMAlert":              &vmcontroller.VMAlertReconciler{},
 	"VMUser":               &vmcontroller.VMUserReconciler{},
diff --git a/internal/webhook/operator/v1/vtcluster_webhook.go b/internal/webhook/operator/v1/vtcluster_webhook.go
new file mode 100644
index 00000000..bacf0f88
--- /dev/null
+++ b/internal/webhook/operator/v1/vtcluster_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"
+)
+
+// SetupVTClusterWebhookWithManager will setup the manager to manage the webhooks
+func SetupVTClusterWebhookWithManager(mgr ctrl.Manager) error {
+	return ctrl.NewWebhookManagedBy(mgr).
+		For(&vmv1.VTCluster{}).
+		WithValidator(&VTClusterCustomValidator{}).
+		Complete()
+}
+
+// +kubebuilder:webhook:path=/validate-operator-victoriametrics-com-v1-vtcluster,mutating=false,failurePolicy=fail,sideEffects=None,groups=operator.victoriametrics.com,resources=vtcluster,verbs=create;update,versions=v1,name=vvtsingles-v1.kb.io,admissionReviewVersions=v1
+type VTClusterCustomValidator struct{}
+
+var _ admission.CustomValidator = (*VTClusterCustomValidator)(nil)
+
+// ValidateCreate implements admission.CustomValidator so a webhook will be registered for the type
+func (*VTClusterCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
+	r, ok := obj.(*vmv1.VTCluster)
+	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 (*VTClusterCustomValidator) ValidateUpdate(_ context.Context, _, newObj runtime.Object) (admission.Warnings, error) {
+	r, ok := newObj.(*vmv1.VTCluster)
+	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 (*VTClusterCustomValidator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
+	return nil, nil
+}
diff --git a/internal/webhook/operator/v1/vtsingle_webhook.go b/internal/webhook/operator/v1/vtsingle_webhook.go
new file mode 100644
index 00000000..9e8cdd19
--- /dev/null
+++ b/internal/webhook/operator/v1/vtsingle_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"
+)
+
+// SetupVTSingleWebhookWithManager will setup the manager to manage the webhooks
+func SetupVTSingleWebhookWithManager(mgr ctrl.Manager) error {
+	return ctrl.NewWebhookManagedBy(mgr).
+		For(&vmv1.VTSingle{}).
+		WithValidator(&VTSingleCustomValidator{}).
+		Complete()
+}
+
+// +kubebuilder:webhook:path=/validate-operator-victoriametrics-com-v1-vtsingle,mutating=false,failurePolicy=fail,sideEffects=None,groups=operator.victoriametrics.com,resources=vtsingles,verbs=create;update,versions=v1,name=vvtsingles-v1.kb.io,admissionReviewVersions=v1
+type VTSingleCustomValidator struct{}
+
+var _ admission.CustomValidator = (*VTSingleCustomValidator)(nil)
+
+// ValidateCreate implements admission.CustomValidator so a webhook will be registered for the type
+func (*VTSingleCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
+	r, ok := obj.(*vmv1.VTSingle)
+	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 (*VTSingleCustomValidator) ValidateUpdate(_ context.Context, _, newObj runtime.Object) (admission.Warnings, error) {
+	r, ok := newObj.(*vmv1.VTSingle)
+	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 (*VTSingleCustomValidator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
+	return nil, nil
+}



Test patch:
diff --git a/internal/controller/operator/factory/k8stools/test_helpers.go b/internal/controller/operator/factory/k8stools/test_helpers.go
index b96a76f2..90d9c7a7 100644
--- a/internal/controller/operator/factory/k8stools/test_helpers.go
+++ b/internal/controller/operator/factory/k8stools/test_helpers.go
@@ -39,7 +39,9 @@ func testGetScheme() *runtime.Scheme {
 		&vmv1beta1.VMClusterList{},
 		&vmv1beta1.VLogsList{},
 		&vmv1.VLSingleList{},
+		&vmv1.VTSingleList{},
 		&vmv1.VLClusterList{},
+		&vmv1.VTClusterList{},
 		&vmv1.VMAnomalyList{},
 		&vmv1.VLAgentList{},
 	)
@@ -63,6 +65,8 @@ func testGetScheme() *runtime.Scheme {
 		&vmv1beta1.VLogs{},
 		&vmv1.VLSingle{},
 		&vmv1.VLCluster{},
+		&vmv1.VTSingle{},
+		&vmv1.VTCluster{},
 		&vmv1.VMAnomaly{},
 		&vmv1.VLAgent{},
 	)
@@ -100,6 +104,8 @@ func GetTestClientWithClientObjects(predefinedObjects []client.Object) client.Cl
 			&vmv1beta1.VMNodeScrape{},
 			&vmv1.VLSingle{},
 			&vmv1.VLCluster{},
+			&vmv1.VTSingle{},
+			&vmv1.VTCluster{},
 			&vmv1.VMAnomaly{},
 			&vmv1.VLAgent{},
 		).
diff --git a/internal/controller/operator/factory/vlsingle/vlsingle_test.go b/internal/controller/operator/factory/vlsingle/vlsingle_test.go
index 16498ec2..00cf7518 100644
--- a/internal/controller/operator/factory/vlsingle/vlsingle_test.go
+++ b/internal/controller/operator/factory/vlsingle/vlsingle_test.go
@@ -181,7 +181,7 @@ func TestCreateOrUpdateVLSingle(t *testing.T) {
 			fclient := k8stools.GetTestClientWithObjects(tt.predefinedObjects)
 			err := CreateOrUpdate(context.TODO(), fclient, tt.args.cr)
 			if (err != nil) != tt.wantErr {
-				t.Errorf("CreateOrUpdateVLogs() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("CreateOrUpdate() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 		})
@@ -269,12 +269,12 @@ func TestCreateOrUpdateVLSingleService(t *testing.T) {
 			fclient := k8stools.GetTestClientWithObjects(tt.predefinedObjects)
 			got, err := createOrUpdateService(context.TODO(), fclient, tt.args.cr, nil)
 			if (err != nil) != tt.wantErr {
-				t.Errorf("CreateOrUpdateVLogsService() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("CreateOrUpdateService() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 
 			if !reflect.DeepEqual(got.Name, tt.want.Name) {
-				t.Errorf("CreateOrUpdateVLogsService(): %s", cmp.Diff(got, tt.want))
+				t.Errorf("CreateOrUpdateService(): %s", cmp.Diff(got, tt.want))
 			}
 			if len(got.Spec.Ports) != tt.wantPortsLen {
 				t.Fatalf("unexpected number of ports: %d, want: %d", len(got.Spec.Ports), tt.wantPortsLen)
diff --git a/internal/controller/operator/factory/vmauth/vmusers_config_test.go b/internal/controller/operator/factory/vmauth/vmusers_config_test.go
index 17b109a6..980b2cd0 100644
--- a/internal/controller/operator/factory/vmauth/vmusers_config_test.go
+++ b/internal/controller/operator/factory/vmauth/vmusers_config_test.go
@@ -1067,6 +1067,59 @@ func Test_buildConfig(t *testing.T) {
 						},
 					},
 				},
+				&vmv1beta1.VMUser{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "user-3",
+						Namespace: "default",
+					},
+					Spec: vmv1beta1.VMUserSpec{
+						BearerToken: ptr.To("bearer-token-3"),
+						TargetRefs: []vmv1beta1.TargetRef{
+							{
+								CRD: &vmv1beta1.CRDRef{
+									Kind:      "VLSingle",
+									Name:      "test",
+									Namespace: "default",
+								},
+								Paths: []string{"/"},
+							},
+						},
+					},
+				},
+				&vmv1beta1.VMUser{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "user-4",
+						Namespace: "default",
+					},
+					Spec: vmv1beta1.VMUserSpec{
+						BearerToken: ptr.To("bearer-token-4"),
+						TargetRefs: []vmv1beta1.TargetRef{
+							{
+								CRD: &vmv1beta1.CRDRef{
+									Kind:      "VTCluster/vtselect",
+									Name:      "test",
+									Namespace: "default",
+								},
+								Paths: []string{"/"},
+							},
+						},
+					},
+				},
+				&vmv1.VTCluster{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test",
+						Namespace: "default",
+					},
+					Spec: vmv1.VTClusterSpec{
+						Select: &vmv1.VTSelect{},
+					},
+				},
+				&vmv1.VLSingle{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test",
+						Namespace: "default",
+					},
+				},
 				&vmv1beta1.VMAgent{
 					ObjectMeta: metav1.ObjectMeta{
 						Name:      "test",
@@ -1082,6 +1135,12 @@ func Test_buildConfig(t *testing.T) {
 - url_prefix:
   - http://vmagent-test.default.svc:8429
   bearer_token: bearer-token-2
+- url_prefix:
+  - http://vlsingle-test.default.svc:9428
+  bearer_token: bearer-token-3
+- url_prefix:
+  - http://vtselect-test.default.svc:10471
+  bearer_token: bearer-token-4
 `,
 		},
 		{
diff --git a/internal/controller/operator/factory/vtcluster/cluster_test.go b/internal/controller/operator/factory/vtcluster/cluster_test.go
new file mode 100644
index 00000000..a4372a8f
--- /dev/null
+++ b/internal/controller/operator/factory/vtcluster/cluster_test.go
@@ -0,0 +1,248 @@
+package vtcluster
+
+import (
+	"context"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	k8serrors "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"
+)
+
+func TestCreateOrUpdate(t *testing.T) {
+	type opts struct {
+		cr                *vmv1.VTCluster
+		validate          func(ctx context.Context, rclient client.Client, cr *vmv1.VTCluster) error
+		predefinedObjects []runtime.Object
+	}
+	f := func(opts opts) {
+		t.Helper()
+		fclient := k8stools.GetTestClientWithObjects(opts.predefinedObjects)
+		build.AddDefaults(fclient.Scheme())
+		fclient.Scheme().Default(opts.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 k8serrors.IsNotFound(err) {
+								continue
+							}
+							t.Errorf("callback error: %s", err)
+							return
+						}
+						return
+					}
+				}
+			}()
+		}
+		if opts.cr.Spec.Storage != nil {
+			var vtst appsv1.StatefulSet
+			eventuallyUpdateStatusToOk(func() error {
+				if err := fclient.Get(ctx, types.NamespacedName{Name: opts.cr.GetVTStorageName(), Namespace: opts.cr.Namespace}, &vtst); err != nil {
+					return err
+				}
+				vtst.Status.ReadyReplicas = *opts.cr.Spec.Storage.ReplicaCount
+				vtst.Status.UpdatedReplicas = *opts.cr.Spec.Storage.ReplicaCount
+				if err := fclient.Status().Update(ctx, &vtst); err != nil {
+					return err
+				}
+
+				return nil
+			})
+		}
+		if opts.cr.Spec.Select != nil {
+			var vts appsv1.Deployment
+			eventuallyUpdateStatusToOk(func() error {
+				if err := fclient.Get(ctx, types.NamespacedName{Name: opts.cr.GetVTSelectName(), Namespace: opts.cr.Namespace}, &vts); err != nil {
+					return err
+				}
+				vts.Status.Conditions = append(vts.Status.Conditions, appsv1.DeploymentCondition{
+					Type:   appsv1.DeploymentProgressing,
+					Reason: "NewReplicaSetAvailable",
+					Status: "True",
+				})
+				vts.Status.UpdatedReplicas = *vts.Spec.Replicas
+				vts.Status.AvailableReplicas = vts.Status.UpdatedReplicas
+				if err := fclient.Status().Update(ctx, &vts); err != nil {
+					return err
+				}
+
+				return nil
+			})
+
+		}
+		if opts.cr.Spec.Insert != nil {
+			var vti appsv1.Deployment
+			eventuallyUpdateStatusToOk(func() error {
+				if err := fclient.Get(ctx, types.NamespacedName{Name: opts.cr.GetVTInsertName(), Namespace: opts.cr.Namespace}, &vti); err != nil {
+					return err
+				}
+				vti.Status.Conditions = append(vti.Status.Conditions, appsv1.DeploymentCondition{
+					Type:   appsv1.DeploymentProgressing,
+					Reason: "NewReplicaSetAvailable",
+					Status: "True",
+				})
+				vti.Status.UpdatedReplicas = *vti.Spec.Replicas
+				vti.Status.AvailableReplicas = vti.Status.UpdatedReplicas
+				if err := fclient.Status().Update(ctx, &vti); err != nil {
+					return err
+				}
+				return nil
+			})
+
+		}
+		if opts.cr.Spec.RequestsLoadBalancer.Enabled {
+			var vmauthLB appsv1.Deployment
+			eventuallyUpdateStatusToOk(func() error {
+				if err := fclient.Get(ctx, types.NamespacedName{Name: opts.cr.GetVMAuthLBName(), Namespace: opts.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, opts.cr); err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if opts.validate != nil {
+			if err := opts.validate(ctx, fclient, opts.cr); err != nil {
+				t.Fatalf("unexpected validation error: %s", err)
+			}
+		}
+	}
+
+	// base cluster
+	o := opts{
+		cr: &vmv1.VTCluster{
+			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.VTClusterSpec{
+				Insert: &vmv1.VTInsert{
+					CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+						ReplicaCount: ptr.To(int32(1)),
+					},
+				},
+				Storage: &vmv1.VTStorage{
+					CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+						ReplicaCount: ptr.To(int32(1)),
+					},
+				},
+				Select: &vmv1.VTSelect{
+					CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+						ReplicaCount: ptr.To(int32(1)),
+					},
+				},
+			},
+		},
+		validate: func(ctx context.Context, rclient client.Client, cr *vmv1.VTCluster) 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.GetVTInsertName(), Namespace: cr.Namespace}, &dep))
+			assert.Len(t, dep.Spec.Template.Spec.Containers, 1)
+			cnt := dep.Spec.Template.Spec.Containers[0]
+			assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:10481", "-internalselect.disable=true", "-storageNode=vtstorage-base-0.vtstorage-base.default:10491"})
+			assert.Nil(t, dep.Annotations)
+			assert.Equal(t, dep.Labels, cr.FinalLabels(cr.VTInsertSelectorLabels()))
+
+			// check select
+			assert.Nil(t, rclient.Get(ctx, types.NamespacedName{Name: cr.GetVTSelectName(), Namespace: cr.Namespace}, &dep))
+			assert.Len(t, dep.Spec.Template.Spec.Containers, 1)
+			cnt = dep.Spec.Template.Spec.Containers[0]
+			assert.Equal(t, cnt.Args, []string{"-httpListenAddr=:10471", "-internalinsert.disable=true", "-storageNode=vtstorage-base-0.vtstorage-base.default:10491"})
+			assert.Nil(t, dep.Annotations)
+			assert.Equal(t, dep.Labels, cr.FinalLabels(cr.VTSelectSelectorLabels()))
+
+			// check storage
+			var sts appsv1.StatefulSet
+			assert.Nil(t, rclient.Get(ctx, types.NamespacedName{Name: cr.GetVTStorageName(), 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=:10491", "-storageDataPath=/vtstorage-data"})
+			assert.Nil(t, sts.Annotations)
+			assert.Equal(t, sts.Labels, cr.FinalLabels(cr.VTStorageSelectorLabels()))
+
+			return nil
+		},
+	}
+	f(o)
+
+	// with storage retention
+	o = opts{
+		cr: &vmv1.VTCluster{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "retention",
+				Namespace: "default",
+			},
+			Spec: vmv1.VTClusterSpec{
+				Storage: &vmv1.VTStorage{
+					RetentionPeriod:                 "1w",
+					RetentionMaxDiskSpaceUsageBytes: "5GB",
+					FutureRetention:                 "2d",
+					CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+						ReplicaCount: ptr.To(int32(1)),
+					},
+				},
+			},
+		},
+		validate: func(ctx context.Context, rclient client.Client, cr *vmv1.VTCluster) error {
+
+			// check storage
+			var sts appsv1.StatefulSet
+			assert.Nil(t, rclient.Get(ctx, types.NamespacedName{Name: cr.GetVTStorageName(), 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{"-futureRetention=2d", "-httpListenAddr=:10491", "-retention.maxDiskSpaceUsageBytes=5GB", "-retentionPeriod=1w", "-storageDataPath=/vtstorage-data"})
+
+			return nil
+		},
+	}
+	f(o)
+}
diff --git a/internal/controller/operator/factory/vtsingle/vtsingle_test.go b/internal/controller/operator/factory/vtsingle/vtsingle_test.go
new file mode 100644
index 00000000..a0de18b7
--- /dev/null
+++ b/internal/controller/operator/factory/vtsingle/vtsingle_test.go
@@ -0,0 +1,230 @@
+package vtsingle
+
+import (
+	"context"
+	"reflect"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/utils/ptr"
+
+	vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+	"github.com/VictoriaMetrics/operator/internal/config"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
+)
+
+func TestCreateOrUpdate(t *testing.T) {
+	type opts struct {
+		cr                *vmv1.VTSingle
+		c                 *config.BaseOperatorConf
+		wantErr           bool
+		predefinedObjects []runtime.Object
+	}
+
+	f := func(opts opts) {
+		t.Helper()
+		fclient := k8stools.GetTestClientWithObjects(opts.predefinedObjects)
+		err := CreateOrUpdate(context.TODO(), fclient, opts.cr)
+		if (err != nil) != opts.wantErr {
+			t.Errorf("CreateOrUpdate() error = %v, wantErr %v", err, opts.wantErr)
+			return
+		}
+	}
+
+	// base gen
+	o := opts{
+		c: config.MustGetBaseConfig(),
+		cr: &vmv1.VTSingle{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "base",
+				Namespace: "default",
+			},
+			Spec: vmv1.VTSingleSpec{
+				CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+					ReplicaCount: ptr.To(int32(1)),
+				},
+			},
+		},
+		predefinedObjects: []runtime.Object{
+			&corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "default",
+					Name:      "vtsingle-0",
+					Labels: map[string]string{
+						"app.kubernetes.io/component": "monitoring",
+						"app.kubernetes.io/name":      "vtsingle",
+						"app.kubernetes.io/instance":  "base",
+						"managed-by":                  "vm-operator",
+					},
+				},
+				Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: "True"}}},
+			},
+			k8stools.NewReadyDeployment("vtsingle-base", "default"),
+		},
+	}
+	f(o)
+
+	// base with specific port
+	o = opts{
+		c: config.MustGetBaseConfig(),
+		cr: &vmv1.VTSingle{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "base",
+				Namespace: "default",
+			},
+			Spec: vmv1.VTSingleSpec{
+				CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+					ReplicaCount: ptr.To(int32(1)),
+				},
+				CommonDefaultableParams: vmv1beta1.CommonDefaultableParams{
+					Port: "10435",
+				},
+			},
+		},
+		predefinedObjects: []runtime.Object{
+			&corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "default",
+					Name:      "vtsingle-0",
+					Labels: map[string]string{
+						"app.kubernetes.io/component": "monitoring",
+						"app.kubernetes.io/name":      "vtsingle",
+						"app.kubernetes.io/instance":  "base",
+						"managed-by":                  "vm-operator",
+					},
+				},
+				Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: "True"}}},
+			},
+			k8stools.NewReadyDeployment("vtsingle-base", "default"),
+		},
+	}
+	f(o)
+
+	// with syslog tls config
+	o = opts{
+		c: config.MustGetBaseConfig(),
+		cr: &vmv1.VTSingle{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "base",
+				Namespace: "default",
+			},
+			Spec: vmv1.VTSingleSpec{
+				CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+					ReplicaCount: ptr.To(int32(1)),
+				},
+				CommonDefaultableParams: vmv1beta1.CommonDefaultableParams{
+					Port: "10435",
+				},
+			},
+		},
+		predefinedObjects: []runtime.Object{
+			&corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "default",
+					Name:      "vtsingle-0",
+					Labels: map[string]string{
+						"app.kubernetes.io/component": "monitoring",
+						"app.kubernetes.io/name":      "vtsingle",
+						"app.kubernetes.io/instance":  "base",
+						"managed-by":                  "vm-operator",
+					},
+				},
+				Status: corev1.PodStatus{Phase: corev1.PodRunning, Conditions: []corev1.PodCondition{{Type: corev1.PodReady, Status: "True"}}},
+			},
+			k8stools.NewReadyDeployment("vtsingle-base", "default"),
+		},
+	}
+	f(o)
+}
+
+func TestCreateOrUpdateService(t *testing.T) {
+	type opts struct {
+		cr                *vmv1.VTSingle
+		c                 *config.BaseOperatorConf
+		want              *corev1.Service
+		wantErr           bool
+		wantPortsLen      int
+		predefinedObjects []runtime.Object
+	}
+
+	f := func(opts opts) {
+		fclient := k8stools.GetTestClientWithObjects(opts.predefinedObjects)
+		got, err := createOrUpdateService(context.TODO(), fclient, opts.cr, nil)
+		if (err != nil) != opts.wantErr {
+			t.Errorf("CreateOrUpdateService() error = %v, wantErr %v", err, opts.wantErr)
+			return
+		}
+
+		if !reflect.DeepEqual(got.Name, opts.want.Name) {
+			t.Errorf("CreateOrUpdateService(): %s", cmp.Diff(got, opts.want))
+		}
+		if len(got.Spec.Ports) != opts.wantPortsLen {
+			t.Fatalf("unexpected number of ports: %d, want: %d", len(got.Spec.Ports), opts.wantPortsLen)
+		}
+	}
+
+	// base service test
+	o := opts{
+		c: config.MustGetBaseConfig(),
+		cr: &vmv1.VTSingle{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "traces-1",
+				Namespace: "default",
+			},
+		},
+		want: &corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "vtsingle-traces-1",
+				Namespace: "default",
+			},
+		},
+		wantPortsLen: 1,
+	}
+	f(o)
+
+	// with extra service nodePort
+	o = opts{
+		c: config.MustGetBaseConfig(),
+		cr: &vmv1.VTSingle{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "traces-1",
+				Namespace: "default",
+			},
+			Spec: vmv1.VTSingleSpec{
+				ServiceSpec: &vmv1beta1.AdditionalServiceSpec{
+					EmbeddedObjectMetadata: vmv1beta1.EmbeddedObjectMetadata{Name: "additional-service"},
+					Spec: corev1.ServiceSpec{
+						Type: corev1.ServiceTypeNodePort,
+					},
+				},
+			},
+		},
+		want: &corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "vtsingle-traces-1",
+				Namespace: "default",
+			},
+		},
+		wantPortsLen: 1,
+		predefinedObjects: []runtime.Object{
+			&corev1.Service{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "some-svc",
+					Namespace: "default",
+					Labels: map[string]string{
+						"app.kubernetes.io/name":      "vtsingle",
+						"app.kubernetes.io/instance":  "traces-1",
+						"app.kubernetes.io/component": "monitoring",
+						"managed-by":                  "vm-operator",
+					},
+				},
+				Spec: corev1.ServiceSpec{},
+			},
+		},
+	}
+	f(o)
+}
diff --git a/test/e2e/vmauth_test.go b/test/e2e/vmauth_test.go
index a5046a99..600731f0 100644
--- a/test/e2e/vmauth_test.go
+++ b/test/e2e/vmauth_test.go
@@ -47,8 +47,9 @@ var _ = Describe("test vmauth Controller", Label("vm", "auth"), func() {
 				}, eventualDeletionTimeout).Should(MatchError(k8serrors.IsNotFound, "IsNotFound"))
 			})
 			DescribeTable("should create vmauth", func(name string, cr *vmv1beta1.VMAuth, verify func(cr *vmv1beta1.VMAuth)) {
-				namespacedName.Name = name
 				cr.Name = name
+				cr.Namespace = namespace
+				namespacedName.Name = name
 				Expect(k8sClient.Create(ctx, cr)).To(Succeed())
 				Eventually(func() error {
 					return expectObjectStatusOperational(ctx, k8sClient, &vmv1beta1.VMAuth{}, namespacedName)
@@ -56,9 +57,6 @@ var _ = Describe("test vmauth Controller", Label("vm", "auth"), func() {
 				verify(cr)
 			},
 				Entry("with 1 replica", "replica-1", &vmv1beta1.VMAuth{
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: namespacedName.Namespace,
-					},
 					Spec: vmv1beta1.VMAuthSpec{
 						CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
 							ReplicaCount: ptr.To[int32](1),
@@ -83,9 +81,6 @@ var _ = Describe("test vmauth Controller", Label("vm", "auth"), func() {
 					Expect(reloaderContainer.Resources.Requests.Memory()).To(Equal(ptr.To(resource.MustParse("25Mi"))))
 				}),
 				Entry("with strict security and vm config-reloader", "strict-with-reloader", &vmv1beta1.VMAuth{
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: namespacedName.Namespace,
-					},
 					Spec: vmv1beta1.VMAuthSpec{
 						CommonDefaultableParams: vmv1beta1.CommonDefaultableParams{
 							UseStrictSecurity:   ptr.To(true),
diff --git a/test/e2e/vtcluster_test.go b/test/e2e/vtcluster_test.go
new file mode 100644
index 00000000..9b8a49fa
--- /dev/null
+++ b/test/e2e/vtcluster_test.go
@@ -0,0 +1,304 @@
+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"
+	k8serrors "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"
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/finalize"
+)
+
+//nolint:dupl,lll
+var _ = Describe("test vtcluster Controller", Label("vt", "cluster", "vtcluster"), func() {
+
+	Context("e2e vtcluster", func() {
+		var ctx context.Context
+		namespace := fmt.Sprintf("default-%d", GinkgoParallelProcess())
+		namespacedName := types.NamespacedName{
+			Namespace: namespace,
+		}
+		BeforeEach(func() {
+			ctx = context.Background()
+		})
+		AfterEach(func() {
+			Expect(finalize.SafeDelete(ctx, k8sClient, &vmv1.VTCluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      namespacedName.Name,
+					Namespace: namespacedName.Namespace,
+				},
+			})).To(Succeed())
+			Eventually(func() error {
+				return k8sClient.Get(ctx, namespacedName, &vmv1.VTCluster{})
+			}, eventualDeletionTimeout).Should(MatchError(k8serrors.IsNotFound, "IsNotFound"))
+		})
+		baseVTCluster := &vmv1.VTCluster{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: namespace,
+			},
+			Spec: vmv1.VTClusterSpec{
+				Insert: &vmv1.VTInsert{},
+				Select: &vmv1.VTSelect{},
+				Storage: &vmv1.VTStorage{
+					RetentionPeriod: "1",
+					CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+						ReplicaCount: ptr.To[int32](1),
+					},
+				},
+			},
+		}
+		type testStep struct {
+			setup  func(*vmv1.VTCluster)
+			modify func(*vmv1.VTCluster)
+			verify func(*vmv1.VTCluster)
+		}
+
+		DescribeTable("should perform update steps",
+			func(name string, initCR *vmv1.VTCluster, 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.VTCluster{}, namespacedName)
+				}, eventualDeploymentAppReadyTimeout).Should(Succeed())
+
+				for _, step := range steps {
+					if step.setup != nil {
+						step.setup(initCR)
+					}
+					// perform update
+					Eventually(func() error {
+						var toUpdate vmv1.VTCluster
+						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.VTCluster{}, namespacedName)
+					}, eventualDeploymentAppReadyTimeout).Should(Succeed())
+
+					var updated vmv1.VTCluster
+					Expect(k8sClient.Get(ctx, namespacedName, &updated)).To(Succeed())
+
+					// verify results
+					step.verify(&updated)
+				}
+			},
+			Entry("add and remove annotations with strict security", "manage-annotations",
+				baseVTCluster.DeepCopy(),
+				testStep{
+					modify: func(cr *vmv1.VTCluster) {
+						cr.Spec.ManagedMetadata = &vmv1beta1.ManagedObjectsMetadata{
+							Annotations: map[string]string{
+								"added-annotation": "some-value",
+							},
+						}
+						cr.Spec.UseStrictSecurity = ptr.To(true)
+					},
+					verify: func(cr *vmv1.VTCluster) {
+						nsss := []types.NamespacedName{
+							{Namespace: namespace, Name: cr.GetVTStorageName()},
+						}
+						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.GetVTInsertName()},
+							{Namespace: namespace, Name: cr.GetVTSelectName()},
+						}
+						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.VTCluster) {
+						delete(cr.Spec.ManagedMetadata.Annotations, "added-annotation")
+					},
+					verify: func(cr *vmv1.VTCluster) {
+						nsss := []types.NamespacedName{
+							{Namespace: namespace, Name: cr.GetVTStorageName()},
+						}
+						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.GetVTInsertName()},
+							{Namespace: namespace, Name: cr.GetVTSelectName()},
+						}
+						for _, nss := range nsss {
+							assertAnnotationsOnObjects(ctx, nss, []client.Object{&appsv1.Deployment{}, &corev1.Service{}}, expectedAnnotations)
+						}
+
+					},
+				},
+			),
+			Entry("vtcluster with requests lb", "requests-lb",
+				baseVTCluster.DeepCopy(),
+				testStep{
+					modify: func(cr *vmv1.VTCluster) {
+						cr.Spec.RequestsLoadBalancer.Enabled = true
+					},
+					verify: func(cr *vmv1.VTCluster) {
+
+						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.GetVTSelectName(), Namespace: namespace}, &svc)).To(Succeed())
+						Expect(svc.Spec.Selector).To(Equal(cr.VMAuthLBSelectorLabels()))
+
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetVTInsertName(), Namespace: namespace}, &svc)).To(Succeed())
+						Expect(svc.Spec.Selector).To(Equal(cr.VMAuthLBSelectorLabels()))
+
+						expectHTTPRequestToSucceed(ctx, cr, httpRequestOpts{
+							dstURL:       fmt.Sprintf("http://%s.%s.svc:10481/insert/ready", cr.GetVTInsertName(), namespace),
+							expectedCode: 200,
+						})
+						expectHTTPRequestToSucceed(ctx, cr, httpRequestOpts{
+							dstURL:       fmt.Sprintf("http://%s.%s.svc:10471/select/logsql/query?query=*", cr.GetVTSelectName(), namespace),
+							payload:      ``,
+							expectedCode: 200,
+						})
+					},
+				},
+				testStep{
+					modify: func(cr *vmv1.VTCluster) {
+						cr.Spec.RequestsLoadBalancer.Enabled = false
+					},
+					verify: func(cr *vmv1.VTCluster) {
+						var dep appsv1.Deployment
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetVMAuthLBName(), Namespace: namespace}, &dep)).To(MatchError(k8serrors.IsNotFound, "isNotFound"))
+
+						var svc corev1.Service
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetVTSelectName(), Namespace: namespace}, &svc)).To(Succeed())
+						Expect(svc.Spec.Selector).To(Equal(cr.VTSelectSelectorLabels()))
+
+						Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.GetVTInsertName(), Namespace: namespace}, &svc)).To(Succeed())
+						Expect(svc.Spec.Selector).To(Equal(cr.VTInsertSelectorLabels()))
+
+						expectHTTPRequestToSucceed(ctx, cr, httpRequestOpts{
+							dstURL:       fmt.Sprintf("http://%s.%s.svc:10481/insert/ready", cr.GetVTInsertName(), namespace),
+							expectedCode: 200,
+						})
+						expectHTTPRequestToSucceed(ctx, cr, httpRequestOpts{
+							dstURL:       fmt.Sprintf("http://%s.%s.svc:10471/select/logsql/query?query=*", cr.GetVTSelectName(), namespace),
+							payload:      ``,
+							expectedCode: 200,
+						})
+					},
+				},
+			),
+
+			Entry("by upscaling and downscaling components", "scale",
+				baseVTCluster.DeepCopy(),
+				testStep{
+					modify: func(cr *vmv1.VTCluster) {
+						By("upscaling vtinsert, removing vtselect", func() {
+							cr.Spec.Select = nil
+							cr.Spec.Insert.ReplicaCount = ptr.To(int32(3))
+							cr.Spec.Storage.ReplicaCount = ptr.To(int32(1))
+						})
+					},
+					verify: func(cr *vmv1.VTCluster) {
+						nsn := types.NamespacedName{Namespace: namespace, Name: cr.GetVTStorageName()}
+						sts := &appsv1.StatefulSet{}
+						Expect(k8sClient.Get(ctx, nsn, sts)).To(Succeed())
+						Expect(*sts.Spec.Replicas).To(Equal(int32(1)))
+						nsn = types.NamespacedName{Namespace: namespace, Name: cr.GetVTInsertName()}
+						dep := &appsv1.Deployment{}
+						Expect(k8sClient.Get(ctx, nsn, dep)).To(Succeed())
+						Expect(*dep.Spec.Replicas).To(Equal(int32(3)))
+
+						// vtselect must be removed
+						nsn = types.NamespacedName{Namespace: namespace, Name: cr.GetVTSelectName()}
+						Eventually(func() error {
+							return k8sClient.Get(ctx, nsn, dep)
+						}, eventualDeletionTimeout).Should(MatchError(k8serrors.IsNotFound, "IsNotFound"))
+					},
+				},
+				testStep{
+					modify: func(cr *vmv1.VTCluster) {
+						By("upscaling vtselect, removing vtinsert", func() {
+							cr.Spec.Select = &vmv1.VTSelect{
+								CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+									ReplicaCount: ptr.To(int32(2)),
+								},
+							}
+							cr.Spec.Insert = nil
+							cr.Spec.Storage.ReplicaCount = ptr.To(int32(2))
+						})
+					},
+					verify: func(cr *vmv1.VTCluster) {
+						nsn := types.NamespacedName{Namespace: namespace, Name: cr.GetVTStorageName()}
+						sts := &appsv1.StatefulSet{}
+						Expect(k8sClient.Get(ctx, nsn, sts)).To(Succeed())
+						Expect(*sts.Spec.Replicas).To(Equal(int32(2)))
+						nsn = types.NamespacedName{Namespace: namespace, Name: cr.GetVTSelectName()}
+						dep := &appsv1.Deployment{}
+						Expect(k8sClient.Get(ctx, nsn, dep)).To(Succeed())
+						Expect(*dep.Spec.Replicas).To(Equal(int32(2)))
+						// vtselect must be removed
+						nsn = types.NamespacedName{Namespace: namespace, Name: cr.GetVTInsertName()}
+						Eventually(func() error {
+							return k8sClient.Get(ctx, nsn, dep)
+						}, eventualDeletionTimeout).Should(MatchError(k8serrors.IsNotFound, "IsNotFound"))
+					},
+				},
+				testStep{
+					modify: func(cr *vmv1.VTCluster) {
+						By("downscaling all components to 0 replicas", func() {
+							cr.Spec.Select = &vmv1.VTSelect{
+								CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+									ReplicaCount: ptr.To(int32(0)),
+								},
+							}
+							cr.Spec.Insert = &vmv1.VTInsert{
+								CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+									ReplicaCount: ptr.To(int32(0)),
+								},
+							}
+							cr.Spec.Storage.ReplicaCount = ptr.To(int32(0))
+						})
+					},
+					verify: func(cr *vmv1.VTCluster) {
+						nsn := types.NamespacedName{Namespace: namespace, Name: cr.GetVTStorageName()}
+						sts := &appsv1.StatefulSet{}
+						Expect(k8sClient.Get(ctx, nsn, sts)).To(Succeed())
+						Expect(*sts.Spec.Replicas).To(Equal(int32(0)))
+						dep := &appsv1.Deployment{}
+						nsn = types.NamespacedName{Namespace: namespace, Name: cr.GetVTInsertName()}
+						Expect(k8sClient.Get(ctx, nsn, dep)).To(Succeed())
+						Expect(*dep.Spec.Replicas).To(Equal(int32(0)))
+						nsn = types.NamespacedName{Namespace: namespace, Name: cr.GetVTSelectName()}
+						Expect(k8sClient.Get(ctx, nsn, dep)).To(Succeed())
+						Expect(*dep.Spec.Replicas).To(Equal(int32(0)))
+					},
+				},
+			),
+		)
+	},
+	)
+
+})
diff --git a/test/e2e/vtsingle_test.go b/test/e2e/vtsingle_test.go
new file mode 100644
index 00000000..dcc39b03
--- /dev/null
+++ b/test/e2e/vtsingle_test.go
@@ -0,0 +1,255 @@
+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"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/resource"
+	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"
+	vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+	"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/finalize"
+)
+
+//nolint:dupl,lll
+var _ = Describe("test vtsingle Controller", Label("vt", "single", "vtsingle"), func() {
+
+	Context("e2e vtsingle", func() {
+		var ctx context.Context
+		namespace := fmt.Sprintf("default-%d", GinkgoParallelProcess())
+		namespacedName := types.NamespacedName{
+			Namespace: namespace,
+		}
+		BeforeEach(func() {
+			ctx = context.Background()
+		})
+		AfterEach(func() {
+			Expect(finalize.SafeDelete(ctx, k8sClient, &vmv1.VTSingle{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      namespacedName.Name,
+					Namespace: namespacedName.Namespace,
+				},
+			})).To(Succeed())
+			Eventually(func() error {
+				return k8sClient.Get(ctx, namespacedName, &vmv1.VTSingle{})
+			}, eventualDeletionTimeout).Should(MatchError(k8serrors.IsNotFound, "IsNotFound"))
+		})
+		Context("crud", func() {
+			DescribeTable("should create",
+				func(name string, cr *vmv1.VTSingle, verify func(*vmv1.VTSingle)) {
+					cr.Name = name
+					namespacedName.Name = name
+					Expect(k8sClient.Create(ctx, cr)).To(Succeed())
+					Eventually(func() error {
+						return expectObjectStatusOperational(ctx, k8sClient, &vmv1.VTSingle{}, namespacedName)
+					}, eventualDeploymentAppReadyTimeout,
+					).Should(Succeed())
+
+					var created vmv1.VTSingle
+					Expect(k8sClient.Get(ctx, namespacedName, &created)).To(Succeed())
+					verify(&created)
+				},
+				Entry("with strict security", "strict-security",
+					&vmv1.VTSingle{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: namespace,
+						},
+						Spec: vmv1.VTSingleSpec{
+							CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+								ReplicaCount: ptr.To[int32](1),
+							},
+							CommonDefaultableParams: vmv1beta1.CommonDefaultableParams{
+								UseStrictSecurity: ptr.To(true),
+							},
+							RetentionPeriod: "1",
+							Storage: &corev1.PersistentVolumeClaimSpec{
+								Resources: corev1.VolumeResourceRequirements{
+									Requests: corev1.ResourceList{
+										corev1.ResourceStorage: resource.MustParse("1Gi"),
+									},
+								},
+							},
+						},
+					},
+					func(cr *vmv1.VTSingle) {
+						createdChildObjects := types.NamespacedName{Namespace: namespace, Name: cr.PrefixedName()}
+						var createdDeploy appsv1.Deployment
+						Expect(k8sClient.Get(ctx, createdChildObjects, &createdDeploy)).To(Succeed())
+						Expect(createdDeploy.Spec.Template.Spec.Containers).To(HaveLen(1))
+						Expect(createdDeploy.Spec.Template.Spec.Containers[0].SecurityContext).NotTo(BeNil())
+						Expect(createdDeploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).NotTo(BeNil())
+						Expect(*createdDeploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue())
+
+					}),
+				Entry("with data emptyDir", "emptydir",
+					&vmv1.VTSingle{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: namespace,
+						},
+						Spec: vmv1.VTSingleSpec{
+							CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+								ReplicaCount: ptr.To[int32](1),
+							},
+							CommonDefaultableParams: vmv1beta1.CommonDefaultableParams{
+								UseStrictSecurity: ptr.To(false),
+							},
+							RetentionPeriod: "1",
+						},
+					},
+					func(cr *vmv1.VTSingle) {
+						createdChildObjects := types.NamespacedName{Namespace: namespace, Name: cr.PrefixedName()}
+						var createdDeploy appsv1.Deployment
+						Expect(k8sClient.Get(ctx, createdChildObjects, &createdDeploy)).To(Succeed())
+						ts := createdDeploy.Spec.Template.Spec
+						Expect(ts.Containers).To(HaveLen(1))
+						Expect(ts.Volumes).To(HaveLen(1))
+						Expect(ts.Containers[0].VolumeMounts).To(HaveLen(1))
+					}),
+				Entry("with external volume", "externalvolume",
+					&vmv1.VTSingle{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: namespace,
+						},
+						Spec: vmv1.VTSingleSpec{
+							CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+								ReplicaCount: ptr.To[int32](1),
+								Volumes: []corev1.Volume{
+									{
+										Name: "data",
+										VolumeSource: corev1.VolumeSource{
+											EmptyDir: &corev1.EmptyDirVolumeSource{},
+										},
+									},
+									{
+										Name: "unused",
+										VolumeSource: corev1.VolumeSource{
+											EmptyDir: &corev1.EmptyDirVolumeSource{},
+										},
+									},
+								},
+								VolumeMounts: []corev1.VolumeMount{
+									{
+										Name:      "unused",
+										MountPath: "/opt/unused/mountpoint",
+									},
+								},
+							},
+							CommonDefaultableParams: vmv1beta1.CommonDefaultableParams{
+								UseStrictSecurity: ptr.To(false),
+							},
+							RetentionPeriod: "1",
+							StorageDataPath: "/custom-path/internal/dir",
+							Storage:         &corev1.PersistentVolumeClaimSpec{},
+						},
+					},
+					func(cr *vmv1.VTSingle) {
+						createdChildObjects := types.NamespacedName{Namespace: namespace, Name: cr.PrefixedName()}
+						var createdDeploy appsv1.Deployment
+						Expect(k8sClient.Get(ctx, createdChildObjects, &createdDeploy)).To(Succeed())
+						ts := createdDeploy.Spec.Template.Spec
+						Expect(ts.Containers).To(HaveLen(1))
+						Expect(ts.Volumes).To(HaveLen(2))
+						Expect(ts.Containers[0].VolumeMounts).To(HaveLen(2))
+						Expect(ts.Containers[0].VolumeMounts[0].Name).To(Equal("data"))
+						Expect(ts.Containers[0].VolumeMounts[1].Name).To(Equal("unused"))
+
+					}),
+			)
+
+			baseVTSingle := &vmv1.VTSingle{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: namespace,
+				},
+				Spec: vmv1.VTSingleSpec{
+					RetentionPeriod: "10",
+					CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{
+						ReplicaCount: ptr.To[int32](1),
+					},
+				},
+			}
+			type testStep struct {
+				setup  func(*vmv1.VTSingle)
+				modify func(*vmv1.VTSingle)
+				verify func(*vmv1.VTSingle)
+			}
+
+			DescribeTable("should update exist",
+				func(name string, initCR *vmv1.VTSingle, 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.VTSingle{}, namespacedName)
+					}, eventualDeploymentAppReadyTimeout).Should(Succeed())
+
+					for _, step := range steps {
+						if step.setup != nil {
+							step.setup(initCR)
+						}
+						// perform update
+						Eventually(func() error {
+							var toUpdate vmv1.VTSingle
+							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.VTSingle{}, namespacedName)
+						}, eventualDeploymentAppReadyTimeout).Should(Succeed())
+
+						var updated vmv1.VTSingle
+						Expect(k8sClient.Get(ctx, namespacedName, &updated)).To(Succeed())
+
+						// verify results
+						step.verify(&updated)
+					}
+				},
+				Entry("add and remove annotations", "manage-annotations",
+					baseVTSingle.DeepCopy(),
+					testStep{
+						modify: func(cr *vmv1.VTSingle) {
+							cr.Spec.ManagedMetadata = &vmv1beta1.ManagedObjectsMetadata{
+								Annotations: map[string]string{
+									"added-annotation": "some-value",
+								},
+							}
+						},
+						verify: func(cr *vmv1.VTSingle) {
+							nss := types.NamespacedName{Namespace: namespace, Name: cr.PrefixedName()}
+
+							expectedAnnotations := map[string]string{"added-annotation": "some-value"}
+							assertAnnotationsOnObjects(ctx, nss, []client.Object{&appsv1.Deployment{}, &corev1.ServiceAccount{}, &corev1.Service{}}, expectedAnnotations)
+							var createdDeploy appsv1.Deployment
+							Expect(k8sClient.Get(ctx, nss, &createdDeploy)).
+								To(Succeed())
+						},
+					},
+					testStep{
+						modify: func(cr *vmv1.VTSingle) {
+							delete(cr.Spec.ManagedMetadata.Annotations, "added-annotation")
+						},
+						verify: func(cr *vmv1.VTSingle) {
+							nss := types.NamespacedName{Namespace: namespace, Name: cr.PrefixedName()}
+							expectedAnnotations := map[string]string{"added-annotation": ""}
+
+							assertAnnotationsOnObjects(ctx, nss, []client.Object{&appsv1.Deployment{}, &corev1.ServiceAccount{}, &corev1.Service{}}, expectedAnnotations)
+
+						},
+					},
+				),
+			)
+		},
+		)
+	})
+})