knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/leaderelection/config.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  	"os"
    23  	"strconv"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/kelseyhightower/envconfig"
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/util/uuid"
    30  	"k8s.io/client-go/tools/leaderelection/resourcelock"
    31  
    32  	cm "knative.dev/pkg/configmap"
    33  )
    34  
    35  const (
    36  	configMapNameEnv = "CONFIG_LEADERELECTION_NAME"
    37  	// knativeResourceLock is the only supported lock mechanism for Knative.
    38  	knativeResourceLock = resourcelock.LeasesResourceLock
    39  )
    40  
    41  // MaxBuckets is the maximum number of buckets to allow users to define.
    42  // This is a variable so that it may be customized in the binary entrypoint.
    43  var MaxBuckets uint32 = 10
    44  
    45  // NewConfigFromMap returns a Config for the given map, or an error.
    46  func NewConfigFromMap(data map[string]string) (*Config, error) {
    47  	config := defaultConfig()
    48  
    49  	if err := cm.Parse(data,
    50  		// Parse legacy keys first
    51  		cm.AsDuration("leaseDuration", &config.LeaseDuration),
    52  		cm.AsDuration("renewDeadline", &config.RenewDeadline),
    53  		cm.AsDuration("retryPeriod", &config.RetryPeriod),
    54  
    55  		cm.AsDuration("lease-duration", &config.LeaseDuration),
    56  		cm.AsDuration("renew-deadline", &config.RenewDeadline),
    57  		cm.AsDuration("retry-period", &config.RetryPeriod),
    58  
    59  		cm.AsUint32("buckets", &config.Buckets),
    60  
    61  		cm.CollectMapEntriesWithPrefix("map-lease-prefix", &config.LeaseNamesPrefixMapping),
    62  	); err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	if config.Buckets < 1 || config.Buckets > MaxBuckets {
    67  		return nil, fmt.Errorf("buckets: value must be between %d <= %d <= %d", 1, config.Buckets, MaxBuckets)
    68  	}
    69  	return config, nil
    70  }
    71  
    72  // NewConfigFromConfigMap returns a new Config from the given ConfigMap.
    73  func NewConfigFromConfigMap(configMap *corev1.ConfigMap) (*Config, error) {
    74  	if configMap == nil {
    75  		config := defaultConfig()
    76  		return config, nil
    77  	}
    78  	return NewConfigFromMap(configMap.Data)
    79  }
    80  
    81  // Config represents the leader election config for a set of components
    82  // contained within a single namespace. Typically these will correspond to a
    83  // single source repository, viz: serving or eventing.
    84  type Config struct {
    85  	Buckets                 uint32
    86  	LeaseDuration           time.Duration
    87  	RenewDeadline           time.Duration
    88  	RetryPeriod             time.Duration
    89  	LeaseNamesPrefixMapping map[string]string
    90  }
    91  
    92  type lecfg struct{}
    93  
    94  // WithConfig associates a leader election configuration with the
    95  // context.
    96  func WithConfig(ctx context.Context, cfg *Config) context.Context {
    97  	return context.WithValue(ctx, lecfg{}, cfg)
    98  }
    99  
   100  // GetConfig gets the leader election config from the provided
   101  // context.
   102  func GetConfig(ctx context.Context) *Config {
   103  	untyped := ctx.Value(lecfg{})
   104  	if untyped == nil {
   105  		return nil
   106  	}
   107  	return untyped.(*Config)
   108  }
   109  
   110  func (c *Config) GetComponentConfig(name string) ComponentConfig {
   111  	return ComponentConfig{
   112  		Component:               name,
   113  		Buckets:                 c.Buckets,
   114  		LeaseDuration:           c.LeaseDuration,
   115  		RenewDeadline:           c.RenewDeadline,
   116  		RetryPeriod:             c.RetryPeriod,
   117  		LeaseNamesPrefixMapping: c.LeaseNamesPrefixMapping,
   118  	}
   119  }
   120  
   121  func defaultConfig() *Config {
   122  	return &Config{
   123  		Buckets:       1,
   124  		LeaseDuration: 60 * time.Second,
   125  		RenewDeadline: 40 * time.Second,
   126  		RetryPeriod:   10 * time.Second,
   127  	}
   128  }
   129  
   130  // ComponentConfig represents the leader election config for a single component.
   131  type ComponentConfig struct {
   132  	Component     string
   133  	Buckets       uint32
   134  	LeaseDuration time.Duration
   135  	RenewDeadline time.Duration
   136  	RetryPeriod   time.Duration
   137  	// LeaseName is a function to customize the lease name given the index i.
   138  	// If not present, a name in format {Component}.{queue-name}.{i}-of-{Buckets}
   139  	// will be use.
   140  	// Autoscaler need to know the Lease names to filter out Leases which are not
   141  	// used for Autoscaler. Instead of exposing the names from leadelection package,
   142  	// we let Autoscaler to pass them in.
   143  	LeaseName func(i uint32) string `json:"-"`
   144  	// Identity is the unique string identifying a resource lock holder across
   145  	// all participants in an election. If not present, a new unique string will
   146  	// be generated to be used as identity for each BuildElector call.
   147  	// Autoscaler uses the pod IP as identity.
   148  	Identity string
   149  
   150  	// LeaseNamesPrefixMapping maps lease prefixes
   151  	// from <component>.<package>.<reconciler_type_name> to the
   152  	// associated value when using standardBuilder.
   153  	LeaseNamesPrefixMapping map[string]string
   154  }
   155  
   156  // statefulSetID is a envconfig Decodable controller ordinal and name.
   157  type statefulSetID struct {
   158  	ssName  string
   159  	ordinal int
   160  }
   161  
   162  func (ssID *statefulSetID) Decode(v string) error {
   163  	if i := strings.LastIndex(v, "-"); i != -1 {
   164  		ui, err := strconv.Atoi(v[i+1:])
   165  		ssID.ordinal = ui
   166  		ssID.ssName = v[:i]
   167  		return err
   168  	}
   169  	return fmt.Errorf("%q is not a valid stateful set controller ordinal", v)
   170  }
   171  
   172  var _ envconfig.Decoder = (*statefulSetID)(nil)
   173  
   174  // statefulSetConfig represents the required information for a StatefulSet service.
   175  type statefulSetConfig struct {
   176  	StatefulSetID statefulSetID `envconfig:"STATEFUL_CONTROLLER_ORDINAL" required:"true"`
   177  	ServiceName   string        `envconfig:"STATEFUL_SERVICE_NAME" required:"true"`
   178  	Port          string        `envconfig:"STATEFUL_SERVICE_PORT" default:"80"`
   179  	Protocol      string        `envconfig:"STATEFUL_SERVICE_PROTOCOL" default:"http"`
   180  }
   181  
   182  // newStatefulSetConfig builds a stateful set LE config.
   183  func newStatefulSetConfig() (*statefulSetConfig, error) {
   184  	ssc := &statefulSetConfig{}
   185  	if err := envconfig.Process("", ssc); err != nil {
   186  		return nil, err
   187  	}
   188  	return ssc, nil
   189  }
   190  
   191  // ConfigMapName returns the name of the configmap to read for leader election
   192  // settings.
   193  func ConfigMapName() string {
   194  	cm := os.Getenv(configMapNameEnv)
   195  	if cm == "" {
   196  		return "config-leader-election"
   197  	}
   198  	return cm
   199  }
   200  
   201  // UniqueID returns a unique ID for use with a leader elector that prevents from
   202  // pods running on the same host from colliding with one another.
   203  func UniqueID() (string, error) {
   204  	id, err := os.Hostname()
   205  	if err != nil {
   206  		return "", err
   207  	}
   208  
   209  	return id + "_" + string(uuid.NewUUID()), nil
   210  }