diff --git a/PROJECT b/PROJECT
index cbdbea245..0beee2827 100644
--- a/PROJECT
+++ b/PROJECT
@@ -237,6 +237,30 @@ resources:
webhooks:
validation: true
webhookVersion: v1
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: victoriametrics.com
+ group: operator
+ kind: VMAnomalyModel
+ 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: VMAnomalyScheduler
+ path: github.com/VictoriaMetrics/operator/api/operator/v1
+ version: v1
+ webhooks:
+ validation: true
+ webhookVersion: v1
- api:
crdVersion: v1
namespaced: true
diff --git a/api/operator/v1/common.go b/api/operator/v1/common.go
index 3ca70c1e7..42dd46527 100644
--- a/api/operator/v1/common.go
+++ b/api/operator/v1/common.go
@@ -6,6 +6,7 @@ import (
"net/url"
corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
@@ -89,6 +90,20 @@ func (o *OAuth2) Validate() error {
return nil
}
+// Selector defines object and namespace selectors
+type Selector struct {
+ // ObjectSelector defines object to be selected for discovery.
+ // +optional
+ ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty"`
+ // NamespaceSelector defines namespaces to be selected for object discovery.
+ // +optional
+ NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`
+}
+
+func (s *Selector) IsUnmanaged() bool {
+ return s == nil || (s.ObjectSelector == nil && s.NamespaceSelector == nil)
+}
+
// TLSConfig specifies TLS configuration parameters
// with optional references to secrets with corresponding sensitive values
type TLSConfig struct {
diff --git a/api/operator/v1/vmanomaly_types.go b/api/operator/v1/vmanomaly_types.go
index 0f373c656..d570a68b1 100644
--- a/api/operator/v1/vmanomaly_types.go
+++ b/api/operator/v1/vmanomaly_types.go
@@ -94,6 +94,12 @@ type VMAnomalySpec struct {
// Monitoring configures how expose anomaly metrics
// See https://docs.victoriametrics.com/anomaly-detection/components/monitoring/
Monitoring *VMAnomalyMonitoringSpec `json:"monitoring,omitempty"`
+ // ModelSelector defines VMAnomalyModel's object and namespace selectors.
+ // +optional
+ ModelSelector *Selector `json:"modelSelector,omitempty"`
+ // SchedulerSelector defines VMAnomalyScheduler's object and namespace selectors.
+ // +optional
+ SchedulerSelector *Selector `json:"schedulerSelector,omitempty"`
// License allows to configure license key to be used for enterprise features.
// Using license key is supported starting from VictoriaMetrics v1.94.0.
// See [here](https://docs.victoriametrics.com/victoriametrics/enterprise/)
@@ -457,6 +463,11 @@ func (cr *VMAnomalySpec) UnmarshalJSON(src []byte) error {
return nil
}
+// IsUnmanaged checks if object should managed any config objects
+func (cr *VMAnomaly) IsUnmanaged() bool {
+ return cr.Spec.ModelSelector.IsUnmanaged() && cr.Spec.SchedulerSelector.IsUnmanaged()
+}
+
// +kubebuilder:object:root=true
// VMAnomalyList contains a list of VMAnomaly.
diff --git a/api/operator/v1/vmanomalymodel_types.go b/api/operator/v1/vmanomalymodel_types.go
new file mode 100644
index 000000000..0efc133f7
--- /dev/null
+++ b/api/operator/v1/vmanomalymodel_types.go
@@ -0,0 +1,87 @@
+/*
+
+
+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"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+
+ vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+)
+
+// VMAnomalyModelSpec defines the desired state of VMAnomalyModel.
+type VMAnomalyModelSpec struct {
+ // ParsingError contents error with context if operator was failed to parse json object from kubernetes api server
+ ParsingError string `json:"-" yaml:"-"`
+ // Class defines anomaly detection model class
+ Class string `json:"class" yaml:"class"`
+ // Params defines anomaly detection model params
+ Params runtime.RawExtension `json:"params,omitempty" yaml:"params,omitempty"`
+}
+
+// UnmarshalJSON implements json.Unmarshaler interface
+func (cr *VMAnomalyModelSpec) UnmarshalJSON(src []byte) error {
+ type pcr VMAnomalyModelSpec
+ if err := json.Unmarshal(src, (*pcr)(cr)); err != nil {
+ cr.ParsingError = fmt.Sprintf("cannot parse spec: %s, err: %s", string(src), err)
+ return nil
+ }
+ return nil
+}
+
+// VMAnomalyModelStatus defines the observed state of VMAnomalyModel.
+type VMAnomalyModelStatus struct {
+ vmv1beta1.StatusMetadata `json:",inline"`
+}
+
+// GetStatusMetadata returns metadata for object status
+func (cr *VMAnomalyModel) GetStatusMetadata() *vmv1beta1.StatusMetadata {
+ return &cr.Status.StatusMetadata
+}
+
+// AsKey returns unique key for object
+func (cr *VMAnomalyModel) AsKey(_ bool) string {
+ return cr.Namespace + "/" + cr.Name
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+
+// VMAnomalyModel is the Schema for the vmanomalymodels API.
+type VMAnomalyModel struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec VMAnomalyModelSpec `json:"spec,omitempty"`
+ Status VMAnomalyModelStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// VMAnomalyModelList contains a list of VMAnomalyModel.
+type VMAnomalyModelList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []VMAnomalyModel `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&VMAnomalyModel{}, &VMAnomalyModelList{})
+}
diff --git a/api/operator/v1/vmanomalyscheduler_types.go b/api/operator/v1/vmanomalyscheduler_types.go
new file mode 100644
index 000000000..0a15b01ac
--- /dev/null
+++ b/api/operator/v1/vmanomalyscheduler_types.go
@@ -0,0 +1,87 @@
+/*
+
+
+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"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+
+ vmv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
+)
+
+// VMAnomalySchedulerSpec defines the desired state of VMAnomalyScheduler.
+type VMAnomalySchedulerSpec struct {
+ // ParsingError contents error with context if operator was failed to parse json object from kubernetes api server
+ ParsingError string `json:"-" yaml:"-"`
+ // Class defines anomaly detection scheduler class
+ Class string `json:"class" yaml:"class"`
+ // Params defines anomaly detection scheduler params
+ Params runtime.RawExtension `json:"params,omitempty" yaml:"params,omitempty"`
+}
+
+// VMAnomalySchedulerStatus defines the observed state of VMAnomalyScheduler.
+type VMAnomalySchedulerStatus struct {
+ vmv1beta1.StatusMetadata `json:",inline"`
+}
+
+// GetStatusMetadata returns metadata for object status
+func (cr *VMAnomalyScheduler) GetStatusMetadata() *vmv1beta1.StatusMetadata {
+ return &cr.Status.StatusMetadata
+}
+
+// AsKey returns unique key for object
+func (cr *VMAnomalyScheduler) AsKey(_ bool) string {
+ return cr.Namespace + "/" + cr.Name
+}
+
+// UnmarshalJSON implements json.Unmarshaler interface
+func (cr *VMAnomalySchedulerSpec) UnmarshalJSON(src []byte) error {
+ type pcr VMAnomalySchedulerSpec
+ if err := json.Unmarshal(src, (*pcr)(cr)); err != nil {
+ cr.ParsingError = fmt.Sprintf("cannot parse spec: %s, err: %s", string(src), err)
+ return nil
+ }
+ return nil
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+
+// VMAnomalyScheduler is the Schema for the vmanomalyschedulers API.
+type VMAnomalyScheduler struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec VMAnomalySchedulerSpec `json:"spec,omitempty"`
+ Status VMAnomalySchedulerStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// VMAnomalySchedulerList contains a list of VMAnomalyScheduler.
+type VMAnomalySchedulerList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []VMAnomalyScheduler `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&VMAnomalyScheduler{}, &VMAnomalySchedulerList{})
+}
diff --git a/api/operator/v1/zz_generated.deepcopy.go b/api/operator/v1/zz_generated.deepcopy.go
index ead5889b4..8e3fa05f7 100644
--- a/api/operator/v1/zz_generated.deepcopy.go
+++ b/api/operator/v1/zz_generated.deepcopy.go
@@ -24,7 +24,8 @@ import (
"github.com/VictoriaMetrics/operator/api/operator/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
- runtime "k8s.io/apimachinery/pkg/runtime"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@@ -64,6 +65,31 @@ func (in *OAuth2) DeepCopy() *OAuth2 {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Selector) DeepCopyInto(out *Selector) {
+ *out = *in
+ if in.ObjectSelector != nil {
+ in, out := &in.ObjectSelector, &out.ObjectSelector
+ *out = new(metav1.LabelSelector)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.NamespaceSelector != nil {
+ in, out := &in.NamespaceSelector, &out.NamespaceSelector
+ *out = new(metav1.LabelSelector)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector.
+func (in *Selector) DeepCopy() *Selector {
+ if in == nil {
+ return nil
+ }
+ out := new(Selector)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SyslogServerSpec) DeepCopyInto(out *SyslogServerSpec) {
*out = *in
@@ -1049,6 +1075,97 @@ func (in *VMAnomalyList) DeepCopyObject() runtime.Object {
return nil
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VMAnomalyModel) DeepCopyInto(out *VMAnomalyModel) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMAnomalyModel.
+func (in *VMAnomalyModel) DeepCopy() *VMAnomalyModel {
+ if in == nil {
+ return nil
+ }
+ out := new(VMAnomalyModel)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *VMAnomalyModel) 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 *VMAnomalyModelList) DeepCopyInto(out *VMAnomalyModelList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]VMAnomalyModel, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMAnomalyModelList.
+func (in *VMAnomalyModelList) DeepCopy() *VMAnomalyModelList {
+ if in == nil {
+ return nil
+ }
+ out := new(VMAnomalyModelList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *VMAnomalyModelList) 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 *VMAnomalyModelSpec) DeepCopyInto(out *VMAnomalyModelSpec) {
+ *out = *in
+ in.Params.DeepCopyInto(&out.Params)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMAnomalyModelSpec.
+func (in *VMAnomalyModelSpec) DeepCopy() *VMAnomalyModelSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(VMAnomalyModelSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VMAnomalyModelStatus) DeepCopyInto(out *VMAnomalyModelStatus) {
+ *out = *in
+ in.StatusMetadata.DeepCopyInto(&out.StatusMetadata)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMAnomalyModelStatus.
+func (in *VMAnomalyModelStatus) DeepCopy() *VMAnomalyModelStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(VMAnomalyModelStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VMAnomalyMonitoringPullSpec) DeepCopyInto(out *VMAnomalyMonitoringPullSpec) {
*out = *in
@@ -1138,6 +1255,97 @@ func (in *VMAnomalyReadersSpec) DeepCopy() *VMAnomalyReadersSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VMAnomalyScheduler) DeepCopyInto(out *VMAnomalyScheduler) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMAnomalyScheduler.
+func (in *VMAnomalyScheduler) DeepCopy() *VMAnomalyScheduler {
+ if in == nil {
+ return nil
+ }
+ out := new(VMAnomalyScheduler)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *VMAnomalyScheduler) 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 *VMAnomalySchedulerList) DeepCopyInto(out *VMAnomalySchedulerList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]VMAnomalyScheduler, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMAnomalySchedulerList.
+func (in *VMAnomalySchedulerList) DeepCopy() *VMAnomalySchedulerList {
+ if in == nil {
+ return nil
+ }
+ out := new(VMAnomalySchedulerList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *VMAnomalySchedulerList) 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 *VMAnomalySchedulerSpec) DeepCopyInto(out *VMAnomalySchedulerSpec) {
+ *out = *in
+ in.Params.DeepCopyInto(&out.Params)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMAnomalySchedulerSpec.
+func (in *VMAnomalySchedulerSpec) DeepCopy() *VMAnomalySchedulerSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(VMAnomalySchedulerSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VMAnomalySchedulerStatus) DeepCopyInto(out *VMAnomalySchedulerStatus) {
+ *out = *in
+ in.StatusMetadata.DeepCopyInto(&out.StatusMetadata)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VMAnomalySchedulerStatus.
+func (in *VMAnomalySchedulerStatus) DeepCopy() *VMAnomalySchedulerStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(VMAnomalySchedulerStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VMAnomalySpec) DeepCopyInto(out *VMAnomalySpec) {
*out = *in
@@ -1208,6 +1416,16 @@ func (in *VMAnomalySpec) DeepCopyInto(out *VMAnomalySpec) {
*out = new(VMAnomalyMonitoringSpec)
(*in).DeepCopyInto(*out)
}
+ if in.ModelSelector != nil {
+ in, out := &in.ModelSelector, &out.ModelSelector
+ *out = new(Selector)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.SchedulerSelector != nil {
+ in, out := &in.SchedulerSelector, &out.SchedulerSelector
+ *out = new(Selector)
+ (*in).DeepCopyInto(*out)
+ }
if in.License != nil {
in, out := &in.License, &out.License
*out = new(v1beta1.License)
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index f85fd86cd..ffb94e30e 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -24,6 +24,8 @@ resources:
- bases/operator.victoriametrics.com_vtsingles.yaml
- bases/operator.victoriametrics.com_vtclusters.yaml
- bases/operator.victoriametrics.com_vmanomalies.yaml
+- bases/operator.victoriametrics.com_vmanomalymodels.yaml
+- bases/operator.victoriametrics.com_vmanomalyschedulers.yaml
patches:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
# patches here are for enabling the conversion webhook for each CRD
diff --git a/config/crd/overlay/crd.specless.yaml b/config/crd/overlay/crd.specless.yaml
index 3b6a79e13..06103d84c 100644
--- a/config/crd/overlay/crd.specless.yaml
+++ b/config/crd/overlay/crd.specless.yaml
@@ -1319,6 +1319,263 @@ spec:
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.18.0
+ name: vmanomalymodels.operator.victoriametrics.com
+spec:
+ group: operator.victoriametrics.com
+ names:
+ kind: VMAnomalyModel
+ listKind: VMAnomalyModelList
+ plural: vmanomalymodels
+ singular: vmanomalymodel
+ scope: Namespaced
+ versions:
+ - name: v1
+ schema:
+ openAPIV3Schema:
+ description: VMAnomalyModel is the Schema for the vmanomalymodels 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: VMAnomalyModelSpec defines the desired state of VMAnomalyModel.
+ required:
+ - class
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ status:
+ description: VMAnomalyModelStatus defines the observed state of VMAnomalyModel.
+ 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: vmanomalyschedulers.operator.victoriametrics.com
+spec:
+ group: operator.victoriametrics.com
+ names:
+ kind: VMAnomalyScheduler
+ listKind: VMAnomalySchedulerList
+ plural: vmanomalyschedulers
+ singular: vmanomalyscheduler
+ scope: Namespaced
+ versions:
+ - name: v1
+ schema:
+ openAPIV3Schema:
+ description: VMAnomalyScheduler is the Schema for the vmanomalyschedulers
+ 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: VMAnomalySchedulerSpec defines the desired state of VMAnomalyScheduler.
+ required:
+ - class
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ status:
+ description: VMAnomalySchedulerStatus defines the observed state of VMAnomalyScheduler.
+ 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
diff --git a/config/crd/overlay/crd.yaml b/config/crd/overlay/crd.yaml
index 59e0d253c..325dc2c3c 100644
--- a/config/crd/overlay/crd.yaml
+++ b/config/crd/overlay/crd.yaml
@@ -21909,6 +21909,105 @@ spec:
Has no effect for VLogs and VMSingle
format: int32
type: integer
+ modelSelector:
+ description: ModelSelector defines VMAnomalyModel's object and namespace
+ selectors.
+ properties:
+ namespaceSelector:
+ description: NamespaceSelector defines namespaces to be selected
+ for object discovery.
+ 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
+ objectSelector:
+ description: ObjectSelector defines object to be selected for
+ discovery.
+ 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
+ type: object
monitoring:
description: |-
Monitoring configures how expose anomaly metrics
@@ -22729,6 +22828,105 @@ spec:
schedulerName:
description: SchedulerName - defines kubernetes scheduler name
type: string
+ schedulerSelector:
+ description: SchedulerSelector defines VMAnomalyScheduler's object
+ and namespace selectors.
+ properties:
+ namespaceSelector:
+ description: NamespaceSelector defines namespaces to be selected
+ for object discovery.
+ 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
+ objectSelector:
+ description: ObjectSelector defines object to be selected for
+ discovery.
+ 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
+ type: object
secrets:
description: |-
Secrets is a list of Secrets in the same namespace as the Application
@@ -23776,6 +23974,277 @@ spec:
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.18.0
+ name: vmanomalymodels.operator.victoriametrics.com
+spec:
+ group: operator.victoriametrics.com
+ names:
+ kind: VMAnomalyModel
+ listKind: VMAnomalyModelList
+ plural: vmanomalymodels
+ singular: vmanomalymodel
+ scope: Namespaced
+ versions:
+ - name: v1
+ schema:
+ openAPIV3Schema:
+ description: VMAnomalyModel is the Schema for the vmanomalymodels 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: VMAnomalyModelSpec defines the desired state of VMAnomalyModel.
+ properties:
+ class:
+ description: Class defines anomaly detection model class
+ type: string
+ params:
+ description: Params defines anomaly detection model params
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - class
+ type: object
+ status:
+ description: VMAnomalyModelStatus defines the observed state of VMAnomalyModel.
+ 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: vmanomalyschedulers.operator.victoriametrics.com
+spec:
+ group: operator.victoriametrics.com
+ names:
+ kind: VMAnomalyScheduler
+ listKind: VMAnomalySchedulerList
+ plural: vmanomalyschedulers
+ singular: vmanomalyscheduler
+ scope: Namespaced
+ versions:
+ - name: v1
+ schema:
+ openAPIV3Schema:
+ description: VMAnomalyScheduler is the Schema for the vmanomalyschedulers
+ 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: VMAnomalySchedulerSpec defines the desired state of VMAnomalyScheduler.
+ properties:
+ class:
+ description: Class defines anomaly detection scheduler class
+ type: string
+ params:
+ description: Params defines anomaly detection scheduler params
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - class
+ type: object
+ status:
+ description: VMAnomalySchedulerStatus defines the observed state of VMAnomalyScheduler.
+ 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
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index ec6058681..0af25f2a6 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -198,6 +198,12 @@ rules:
- vmanomalies
- vmanomalies/finalizers
- vmanomalies/status
+ - vmanomalymodels
+ - vmanomalymodels/finalizers
+ - vmanomalymodels/status
+ - vmanomalyschedulers
+ - vmanomalyschedulers/finalizers
+ - vmanomalyschedulers/status
verbs:
- '*'
- apiGroups:
diff --git a/config/samples/operator_v1_vmanomalymodel.yaml b/config/samples/operator_v1_vmanomalymodel.yaml
new file mode 100644
index 000000000..929c2b28d
--- /dev/null
+++ b/config/samples/operator_v1_vmanomalymodel.yaml
@@ -0,0 +1,9 @@
+apiVersion: operator.victoriametrics.com/v1
+kind: VMAnomalyModel
+metadata:
+ labels:
+ app.kubernetes.io/name: victoriametrics-operator
+ app.kubernetes.io/managed-by: kustomize
+ name: vmanomalymodel-sample
+spec:
+ # TODO(user): Add fields here
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index d6913adcc..9920997f6 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -16,6 +16,7 @@ aliases:
* FEATURE: [vmagent](https://docs.victoriametrics.com/operator/resources/vmagent/): support `namespace` parameter in `attach_metadata` section for all scrape configurations. See [#1654](https://github.com/VictoriaMetrics/operator/issues/1654).
* FEATURE: [vlagent](https://docs.victoriametrics.com/operator/resources/vlagent): support logs collection. See [#1501](https://github.com/VictoriaMetrics/operator/issues/1501).
* FEATURE: [vmoperator](https://docs.victoriametrics.com/operator/): use `operator_bad_objects_total` metric with `object_namespace` and `crd` labels to track invalid objects managed by VMAgent, VMAuth, VMAlert and VMAlertmanager. Old `operator_alertmanager_bad_objects_count` and `operator_vmalert_bad_objects_count` are deprecated and will be removed in next releases.
+* FEATURE: [vmoperator](https://docs.victoriametrics.com/operator/): add `object_namespace` label to the `operator_alertmanager_bad_objects_count` and `operator_vmalert_bad_objects_count` metrics.
* BUGFIX: [vmoperator](https://docs.victoriametrics.com/operator/): fixed HPA cleanup logic for all cluster resources, before it was constantly recreated. Bug introduced in [this commit](https://github.com/VictoriaMetrics/operator/commit/983d1678c37497a7d03d2f57821219fd4975deec).
@@ -104,6 +105,7 @@ This change could be reverted by providing env variable `VM_USECUSTOMCONFIGRELOA
* BUGFIX: [vmoperator](https://docs.victoriametrics.com/operator/): fix an issue where the return value from a couple of controllers was always `nil`. See [#1532](https://github.com/VictoriaMetrics/operator/pull/1532) for details.
* BUGFIX: [VMCluster](https://docs.victoriametrics.com/operator/resources/vmcluster/): emit warning if `vmcluster.spec.vmselect.persistentVolume` is set, previously it was emitted for `vmcluster.spec.vmselect.storage`.
* BUGFIX: [vmoperator](https://docs.victoriametrics.com/operator/): Prevent endless Service reconcile loop by correctly track changes to Service.spec.LoadBalancerClass. See this issue [#1550](https://github.com/VictoriaMetrics/operator/issues/1550) for details.
+* BUGFIX: [vmagent](https://docs.victoriametrics.com/operator/resources/vmagent/) and [vmanomaly](https://docs.victoriametrics.com/operator/resources/vmanomaly/): create PDB per shard to guarantee proper application protection. See [#1548](https://github.com/VictoriaMetrics/operator/issues/1548).
## [v0.63.0](https://github.com/VictoriaMetrics/operator/releases/tag/v0.63.0)
diff --git a/docs/api.md b/docs/api.md
index d2d7a8457..fbd4be4b7 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)
+- [VMAnomalyModel](#vmanomalymodel)
+- [VMAnomalyScheduler](#vmanomalyscheduler)
- [VTCluster](#vtcluster)
- [VTSingle](#vtsingle)
@@ -66,6 +68,20 @@ Appears in: [VLAgentRemoteWriteSpec](#vlagentremotewritespec)
| tokenURL#
_string_ | _(Required)_
TokenURL defines URL to fetch the token from |
+#### Selector
+
+
+
+Selector defines object and namespace selectors
+
+Appears in: [VMAnomalySpec](#vmanomalyspec)
+
+| Field | Description |
+| --- | --- |
+| namespaceSelector#
_[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#labelselector-v1-meta)_ | _(Optional)_
NamespaceSelector defines namespaces to be selected for object discovery. |
+| objectSelector#
_[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#labelselector-v1-meta)_ | _(Optional)_
ObjectSelector defines object to be selected for discovery. |
+
+
#### SyslogServerSpec
@@ -650,6 +666,38 @@ Appears in: [VMAnomalyMonitoringPushSpec](#vmanomalymonitoringpushspec), [VMAnom
| tlsConfig#
_[TLSConfig](#tlsconfig)_ | _(Required)_
TLSConfig defines tls connection configuration |
+#### VMAnomalyModel
+
+
+
+VMAnomalyModel is the Schema for the vmanomalymodels API.
+
+
+
+| Field | Description |
+| --- | --- |
+| apiVersion
_string_ | (Required)
`operator.victoriametrics.com/v1` |
+| kind
_string_ | (Required)
`VMAnomalyModel` |
+| metadata#
_[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#objectmeta-v1-meta)_ | _(Required)_
Refer to Kubernetes API documentation for fields of `metadata`. |
+| spec#
_[VMAnomalyModelSpec](#vmanomalymodelspec)_ | _(Required)_
|
+
+
+#### VMAnomalyModelSpec
+
+
+
+VMAnomalyModelSpec defines the desired state of VMAnomalyModel.
+
+Appears in: [VMAnomalyModel](#vmanomalymodel)
+
+| Field | Description |
+| --- | --- |
+| class#
_string_ | _(Required)_
Class defines anomaly detection model class |
+| params#
_[RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#rawextension-runtime-pkg)_ | _(Required)_
Params defines anomaly detection model params |
+
+
+
+
#### VMAnomalyMonitoringPullSpec
@@ -729,6 +777,38 @@ Appears in: [VMAnomalySpec](#vmanomalyspec)
| tz
_string_ | _(Required)_
Optional argumentspecifies the IANA timezone to account for local shifts, like DST, in models sensitive to seasonal patterns |
+#### VMAnomalyScheduler
+
+
+
+VMAnomalyScheduler is the Schema for the vmanomalyschedulers API.
+
+
+
+| Field | Description |
+| --- | --- |
+| apiVersion
_string_ | (Required)
`operator.victoriametrics.com/v1` |
+| kind
_string_ | (Required)
`VMAnomalyScheduler` |
+| metadata#
_[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#objectmeta-v1-meta)_ | _(Required)_
Refer to Kubernetes API documentation for fields of `metadata`. |
+| spec#
_[VMAnomalySchedulerSpec](#vmanomalyschedulerspec)_ | _(Required)_
|
+
+
+#### VMAnomalySchedulerSpec
+
+
+
+VMAnomalySchedulerSpec defines the desired state of VMAnomalyScheduler.
+
+Appears in: [VMAnomalyScheduler](#vmanomalyscheduler)
+
+| Field | Description |
+| --- | --- |
+| class#
_string_ | _(Required)_
Class defines anomaly detection scheduler class |
+| params#
_[RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#rawextension-runtime-pkg)_ | _(Required)_
Params defines anomaly detection scheduler params |
+
+
+
+
#### VMAnomalySpec
@@ -762,6 +842,7 @@ Appears in: [VMAnomaly](#vmanomaly)
| logLevel#
_string_ | _(Optional)_
LogLevel for VMAnomaly to be configured with.
INFO, WARN, ERROR, FATAL, PANIC |
| managedMetadata#
_[ManagedObjectsMetadata](#managedobjectsmetadata)_ | _(Required)_
ManagedMetadata defines metadata that will be added to the all objects
created by operator for the given CustomResource |
| minReadySeconds#
_integer_ | _(Optional)_
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 |
+| modelSelector#
_[Selector](#selector)_ | _(Optional)_
ModelSelector defines VMAnomalyModel's object and namespace selectors. |
| monitoring#
_[VMAnomalyMonitoringSpec](#vmanomalymonitoringspec)_ | _(Required)_
Monitoring configures how expose anomaly metrics
See https://docs.victoriametrics.com/anomaly-detection/components/monitoring/ |
| nodeSelector#
_object (keys:string, values:string)_ | _(Optional)_
NodeSelector Define which Nodes the Pods are scheduled on. |
| paused#
_boolean_ | _(Optional)_
Paused If set to true all actions on the underlying managed objects are not
going to be performed, except for delete actions. |
@@ -778,6 +859,7 @@ Appears in: [VMAnomaly](#vmanomaly)
| rollingUpdateStrategy#
_[StatefulSetUpdateStrategyType](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#statefulsetupdatestrategytype-v1-apps)_ | _(Optional)_
RollingUpdateStrategy allows configuration for strategyType
set it to RollingUpdate for disabling operator statefulSet rollingUpdate |
| runtimeClassName#
_string_ | _(Optional)_
RuntimeClassName - defines runtime class for kubernetes pod.
https://kubernetes.io/docs/concepts/containers/runtime-class/ |
| schedulerName#
_string_ | _(Optional)_
SchedulerName - defines kubernetes scheduler name |
+| schedulerSelector#
_[Selector](#selector)_ | _(Optional)_
SchedulerSelector defines VMAnomalyScheduler's object and namespace selectors. |
| secrets#
_string array_ | _(Optional)_
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 |
| securityContext#
_[SecurityContext](#securitycontext)_ | _(Optional)_
SecurityContext holds pod-level security attributes and common container settings.
This defaults to the default PodSecurityContext. |
| serviceAccountName#
_string_ | _(Optional)_
ServiceAccountName is the name of the ServiceAccount to use to run the pods |
@@ -1502,7 +1584,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), [VTClusterStatus](#vtclusterstatus), [VTSingleStatus](#vtsinglestatus)
+Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [StatusMetadata](#statusmetadata), [VLAgentStatus](#vlagentstatus), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAnomalyModelStatus](#vmanomalymodelstatus), [VMAnomalySchedulerStatus](#vmanomalyschedulerstatus), [VMAnomalyStatus](#vmanomalystatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus), [VTClusterStatus](#vtclusterstatus), [VTSingleStatus](#vtsinglestatus)
| Field | Description |
| --- | --- |
@@ -2971,7 +3053,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), [VTClusterStatus](#vtclusterstatus), [VTSingleStatus](#vtsinglestatus)
+Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [VLAgentStatus](#vlagentstatus), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAnomalyModelStatus](#vmanomalymodelstatus), [VMAnomalySchedulerStatus](#vmanomalyschedulerstatus), [VMAnomalyStatus](#vmanomalystatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus), [VTClusterStatus](#vtclusterstatus), [VTSingleStatus](#vtsinglestatus)
| Field | Description |
| --- | --- |
@@ -3310,7 +3392,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), [VTClusterStatus](#vtclusterstatus), [VTSingleStatus](#vtsinglestatus)
+Appears in: [ScrapeObjectStatus](#scrapeobjectstatus), [StatusMetadata](#statusmetadata), [VLAgentStatus](#vlagentstatus), [VLClusterStatus](#vlclusterstatus), [VLSingleStatus](#vlsinglestatus), [VLogsStatus](#vlogsstatus), [VMAgentStatus](#vmagentstatus), [VMAlertStatus](#vmalertstatus), [VMAlertmanagerConfigStatus](#vmalertmanagerconfigstatus), [VMAlertmanagerStatus](#vmalertmanagerstatus), [VMAnomalyModelStatus](#vmanomalymodelstatus), [VMAnomalySchedulerStatus](#vmanomalyschedulerstatus), [VMAnomalyStatus](#vmanomalystatus), [VMAuthStatus](#vmauthstatus), [VMClusterStatus](#vmclusterstatus), [VMRuleStatus](#vmrulestatus), [VMSingleStatus](#vmsinglestatus), [VMUserStatus](#vmuserstatus), [VTClusterStatus](#vtclusterstatus), [VTSingleStatus](#vtsinglestatus)
diff --git a/docs/resources/vmanomaly.md b/docs/resources/vmanomaly.md
index 14f61f2aa..6c5bc52fb 100644
--- a/docs/resources/vmanomaly.md
+++ b/docs/resources/vmanomaly.md
@@ -326,6 +326,43 @@ spec:
z_threshold: 2.5
```
+## Dynamic configuration
+
+`VMAnomaly` supports discovering of configuration sections:
+
+- [VMAnomalyModel](https://docs.victoriametrics.com/operator/resources/vmanomalymodel/) - discovers anomaly detection [models](https://docs.victoriametrics.com/anomaly-detection/components/models/).
+- [VMAnomalyScheduler](https://docs.victoriametrics.com/operator/resources/vmanomalyscheduler/) - discovers anomaly detection [schedulers](https://docs.victoriametrics.com/anomaly-detection/components/schedulers/).
+
+For filtering scrape objects `VMAnomaly` uses selectors.
+Selectors are defined with suffixes - `namespaceSelector` and `objectSelector` for each type of configuration objects in spec of `VMAnomaly`:
+
+- `spec.modelSelector.namespaceSelector` and `spec.modelSelector.objectSelector` for selecting [VMAnomalyModel](https://docs.victoriametrics.com/operator/resources/vmanomalymodel/) objects
+- `spec.schedulerSelector.namespaceSelector` and `spec.schedulerSelector.objectSelector` for selecting [VMAnomalyScheduler](https://docs.victoriametrics.com/operator/resources/vmanomalyscheduler/) objects
+
+It allows configuring objects access control across namespaces and different environments.
+Specification of selectors you can see in [this doc](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#labelselector-v1-meta/).
+
+Following rules are applied:
+
+- If `namespaceSelector` and `objectSelector` both undefined, then by default select nothing.
+- If `namespaceSelector` defined, `objectSelector` undefined, then all objects are matching at namespaces for given `namespaceSelector`.
+- If `namespaceSelector` undefined, `objectSelector` defined, then all objects at `VMAnomaly`'s namespaces are matching for given `objectSelector`.
+- If `namespaceSelector` and `objectSelector` both defined, then only objects at namespaces matched `namespaceSelector` for given `objectSelector` are matching.
+
+Here's a more visual and more detailed view:
+
+| `namespaceSelector` | `objectSelector` | `WATCH_NAMESPACE` | Selected objects |
+|------------------------|------------------|-------------------|-------------------------------------------------------------------------------------------------------------|
+| undefined | undefined | undefined | nothing |
+| undefined | undefined | undefined | all objects of given type (`...`) in the cluster |
+| **defined** | undefined | undefined | all objects of given type (`...`) at namespaces for given `namespaceSelector` |
+| undefined | **defined** | undefined | all objects of given type (`...`) only at `VMAnomaly`'s namespace are matching for given `objectSelector` |
+| **defined** | **defined** | undefined | all objects of given type (`...`) only at namespaces matched `namespaceSelector` for given `objectSelector` |
+| *any* | undefined | **defined** | all objects of given type (`...`) only at `VMAnomaly`'s namespace |
+| *any* | **defined** | **defined** | all objects of given type (`...`) only at `VMAnomaly`'s namespace for given `objectSelector` |
+
+More details about `WATCH_NAMESPACE` variable you can read in [this doc](https://docs.victoriametrics.com/operator/configuration/#namespaced-mode).
+
## Version management
To set `VMAnomaly` version add `spec.image.tag` name from [releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases)
diff --git a/docs/resources/vmanomalymodel.md b/docs/resources/vmanomalymodel.md
new file mode 100644
index 000000000..b1a203d35
--- /dev/null
+++ b/docs/resources/vmanomalymodel.md
@@ -0,0 +1,112 @@
+---
+weight: 15
+title: VMAnomalyModel
+menu:
+ docs:
+ identifier: operator-cr-vmanomalymodel
+ parent: operator-cr
+ weight: 8
+aliases:
+ - /operator/resources/vmanomalymodel/
+ - /operator/resources/vmanomalymodel/index.html
+tags:
+ - kubernetes
+ - metrics
+ - anomaly
+---
+The `VMAnomalyModel` CRD allows to declaratively define anomaly detection [models](https://docs.victoriametrics.com/anomaly-detection/components/models/).
+
+`VMAnomalyModel` object updates [model](https://docs.victoriametrics.com/anomaly-detection/components/models/) section of [VMAnomaly](https://docs.victoriametrics.com/anomaly-detection/)
+configuration by adding item with `{metadata.namespace}-{metadata.name}` key and `spec.params` value. If item with given key exists operator will override it:
+
+With given `VMAnomaly` CR:
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VMAnomaly
+metadata:
+ name: example
+ namespace: test
+spec:
+ replicaCount: 2
+ license:
+ key: "xx"
+ modelSelector:
+ objectSelector:
+ matchExpressions:
+ - key: app
+ operator: In
+ values: [test]
+ configRawYaml: |
+ reader:
+ queries:
+ ingestion_rate:
+ expr: 'sum(rate(vm_rows_inserted_total[5m])) by (type) > 0'
+ step: '1m'
+ schedulers:
+ scheduler_periodic_1m:
+ class: "periodic"
+ # class: "periodic" # or class: "scheduler.periodic.PeriodicScheduler" until v1.13.0 with class alias support)
+ infer_every: "1m"
+ fit_every: "2m"
+ fit_window: "3h"
+ models:
+ test-example-univariate:
+ class: 'zscore'
+ z_threshold: 2.5
+ reader:
+ datasourceURL: http://vmsingle-read-example:8428
+ samplingPeriod: 10s
+ writer:
+ datasourceURL: http://vmsingle-write-example:8428
+ monitoring:
+ push:
+ url: http://vmsingle-monitoring-example:8428
+```
+
+and `VMAnomalyModel` CR
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VMAnomalyModel
+metadata:
+ name: example-univariate
+ namespace: test
+ labels:
+ app: test
+spec:
+ class: zscore
+ params:
+ z_threshold: 3.0
+```
+
+result anomaly detection configuration is:
+
+```yaml
+schedulers:
+ scheduler_periodic_1m:
+ class: "periodic"
+ infer_every: "1m"
+ fit_every: "2m"
+ fit_window: "3h"
+models:
+ test-example-univariate:
+ class: 'zscore'
+ z_threshold: 3.0
+reader:
+ datasourceURL: http://vmsingle-read-example:8428
+ samplingPeriod: 10s
+ queries:
+ ingestion_rate:
+ expr: 'sum(rate(vm_rows_inserted_total[5m])) by (type) > 0'
+ step: '1m'
+writer:
+ datasourceURL: http://vmsingle-write-example:8428
+```
+
+## Specification
+
+You can see the full actual specification of the `VMAnomalyModel` resource in
+the **[API docs -> VMAnomalyModel](https://docs.victoriametrics.com/operator/api/#vmanomalymodel)**.
+
+
diff --git a/docs/resources/vmanomalyscheduler.md b/docs/resources/vmanomalyscheduler.md
new file mode 100644
index 000000000..abec2a5f8
--- /dev/null
+++ b/docs/resources/vmanomalyscheduler.md
@@ -0,0 +1,113 @@
+---
+weight: 15
+title: VMAnomalyScheduler
+menu:
+ docs:
+ identifier: operator-cr-vmanomalyscheduler
+ parent: operator-cr
+ weight: 8
+aliases:
+ - /operator/resources/vmanomalyscheduler/
+ - /operator/resources/vmanomalyscheduler/index.html
+tags:
+ - kubernetes
+ - metrics
+ - anomaly
+---
+The `VMAnomalyScheduler` CRD allows to declaratively define anomaly detection [schedulers](https://docs.victoriametrics.com/anomaly-detection/components/schedulers/).
+
+`VMAnomalyScheduler` object updates [scheduler](https://docs.victoriametrics.com/anomaly-detection/components/schedulers/) section of [VMAnomaly](https://docs.victoriametrics.com/anomaly-detection/)
+configuration by adding item with `{metadata.namespace}-{metadata.name}` key and `spec.params` value. If item with given key exists operator will override it:
+
+With given `VMAnomaly` CR:
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VMAnomaly
+metadata:
+ name: example
+ namespace: test
+spec:
+ replicaCount: 2
+ license:
+ key: "xx"
+ schedulerSelector:
+ objectSelector:
+ matchExpressions:
+ - key: app
+ operator: In
+ values: [test]
+ configRawYaml: |
+ reader:
+ queries:
+ ingestion_rate:
+ expr: 'sum(rate(vm_rows_inserted_total[5m])) by (type) > 0'
+ step: '1m'
+ schedulers:
+ test-example-periodic:
+ class: "periodic"
+ infer_every: "1m"
+ fit_every: "2m"
+ fit_window: "3h"
+ models:
+ test:
+ class: 'zscore'
+ z_threshold: 2.5
+ reader:
+ datasourceURL: http://vmsingle-read-example:8428
+ samplingPeriod: 10s
+ writer:
+ datasourceURL: http://vmsingle-write-example:8428
+ monitoring:
+ push:
+ url: http://vmsingle-monitoring-example:8428
+```
+
+and `VMAnomalyScheduler` CR
+
+```yaml
+apiVersion: operator.victoriametrics.com/v1
+kind: VMAnomalyScheduler
+metadata:
+ name: example-periodic
+ namespace: test
+ labels:
+ app: test
+spec:
+ class: periodic
+ params:
+ infer_every: 2m
+ fit_every: 4m
+ fit_window: 5h
+```
+
+result anomaly detection configuration is:
+
+```yaml
+schedulers:
+ test-example-periodic:
+ class: "periodic"
+ infer_every: 2m
+ fit_every: 4m
+ fit_window: 5h
+models:
+ test:
+ class: 'zscore'
+ z_threshold: 2.5
+reader:
+ datasourceURL: http://vmsingle-read-example:8428
+ samplingPeriod: 10s
+ queries:
+ ingestion_rate:
+ expr: 'sum(rate(vm_rows_inserted_total[5m])) by (type) > 0'
+ step: '1m'
+writer:
+ datasourceURL: http://vmsingle-write-example:8428
+```
+
+## Specification
+
+You can see the full actual specification of the `VMAnomalyScheduler` resource in
+the **[API docs -> VMAnomalyScheduler](https://docs.victoriametrics.com/operator/api/#vmanomalyscheduler)**.
+
+
diff --git a/internal/controller/operator/factory/build/pdb.go b/internal/controller/operator/factory/build/pdb.go
index 8886b8366..a70d8d460 100644
--- a/internal/controller/operator/factory/build/pdb.go
+++ b/internal/controller/operator/factory/build/pdb.go
@@ -1,6 +1,9 @@
package build
import (
+ "fmt"
+ "strconv"
+
policyv1 "k8s.io/api/policy/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -9,6 +12,11 @@ import (
// PodDisruptionBudget creates object for given CRD
func PodDisruptionBudget(cr builderOpts, spec *vmv1beta1.EmbeddedPodDisruptionBudgetSpec) *policyv1.PodDisruptionBudget {
+ return PodDisruptionBudgetSharded(cr, spec, nil)
+}
+
+// PodDisruptionBudgetSharded creates object for given CRD and shard num
+func PodDisruptionBudgetSharded(cr builderOpts, spec *vmv1beta1.EmbeddedPodDisruptionBudgetSpec, num *int) *policyv1.PodDisruptionBudget {
pdb := policyv1.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: cr.PrefixedName(),
@@ -25,6 +33,10 @@ func PodDisruptionBudget(cr builderOpts, spec *vmv1beta1.EmbeddedPodDisruptionBu
},
},
}
+ if num != nil {
+ pdb.Name = fmt.Sprintf("%s-%d", pdb.Name, *num)
+ pdb.Spec.Selector.MatchLabels[shardLabelName] = strconv.Itoa(*num)
+ }
if len(spec.UnhealthyPodEvictionPolicy) > 0 {
p := policyv1.UnhealthyPodEvictionPolicyType(spec.UnhealthyPodEvictionPolicy)
pdb.Spec.UnhealthyPodEvictionPolicy = &p
diff --git a/internal/controller/operator/factory/k8stools/test_helpers.go b/internal/controller/operator/factory/k8stools/test_helpers.go
index 89f6066df..7daf48a17 100644
--- a/internal/controller/operator/factory/k8stools/test_helpers.go
+++ b/internal/controller/operator/factory/k8stools/test_helpers.go
@@ -45,6 +45,8 @@ func testGetScheme() *runtime.Scheme {
&vmv1.VLClusterList{},
&vmv1.VTClusterList{},
&vmv1.VMAnomalyList{},
+ &vmv1.VMAnomalyModelList{},
+ &vmv1.VMAnomalySchedulerList{},
&vmv1.VLAgentList{},
&gwapiv1.HTTPRouteList{},
&apiextensionsv1.CustomResourceDefinitionList{},
@@ -72,6 +74,8 @@ func testGetScheme() *runtime.Scheme {
&vmv1.VTSingle{},
&vmv1.VTCluster{},
&vmv1.VMAnomaly{},
+ &vmv1.VMAnomalyModel{},
+ &vmv1.VMAnomalyScheduler{},
&vmv1.VLAgent{},
&gwapiv1.HTTPRoute{},
&apiextensionsv1.CustomResourceDefinition{},
@@ -113,6 +117,8 @@ func GetTestClientWithClientObjects(predefinedObjects []client.Object) client.Cl
&vmv1.VTSingle{},
&vmv1.VTCluster{},
&vmv1.VMAnomaly{},
+ &vmv1.VMAnomalyModel{},
+ &vmv1.VMAnomalyScheduler{},
&vmv1.VLAgent{},
&gwapiv1.HTTPRoute{},
).
diff --git a/internal/controller/operator/factory/vmagent/vmagent_scrapeconfig.go b/internal/controller/operator/factory/vmagent/vmagent_scrapeconfig.go
index c5600ee32..478c24036 100644
--- a/internal/controller/operator/factory/vmagent/vmagent_scrapeconfig.go
+++ b/internal/controller/operator/factory/vmagent/vmagent_scrapeconfig.go
@@ -248,11 +248,7 @@ func createOrUpdateConfigurationSecret(ctx context.Context, rclient client.Clien
}
}
- if err := pos.updateStatusesForScrapeObjects(ctx, rclient, cr, childObject); err != nil {
- return err
- }
-
- return nil
+ return pos.updateStatusesForScrapeObjects(ctx, rclient, cr, childObject)
}
func (pos *parsedObjects) updateStatusesForScrapeObjects(ctx context.Context, rclient client.Client, cr *vmv1beta1.VMAgent, childObject client.Object) error {
diff --git a/internal/controller/operator/factory/vmanomaly/config.go b/internal/controller/operator/factory/vmanomaly/config.go
index 6a23facab..19a5c73de 100644
--- a/internal/controller/operator/factory/vmanomaly/config.go
+++ b/internal/controller/operator/factory/vmanomaly/config.go
@@ -16,16 +16,34 @@ import (
"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/vmanomaly/config"
)
+// CreateOrUpdateConfig builds configuration for VMAnomaly
+func CreateOrUpdateConfig(ctx context.Context, rclient client.Client, cr *vmv1.VMAnomaly, childObject client.Object) error {
+ var prevCR *vmv1.VMAnomaly
+ if cr.ParsedLastAppliedSpec != nil {
+ prevCR = cr.DeepCopy()
+ prevCR.Spec = *cr.ParsedLastAppliedSpec
+ }
+ ac := getAssetsCache(ctx, rclient, cr)
+ if _, err := createOrUpdateConfig(ctx, rclient, cr, prevCR, childObject, ac); err != nil {
+ return err
+ }
+ return nil
+}
+
// createOrUpdateConfig reconcile configuration for vmanomaly and returns configuration consistent hash
-func createOrUpdateConfig(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VMAnomaly, ac *build.AssetsCache) (string, error) {
- data, err := config.Load(cr, ac)
+func createOrUpdateConfig(ctx context.Context, rclient client.Client, cr, prevCR *vmv1.VMAnomaly, childObject client.Object, ac *build.AssetsCache) (string, error) {
+ pos, err := config.NewParsedObjects(ctx, rclient, cr)
+ if err != nil {
+ return "", err
+ }
+ data, err := pos.Load(cr, ac)
if err != nil {
return "", err
}
newSecretConfig := &corev1.Secret{
ObjectMeta: build.ResourceMeta(build.SecretConfigResourceKind, cr),
Data: map[string][]byte{
- secretConfigKey: data,
+ configEnvsubstFilename: data,
},
}
@@ -49,6 +67,10 @@ func createOrUpdateConfig(ctx context.Context, rclient client.Client, cr, prevCR
return "", err
}
+ if err := pos.UpdateStatusesForChildObjects(ctx, rclient, cr, childObject); err != nil {
+ return "", err
+ }
+
hash := sha256.New()
hash.Write(data)
hashBytes := hash.Sum(nil)
diff --git a/internal/controller/operator/factory/vmanomaly/config/config.go b/internal/controller/operator/factory/vmanomaly/config/config.go
index 1e4e8144f..28fb3ef6c 100644
--- a/internal/controller/operator/factory/vmanomaly/config/config.go
+++ b/internal/controller/operator/factory/vmanomaly/config/config.go
@@ -1,17 +1,92 @@
package config
import (
+ "context"
"fmt"
+ "reflect"
"strconv"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
"gopkg.in/yaml.v2"
+ "sigs.k8s.io/controller-runtime/pkg/client"
vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/build"
+ "github.com/VictoriaMetrics/operator/internal/controller/operator/factory/reconcile"
)
+func NewParsedObjects(ctx context.Context, rclient client.Client, cr *vmv1.VMAnomaly) (*ParsedObjects, error) {
+ models, err := selectModels(ctx, rclient, cr)
+ if err != nil {
+ return nil, fmt.Errorf("selecting VMAnomalyModels failed: %w", err)
+ }
+ schedulers, err := selectSchedulers(ctx, rclient, cr)
+ if err != nil {
+ return nil, fmt.Errorf("selecting VMAnomalySchedulers failed: %w", err)
+ }
+ return &ParsedObjects{
+ models: models,
+ schedulers: schedulers,
+ }, nil
+}
+
+type ParsedObjects struct {
+ models *build.ChildObjects[*vmv1.VMAnomalyModel]
+ schedulers *build.ChildObjects[*vmv1.VMAnomalyScheduler]
+}
+
+// Load returns vmanomaly config merged with provided secrets
+func (pos *ParsedObjects) Load(cr *vmv1.VMAnomaly, ac *build.AssetsCache) ([]byte, error) {
+ var data []byte
+ switch {
+ case cr.Spec.ConfigSecret != nil:
+ secret, err := ac.LoadKeyFromSecret(cr.Namespace, cr.Spec.ConfigSecret)
+ if err != nil {
+ return nil, fmt.Errorf("cannot fetch secret content for anomaly config secret, name=%q: %w", cr.Name, err)
+ }
+ data = []byte(secret)
+ case cr.Spec.ConfigRawYaml != "":
+ data = []byte(cr.Spec.ConfigRawYaml)
+ default:
+ return nil, fmt.Errorf(`either "configRawYaml" or "configSecret" are required`)
+ }
+ c := &config{}
+ err := yaml.UnmarshalStrict(data, c)
+ if err != nil {
+ return nil, fmt.Errorf("failed to unmarshal anomaly configuration, name=%q: %w", cr.Name, err)
+ }
+ if err = c.override(cr, pos, ac); err != nil {
+ return nil, fmt.Errorf("failed to update secret values with values from anomaly instance, name=%q: %w", cr.Name, err)
+ }
+ if err = c.validate(); err != nil {
+ return nil, fmt.Errorf("failed to validate anomaly configuration, name=%q: %w", cr.Name, err)
+ }
+ output := c.marshal()
+ if data, err = yaml.Marshal(output); err != nil {
+ return nil, fmt.Errorf("failed to marshal anomaly configuration, name=%q: %w", cr.Name, err)
+ }
+ return data, nil
+}
+
+func (pos *ParsedObjects) UpdateStatusesForChildObjects(ctx context.Context, rclient client.Client, cr *vmv1.VMAnomaly, childObject client.Object) error {
+ parentObject := fmt.Sprintf("%s.%s.vmanomaly", cr.Name, cr.Namespace)
+ if childObject != nil && !reflect.ValueOf(childObject).IsNil() {
+ // fast path
+ switch obj := childObject.(type) {
+ case *vmv1.VMAnomalyModel:
+ if o := pos.models.Get(obj); o != nil {
+ return reconcile.StatusForChildObjects(ctx, rclient, parentObject, []*vmv1.VMAnomalyModel{o})
+ }
+ case *vmv1.VMAnomalyScheduler:
+ if o := pos.schedulers.Get(obj); o != nil {
+ return reconcile.StatusForChildObjects(ctx, rclient, parentObject, []*vmv1.VMAnomalyScheduler{o})
+ }
+ }
+ }
+ return nil
+}
+
type header struct {
Class string `yaml:"class"`
Fields map[string]any `yaml:",inline"`
@@ -22,8 +97,8 @@ type validatable interface {
}
type config struct {
- Schedulers map[string]*scheduler `yaml:"schedulers,omitempty"`
- Models map[string]*model `yaml:"models,omitempty"`
+ Schedulers map[string]*Scheduler `yaml:"schedulers,omitempty"`
+ Models map[string]*Model `yaml:"models,omitempty"`
Reader *reader `yaml:"reader,omitempty"`
Writer *writer `yaml:"writer,omitempty"`
Monitoring *monitoring `yaml:"monitoring,omitempty"`
@@ -38,23 +113,23 @@ type settings struct {
RestoreState bool `yaml:"restore_state,omitempty"`
}
-func (c *config) override(cr *vmv1.VMAnomaly, ac *build.AssetsCache) error {
+func (c *config) override(cr *vmv1.VMAnomaly, pos *ParsedObjects, ac *build.AssetsCache) error {
c.Preset = strings.ToLower(c.Preset)
if strings.HasPrefix(c.Preset, "ui") {
+ s := new(noopScheduler)
+ s.setClass("noop")
c.Reader = &reader{
Class: "noop",
}
c.Writer = &writer{
Class: "noop",
}
- c.Schedulers = map[string]*scheduler{
+ c.Schedulers = map[string]*Scheduler{
"noop": {
- validatable: &noopScheduler{
- Class: "noop",
- },
+ anomalyScheduler: s,
},
}
- c.Models = map[string]*model{
+ c.Models = map[string]*Model{
"placeholder": {
anomalyModel: &zScoreModel{
commonModelParams: commonModelParams{
@@ -140,6 +215,28 @@ func (c *config) override(cr *vmv1.VMAnomaly, ac *build.AssetsCache) error {
}
c.Monitoring = &m
}
+
+ // override models
+ pos.models.ForEachCollectSkipInvalid(func(m *vmv1.VMAnomalyModel) error {
+ name := fmt.Sprintf("%s-%s", m.Namespace, m.Name)
+ nm, err := modelFromSpec(&m.Spec)
+ if err != nil {
+ return fmt.Errorf("failed to unmarshal model=%q: %w", name, err)
+ }
+ c.Models[name] = nm
+ return nil
+ })
+
+ // override schedulers
+ pos.schedulers.ForEachCollectSkipInvalid(func(s *vmv1.VMAnomalyScheduler) error {
+ name := fmt.Sprintf("%s-%s", s.Namespace, s.Name)
+ ns, err := schedulerFromSpec(&s.Spec)
+ if err != nil {
+ return fmt.Errorf("failed to unmarshal scheduler=%q: %w", name, err)
+ }
+ c.Schedulers[name] = ns
+ return nil
+ })
return nil
}
@@ -289,36 +386,3 @@ func (c *clientConfig) override(cr *vmv1.VMAnomaly, cfg *vmv1.VMAnomalyHTTPClien
}
return nil
}
-
-// Load returns vmanomaly config merged with provided secrets
-func Load(cr *vmv1.VMAnomaly, ac *build.AssetsCache) ([]byte, error) {
- var data []byte
- switch {
- case cr.Spec.ConfigSecret != nil:
- secret, err := ac.LoadKeyFromSecret(cr.Namespace, cr.Spec.ConfigSecret)
- if err != nil {
- return nil, fmt.Errorf("cannot fetch secret content for anomaly config secret, name=%q: %w", cr.Name, err)
- }
- data = []byte(secret)
- case cr.Spec.ConfigRawYaml != "":
- data = []byte(cr.Spec.ConfigRawYaml)
- default:
- return nil, fmt.Errorf(`either "configRawYaml" or "configSecret" are required`)
- }
- c := &config{}
- err := yaml.UnmarshalStrict(data, c)
- if err != nil {
- return nil, fmt.Errorf("failed to unmarshal anomaly configuration, name=%q: %w", cr.Name, err)
- }
- if err = c.override(cr, ac); err != nil {
- return nil, fmt.Errorf("failed to update secret values with values from anomaly instance, name=%q: %w", cr.Name, err)
- }
- if err = c.validate(); err != nil {
- return nil, fmt.Errorf("failed to validate anomaly configuration, name=%q: %w", cr.Name, err)
- }
- output := c.marshal()
- if data, err = yaml.Marshal(output); err != nil {
- return nil, fmt.Errorf("failed to marshal anomaly configuration, name=%q: %w", cr.Name, err)
- }
- return data, nil
-}
diff --git a/internal/controller/operator/factory/vmanomaly/config/config_test.go b/internal/controller/operator/factory/vmanomaly/config/config_test.go
index 289cb24d5..4336e3d02 100644
--- a/internal/controller/operator/factory/vmanomaly/config/config_test.go
+++ b/internal/controller/operator/factory/vmanomaly/config/config_test.go
@@ -5,6 +5,7 @@ import (
"strings"
"testing"
+ "github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -38,7 +39,9 @@ func TestLoad(t *testing.T) {
},
}
ac := build.NewAssetsCache(ctx, fclient, cfg)
- loaded, err := Load(o.cr, ac)
+ pos, err := NewParsedObjects(ctx, fclient, o.cr)
+ assert.NoError(t, err)
+ loaded, err := pos.Load(o.cr, ac)
if (err != nil) != o.wantErr {
t.Fatalf("Load() error = %v, wantErr %v", err, o.wantErr)
}
@@ -307,6 +310,180 @@ monitoring:
label1: value1
settings:
restore_state: true
+`,
+ })
+
+ // with external models
+ f(opts{
+ cr: &vmv1.VMAnomaly{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-anomaly",
+ Namespace: "default",
+ },
+ Spec: vmv1.VMAnomalySpec{
+ License: &vmv1beta1.License{
+ Key: ptr.To("test"),
+ },
+ ConfigRawYaml: `
+reader:
+ class: vm
+ datasource_url: "http://test.com"
+ sampling_period: 1m
+ queries:
+ test:
+ expr: vm_metric
+ data_range: [0, inf]
+writer:
+ class: vm
+ datasource_url: "http://test.com"
+models:
+ model_univariate_1:
+ class: 'zscore'
+ z_threshold: 2.5
+ queries: ['test']
+schedulers:
+ scheduler_periodic_1m:
+ class: "scheduler.periodic.PeriodicScheduler"
+ infer_every: 1m
+ fit_every: 2m
+ fit_window: 3h
+settings:
+ restore_state: true
+`,
+ ModelSelector: &vmv1.Selector{
+ ObjectSelector: &metav1.LabelSelector{
+ MatchExpressions: []metav1.LabelSelectorRequirement{
+ {
+ Key: "app",
+ Operator: metav1.LabelSelectorOpIn,
+ Values: []string{"prod"},
+ },
+ },
+ },
+ },
+ SchedulerSelector: &vmv1.Selector{
+ ObjectSelector: &metav1.LabelSelector{
+ MatchExpressions: []metav1.LabelSelectorRequirement{
+ {
+ Key: "app",
+ Operator: metav1.LabelSelectorOpIn,
+ Values: []string{"prod"},
+ },
+ },
+ },
+ },
+ Writer: &vmv1.VMAnomalyWritersSpec{
+ DatasourceURL: "http://write.endpoint",
+ MetricFormat: vmv1.VMAnomalyVMWriterMetricFormatSpec{
+ Name: "metrics_$VAR",
+ For: "custom_$QUERY_KEY",
+ ExtraLabels: map[string]string{
+ "label1": "value1",
+ "label2": "value2",
+ },
+ },
+ VMAnomalyHTTPClientSpec: vmv1.VMAnomalyHTTPClientSpec{
+ TenantID: "0:2",
+ },
+ },
+ Reader: &vmv1.VMAnomalyReadersSpec{
+ DatasourceURL: "http://custom.ds",
+ QueryRangePath: "/api/v1/query_range",
+ SamplingPeriod: "10s",
+ VMAnomalyHTTPClientSpec: vmv1.VMAnomalyHTTPClientSpec{
+ TenantID: "0:1",
+ },
+ },
+ },
+ },
+ predefinedObjects: []runtime.Object{
+ &vmv1.VMAnomalyModel{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-anomaly",
+ Labels: map[string]string{
+ "app": "prod",
+ },
+ Namespace: "default",
+ },
+ Spec: vmv1.VMAnomalyModelSpec{
+ Class: "zscore",
+ Params: runtime.RawExtension{
+ Raw: []byte(`{
+ "queries": ["test"],
+ "z_threshold": 2.5
+}`),
+ },
+ },
+ },
+ &vmv1.VMAnomalyScheduler{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-anomaly",
+ Labels: map[string]string{
+ "app": "prod",
+ },
+ Namespace: "default",
+ },
+ Spec: vmv1.VMAnomalySchedulerSpec{
+ Class: "periodic",
+ Params: runtime.RawExtension{
+ Raw: []byte(`{
+ "fit_every": "12m",
+ "fit_window": "13h",
+ "infer_every": "11m"
+}`),
+ },
+ },
+ },
+ },
+ expected: `
+models:
+ default-test-anomaly:
+ class: zscore
+ queries:
+ - test
+ z_threshold: 2.5
+ model_univariate_1:
+ class: zscore
+ queries:
+ - test
+ z_threshold: 2.5
+schedulers:
+ default-test-anomaly:
+ class: periodic
+ fit_every: 12m
+ fit_window: 13h
+ infer_every: 11m
+ scheduler_periodic_1m:
+ class: scheduler.periodic.PeriodicScheduler
+ fit_every: 2m
+ fit_window: 3h
+ infer_every: 1m
+reader:
+ class: vm
+ datasource_url: http://custom.ds
+ sampling_period: 10s
+ query_range_path: /api/v1/query_range
+ queries:
+ test:
+ expr: vm_metric
+ data_range:
+ - "0"
+ - inf
+ tenant_id: "0:1"
+writer:
+ class: vm
+ datasource_url: http://write.endpoint
+ metric_format:
+ __name__: metrics_$VAR
+ for: custom_$QUERY_KEY
+ label1: value1
+ label2: value2
+ tenant_id: "0:2"
+monitoring:
+ pull:
+ port: "8080"
+settings:
+ restore_state: true
`,
})
}
diff --git a/internal/controller/operator/factory/vmanomaly/config/models.go b/internal/controller/operator/factory/vmanomaly/config/models.go
index ca68fccd6..016660278 100644
--- a/internal/controller/operator/factory/vmanomaly/config/models.go
+++ b/internal/controller/operator/factory/vmanomaly/config/models.go
@@ -1,10 +1,16 @@
package config
import (
+ "context"
"fmt"
"time"
"gopkg.in/yaml.v2"
+ "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"
)
type modelDetectionDirection string
@@ -36,47 +42,24 @@ func (p commonModelParams) schedulers() []string {
return p.Schedulers
}
+func (p *commonModelParams) setClass(class string) {
+ p.Class = class
+}
+
type anomalyModel interface {
validatable
+ setClass(string)
schedulers() []string
queries() []string
}
-type model struct {
+type Model struct {
anomalyModel
}
-var (
- _ yaml.Marshaler = (*model)(nil)
- _ yaml.Unmarshaler = (*model)(nil)
-)
-
-// MarshalYAML implements yaml.Marshaller interface
-func (m *model) MarshalYAML() (any, error) {
- return m.anomalyModel, nil
-}
-
-type onlineModel struct {
- Decay float64 `yaml:"decay,omitempty"`
-}
-
-func (m *onlineModel) validate() error {
- // See https://docs.victoriametrics.com/anomaly-detection/components/models/#decay
- // Valid values are in the range [0, 1].
- if m.Decay < 0 || m.Decay > 1 {
- return fmt.Errorf("decay must be in range [0, 1], got %f", m.Decay)
- }
- return nil
-}
-
-// UnmarshalYAML implements yaml.Unmarshaler interface
-func (m *model) UnmarshalYAML(unmarshal func(any) error) error {
- var h header
- if err := unmarshal(&h); err != nil {
- return err
- }
+func (m *Model) init(class string) error {
var mdl anomalyModel
- switch h.Class {
+ switch class {
case "model.auto.AutoTunedModel", "auto":
mdl = new(autoTunedModel)
case "model.prophet.ProphetModel", "prophet":
@@ -102,12 +85,67 @@ func (m *model) UnmarshalYAML(unmarshal func(any) error) error {
case "model.isolation_forest.IsolationForestMultivariateModel", "isolation_forest_multivariate":
mdl = new(isolationForestMultivariateModel)
default:
- return fmt.Errorf("model class=%q is not supported", h.Class)
+ return fmt.Errorf("model class=%q is not supported", class)
}
- if err := unmarshal(mdl); err != nil {
+ m.anomalyModel = mdl
+ return nil
+}
+
+var (
+ _ yaml.Marshaler = (*Model)(nil)
+ _ yaml.Unmarshaler = (*Model)(nil)
+)
+
+// Validate validates raw config
+func (m *Model) Validate(data []byte) error {
+ if err := yaml.Unmarshal(data, m); err != nil {
+ return err
+ }
+ return m.validate()
+}
+
+func modelFromSpec(spec *vmv1.VMAnomalyModelSpec) (*Model, error) {
+ var m Model
+ if err := m.init(spec.Class); err != nil {
+ return nil, err
+ }
+ if err := yaml.Unmarshal(spec.Params.Raw, m.anomalyModel); err != nil {
+ return nil, err
+ }
+ m.setClass(spec.Class)
+ return &m, nil
+}
+
+// MarshalYAML implements yaml.Marshaller interface
+func (m *Model) MarshalYAML() (any, error) {
+ return m.anomalyModel, nil
+}
+
+// UnmarshalYAML implements yaml.Unmarshaler interface
+func (m *Model) UnmarshalYAML(unmarshal func(any) error) error {
+ var h header
+ if err := unmarshal(&h); err != nil {
+ return err
+ }
+ if err := m.init(h.Class); err != nil {
+ return err
+ }
+ if err := unmarshal(m.anomalyModel); err != nil {
return err
}
- m.anomalyModel = mdl
+ return nil
+}
+
+type onlineModel struct {
+ Decay float64 `yaml:"decay,omitempty"`
+}
+
+func (m *onlineModel) validate() error {
+ // See https://docs.victoriametrics.com/anomaly-detection/components/models/#decay
+ // Valid values are in the range [0, 1].
+ if m.Decay < 0 || m.Decay > 1 {
+ return fmt.Errorf("decay must be in range [0, 1], got %f", m.Decay)
+ }
return nil
}
@@ -200,6 +238,7 @@ type onlineQuantileModel struct {
SeasonStartsFrom time.Time `yaml:"season_starts_from,omitempty"`
MinSamplesSeen int `yaml:"min_n_samples_seen,omitempty"`
Compression int `yaml:"compression,omitempty"`
+ IqrThreshold float64 `yaml:"iqr_threshold,omitempty"`
}
func (m *onlineQuantileModel) validate() error {
@@ -271,3 +310,29 @@ type stdModel struct {
func (m *stdModel) validate() error {
return nil
}
+
+func selectModels(ctx context.Context, rclient client.Client, cr *vmv1.VMAnomaly) (*build.ChildObjects[*vmv1.VMAnomalyModel], error) {
+ var selectedConfigs []*vmv1.VMAnomalyModel
+ var nsn []string
+ opts := &k8stools.SelectorOpts{
+ DefaultNamespace: cr.Namespace,
+ }
+ if cr.Spec.ModelSelector != nil {
+ opts.ObjectSelector = cr.Spec.ModelSelector.ObjectSelector
+ opts.NamespaceSelector = cr.Spec.ModelSelector.NamespaceSelector
+ }
+ if err := k8stools.VisitSelected(ctx, rclient, opts, func(list *vmv1.VMAnomalyModelList) {
+ for i := range list.Items {
+ item := &list.Items[i]
+ if !item.DeletionTimestamp.IsZero() {
+ continue
+ }
+ rclient.Scheme().Default(item)
+ nsn = append(nsn, fmt.Sprintf("%s/%s", item.Namespace, item.Name))
+ selectedConfigs = append(selectedConfigs, item)
+ }
+ }); err != nil {
+ return nil, err
+ }
+ return build.NewChildObjects("vmanomalymodels", selectedConfigs, nsn), nil
+}
diff --git a/internal/controller/operator/factory/vmanomaly/config/schedulers.go b/internal/controller/operator/factory/vmanomaly/config/schedulers.go
index fab5b32fb..76f242bfc 100644
--- a/internal/controller/operator/factory/vmanomaly/config/schedulers.go
+++ b/internal/controller/operator/factory/vmanomaly/config/schedulers.go
@@ -1,29 +1,58 @@
package config
import (
+ "context"
"fmt"
"time"
"gopkg.in/yaml.v2"
+ "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"
)
-type scheduler struct {
+type anomalyScheduler interface {
validatable
+ setClass(string)
+}
+
+type Scheduler struct {
+ anomalyScheduler
}
var (
- _ yaml.Marshaler = (*scheduler)(nil)
- _ yaml.Unmarshaler = (*scheduler)(nil)
+ _ yaml.Marshaler = (*Scheduler)(nil)
+ _ yaml.Unmarshaler = (*Scheduler)(nil)
)
-// UnmarshalYAML implements yaml.Unmarshaller interface
-func (s *scheduler) UnmarshalYAML(unmarshal func(interface{}) error) error {
+// Validate validates raw config
+func (s *Scheduler) Validate(data []byte) error {
+ if err := yaml.Unmarshal(data, s); err != nil {
+ return err
+ }
+ return s.validate()
+}
+
+// UnmarshalYAML implements yaml.Unmarshaler interface
+func (s *Scheduler) UnmarshalYAML(unmarshal func(any) error) error {
var h header
if err := unmarshal(&h); err != nil {
return err
}
- var sch validatable
- switch h.Class {
+ if err := s.init(h.Class); err != nil {
+ return err
+ }
+ if err := unmarshal(s.anomalyScheduler); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (s *Scheduler) init(class string) error {
+ var sch anomalyScheduler
+ switch class {
case "scheduler.periodic.PeriodicScheduler", "periodic":
sch = new(periodicScheduler)
case "scheduler.oneoff.OneoffScheduler", "oneoff":
@@ -31,35 +60,52 @@ func (s *scheduler) UnmarshalYAML(unmarshal func(interface{}) error) error {
case "scheduler.backtesting.BacktestingScheduler", "backtesting":
sch = new(backtestingScheduler)
default:
- return fmt.Errorf("anomaly scheduler class=%q is not supported", h.Class)
- }
- if err := unmarshal(sch); err != nil {
- return err
+ return fmt.Errorf("anomaly scheduler class=%q is not supported", class)
}
- s.validatable = sch
+ s.anomalyScheduler = sch
return nil
}
// MarshalYAML implements yaml.Marshaler interface
-func (s *scheduler) MarshalYAML() (any, error) {
- return s.validatable, nil
+func (s *Scheduler) MarshalYAML() (any, error) {
+ return s.anomalyScheduler, nil
}
-type noopScheduler struct {
+func schedulerFromSpec(spec *vmv1.VMAnomalySchedulerSpec) (*Scheduler, error) {
+ var s Scheduler
+ if err := s.init(spec.Class); err != nil {
+ return nil, err
+ }
+ if err := yaml.Unmarshal(spec.Params.Raw, s.anomalyScheduler); err != nil {
+ return nil, err
+ }
+ s.setClass(spec.Class)
+ return &s, nil
+}
+
+type commonSchedulerParams struct {
Class string `yaml:"class"`
}
+func (p *commonSchedulerParams) setClass(class string) {
+ p.Class = class
+}
+
+type noopScheduler struct {
+ commonSchedulerParams `yaml:",inline"`
+}
+
func (s *noopScheduler) validate() error {
return nil
}
type periodicScheduler struct {
- Class string `yaml:"class"`
- FitEvery *duration `yaml:"fit_every,omitempty"`
- FitWindow *duration `yaml:"fit_window"`
- InferEvery *duration `yaml:"infer_every"`
- StartFrom time.Time `yaml:"start_from,omitempty"`
- Timezone time.Location `yaml:"tz,omitempty"`
+ commonSchedulerParams `yaml:",inline"`
+ FitEvery *duration `yaml:"fit_every,omitempty"`
+ FitWindow *duration `yaml:"fit_window"`
+ InferEvery *duration `yaml:"infer_every"`
+ StartFrom time.Time `yaml:"start_from,omitempty"`
+ Timezone time.Location `yaml:"tz,omitempty"`
}
func (s *periodicScheduler) validate() error {
@@ -67,15 +113,15 @@ func (s *periodicScheduler) validate() error {
}
type oneoffScheduler struct {
- Class string `yaml:"class"`
- InferStartISO time.Time `yaml:"infer_start_iso,omitempty"`
- InferStartS int64 `yaml:"infer_start_s,omitempty"`
- InferEndISO time.Time `yaml:"infer_end_iso,omitempty"`
- InferEndS int64 `yaml:"infer_end_s,omitempty"`
- FitStartISO time.Time `yaml:"fit_start_iso"`
- FitStartS int64 `yaml:"fit_start_s"`
- FitEndISO time.Time `yaml:"fit_end_iso"`
- FitEndS int64 `yaml:"fit_end_s"`
+ commonSchedulerParams `yaml:",inline"`
+ InferStartISO time.Time `yaml:"infer_start_iso,omitempty"`
+ InferStartS int64 `yaml:"infer_start_s,omitempty"`
+ InferEndISO time.Time `yaml:"infer_end_iso,omitempty"`
+ InferEndS int64 `yaml:"infer_end_s,omitempty"`
+ FitStartISO time.Time `yaml:"fit_start_iso"`
+ FitStartS int64 `yaml:"fit_start_s"`
+ FitEndISO time.Time `yaml:"fit_end_iso"`
+ FitEndS int64 `yaml:"fit_end_s"`
}
func (s *oneoffScheduler) validate() error {
@@ -124,16 +170,20 @@ func (s *oneoffScheduler) validate() error {
return nil
}
+func (s *oneoffScheduler) setClass(class string) {
+ s.Class = class
+}
+
type backtestingScheduler struct {
- Class string `yaml:"class"`
- FitWindow *duration `yaml:"fit_window"`
- FromISO time.Time `yaml:"from_iso"`
- FromS int64 `yaml:"from_s"`
- ToISO time.Time `yaml:"to_iso"`
- ToS int64 `yaml:"to_s"`
- FitEvery *duration `yaml:"fit_every"`
- Jobs int `yaml:"n_jobs,omitempty"`
- InferenceOnly bool `yaml:"inference_only,omitempty"`
+ commonSchedulerParams `yaml:",inline"`
+ FitWindow *duration `yaml:"fit_window"`
+ FromISO time.Time `yaml:"from_iso"`
+ FromS int64 `yaml:"from_s"`
+ ToISO time.Time `yaml:"to_iso"`
+ ToS int64 `yaml:"to_s"`
+ FitEvery *duration `yaml:"fit_every"`
+ Jobs int `yaml:"n_jobs,omitempty"`
+ InferenceOnly bool `yaml:"inference_only,omitempty"`
}
func (s *backtestingScheduler) validate() error {
@@ -163,3 +213,29 @@ func (s *backtestingScheduler) validate() error {
}
return nil
}
+
+func selectSchedulers(ctx context.Context, rclient client.Client, cr *vmv1.VMAnomaly) (*build.ChildObjects[*vmv1.VMAnomalyScheduler], error) {
+ var selectedConfigs []*vmv1.VMAnomalyScheduler
+ var nsn []string
+ opts := &k8stools.SelectorOpts{
+ DefaultNamespace: cr.Namespace,
+ }
+ if cr.Spec.SchedulerSelector != nil {
+ opts.ObjectSelector = cr.Spec.SchedulerSelector.ObjectSelector
+ opts.NamespaceSelector = cr.Spec.SchedulerSelector.NamespaceSelector
+ }
+ if err := k8stools.VisitSelected(ctx, rclient, opts, func(list *vmv1.VMAnomalySchedulerList) {
+ for i := range list.Items {
+ item := &list.Items[i]
+ if !item.DeletionTimestamp.IsZero() {
+ continue
+ }
+ rclient.Scheme().Default(item)
+ nsn = append(nsn, fmt.Sprintf("%s/%s", item.Namespace, item.Name))
+ selectedConfigs = append(selectedConfigs, item)
+ }
+ }); err != nil {
+ return nil, err
+ }
+ return build.NewChildObjects("vmanomalyschedulers", selectedConfigs, nsn), nil
+}
diff --git a/internal/controller/operator/factory/vmanomaly/pod.go b/internal/controller/operator/factory/vmanomaly/pod.go
index c146be690..4f24cf87c 100644
--- a/internal/controller/operator/factory/vmanomaly/pod.go
+++ b/internal/controller/operator/factory/vmanomaly/pod.go
@@ -7,6 +7,7 @@ import (
"strconv"
"strings"
+ "github.com/Masterminds/semver/v3"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
@@ -18,16 +19,25 @@ import (
"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
)
+var reloadMinVersion = semver.MustParse("v1.25.0")
+
const (
- secretConfigKey = "vmanomaly.yaml"
- anomalyDir = "/etc/vmanomaly"
- confDir = anomalyDir + "/config"
- confFile = confDir + "/vmanomaly.yaml"
- tlsAssetsDir = anomalyDir + "/tls"
- storageDir = "/storage"
- configVolumeName = "config-volume"
+ anomalyDir = "/etc/vmanomaly"
+ confDir = anomalyDir + "/config"
+ tlsAssetsDir = anomalyDir + "/tls"
+ storageDir = "/storage"
+ configVolumeName = "config"
+ configEnvsubstFilename = "vmanomaly.env.yaml"
)
+func reloadSupported(cr *vmv1.VMAnomaly) bool {
+ anomalyVersion, err := semver.NewVersion(cr.Spec.Image.Tag)
+ if err == nil {
+ return anomalyVersion.GreaterThanEqual(reloadMinVersion)
+ }
+ return false
+}
+
func newPodSpec(cr *vmv1.VMAnomaly, ac *build.AssetsCache) (*corev1.PodSpec, error) {
image := fmt.Sprintf("%s:%s", cr.Spec.Image.Repository, cr.Spec.Image.Tag)
@@ -167,9 +177,11 @@ func newPodSpec(cr *vmv1.VMAnomaly, ac *build.AssetsCache) (*corev1.PodSpec, err
}
}
}
-
// vmanomaly accepts configuration file as a last element of args
- args = append(args, confFile)
+ if reloadSupported(cr) {
+ args = append(args, "--watch")
+ }
+ args = append(args, path.Join(confDir, configEnvsubstFilename))
container := corev1.Container{
Args: args,
diff --git a/internal/controller/operator/factory/vmanomaly/statefulset.go b/internal/controller/operator/factory/vmanomaly/statefulset.go
index 4ec9b7cdc..eb3b7a1b7 100644
--- a/internal/controller/operator/factory/vmanomaly/statefulset.go
+++ b/internal/controller/operator/factory/vmanomaly/statefulset.go
@@ -53,14 +53,8 @@ func CreateOrUpdate(ctx context.Context, cr *vmv1.VMAnomaly, rclient client.Clie
}
}
- rcfg := map[build.ResourceKind]*build.ResourceCfg{
- build.TLSAssetsResourceKind: {
- MountDir: tlsAssetsDir,
- SecretName: build.ResourceName(build.TLSAssetsResourceKind, cr),
- },
- }
- ac := build.NewAssetsCache(ctx, rclient, rcfg)
- configHash, err := createOrUpdateConfig(ctx, rclient, cr, prevCR, ac)
+ ac := getAssetsCache(ctx, rclient, cr)
+ configHash, err := createOrUpdateConfig(ctx, rclient, cr, prevCR, nil, ac)
if err != nil {
return err
}
@@ -117,7 +111,7 @@ func newK8sApp(cr *vmv1.VMAnomaly, configHash string, ac *build.AssetsCache) (*a
cfg := config.MustGetBaseConfig()
useStrictSecurity := ptr.Deref(cr.Spec.UseStrictSecurity, cfg.EnableStrictSecurity)
podAnnotations := cr.PodAnnotations()
- if len(configHash) > 0 {
+ if len(configHash) > 0 && !reloadSupported(cr) {
podAnnotations = labels.Merge(podAnnotations, map[string]string{
"checksum/config": configHash,
})
@@ -284,3 +278,13 @@ func getShard(cr *vmv1.VMAnomaly, appTpl *appsv1.StatefulSet, num int) (*appsv1.
patchShardContainers(app.Spec.Template.Spec.Containers, num, cr.GetShardCount())
return app, nil
}
+
+func getAssetsCache(ctx context.Context, rclient client.Client, cr *vmv1.VMAnomaly) *build.AssetsCache {
+ cfg := map[build.ResourceKind]*build.ResourceCfg{
+ build.TLSAssetsResourceKind: {
+ MountDir: tlsAssetsDir,
+ SecretName: build.ResourceName(build.TLSAssetsResourceKind, cr),
+ },
+ }
+ return build.NewAssetsCache(ctx, rclient, cfg)
+}
diff --git a/internal/controller/operator/factory/vmanomaly/statefulset_test.go b/internal/controller/operator/factory/vmanomaly/statefulset_test.go
index e8051cc79..a27431bf8 100644
--- a/internal/controller/operator/factory/vmanomaly/statefulset_test.go
+++ b/internal/controller/operator/factory/vmanomaly/statefulset_test.go
@@ -195,23 +195,12 @@ schedulers:
},
},
validate: func(set *appsv1.StatefulSet) error {
- if len(set.Spec.Template.Spec.Containers) != 1 {
- return fmt.Errorf("unexpected count of container, got: %d, want: %d", len(set.Spec.Template.Spec.Containers), 2)
- }
+ assert.Len(t, set.Spec.Template.Spec.Containers, 1)
container := set.Spec.Template.Spec.Containers[0]
- if container.Name != "vmanomaly" {
- return fmt.Errorf("unexpected container name, got: %s, want: %s", container.Name, "vmanomaly")
- }
- if container.LivenessProbe.TimeoutSeconds != 20 {
- return fmt.Errorf("unexpected liveness probe config, want timeout: %d, got: %d", container.LivenessProbe.TimeoutSeconds, 20)
- }
- if container.LivenessProbe.HTTPGet.Path != "/health" {
- return fmt.Errorf("unexpected path for probe, got: %s, want: %s", container.LivenessProbe.HTTPGet.Path, "/health")
- }
- if container.ReadinessProbe.HTTPGet.Path != "/health" {
- return fmt.Errorf("unexpected path for probe, got: %s, want: %s", container.ReadinessProbe.HTTPGet.Path, "/health")
- }
-
+ assert.Equal(t, container.Name, "vmanomaly")
+ assert.Equal(t, container.LivenessProbe.TimeoutSeconds, int32(20))
+ assert.Equal(t, container.LivenessProbe.HTTPGet.Path, "/health")
+ assert.Equal(t, container.ReadinessProbe.HTTPGet.Path, "/health")
return nil
},
})
@@ -235,7 +224,7 @@ func Test_createDefaultConfig(t *testing.T) {
}
ctx := context.TODO()
ac := build.NewAssetsCache(ctx, fclient, cfg)
- if _, err := createOrUpdateConfig(ctx, fclient, o.cr, nil, ac); (err != nil) != o.wantErr {
+ if _, err := createOrUpdateConfig(ctx, fclient, o.cr, nil, nil, ac); (err != nil) != o.wantErr {
t.Fatalf("createOrUpdateConfig() error = %v, wantErr %v", err, o.wantErr)
}
if o.wantErr {
diff --git a/internal/controller/operator/objects_stat.go b/internal/controller/operator/objects_stat.go
index 712206011..f35ef262c 100644
--- a/internal/controller/operator/objects_stat.go
+++ b/internal/controller/operator/objects_stat.go
@@ -48,8 +48,8 @@ func newCollector() *objectCollector {
registeredObjects := []string{
"vmagent", "vmalert", "vmsingle", "vmcluster", "vmalertmanager", "vmauth", "vlogs", "vlsingle",
"vlcluster", "vmalertmanagerconfig", "vmrule", "vmuser", "vmservicescrape", "vmstaticscrape",
- "vmnodescrape", "vmpodscrape", "vmprobescrape", "vmscrapeconfig", "vmanomaly", "vlagent",
- "vtsingle", "vtcluster",
+ "vmnodescrape", "vmpodscrape", "vmprobescrape", "vmscrapeconfig", "vmanomaly", "vmanomalymodel",
+ "vmanomalyscheduler", "vlagent", "vtsingle", "vtcluster",
}
for _, controller := range registeredObjects {
oc.objectsByController[controller] = map[string]struct{}{}
diff --git a/internal/controller/operator/vmanomaly_controller.go b/internal/controller/operator/vmanomaly_controller.go
index ca7002a06..0e9a5d26d 100644
--- a/internal/controller/operator/vmanomaly_controller.go
+++ b/internal/controller/operator/vmanomaly_controller.go
@@ -18,6 +18,7 @@ package operator
import (
"context"
+ "sync"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
@@ -29,10 +30,16 @@ import (
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/limiter"
"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/logger"
"github.com/VictoriaMetrics/operator/internal/controller/operator/factory/vmanomaly"
)
+var (
+ anomalySync sync.Mutex
+ anomalyReconcileLimit = limiter.NewRateLimiter("vmanomaly", 5)
+)
+
// VMAnomalyReconciler reconciles a VMAnomaly object
type VMAnomalyReconciler struct {
client.Client
@@ -72,6 +79,11 @@ func (r *VMAnomalyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
return result, &getError{origin: err, controller: "vmanomaly", requestObject: req}
}
+ if !instance.IsUnmanaged() {
+ anomalySync.Lock()
+ defer anomalySync.Unlock()
+ }
+
RegisterObjectStat(instance, "vmanomaly")
if !instance.DeletionTimestamp.IsZero() {
if err := finalize.OnVMAnomalyDelete(ctx, r.Client, instance); err != nil {
diff --git a/internal/controller/operator/vmanomalymodel_controller.go b/internal/controller/operator/vmanomalymodel_controller.go
new file mode 100644
index 000000000..ef5a135d5
--- /dev/null
+++ b/internal/controller/operator/vmanomalymodel_controller.go
@@ -0,0 +1,133 @@
+/*
+
+
+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"
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+
+ vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+ "github.com/VictoriaMetrics/operator/internal/config"
+ "github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
+ "github.com/VictoriaMetrics/operator/internal/controller/operator/factory/logger"
+ "github.com/VictoriaMetrics/operator/internal/controller/operator/factory/vmanomaly"
+)
+
+// VMAnomalyModelReconciler reconciles a VMAnomalyModel object
+type VMAnomalyModelReconciler struct {
+ client.Client
+ Log logr.Logger
+ OriginScheme *runtime.Scheme
+ BaseConf *config.BaseOperatorConf
+}
+
+// Init implements crdController interface
+func (r *VMAnomalyModelReconciler) Init(rclient client.Client, l logr.Logger, sc *runtime.Scheme, cf *config.BaseOperatorConf) {
+ r.Client = rclient
+ r.Log = l.WithName("controller.VMAnomalyModel")
+ r.OriginScheme = sc
+ r.BaseConf = cf
+}
+
+// Scheme implements interface.
+func (r *VMAnomalyModelReconciler) Scheme() *runtime.Scheme {
+ return r.OriginScheme
+}
+
+// Reconcile general reconcile method for controller
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vmanomalymodels,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vmanomalymodels/status,verbs=get;update;patch
+func (r *VMAnomalyModelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
+ instance := &vmv1.VMAnomalyModel{}
+ l := r.Log.WithValues("vmanomalymodel", req.Name, "namespace", req.Namespace)
+ ctx = logger.AddToContext(ctx, l)
+ defer func() {
+ result, err = handleReconcileErrWithoutStatus(ctx, r.Client, instance, result, err)
+ }()
+
+ // Fetch the VMAnomalyModel instance
+ if err := r.Get(ctx, req.NamespacedName, instance); err != nil {
+ return result, &getError{err, "vmanomalymodel", req}
+ }
+
+ RegisterObjectStat(instance, "vmanomalymodel")
+ if instance.Spec.ParsingError != "" {
+ return result, &parsingError{instance.Spec.ParsingError, "vmanomalymodel"}
+ }
+
+ if anomalyReconcileLimit.MustThrottleReconcile() {
+ // fast path, rate limited
+ return
+ }
+
+ anomalySync.Lock()
+ defer anomalySync.Unlock()
+ var objects vmv1.VMAnomalyList
+ if err := k8stools.ListObjectsByNamespace(ctx, r.Client, r.BaseConf.WatchNamespaces, func(dst *vmv1.VMAnomalyList) {
+ objects.Items = append(objects.Items, dst.Items...)
+ }); err != nil {
+ return result, fmt.Errorf("cannot list vmanomalies for vmanomalymodel: %w", err)
+ }
+
+ for i := range objects.Items {
+ item := &objects.Items[i]
+ if !item.DeletionTimestamp.IsZero() || item.Spec.ParsingError != "" || item.Spec.ModelSelector.IsUnmanaged() {
+ continue
+ }
+ l := l.WithValues("vmanomaly", item.Name, "parent_namespace", item.Namespace)
+ ctx := logger.AddToContext(ctx, l)
+ // only check selector when deleting object,
+ // since labels can be changed when updating and we can't tell if it was selected before, and we can't tell if it's creating or updating.
+ if !instance.DeletionTimestamp.IsZero() {
+ opts := &k8stools.SelectorOpts{
+ DefaultNamespace: instance.Namespace,
+ }
+ if item.Spec.ModelSelector != nil {
+ opts.ObjectSelector = item.Spec.ModelSelector.ObjectSelector
+ opts.NamespaceSelector = item.Spec.ModelSelector.NamespaceSelector
+ }
+ match, err := isSelectorsMatchesTargetCRD(ctx, r.Client, instance, item, opts)
+ if err != nil {
+ l.Error(err, "cannot match vmanomaly and vmanomalymodel")
+ continue
+ }
+ if !match {
+ continue
+ }
+ }
+
+ if err := vmanomaly.CreateOrUpdateConfig(ctx, r, item, instance); err != nil {
+ continue
+ }
+ }
+ return
+}
+
+// SetupWithManager general setup method
+func (r *VMAnomalyModelReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&vmv1.VMAnomalyModel{}).
+ WithEventFilter(predicate.TypedGenerationChangedPredicate[client.Object]{}).
+ WithOptions(getDefaultOptions()).
+ Complete(r)
+}
diff --git a/internal/controller/operator/vmanomalyscheduler_controller.go b/internal/controller/operator/vmanomalyscheduler_controller.go
new file mode 100644
index 000000000..518dbf2c6
--- /dev/null
+++ b/internal/controller/operator/vmanomalyscheduler_controller.go
@@ -0,0 +1,133 @@
+/*
+
+
+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"
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/predicate"
+
+ vmv1 "github.com/VictoriaMetrics/operator/api/operator/v1"
+ "github.com/VictoriaMetrics/operator/internal/config"
+ "github.com/VictoriaMetrics/operator/internal/controller/operator/factory/k8stools"
+ "github.com/VictoriaMetrics/operator/internal/controller/operator/factory/logger"
+ "github.com/VictoriaMetrics/operator/internal/controller/operator/factory/vmanomaly"
+)
+
+// VMAnomalySchedulerReconciler reconciles a VMAnomalyScheduler object
+type VMAnomalySchedulerReconciler struct {
+ client.Client
+ Log logr.Logger
+ OriginScheme *runtime.Scheme
+ BaseConf *config.BaseOperatorConf
+}
+
+// Init implements crdController interface
+func (r *VMAnomalySchedulerReconciler) Init(rclient client.Client, l logr.Logger, sc *runtime.Scheme, cf *config.BaseOperatorConf) {
+ r.Client = rclient
+ r.Log = l.WithName("controller.VMAnomalyScheduler")
+ r.OriginScheme = sc
+ r.BaseConf = cf
+}
+
+// Scheme implements interface.
+func (r *VMAnomalySchedulerReconciler) Scheme() *runtime.Scheme {
+ return r.OriginScheme
+}
+
+// Reconcile general reconcile method for controller
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vmanomalyschedulers,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=operator.victoriametrics.com,resources=vmanomalyschedulers/status,verbs=get;update;patch
+func (r *VMAnomalySchedulerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
+ instance := &vmv1.VMAnomalyScheduler{}
+ l := r.Log.WithValues("vmanomalyscheduler", req.Name, "namespace", req.Namespace)
+ ctx = logger.AddToContext(ctx, l)
+ defer func() {
+ result, err = handleReconcileErrWithoutStatus(ctx, r.Client, instance, result, err)
+ }()
+
+ // Fetch the VMAnomalyScheduler instance
+ if err := r.Get(ctx, req.NamespacedName, instance); err != nil {
+ return result, &getError{err, "vmanomalyscheduler", req}
+ }
+
+ RegisterObjectStat(instance, "vmanomalyscheduler")
+ if instance.Spec.ParsingError != "" {
+ return result, &parsingError{instance.Spec.ParsingError, "vmanomalyscheduler"}
+ }
+
+ if anomalyReconcileLimit.MustThrottleReconcile() {
+ // fast path, rate limited
+ return
+ }
+
+ anomalySync.Lock()
+ defer anomalySync.Unlock()
+ var objects vmv1.VMAnomalyList
+ if err := k8stools.ListObjectsByNamespace(ctx, r.Client, r.BaseConf.WatchNamespaces, func(dst *vmv1.VMAnomalyList) {
+ objects.Items = append(objects.Items, dst.Items...)
+ }); err != nil {
+ return result, fmt.Errorf("cannot list vmanomalies for vmanomalyscheduler: %w", err)
+ }
+
+ for i := range objects.Items {
+ item := &objects.Items[i]
+ if !item.DeletionTimestamp.IsZero() || item.Spec.ParsingError != "" || item.Spec.SchedulerSelector.IsUnmanaged() {
+ continue
+ }
+ l := l.WithValues("vmanomaly", item.Name, "parent_namespace", item.Namespace)
+ ctx := logger.AddToContext(ctx, l)
+ // only check selector when deleting object,
+ // since labels can be changed when updating and we can't tell if it was selected before, and we can't tell if it's creating or updating.
+ if !instance.DeletionTimestamp.IsZero() {
+ opts := &k8stools.SelectorOpts{
+ DefaultNamespace: instance.Namespace,
+ }
+ if item.Spec.SchedulerSelector != nil {
+ opts.ObjectSelector = item.Spec.SchedulerSelector.ObjectSelector
+ opts.NamespaceSelector = item.Spec.SchedulerSelector.NamespaceSelector
+ }
+ match, err := isSelectorsMatchesTargetCRD(ctx, r.Client, instance, item, opts)
+ if err != nil {
+ l.Error(err, "cannot match vmanomaly and vmanomalyscheduler")
+ continue
+ }
+ if !match {
+ continue
+ }
+ }
+
+ if err := vmanomaly.CreateOrUpdateConfig(ctx, r, item, instance); err != nil {
+ continue
+ }
+ }
+ return
+}
+
+// SetupWithManager general setup method
+func (r *VMAnomalySchedulerReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&vmv1.VMAnomalyScheduler{}).
+ WithEventFilter(predicate.TypedGenerationChangedPredicate[client.Object]{}).
+ WithOptions(getDefaultOptions()).
+ Complete(r)
+}
diff --git a/internal/manager/manager.go b/internal/manager/manager.go
index 820260fe1..4b83a3555 100644
--- a/internal/manager/manager.go
+++ b/internal/manager/manager.go
@@ -367,6 +367,8 @@ func addWebhooks(mgr ctrl.Manager) error {
webhookv1beta1.SetupVMAgentWebhookWithManager,
webhookv1beta1.SetupVMAlertWebhookWithManager,
webhookv1.SetupVMAnomalyWebhookWithManager,
+ webhookv1.SetupVMAnomalyModelWebhookWithManager,
+ webhookv1.SetupVMAnomalySchedulerWebhookWithManager,
webhookv1beta1.SetupVMSingleWebhookWithManager,
webhookv1beta1.SetupVMClusterWebhookWithManager,
webhookv1beta1.SetupVLogsWebhookWithManager,
@@ -469,6 +471,8 @@ var controllersByName = map[string]crdController{
"VMCluster": &vmcontroller.VMClusterReconciler{},
"VMAgent": &vmcontroller.VMAgentReconciler{},
"VMAnomaly": &vmcontroller.VMAnomalyReconciler{},
+ "VMAnomalyModel": &vmcontroller.VMAnomalyModelReconciler{},
+ "VMAnomalyScheduler": &vmcontroller.VMAnomalySchedulerReconciler{},
"VMAuth": &vmcontroller.VMAuthReconciler{},
"VMSingle": &vmcontroller.VMSingleReconciler{},
"VLAgent": &vmcontroller.VLAgentReconciler{},
diff --git a/internal/webhook/operator/v1/vmanomalymodel_webhook.go b/internal/webhook/operator/v1/vmanomalymodel_webhook.go
new file mode 100644
index 000000000..8e498d7c0
--- /dev/null
+++ b/internal/webhook/operator/v1/vmanomalymodel_webhook.go
@@ -0,0 +1,80 @@
+/*
+
+
+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"
+ "github.com/VictoriaMetrics/operator/internal/controller/operator/factory/vmanomaly/config"
+)
+
+// SetupVMAnomalyModelWebhookWithManager will setup the manager to manage the webhooks
+func SetupVMAnomalyModelWebhookWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewWebhookManagedBy(mgr).
+ For(&vmv1.VMAnomalyModel{}).
+ WithValidator(&VMAnomalyModelCustomValidator{}).
+ Complete()
+}
+
+// +kubebuilder:webhook:path=/validate-operator-victoriametrics-com-v1-vmanomalymodel,mutating=false,failurePolicy=fail,sideEffects=None,groups=operator.victoriametrics.com,resources=vmanomalymodels,verbs=create;update,versions=v1,name=vvmanomalymodel-v1.kb.io,admissionReviewVersions=v1
+type VMAnomalyModelCustomValidator struct{}
+
+var _ admission.CustomValidator = &VMAnomalyModelCustomValidator{}
+
+// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
+func (*VMAnomalyModelCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
+ r, ok := obj.(*vmv1.VMAnomalyModel)
+ if !ok {
+ return nil, fmt.Errorf("BUG: unexpected type: %T", obj)
+ }
+ if r.Spec.ParsingError != "" {
+ return nil, errors.New(r.Spec.ParsingError)
+ }
+ var m config.Model
+ if err := m.Validate(r.Spec.Params.Raw); err != nil {
+ return nil, err
+ }
+ return nil, nil
+}
+
+// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
+func (*VMAnomalyModelCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
+ r, ok := newObj.(*vmv1.VMAnomalyModel)
+ if !ok {
+ return nil, fmt.Errorf("BUG: unexpected type: %T", newObj)
+ }
+ if r.Spec.ParsingError != "" {
+ return nil, errors.New(r.Spec.ParsingError)
+ }
+ var m config.Model
+ if err := m.Validate(r.Spec.Params.Raw); err != nil {
+ return nil, err
+ }
+ return nil, nil
+}
+
+// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
+func (*VMAnomalyModelCustomValidator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
+ return nil, nil
+}
diff --git a/internal/webhook/operator/v1/vmanomalyscheduler_webhook.go b/internal/webhook/operator/v1/vmanomalyscheduler_webhook.go
new file mode 100644
index 000000000..c67bba85a
--- /dev/null
+++ b/internal/webhook/operator/v1/vmanomalyscheduler_webhook.go
@@ -0,0 +1,80 @@
+/*
+
+
+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"
+ "github.com/VictoriaMetrics/operator/internal/controller/operator/factory/vmanomaly/config"
+)
+
+// SetupVMAnomalySchedulerWebhookWithManager will setup the manager to manage the webhooks
+func SetupVMAnomalySchedulerWebhookWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewWebhookManagedBy(mgr).
+ For(&vmv1.VMAnomalyScheduler{}).
+ WithValidator(&VMAnomalySchedulerCustomValidator{}).
+ Complete()
+}
+
+// +kubebuilder:webhook:path=/validate-operator-victoriametrics-com-v1-vmanomalyscheduler,mutating=false,failurePolicy=fail,sideEffects=None,groups=operator.victoriametrics.com,resources=vmanomalyschedulers,verbs=create;update,versions=v1,name=vvmanomalyscheduler-v1.kb.io,admissionReviewVersions=v1
+type VMAnomalySchedulerCustomValidator struct{}
+
+var _ admission.CustomValidator = &VMAnomalySchedulerCustomValidator{}
+
+// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
+func (*VMAnomalySchedulerCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
+ r, ok := obj.(*vmv1.VMAnomalyScheduler)
+ if !ok {
+ return nil, fmt.Errorf("BUG: unexpected type: %T", obj)
+ }
+ if r.Spec.ParsingError != "" {
+ return nil, errors.New(r.Spec.ParsingError)
+ }
+ var s config.Scheduler
+ if err := s.Validate(r.Spec.Params.Raw); err != nil {
+ return nil, err
+ }
+ return nil, nil
+}
+
+// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
+func (*VMAnomalySchedulerCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
+ r, ok := newObj.(*vmv1.VMAnomalyScheduler)
+ if !ok {
+ return nil, fmt.Errorf("BUG: unexpected type: %T", newObj)
+ }
+ if r.Spec.ParsingError != "" {
+ return nil, errors.New(r.Spec.ParsingError)
+ }
+ var s config.Scheduler
+ if err := s.Validate(r.Spec.Params.Raw); err != nil {
+ return nil, err
+ }
+ return nil, nil
+}
+
+// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
+func (*VMAnomalySchedulerCustomValidator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
+ return nil, nil
+}