88 "context"
99 "fmt"
1010 "io"
11+ "math/rand"
12+ "os"
1113 "reflect"
1214 "regexp"
1315 "sort"
@@ -24,6 +26,7 @@ import (
2426 "k8s.io/apimachinery/pkg/api/meta"
2527 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2628 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29+ "k8s.io/apimachinery/pkg/labels"
2730 "k8s.io/apimachinery/pkg/runtime"
2831 "k8s.io/apimachinery/pkg/runtime/schema"
2932 "k8s.io/apimachinery/pkg/types"
@@ -604,11 +607,14 @@ func (r *Reconciler) generateRepoHostIntent(ctx context.Context, postgresCluster
604607 Namespace : postgresCluster .GetNamespace (),
605608 }
606609
607- if err := r .Client .Get (ctx , secretKey , existingSecret ); err == nil {
608- if podAnnotations == nil {
609- podAnnotations = make (map [string ]string )
610+ if podAnnotations == nil {
611+ podAnnotations = make (map [string ]string )
612+ }
613+
614+ if shouldAnnotateRepoHost (podAnnotations ) {
615+ if err := r .Client .Get (ctx , secretKey , existingSecret ); err == nil {
616+ podAnnotations ["postgres-operator.crunchydata.com/pgbackrest-secret-version" ] = existingSecret .ResourceVersion
610617 }
611- podAnnotations ["postgres-operator.crunchydata.com/pgbackrest-secret-version" ] = existingSecret .ResourceVersion
612618 }
613619
614620 repo := & appsv1.StatefulSet {
@@ -764,6 +770,36 @@ func (r *Reconciler) generateRepoHostIntent(ctx context.Context, postgresCluster
764770 return repo , nil
765771}
766772
773+ // In order to avoid multiple repo-hosts restarting per cycle, we adopt a gradual rollout strategy.
774+ // Distribution is (pseudo-)random, but we should see ~20 restarts/per cycle.
775+ // When all repo-hosts are annotated, this function can be removed.
776+ func shouldAnnotateRepoHost (annotations labels.Set ) bool {
777+ if _ , exists := annotations ["postgres-operator.crunchydata.com/pgbackrest-secret-version" ]; exists {
778+ // 1. If the annotation already exist, we keep it.
779+ return true
780+ }
781+
782+ // 2. Otherwise, given the start time of the rollout, we calculate a linear increasing threshold and
783+ // roll a d100. If the value of the dice is lower than the threshold, we add the annotation in this
784+ // reconciliation cycle. Note that this means a machine restart.
785+ // By the end of a week, the threshold should reach 100 and any dice value will allow for the
786+ // annotation to be added, effectively annotating all remaining pods.
787+ if rolloutStartStr := os .Getenv ("PGBACKREST_SECRET_ROLLOUT_START_TIME" ); rolloutStartStr != "" {
788+ if rolloutStart , err := time .Parse (time .RFC3339 , rolloutStartStr ); err == nil {
789+ oneWeekInMinutes := 7 * 24 * 60
790+ minutesElapsed := int (time .Since (rolloutStart ).Minutes ())
791+
792+ // Increases every minute. Reconciliation cycles happen every 10 minutes.
793+ threshold := min ((minutesElapsed * 100 )/ oneWeekInMinutes , 100 )
794+ d100 := rand .Intn (100 )
795+
796+ return d100 <= threshold
797+ }
798+ }
799+
800+ return false
801+ }
802+
767803func (r * Reconciler ) generateRepoVolumeIntent (postgresCluster * v1beta1.PostgresCluster ,
768804 spec corev1.PersistentVolumeClaimSpec , repoName string ,
769805 repoResources * RepoResources ) (* corev1.PersistentVolumeClaim , error ) {
0 commit comments