diff --git a/pkg/cloudprovider/servicelb.go b/pkg/cloudprovider/servicelb.go index f57046afc47e..192a7e80535f 100644 --- a/pkg/cloudprovider/servicelb.go +++ b/pkg/cloudprovider/servicelb.go @@ -7,6 +7,8 @@ import ( "strconv" "strings" "time" + "encoding/json" + "sigs.k8s.io/yaml" "github.com/k3s-io/k3s/pkg/util" "github.com/k3s-io/k3s/pkg/version" @@ -42,6 +44,7 @@ var ( daemonsetNodePoolLabel = "svccontroller." + version.Program + ".cattle.io/lbpool" nodeSelectorLabel = "svccontroller." + version.Program + ".cattle.io/nodeselector" priorityAnnotation = "svccontroller." + version.Program + ".cattle.io/priorityclassname" + tolerationsAnnotation = "svccontroller." + version.Program + ".cattle.io/tolerations" controllerName = names.ServiceLBController ) @@ -597,6 +600,14 @@ func (k *k3s) newDaemonSet(svc *core.Service) (*apps.DaemonSet, error) { ds.Labels[nodeSelectorLabel] = "true" } + // Fetch tolerations from the "svccontroller.k3s.cattle.io/tolerations" annotation on the service + // and append them to the DaemonSet's pod tolerations. + tolerations, err := k.getTolerations(svc) + if err != nil { + return nil, err + } + ds.Spec.Template.Spec.Tolerations = append(ds.Spec.Template.Spec.Tolerations, tolerations...) + return ds, nil } @@ -699,6 +710,47 @@ func (k *k3s) getPriorityClassName(svc *core.Service) string { return k.LBDefaultPriorityClassName } +// getTolerations retrieves the tolerations from a service's annotations. +// It parses the tolerations from a JSON or YAML string stored in the annotations. +func (k *k3s) getTolerations(svc *core.Service) ([]core.Toleration, error) { + tolerationsStr, ok := svc.Annotations[tolerationsAnnotation] + if !ok { + return []core.Toleration{}, nil + } + + var tolerations []core.Toleration + if err := json.Unmarshal([]byte(tolerationsStr), &tolerations); err != nil { + if err := yaml.Unmarshal([]byte(tolerationsStr), &tolerations); err != nil { + return nil, fmt.Errorf("failed to parse tolerations from annotation %s: %v", tolerationsAnnotation, err) + } + } + + for i := range tolerations { + if err := validateToleration(&tolerations[i]); err != nil { + return nil, fmt.Errorf("validation failed for toleration %d: %v", i, err) + } + } + + return tolerations, nil +} + +// validateToleration ensures a toleration has valid fields according to its operator. +func validateToleration(toleration *core.Toleration) error { + if toleration.Operator == "" { + toleration.Operator = core.TolerationOpEqual + } + + if toleration.Key == "" && toleration.Operator != core.TolerationOpExists { + return fmt.Errorf("toleration with empty key must have operator 'Exists'") + } + + if toleration.Operator == core.TolerationOpExists && toleration.Value != "" { + return fmt.Errorf("toleration with operator 'Exists' must have an empty value") + } + + return nil +} + // generateName generates a distinct name for the DaemonSet based on the service name and UID func generateName(svc *core.Service) string { name := svc.Name