From 9ea59e2ef92a023f4033334327f291fd37df8163 Mon Sep 17 00:00:00 2001 From: Haleygo Date: Thu, 10 Aug 2023 22:31:55 +0800 Subject: [PATCH] fix recreate sts check for spec.volumeClaimTemplates (#719) * fix recreate sts when check volumeClaimTemplates statefulset should be recreated if any of the volumeClaimTemplates got changed * add changelog * fix changelog display issue --- controllers/factory/k8stools/expansion.go | 27 ++++---- .../factory/k8stools/expansion_test.go | 69 +++++++++++++------ controllers/factory/k8stools/sts.go | 5 +- docs/CHANGELOG.MD | 67 +++++++++--------- 4 files changed, 94 insertions(+), 74 deletions(-) diff --git a/controllers/factory/k8stools/expansion.go b/controllers/factory/k8stools/expansion.go index 92c1944e..c611de33 100644 --- a/controllers/factory/k8stools/expansion.go +++ b/controllers/factory/k8stools/expansion.go @@ -36,8 +36,7 @@ func cleanUpFinalize(ctx context.Context, rclient client.Client, instance client // One of possible solutions - save current sts to the object annotation and remove it later if needed. // Other solution, to check orphaned objects by selector. // Lets leave it as this for now and handle later. -func wasCreatedSTS(ctx context.Context, rclient client.Client, pvcName string, newSTS, existingSTS *appsv1.StatefulSet) (bool, error) { - +func wasCreatedSTS(ctx context.Context, rclient client.Client, newSTS, existingSTS *appsv1.StatefulSet) (bool, error) { handleRemove := func() error { // removes finalizer from exist sts, it allows to delete it if err := cleanUpFinalize(ctx, rclient, existingSTS); err != nil { @@ -67,7 +66,7 @@ func wasCreatedSTS(ctx context.Context, rclient client.Client, pvcName string, n // try to restore previous one and throw error existingSTS.ResourceVersion = "" if err2 := rclient.Create(ctx, existingSTS); err2 != nil { - return fmt.Errorf("cannot restore previous sts: %s configruation after remove original error: %s: restore error %w", existingSTS.Name, err, err2) + return fmt.Errorf("cannot restore previous sts: %s configuration after remove original error: %s: restore error %w", existingSTS.Name, err, err2) } return fmt.Errorf("cannot create new sts: %s instead of replaced, some manual action is required, err: %w", newSTS.Name, err) } @@ -83,7 +82,7 @@ func wasCreatedSTS(ctx context.Context, rclient client.Client, pvcName string, n } return nil } - needRecreateOnStorageChange := func() bool { + needRecreateOnStorageChange := func(pvcName string) bool { actualPVC := getPVCFromSTS(pvcName, existingSTS) newPVC := getPVCFromSTS(pvcName, newSTS) // fast path @@ -114,19 +113,18 @@ func wasCreatedSTS(ctx context.Context, rclient client.Client, pvcName string, n return false } - needRecreateOnSpecChange := func() bool { - // vct changed - added or removed. - if len(newSTS.Spec.VolumeClaimTemplates) != len(existingSTS.Spec.VolumeClaimTemplates) { - log.Info("VolumeClaimTemplate for statefulset was changed, recreating it", "sts", newSTS.Name) - return true - } - return false - } - if needRecreateOnSpecChange() || needRecreateOnStorageChange() { + // if vct got added, removed or changed, recreate the sts + if len(newSTS.Spec.VolumeClaimTemplates) != len(existingSTS.Spec.VolumeClaimTemplates) { + log.Info("VolumeClaimTemplates for statefulset was changed, recreating it", "sts", newSTS.Name) return true, handleRemove() } - + for _, newVCT := range newSTS.Spec.VolumeClaimTemplates { + if needRecreateOnStorageChange(newVCT.Name) { + log.Info("VolumeClaimTemplate for statefulset was changed, recreating it", "sts", newSTS.Name, "VolumeClaimTemplates", newVCT.Name) + return true, handleRemove() + } + } return false, nil } @@ -163,7 +161,6 @@ func growSTSPVC(ctx context.Context, rclient client.Client, sts *appsv1.Stateful // isStorageClassExpandable check is it possible to update size of given pvc func isStorageClassExpandable(ctx context.Context, rclient client.Client, pvc *corev1.PersistentVolumeClaim) (bool, error) { - // do not perform any checks if user set annotation explicitly. if pvc.Annotations[victoriametricsv1beta1.PVCExpandableLabel] == "true" { return true, nil diff --git a/controllers/factory/k8stools/expansion_test.go b/controllers/factory/k8stools/expansion_test.go index 268ac721..e1f63e6b 100644 --- a/controllers/factory/k8stools/expansion_test.go +++ b/controllers/factory/k8stools/expansion_test.go @@ -22,7 +22,6 @@ import ( func Test_reCreateSTS(t *testing.T) { type args struct { ctx context.Context - pvcName string newSTS *appsv1.StatefulSet existingSTS *appsv1.StatefulSet } @@ -57,15 +56,6 @@ func Test_reCreateSTS(t *testing.T) { }, }}, }, - pvcName: "new-claim", - }, - predefinedObjects: []runtime.Object{ - &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "vmselect", - Namespace: "default", - }, - }, }, validate: func(sts *appsv1.StatefulSet) error { if len(sts.Spec.VolumeClaimTemplates) != 1 { @@ -110,10 +100,23 @@ func Test_reCreateSTS(t *testing.T) { }, }}, }, - pvcName: "new-claim", }, - predefinedObjects: []runtime.Object{ - &appsv1.StatefulSet{ + validate: func(sts *appsv1.StatefulSet) error { + if len(sts.Spec.VolumeClaimTemplates) != 1 { + return fmt.Errorf("unexpected configuration for volumeclaim at sts: %v, want at least one, got: %v", sts.Name, sts.Spec.VolumeClaimTemplates) + } + sz := sts.Spec.VolumeClaimTemplates[0].Spec.Resources.Requests.Storage().String() + if sz != "15Gi" { + return fmt.Errorf("unexpected sts size, got: %v, want: %v", sz, "15Gi") + } + return nil + }, + }, + { + name: "change claim storageClass name", + args: args{ + ctx: context.TODO(), + existingSTS: &appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: "vmselect", Namespace: "default", @@ -121,11 +124,33 @@ func Test_reCreateSTS(t *testing.T) { Spec: appsv1.StatefulSetSpec{VolumeClaimTemplates: []v1.PersistentVolumeClaim{ { ObjectMeta: metav1.ObjectMeta{Name: "new-claim"}, - Spec: v1.PersistentVolumeClaimSpec{Resources: v1.ResourceRequirements{ - Requests: map[v1.ResourceName]resource.Quantity{ - v1.ResourceStorage: resource.MustParse("10Gi"), + Spec: v1.PersistentVolumeClaimSpec{ + Resources: v1.ResourceRequirements{ + Requests: map[v1.ResourceName]resource.Quantity{ + v1.ResourceStorage: resource.MustParse("10Gi"), + }, }, - }}, + StorageClassName: pointer.String("old-sc"), + }, + }, + }}, + }, + newSTS: &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vmselect", + Namespace: "default", + }, + Spec: appsv1.StatefulSetSpec{VolumeClaimTemplates: []v1.PersistentVolumeClaim{ + { + ObjectMeta: metav1.ObjectMeta{Name: "new-claim"}, + Spec: v1.PersistentVolumeClaimSpec{ + Resources: v1.ResourceRequirements{ + Requests: map[v1.ResourceName]resource.Quantity{ + v1.ResourceStorage: resource.MustParse("10Gi"), + }, + }, + StorageClassName: pointer.String("new-sc"), + }, }, }}, }, @@ -134,9 +159,9 @@ func Test_reCreateSTS(t *testing.T) { if len(sts.Spec.VolumeClaimTemplates) != 1 { return fmt.Errorf("unexpected configuration for volumeclaim at sts: %v, want at least one, got: %v", sts.Name, sts.Spec.VolumeClaimTemplates) } - sz := sts.Spec.VolumeClaimTemplates[0].Spec.Resources.Requests.Storage().String() - if sz != "15Gi" { - return fmt.Errorf("unexpected sts size, got: %v, want: %v", sz, "15Gi") + name := *sts.Spec.VolumeClaimTemplates[0].Spec.StorageClassName + if name != "new-sc" { + return fmt.Errorf("unexpected sts storageClass name, got: %v, want: %v", name, "new-sc") } return nil }, @@ -144,8 +169,8 @@ func Test_reCreateSTS(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cl := GetTestClientWithObjects(tt.predefinedObjects) - _, err := wasCreatedSTS(tt.args.ctx, cl, tt.args.pvcName, tt.args.newSTS, tt.args.existingSTS) + cl := GetTestClientWithObjects([]runtime.Object{tt.args.existingSTS}) + _, err := wasCreatedSTS(tt.args.ctx, cl, tt.args.newSTS, tt.args.existingSTS) if (err != nil) != tt.wantErr { t.Errorf("wasCreatedSTS() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/controllers/factory/k8stools/sts.go b/controllers/factory/k8stools/sts.go index cd10f298..1d40507e 100644 --- a/controllers/factory/k8stools/sts.go +++ b/controllers/factory/k8stools/sts.go @@ -33,7 +33,6 @@ type STSOptions struct { // HandleSTSUpdate performs create and update operations for given statefulSet with STSOptions func HandleSTSUpdate(ctx context.Context, rclient client.Client, cr STSOptions, newSts *appsv1.StatefulSet, c *config.BaseOperatorConf) error { - var currentSts appsv1.StatefulSet if err := rclient.Get(ctx, types.NamespacedName{Name: newSts.Name, Namespace: newSts.Namespace}, ¤tSts); err != nil { if errors.IsNotFound(err) { @@ -57,7 +56,7 @@ func HandleSTSUpdate(ctx context.Context, rclient client.Client, cr STSOptions, newSts.Spec.Template.Annotations = MergeAnnotations(currentSts.Spec.Template.Annotations, newSts.Spec.Template.Annotations) newSts.Finalizers = victoriametricsv1beta1.MergeFinalizers(¤tSts, victoriametricsv1beta1.FinalizerName) - isRecreated, err := wasCreatedSTS(ctx, rclient, cr.VolumeName(), newSts, ¤tSts) + isRecreated, err := wasCreatedSTS(ctx, rclient, newSts, ¤tSts) if err != nil { return err } @@ -243,12 +242,10 @@ func performRollingUpdateOnSts(ctx context.Context, wasRecreated bool, rclient c } return nil - } // PodIsFailedWithReason reports if pod failed and the reason of fail func PodIsFailedWithReason(pod corev1.Pod) (bool, string) { - var reasons []string for _, containerCond := range pod.Status.ContainerStatuses { if containerCond.Ready { diff --git a/docs/CHANGELOG.MD b/docs/CHANGELOG.MD index b4f7152c..1df8d24b 100644 --- a/docs/CHANGELOG.MD +++ b/docs/CHANGELOG.MD @@ -24,6 +24,7 @@ - operator set resource requests for config-reloader container by default. See [this PR](https://github.com/VictoriaMetrics/operator/pull/695/) for details. - fix `attachMetadata` value miscovert for scrape objects. See [this issue](https://github.com/VictoriaMetrics/operator/issues/697) and [this PR](https://github.com/VictoriaMetrics/operator/pull/698) for details. +- fix volumeClaimTemplates change check for objects that generate statefulset, like vmstorage, vmselect. Before, the statefulset won't be recreated if additional `claimTemplates` object changed. See [this issue](https://github.com/VictoriaMetrics/operator/issues/507) and [this PR](https://github.com/VictoriaMetrics/operator/pull/719) for details. - [vmalert](https://docs.victoriametrics.com/operator/api.html#vmalert): fix `tlsCAFile` argument value generation when using secret or configMap. See [this issue](https://github.com/VictoriaMetrics/operator/issues/699) and [this PR](https://github.com/VictoriaMetrics/operator/issues/699) for details. - [vmalertmanager](https://docs.victoriametrics.com/operator/api.html#vmalertmanager): fix default request memory and apply default resources if not set. See [this issue](https://github.com/VictoriaMetrics/operator/issues/706) and [this PR](https://github.com/VictoriaMetrics/operator/pull/710) for details. - [vmagent](https://docs.victoriametrics.com/operator/api.html#vmagent): fix missing additional VolumeClaimTemplates when using `ClaimTemplates` under StatefulMode. @@ -94,18 +95,18 @@ ### Fixes -* [VMNodeScrape]: fixed selectors for Exists and NotExists operators with empty label Thanks [@Amper](https://github.com/Amper) in https://github.com/VictoriaMetrics/operator/pull/646 -* [VMRule] Add config for vmrule in validating webhook Thanks in https://github.com/VictoriaMetrics/operator/pull/650 -* [VMAgent]: skips misconfigured objects with missed secret references: https://github.com/VictoriaMetrics/operator/issues/648 -* [VMAgent]: correctly renders initContainer for configuration download: https://github.com/VictoriaMetrics/operator/issues/649 +- [VMNodeScrape]: fixed selectors for Exists and NotExists operators with empty label Thanks [@Amper](https://github.com/Amper) in https://github.com/VictoriaMetrics/operator/pull/646 +- [VMRule]: Add config for vmrule in validating webhook Thanks in https://github.com/VictoriaMetrics/operator/pull/650 +- [VMAgent]: skips misconfigured objects with missed secret references: https://github.com/VictoriaMetrics/operator/issues/648 +- [VMAgent]: correctly renders initContainer for configuration download: https://github.com/VictoriaMetrics/operator/issues/649 ### Features -* [VMAlertmanager]: Bump alertmanager to v0.25.0 Thanks [@tamcore](https://github.com/tamcore) in https://github.com/VictoriaMetrics/operator/pull/636 -* [VMCluster] added `clusterNativePort` field to VMSelect/VMInsert for multi-level cluster setup ([#634](https://github.com/VictoriaMetrics/operator/issues/634)) Thanks [@Amper](https://github.com/Amper) in https://github.com/VictoriaMetrics/operator/pull/639 -* [VMRule]: add notifierHeader field in vmrule spec Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/622 -* [VMPodScrape]: adds FilterRunning option as prometheus does in https://github.com/VictoriaMetrics/operator/pull/640 -* [VMAuth]: adds latest features in https://github.com/VictoriaMetrics/operator/pull/642 +- [VMAlertmanager]: Bump alertmanager to v0.25.0 Thanks [@tamcore](https://github.com/tamcore) in https://github.com/VictoriaMetrics/operator/pull/636 +- [VMCluster]: added `clusterNativePort` field to VMSelect/VMInsert for multi-level cluster setup ([#634](https://github.com/VictoriaMetrics/operator/issues/634)) Thanks [@Amper](https://github.com/Amper) in https://github.com/VictoriaMetrics/operator/pull/639 +- [VMRule]: add notifierHeader field in vmrule spec Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/622 +- [VMPodScrape]: adds FilterRunning option as prometheus does in https://github.com/VictoriaMetrics/operator/pull/640 +- [VMAuth]: adds latest features in https://github.com/VictoriaMetrics/operator/pull/642 [Changes][v0.34.0] @@ -115,22 +116,22 @@ ### Fixes -* [VMAlert]: skip bad rules and improve logging for rules exceed max configmap size https://github.com/VictoriaMetrics/operator/commit/bb754d5c20bb371a197cd6ff5afac1ba86a4d92b -* [VMAlertmanagerConfig]: fixed error with headers in VMAlertmanagerConfig.Receivers.EmailConfigs.Headers unmarshalling. Thanks [@Amper](https://github.com/Amper) in https://github.com/VictoriaMetrics/operator/pull/610 -* [VMAgent]: fixed keepInput setting for streaming aggregation. Thanks [@Amper](https://github.com/Amper) in https://github.com/VictoriaMetrics/operator/pull/618 -* [VMAlertmanagerConfig]: fix webhook config maxAlerts not work. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/625 -* [VMAgent]: Remove single quotes from remote write headers. Thanks [@axelsccp](https://github.com/axelsccp) in https://github.com/VictoriaMetrics/operator/pull/613 -* [VMAlertmanagerConfig]: fix parse route error and some comments. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/630 -* [VMUser]: properly removes finalizers for objects https://github.com/VictoriaMetrics/operator/commit/8f10113920a353f21fbcc8637076905f2e57bb34 +- [VMAlert]: skip bad rules and improve logging for rules exceed max configmap size https://github.com/VictoriaMetrics/operator/commit/bb754d5c20bb371a197cd6ff5afac1ba86a4d92b +- [VMAlertmanagerConfig]: fixed error with headers in VMAlertmanagerConfig.Receivers.EmailConfigs.Headers unmarshalling. Thanks [@Amper](https://github.com/Amper) in https://github.com/VictoriaMetrics/operator/pull/610 +- [VMAgent]: fixed keepInput setting for streaming aggregation. Thanks [@Amper](https://github.com/Amper) in https://github.com/VictoriaMetrics/operator/pull/618 +- [VMAlertmanagerConfig]: fix webhook config maxAlerts not work. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/625 +- [VMAgent]: Remove single quotes from remote write headers. Thanks [@axelsccp](https://github.com/axelsccp) in https://github.com/VictoriaMetrics/operator/pull/613 +- [VMAlertmanagerConfig]: fix parse route error and some comments. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/630 +- [VMUser]: properly removes finalizers for objects https://github.com/VictoriaMetrics/operator/commit/8f10113920a353f21fbcc8637076905f2e57bb34 ### Features -* [VMAlertmanager]: add option to disable route continue enforce. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/621 -* [VMAlertmanagerConfig]: support set require_tls to false. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/624 -* [VMAlertmanagerConfig]: add sanity check. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/627 -* Makefile: bump Alpine base image to latest v3.17.3. Thanks [@denisgolius](https://github.com/denisgolius) in https://github.com/VictoriaMetrics/operator/pull/628 -* [VMAlertmanagerConfig]: support sound field in pushover config. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/631 -* [VMAgent/VMAuth]: download initial config with initContainer https://github.com/VictoriaMetrics/operator/commit/612e7c8f40659731e7938ef9556eb088c67eb4b7 +- [VMAlertmanager]: add option to disable route continue enforce. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/621 +- [VMAlertmanagerConfig]: support set require_tls to false. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/624 +- [VMAlertmanagerConfig]: add sanity check. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/627 +- Makefile: bump Alpine base image to latest v3.17.3. Thanks [@denisgolius](https://github.com/denisgolius) in https://github.com/VictoriaMetrics/operator/pull/628 +- [VMAlertmanagerConfig]: support sound field in pushover config. Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/631 +- [VMAgent/VMAuth]: download initial config with initContainer https://github.com/VictoriaMetrics/operator/commit/612e7c8f40659731e7938ef9556eb088c67eb4b7 [Changes][v0.33.0] @@ -140,8 +141,8 @@ ### Fixes -* [config]: fixes typo at default vm apps version https://github.com/VictoriaMetrics/operator/issues/608 -* [vmsingle]: conditionally adds stream aggregation config https://github.com/VictoriaMetrics/operator/commit/4a0ca54113afcde439ca4c77e22d3ef1c0d36241 +- [config]: fixes typo at default vm apps version https://github.com/VictoriaMetrics/operator/issues/608 +- [vmsingle]: conditionally adds stream aggregation config https://github.com/VictoriaMetrics/operator/commit/4a0ca54113afcde439ca4c77e22d3ef1c0d36241 [Changes][v0.32.1] @@ -151,14 +152,14 @@ ### Fixes -* [security]: builds docker image with latest `alpine` base image and go `v1.20`. +- [security]: builds docker image with latest `alpine` base image and go `v1.20`. ### Features -* [vmauth]: automatically configures `proxy-protocol` client and `reloadAuthKey` for `config-reloader` container. https://github.com/VictoriaMetrics/operator/commit/611819233bf595a4dbd04b07d7be24b7e994379c -* [vmagent]: adds `scrapeTimeout` global configuration for `VMAgent` https://github.com/VictoriaMetrics/operator/commit/d1d5024c6befa0961f8d56c82a0554935a4b1878 -* [vmagent]: adds [streaming aggregation](https://docs.victoriametrics.com/stream-aggregation.html) for `remoteWrite` targets https://github.com/VictoriaMetrics/operator/commit/b8baa6c2b72bdda64ebfcc9c3d86d846cd9b3c98 Thanks [@Amper](https://github.com/Amper) -* [vmsingle]: adds [streaming aggregation](https://docs.victoriametrics.com/stream-aggregation.html) as global configuration for database https://github.com/VictoriaMetrics/operator/commit/b8baa6c2b72bdda64ebfcc9c3d86d846cd9b3c98 Thanks [@Amper](https://github.com/Amper) +- [vmauth]: automatically configures `proxy-protocol` client and `reloadAuthKey` for `config-reloader` container. https://github.com/VictoriaMetrics/operator/commit/611819233bf595a4dbd04b07d7be24b7e994379c +- [vmagent]: adds `scrapeTimeout` global configuration for `VMAgent` https://github.com/VictoriaMetrics/operator/commit/d1d5024c6befa0961f8d56c82a0554935a4b1878 +- [vmagent]: adds [streaming aggregation](https://docs.victoriametrics.com/stream-aggregation.html) for `remoteWrite` targets https://github.com/VictoriaMetrics/operator/commit/b8baa6c2b72bdda64ebfcc9c3d86d846cd9b3c98 Thanks [@Amper](https://github.com/Amper) +- [vmsingle]: adds [streaming aggregation](https://docs.victoriametrics.com/stream-aggregation.html) as global configuration for database https://github.com/VictoriaMetrics/operator/commit/b8baa6c2b72bdda64ebfcc9c3d86d846cd9b3c98 Thanks [@Amper](https://github.com/Amper) [Changes][v0.32.0] @@ -168,13 +169,13 @@ ### Fixes -* [hpa]: Fix hpa object since v2beta deprecated in 1.26+ Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/593 -* [api]: adds missing generated client CRD entities https://github.com/VictoriaMetrics/operator/issues/599 +- [hpa]: Fix hpa object since v2beta deprecated in 1.26+ Thanks [@Haleygo](https://github.com/Haleygo) in https://github.com/VictoriaMetrics/operator/pull/593 +- [api]: adds missing generated client CRD entities https://github.com/VictoriaMetrics/operator/issues/599 ### Features -* [vmalertmanager]: Add support of vmalertmanager.spec.templates and autoreload dirs for templates and configmaps thanks [@Amper](https://github.com/Amper) https://github.com/VictoriaMetrics/operator/issues/590 https://github.com/VictoriaMetrics/operator/issues/592 -* [vmalertmanager]: Add support "%SHARD_NUM%" placeholder for vmagent sts/deployment Thanks [@Amper](https://github.com/Amper) https://github.com/VictoriaMetrics/operator/issues/508 +- [vmalertmanager]: Add support of vmalertmanager.spec.templates and autoreload dirs for templates and configmaps thanks [@Amper](https://github.com/Amper) https://github.com/VictoriaMetrics/operator/issues/590 https://github.com/VictoriaMetrics/operator/issues/592 +- [vmalertmanager]: Add support "%SHARD_NUM%" placeholder for vmagent sts/deployment Thanks [@Amper](https://github.com/Amper) https://github.com/VictoriaMetrics/operator/issues/508 [Changes][v0.31.0]