feat: add highest random weight in kubernetes CRD
This commit is contained in:
parent
c09d3fb03c
commit
634f892370
17 changed files with 1328 additions and 24 deletions
|
|
@ -259,7 +259,7 @@ const (
|
|||
BalancerStrategyWRR BalancerStrategy = "wrr"
|
||||
// BalancerStrategyP2C is the power of two choices strategy.
|
||||
BalancerStrategyP2C BalancerStrategy = "p2c"
|
||||
// BalancerStrategyHRW is the power of two choices strategy.
|
||||
// BalancerStrategyHRW is the highest random weight strategy.
|
||||
BalancerStrategyHRW BalancerStrategy = "hrw"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: whoami1-abc
|
||||
namespace: default
|
||||
labels:
|
||||
kubernetes.io/service-name: whoami1
|
||||
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- name: web
|
||||
port: 8080
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.0.1
|
||||
- 10.10.0.2
|
||||
conditions:
|
||||
ready: true
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoami1
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 8080
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoami1
|
||||
|
||||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: whoami2-abc
|
||||
namespace: default
|
||||
labels:
|
||||
kubernetes.io/service-name: whoami2
|
||||
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- name: web
|
||||
port: 8080
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.0.3
|
||||
- 10.10.0.4
|
||||
conditions:
|
||||
ready: true
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoami2
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 8080
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoami2
|
||||
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: hrw1
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
highestRandomWeight:
|
||||
services:
|
||||
- name: whoami1
|
||||
kind: Service
|
||||
port: 8080
|
||||
weight: 10
|
||||
- name: whoami2
|
||||
kind: Service
|
||||
port: 8080
|
||||
weight: 20
|
||||
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/foo`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
services:
|
||||
- name: hrw1
|
||||
kind: TraefikService
|
||||
|
|
@ -216,13 +216,17 @@ type configBuilder struct {
|
|||
func (c configBuilder) buildTraefikService(ctx context.Context, tService *traefikv1alpha1.TraefikService, conf map[string]*dynamic.Service) error {
|
||||
id := provider.Normalize(makeID(tService.Namespace, tService.Name))
|
||||
|
||||
if tService.Spec.Weighted != nil {
|
||||
switch {
|
||||
case tService.Spec.Weighted != nil:
|
||||
return c.buildServicesLB(ctx, tService.Namespace, tService.Spec, id, conf)
|
||||
} else if tService.Spec.Mirroring != nil {
|
||||
case tService.Spec.Mirroring != nil:
|
||||
return c.buildMirroring(ctx, tService, id, conf)
|
||||
}
|
||||
case tService.Spec.HighestRandomWeight != nil:
|
||||
return c.buildHRW(ctx, tService, id, conf)
|
||||
default:
|
||||
|
||||
return errors.New("unspecified service type")
|
||||
return errors.New("unspecified service type")
|
||||
}
|
||||
}
|
||||
|
||||
// buildServicesLB creates the configuration for the load-balancer of services named id, and defined in tService.
|
||||
|
|
@ -329,7 +333,7 @@ func (c configBuilder) buildServersLB(namespace string, svc traefikv1alpha1.Load
|
|||
// TODO: remove this when the fake client apply default values.
|
||||
if svc.Strategy != "" {
|
||||
switch svc.Strategy {
|
||||
case dynamic.BalancerStrategyWRR, dynamic.BalancerStrategyP2C:
|
||||
case dynamic.BalancerStrategyWRR, dynamic.BalancerStrategyP2C, dynamic.BalancerStrategyHRW:
|
||||
lb.Strategy = svc.Strategy
|
||||
|
||||
// Here we are just logging a warning as the default value is already applied.
|
||||
|
|
@ -644,6 +648,38 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin
|
|||
}
|
||||
}
|
||||
|
||||
func (c configBuilder) buildHRW(ctx context.Context, tService *traefikv1alpha1.TraefikService, id string, conf map[string]*dynamic.Service) error {
|
||||
var hrwServices []dynamic.HRWService
|
||||
for _, hrwService := range tService.Spec.HighestRandomWeight.Services {
|
||||
hrwServiceName, k8sService, err := c.nameAndService(ctx, tService.Namespace, hrwService.LoadBalancerSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if k8sService != nil {
|
||||
conf[hrwServiceName] = k8sService
|
||||
}
|
||||
|
||||
weight := hrwService.Weight
|
||||
if weight == nil {
|
||||
weight = func(i int) *int { return &i }(1)
|
||||
}
|
||||
|
||||
hrwServices = append(hrwServices, dynamic.HRWService{
|
||||
Name: hrwServiceName,
|
||||
Weight: weight,
|
||||
})
|
||||
}
|
||||
|
||||
conf[id] = &dynamic.Service{
|
||||
HighestRandomWeight: &dynamic.HighestRandomWeight{
|
||||
Services: hrwServices,
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitSvcNameProvider(name string) (string, string) {
|
||||
parts := strings.Split(name, providerNamespaceSeparator)
|
||||
|
||||
|
|
|
|||
|
|
@ -3150,6 +3150,79 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "one kube services in a highest random weight",
|
||||
paths: []string{"with_highest_random_weight.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
ServersTransports: map[string]*dynamic.TCPServersTransport{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-route-77c62dfe9517144aeeaa": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "default-hrw1",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/foo`)",
|
||||
Priority: 12,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-hrw1": {
|
||||
HighestRandomWeight: &dynamic.HighestRandomWeight{
|
||||
Services: []dynamic.HRWService{
|
||||
{Name: "default-whoami1-8080", Weight: pointer(10)},
|
||||
{Name: "default-whoami2-8080", Weight: pointer(20)},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami1-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: pointer(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami2-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.3:8080",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.4:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: pointer(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "One ingress Route with two different services, with weights",
|
||||
paths: []string{"services.yml", "with_two_services_weight.yml"},
|
||||
|
|
|
|||
|
|
@ -114,10 +114,10 @@ type LoadBalancerSpec struct {
|
|||
// It defaults to https when Kubernetes Service port is 443, http otherwise.
|
||||
Scheme string `json:"scheme,omitempty"`
|
||||
// Strategy defines the load balancing strategy between the servers.
|
||||
// Supported values are: wrr (Weighed round-robin) and p2c (Power of two choices).
|
||||
// Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight).
|
||||
// RoundRobin value is deprecated and supported for backward compatibility.
|
||||
// TODO: when the deprecated RoundRobin value will be removed, set the default value to wrr.
|
||||
// +kubebuilder:validation:Enum=wrr;p2c;RoundRobin
|
||||
// +kubebuilder:validation:Enum=wrr;p2c;hrw;RoundRobin
|
||||
Strategy dynamic.BalancerStrategy `json:"strategy,omitempty"`
|
||||
// PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service.
|
||||
// By default, passHostHeader is true.
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ type TraefikServiceSpec struct {
|
|||
Weighted *WeightedRoundRobin `json:"weighted,omitempty"`
|
||||
// Mirroring defines the Mirroring service configuration.
|
||||
Mirroring *Mirroring `json:"mirroring,omitempty"`
|
||||
// HighestRandomWeight defines the highest random weight service configuration.
|
||||
HighestRandomWeight *HighestRandomWeight `json:"highestRandomWeight,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
@ -86,3 +88,12 @@ type WeightedRoundRobin struct {
|
|||
// More info: https://doc.traefik.io/traefik/v3.5/routing/providers/kubernetes-crd/#stickiness-and-load-balancing
|
||||
Sticky *dynamic.Sticky `json:"sticky,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// HighestRandomWeight holds the highest random weight configuration.
|
||||
// More info: https://doc.traefik.io/traefik/v3.5/routing/services/#highest-random-configuration
|
||||
type HighestRandomWeight struct {
|
||||
// Services defines the list of Kubernetes Service and/or TraefikService to load-balance, with weight.
|
||||
Services []Service `json:"services,omitempty"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -349,6 +349,29 @@ func (in *ForwardingTimeouts) DeepCopy() *ForwardingTimeouts {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HighestRandomWeight) DeepCopyInto(out *HighestRandomWeight) {
|
||||
*out = *in
|
||||
if in.Services != nil {
|
||||
in, out := &in.Services, &out.Services
|
||||
*out = make([]Service, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HighestRandomWeight.
|
||||
func (in *HighestRandomWeight) DeepCopy() *HighestRandomWeight {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HighestRandomWeight)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IngressRoute) DeepCopyInto(out *IngressRoute) {
|
||||
*out = *in
|
||||
|
|
@ -2028,6 +2051,11 @@ func (in *TraefikServiceSpec) DeepCopyInto(out *TraefikServiceSpec) {
|
|||
*out = new(Mirroring)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.HighestRandomWeight != nil {
|
||||
in, out := &in.HighestRandomWeight, &out.HighestRandomWeight
|
||||
*out = new(HighestRandomWeight)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue