@@ -16,6 +16,7 @@ package prometheus
1616
1717import (
1818 "context"
19+ "encoding/json"
1920 "errors"
2021 "fmt"
2122 "log/slog"
@@ -25,12 +26,15 @@ import (
2526
2627 "github.com/mitchellh/hashstructure"
2728 "github.com/prometheus/client_golang/prometheus"
29+ "github.com/prometheus/common/model"
2830 appsv1 "k8s.io/api/apps/v1"
2931 corev1 "k8s.io/api/core/v1"
3032 apierrors "k8s.io/apimachinery/pkg/api/errors"
3133 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3234 "k8s.io/apimachinery/pkg/labels"
35+ "k8s.io/apimachinery/pkg/types"
3336 "k8s.io/apimachinery/pkg/util/intstr"
37+ "k8s.io/apimachinery/pkg/util/wait"
3438 "k8s.io/client-go/dynamic"
3539 "k8s.io/client-go/kubernetes"
3640 "k8s.io/client-go/metadata"
@@ -60,6 +64,9 @@ const (
6064
6165 unmanagedConfigurationReason = "ConfigurationUnmanaged"
6266 unmanagedConfigurationMessage = "the operator doesn't manage the Prometheus configuration secret because neither serviceMonitorSelector nor podMonitorSelector, nor probeSelector, nor scrapeConfigSelector is specified. Unmanaged Prometheus configuration is deprecated, use additionalScrapeConfigs or the ScrapeConfig Custom Resource Definition instead. Unmanaged Prometheus configuration can also be disabled from the operator's command-line (check './operator --help')."
67+
68+ deletionDeadlineAnnotation = "operator.prometheus.io/deletion-deadline"
69+ annotationTimeFormat = time .RFC3339
6370)
6471
6572// Operator manages the life cycle of Prometheus deployments and
@@ -623,6 +630,45 @@ func (c *Operator) Run(ctx context.Context) error {
623630 // TODO(simonpasquier): watch for Prometheus pods instead of polling.
624631 go operator .StatusPoller (ctx , c )
625632
633+ if c .retentionPoliciesEnabled {
634+ // Periodically scan statefulsets for expired shards.
635+ go func () {
636+ _ = wait .PollUntilContextCancel (ctx , time .Minute , true , func (context.Context ) (bool , error ) {
637+ _ = c .ssetInfs .ListAll (labels .Everything (), func (o any ) {
638+ sset := o .(* appsv1.StatefulSet )
639+
640+ deadline , found := sset .Annotations [deletionDeadlineAnnotation ]
641+ if ! found {
642+ return
643+ }
644+
645+ logger := c .logger .With ("statefulset" , fmt .Sprintf ("%s/%s" , sset .Namespace , sset .Name ))
646+ expired , err := deadlineExpired (deadline )
647+ if err != nil {
648+ logger .Warn ("failed to parse deletion deadline annotation" , "err" , err )
649+ return
650+ }
651+
652+ if ! expired {
653+ logger .Debug ("deletion deadline not expired" , "deadline" , deadline )
654+ return
655+ }
656+
657+ logger .Info ("deletion deadline expired" , "deadline" , deadline )
658+ owner := c .rr .FindOwner (sset )
659+ if owner == nil {
660+ logger .Warn ("owner not found" )
661+ return
662+ }
663+
664+ c .rr .EnqueueForReconciliation (owner )
665+ })
666+
667+ return false , nil
668+ })
669+ }()
670+ }
671+
626672 c .metrics .Ready ().Set (1 )
627673 <- ctx .Done ()
628674 return nil
@@ -962,7 +1008,7 @@ func (c *Operator) sync(ctx context.Context, key string) (func(context.Context)
9621008
9631009 ssetClient := c .kclient .AppsV1 ().StatefulSets (p .Namespace )
9641010
965- // Ensure we have a StatefulSet running Prometheus deployed and that StatefulSet names are created correctly .
1011+ // Reconcile all active statefulset shards .
9661012 expected := prompkg .ExpectedStatefulSetShardNames (p )
9671013 for shard , ssetName := range expected {
9681014 logger := logger .With ("statefulset" , ssetName , "shard" , fmt .Sprintf ("%d" , shard ))
@@ -1060,18 +1106,19 @@ func (c *Operator) sync(ctx context.Context, key string) (func(context.Context)
10601106 return
10611107 }
10621108
1063- shouldRetain , retainErr := c .shouldRetain ( p )
1064- if retainErr != nil {
1065- deleteErrs = append ( deleteErrs , fmt . Errorf ("failed to determine if StatefulSet %s should be retained: %w " , s .GetName (), retainErr ))
1109+ shouldDelete , err := c .processShardRetention ( ctx , p , s )
1110+ if err != nil {
1111+ logger . Warn ("failed to process shard retention, not deleting the shard" , "err" , err , "statefulset" , fmt . Sprintf ( "%s/%s " , s .Namespace , s . Name ))
10661112 return
10671113 }
1068- if shouldRetain {
1114+
1115+ if ! shouldDelete {
10691116 return
10701117 }
10711118
1072- if delErr := ssetClient .Delete (ctx , s .GetName (), metav1.DeleteOptions {PropagationPolicy : ptr .To (metav1 .DeletePropagationForeground )}); delErr != nil {
1073- if ! apierrors .IsNotFound (delErr ) {
1074- deleteErrs = append (deleteErrs , fmt .Errorf ("failed to delete StatefulSet %s: %w" , s .GetName (), delErr ))
1119+ if err := ssetClient .Delete (ctx , s .GetName (), metav1.DeleteOptions {PropagationPolicy : ptr .To (metav1 .DeletePropagationForeground )}); err != nil {
1120+ if ! apierrors .IsNotFound (err ) {
1121+ deleteErrs = append (deleteErrs , fmt .Errorf ("failed to delete StatefulSet %s: %w" , s .GetName (), err ))
10751122 }
10761123 }
10771124 })
@@ -1202,23 +1249,95 @@ func (c *Operator) configResStatusCleanup(ctx context.Context, p *monitoringv1.P
12021249 return nil
12031250}
12041251
1205- // As the ShardRetentionPolicy feature evolves, should retain will evolve accordingly.
1206- // For now, shouldRetain just returns the appropriate boolean based on the retention type .
1207- func (c * Operator ) shouldRetain ( p * monitoringv1.Prometheus ) (bool , error ) {
1252+ // processShardRetention returns true if the associated statefulset should be
1253+ // deleted .
1254+ func (c * Operator ) processShardRetention ( ctx context. Context , p * monitoringv1.Prometheus , sset * appsv1. StatefulSet ) (bool , error ) {
12081255 if ! c .retentionPoliciesEnabled {
1209- // Feature- gate is disabled, default behavior is always to delete.
1210- return false , nil
1256+ // When the feature gate is disabled, the default behavior is always to delete.
1257+ return true , nil
12111258 }
1259+
12121260 if p .Spec .ShardRetentionPolicy == nil {
1213- // ShardRetentionPolicy not configured, default behavior is to delete.
1214- return false , nil
1261+ // ShardRetentionPolicy not configured, the default behavior is to delete.
1262+ return true , nil
12151263 }
1216- if ptr . Deref ( p . Spec . ShardRetentionPolicy . WhenScaled ,
1217- monitoringv1 .DeleteWhenScaledRetentionType ) = = monitoringv1 .RetainWhenScaledRetentionType {
1264+
1265+ if ptr . Deref ( p . Spec . ShardRetentionPolicy . WhenScaled , monitoringv1 .DeleteWhenScaledRetentionType ) ! = monitoringv1 .RetainWhenScaledRetentionType {
12181266 return true , nil
12191267 }
12201268
1221- return false , nil
1269+ if deadline , found := sset .Annotations [deletionDeadlineAnnotation ]; found {
1270+ expired , err := deadlineExpired (deadline )
1271+ if err != nil {
1272+ return false , err
1273+ }
1274+
1275+ return expired , nil
1276+ }
1277+
1278+ // Set the annotation on the statefulset. If the deadline never expires,
1279+ // the annotation is set to the zero time value.
1280+ gracePeriod , err := gracePeriodForPrometheusStorage (p )
1281+ if err != nil {
1282+ return false , err
1283+ }
1284+
1285+ var deadline time.Time
1286+ if gracePeriod > 0 {
1287+ deadline = time .Now ().UTC ().Add (gracePeriod )
1288+ }
1289+
1290+ patchData , err := json .Marshal (map [string ]any {
1291+ "metadata" : map [string ]any {
1292+ "annotations" : map [string ]string {
1293+ deletionDeadlineAnnotation : deadline .Format (annotationTimeFormat ),
1294+ },
1295+ },
1296+ })
1297+ if err != nil {
1298+ return false , err
1299+ }
1300+
1301+ _ , err = c .kclient .AppsV1 ().StatefulSets (sset .Namespace ).Patch (
1302+ ctx ,
1303+ sset .Name ,
1304+ types .StrategicMergePatchType ,
1305+ patchData ,
1306+ metav1.PatchOptions {FieldManager : k8s .PrometheusOperatorFieldManager })
1307+
1308+ return false , err
1309+ }
1310+
1311+ func deadlineExpired (deadline string ) (bool , error ) {
1312+ t , err := time .Parse (annotationTimeFormat , deadline )
1313+ if err != nil {
1314+ return false , err
1315+ }
1316+
1317+ // A zero time means that the deadline never expires.
1318+ return ! t .IsZero () && time .Now ().UTC ().After (t ), nil
1319+ }
1320+
1321+ // gracePeriodForPrometheusStorage returns how long the Prometheus data can be available based
1322+ // on the retention settings.
1323+ // If Prometheus is configured with size-based retention only, it returns a
1324+ // zero value.
1325+ func gracePeriodForPrometheusStorage (p * monitoringv1.Prometheus ) (time.Duration , error ) {
1326+ if p .Spec .RetentionSize != "" && p .Spec .Retention == "" {
1327+ return time .Duration (0 ), nil
1328+ }
1329+
1330+ retention := p .Spec .Retention
1331+ if retention == "" {
1332+ retention = defaultRetention
1333+ }
1334+
1335+ d , err := model .ParseDuration (string (retention ))
1336+ if err != nil {
1337+ return time .Duration (0 ), err
1338+ }
1339+
1340+ return time .Duration (d ), nil
12221341}
12231342
12241343// UpdateStatus updates the status subresource of the object identified by the given
0 commit comments