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 }