knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/leaderelection/context.go (about)

     1  /*
     2  Copyright 2020 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package leaderelection
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"sync"
    24  
    25  	"k8s.io/apimachinery/pkg/types"
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	"k8s.io/client-go/kubernetes"
    28  	"k8s.io/client-go/tools/leaderelection"
    29  	"k8s.io/client-go/tools/leaderelection/resourcelock"
    30  
    31  	"knative.dev/pkg/hash"
    32  	"knative.dev/pkg/logging"
    33  	"knative.dev/pkg/network"
    34  	"knative.dev/pkg/reconciler"
    35  	"knative.dev/pkg/system"
    36  )
    37  
    38  // WithDynamicLeaderElectorBuilder sets up the statefulset elector based on environment,
    39  // falling back on the standard elector.
    40  func WithDynamicLeaderElectorBuilder(ctx context.Context, kc kubernetes.Interface, cc ComponentConfig) context.Context {
    41  	logger := logging.FromContext(ctx)
    42  	b, _, err := NewStatefulSetBucketAndSet(int(cc.Buckets))
    43  	if err == nil {
    44  		logger.Info("Running with StatefulSet leader election")
    45  		return WithStatefulSetElectorBuilder(ctx, cc, b)
    46  	}
    47  	logger.Info("Running with Standard leader election")
    48  	return WithStandardLeaderElectorBuilder(ctx, kc, cc)
    49  }
    50  
    51  // WithStandardLeaderElectorBuilder infuses a context with the ability to build
    52  // LeaderElectors with the provided component configuration acquiring resource
    53  // locks via the provided kubernetes client.
    54  func WithStandardLeaderElectorBuilder(ctx context.Context, kc kubernetes.Interface, cc ComponentConfig) context.Context {
    55  	return context.WithValue(ctx, builderKey{}, &standardBuilder{
    56  		kc:  kc,
    57  		lec: cc,
    58  	})
    59  }
    60  
    61  // WithStatefulSetElectorBuilder infuses a context with the ability to build
    62  // Electors which are assigned leadership based on the StatefulSet ordinal from
    63  // the provided component configuration.
    64  func WithStatefulSetElectorBuilder(ctx context.Context, cc ComponentConfig, bkt reconciler.Bucket) context.Context {
    65  	return context.WithValue(ctx, builderKey{}, &statefulSetBuilder{
    66  		lec: cc,
    67  		bkt: bkt,
    68  	})
    69  }
    70  
    71  // HasLeaderElection returns whether there is leader election configuration
    72  // associated with the context
    73  func HasLeaderElection(ctx context.Context) bool {
    74  	val := ctx.Value(builderKey{})
    75  	return val != nil
    76  }
    77  
    78  // Elector is the interface for running a leader elector.
    79  type Elector interface {
    80  	Run(context.Context)
    81  }
    82  
    83  // ElectorWithInitialBuckets is an optional interface for electors to
    84  // supply an initial set of buckets
    85  type ElectorWithInitialBuckets interface {
    86  	InitialBuckets() []reconciler.Bucket
    87  }
    88  
    89  // BuildElector builds a leaderelection.LeaderElector for the named LeaderAware
    90  // reconciler using a builder added to the context via WithStandardLeaderElectorBuilder.
    91  func BuildElector(ctx context.Context, la reconciler.LeaderAware, queueName string, enq func(reconciler.Bucket, types.NamespacedName)) (Elector, error) {
    92  	if val := ctx.Value(builderKey{}); val != nil {
    93  		switch builder := val.(type) {
    94  		case *standardBuilder:
    95  			return builder.buildElector(ctx, la, queueName, enq)
    96  		case *statefulSetBuilder:
    97  			return builder.buildElector(ctx, la, enq)
    98  		}
    99  	}
   100  
   101  	return &unopposedElector{
   102  		la:  la,
   103  		bkt: reconciler.UniversalBucket(),
   104  		// The UniversalBucket owns everything, so there is never a need to
   105  		// enqueue things (no possible state change).  We pass nil here to
   106  		// avoid filling the queue for an extra resynce at startup along
   107  		// this path.
   108  		enq: nil,
   109  	}, nil
   110  }
   111  
   112  type builderKey struct{}
   113  
   114  type standardBuilder struct {
   115  	kc  kubernetes.Interface
   116  	lec ComponentConfig
   117  }
   118  
   119  func (b *standardBuilder) buildElector(ctx context.Context, la reconciler.LeaderAware,
   120  	queueName string, enq func(reconciler.Bucket, types.NamespacedName),
   121  ) (Elector, error) {
   122  	logger := logging.FromContext(ctx)
   123  
   124  	id := b.lec.Identity
   125  	if id == "" {
   126  		uid, err := UniqueID()
   127  		if err != nil {
   128  			return nil, err
   129  		}
   130  		id = uid
   131  	}
   132  
   133  	bkts := newStandardBuckets(queueName, b.lec)
   134  	electors := make([]Elector, 0, b.lec.Buckets)
   135  	for _, bkt := range bkts {
   136  		rl, err := resourcelock.New(knativeResourceLock,
   137  			system.Namespace(), // use namespace we are running in
   138  			bkt.Name(),
   139  			b.kc.CoreV1(),
   140  			b.kc.CoordinationV1(),
   141  			resourcelock.ResourceLockConfig{
   142  				Identity: id,
   143  			})
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  		logger.Infof("%s will run in leader-elected mode with id %q", bkt.Name(), rl.Identity())
   148  
   149  		le, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
   150  			Lock:          rl,
   151  			LeaseDuration: b.lec.LeaseDuration,
   152  			RenewDeadline: b.lec.RenewDeadline,
   153  			RetryPeriod:   b.lec.RetryPeriod,
   154  			Callbacks: leaderelection.LeaderCallbacks{
   155  				OnStartedLeading: func(context.Context) {
   156  					logger.Infof("%q has started leading %q", rl.Identity(), bkt.Name())
   157  					if err := la.Promote(bkt, enq); err != nil {
   158  						// TODO(mattmoor): We expect this to effectively never happen,
   159  						// but if it does, we should support wrapping `le` in an elector
   160  						// we can cancel here.
   161  						logger.Fatalf("%q failed to Promote: %v", rl.Identity(), err)
   162  					}
   163  				},
   164  				OnStoppedLeading: func() {
   165  					logger.Infof("%q has stopped leading %q", rl.Identity(), bkt.Name())
   166  					la.Demote(bkt)
   167  				},
   168  			},
   169  			ReleaseOnCancel: true,
   170  			Name:            rl.Identity(),
   171  		})
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  		// TODO: use health check watchdog, knative/pkg#1048
   176  		// if lec.WatchDog != nil {
   177  		// 	lec.WatchDog.SetLeaderElection(le)
   178  		// }
   179  		electors = append(electors, &runUntilCancelled{Elector: le})
   180  	}
   181  	return &runAll{les: electors}, nil
   182  }
   183  
   184  func newStandardBuckets(queueName string, cc ComponentConfig) []reconciler.Bucket {
   185  	ln := cc.LeaseName
   186  	if ln == nil {
   187  		ln = func(i uint32) string {
   188  			return standardBucketName(i, queueName, cc)
   189  		}
   190  	}
   191  	names := make(sets.Set[string], cc.Buckets)
   192  	for i := range cc.Buckets {
   193  		names.Insert(ln(i))
   194  	}
   195  
   196  	return hash.NewBucketSet(names).Buckets()
   197  }
   198  
   199  func standardBucketName(ordinal uint32, queueName string, cc ComponentConfig) string {
   200  	prefix := fmt.Sprintf("%s.%s", cc.Component, queueName)
   201  	if v, ok := cc.LeaseNamesPrefixMapping[prefix]; ok && len(v) > 0 {
   202  		prefix = v
   203  	}
   204  	return strings.ToLower(fmt.Sprintf("%s.%02d-of-%02d", prefix, ordinal, cc.Buckets))
   205  }
   206  
   207  type statefulSetBuilder struct {
   208  	lec ComponentConfig
   209  	bkt reconciler.Bucket
   210  }
   211  
   212  func (b *statefulSetBuilder) buildElector(ctx context.Context, la reconciler.LeaderAware, enq func(reconciler.Bucket, types.NamespacedName)) (Elector, error) {
   213  	logger := logging.FromContext(ctx)
   214  	logger.Infof("%s will run in StatefulSet ordinal assignment mode with bucket name %s",
   215  		b.lec.Component, b.bkt.Name())
   216  
   217  	return &unopposedElector{
   218  		bkt: b.bkt,
   219  		la:  la,
   220  		enq: enq,
   221  	}, nil
   222  }
   223  
   224  // NewStatefulSetBucketAndSet creates a BucketSet for StatefulSet controller with
   225  // the given bucket size and the information from environment variables. Then uses
   226  // the created BucketSet to create a Bucket for this StatefulSet Pod.
   227  func NewStatefulSetBucketAndSet(buckets int) (reconciler.Bucket, *hash.BucketSet, error) {
   228  	ssc, err := newStatefulSetConfig()
   229  	if err != nil {
   230  		return nil, nil, err
   231  	}
   232  
   233  	if ssc.StatefulSetID.ordinal >= buckets {
   234  		return nil, nil, fmt.Errorf("ordinal %d is out of range [0, %d)",
   235  			ssc.StatefulSetID.ordinal, buckets)
   236  	}
   237  
   238  	names := make(sets.Set[string], buckets)
   239  	for i := range buckets {
   240  		names.Insert(statefulSetPodDNS(i, ssc))
   241  	}
   242  
   243  	bs := hash.NewBucketSet(names)
   244  	// Buckets is sorted in order of names so we can use ordinal to
   245  	// get the correct Bucket for this binary.
   246  	return bs.Buckets()[ssc.StatefulSetID.ordinal], bs, nil
   247  }
   248  
   249  func statefulSetPodDNS(ordinal int, ssc *statefulSetConfig) string {
   250  	return fmt.Sprintf("%s://%s-%d.%s.%s.svc.%s:%s", ssc.Protocol,
   251  		ssc.StatefulSetID.ssName, ordinal, ssc.ServiceName,
   252  		system.Namespace(), network.GetClusterDomainName(), ssc.Port)
   253  }
   254  
   255  // unopposedElector promotes when run without needing to be elected.
   256  type unopposedElector struct {
   257  	bkt reconciler.Bucket
   258  	la  reconciler.LeaderAware
   259  	enq func(reconciler.Bucket, types.NamespacedName)
   260  }
   261  
   262  var (
   263  	_ Elector                   = (*unopposedElector)(nil)
   264  	_ ElectorWithInitialBuckets = (*unopposedElector)(nil)
   265  )
   266  
   267  // Run implements Elector
   268  func (ue *unopposedElector) Run(ctx context.Context) {
   269  	ue.la.Promote(ue.bkt, ue.enq)
   270  }
   271  
   272  func (ue *unopposedElector) InitialBuckets() []reconciler.Bucket {
   273  	return []reconciler.Bucket{
   274  		ue.bkt,
   275  	}
   276  }
   277  
   278  type runAll struct {
   279  	les []Elector
   280  }
   281  
   282  // Run implements Elector
   283  func (ra *runAll) Run(ctx context.Context) {
   284  	sg := sync.WaitGroup{}
   285  	defer sg.Wait()
   286  
   287  	for _, le := range ra.les {
   288  		sg.Add(1)
   289  		go func(le Elector) {
   290  			defer sg.Done()
   291  			le.Run(ctx)
   292  		}(le)
   293  	}
   294  }
   295  
   296  // runUntilCancelled wraps a single-term Elector into one that runs until
   297  // the passed context is cancelled.
   298  type runUntilCancelled struct {
   299  	// Elector is a single-term elector as we get from K8s leaderelection package.
   300  	Elector
   301  }
   302  
   303  // Run implements Elector
   304  func (ruc *runUntilCancelled) Run(ctx context.Context) {
   305  	// Turn the single-term elector into a continuous election cycle.
   306  	for {
   307  		ruc.Elector.Run(ctx)
   308  		select {
   309  		case <-ctx.Done():
   310  			return // Run quit because context was cancelled, we are done!
   311  		default:
   312  			// Context wasn't cancelled, start over.
   313  		}
   314  	}
   315  }