1
0
Fork 0

Add least time load balancing strategy

This commit is contained in:
Simon Delicata 2025-10-23 16:16:05 +02:00 committed by GitHub
parent 067c7e7152
commit a754236ce5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1830 additions and 134 deletions

View file

@ -4,7 +4,6 @@ import (
"encoding/json"
"io"
"net/http"
"strings"
"testing"
"time"
@ -56,56 +55,6 @@ func (s *DockerSuite) TestSimpleConfiguration() {
require.NoError(s.T(), err)
}
func (s *DockerSuite) TestWRRServer() {
tempObjects := struct {
DockerHost string
DefaultRule string
}{
DockerHost: s.getDockerHost(),
DefaultRule: "Host(`{{ normalize .Name }}.docker.localhost`)",
}
file := s.adaptFile("fixtures/docker/simple.toml", tempObjects)
s.composeUp()
s.traefikCmd(withConfigFile(file))
whoami1IP := s.getComposeServiceIP("wrr-server")
whoami2IP := s.getComposeServiceIP("wrr-server2")
// Expected a 404 as we did not configure anything
err := try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
require.NoError(s.T(), err)
err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("wrr-server"))
require.NoError(s.T(), err)
repartition := map[string]int{}
for range 4 {
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
req.Host = "my.wrr.host"
require.NoError(s.T(), err)
response, err := http.DefaultClient.Do(req)
require.NoError(s.T(), err)
assert.Equal(s.T(), http.StatusOK, response.StatusCode)
body, err := io.ReadAll(response.Body)
require.NoError(s.T(), err)
if strings.Contains(string(body), whoami1IP) {
repartition[whoami1IP]++
}
if strings.Contains(string(body), whoami2IP) {
repartition[whoami2IP]++
}
}
assert.Equal(s.T(), 3, repartition[whoami1IP])
assert.Equal(s.T(), 1, repartition[whoami2IP])
}
func (s *DockerSuite) TestDefaultDockerContainers() {
tempObjects := struct {
DockerHost string

View file

@ -351,12 +351,13 @@ spec:
strategy:
description: |-
Strategy defines the load balancing strategy between the servers.
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight).
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time).
RoundRobin value is deprecated and supported for backward compatibility.
enum:
- wrr
- p2c
- hrw
- leasttime
- RoundRobin
type: string
weight:
@ -1313,12 +1314,13 @@ spec:
strategy:
description: |-
Strategy defines the load balancing strategy between the servers.
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight).
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time).
RoundRobin value is deprecated and supported for backward compatibility.
enum:
- wrr
- p2c
- hrw
- leasttime
- RoundRobin
type: string
weight:
@ -3031,12 +3033,13 @@ spec:
strategy:
description: |-
Strategy defines the load balancing strategy between the servers.
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight).
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time).
RoundRobin value is deprecated and supported for backward compatibility.
enum:
- wrr
- p2c
- hrw
- leasttime
- RoundRobin
type: string
weight:
@ -3359,12 +3362,13 @@ spec:
strategy:
description: |-
Strategy defines the load balancing strategy between the servers.
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight).
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time).
RoundRobin value is deprecated and supported for backward compatibility.
enum:
- wrr
- p2c
- hrw
- leasttime
- RoundRobin
type: string
weight:
@ -3506,12 +3510,13 @@ spec:
strategy:
description: |-
Strategy defines the load balancing strategy between the servers.
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight).
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time).
RoundRobin value is deprecated and supported for backward compatibility.
enum:
- wrr
- p2c
- hrw
- leasttime
- RoundRobin
type: string
weight:
@ -3740,12 +3745,13 @@ spec:
strategy:
description: |-
Strategy defines the load balancing strategy between the servers.
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), and hrw (Highest Random Weight).
Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time).
RoundRobin value is deprecated and supported for backward compatibility.
enum:
- wrr
- p2c
- hrw
- leasttime
- RoundRobin
type: string
weight:

View file

@ -0,0 +1,36 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[api]
insecure = true
[log]
level = "DEBUG"
noColor = true
[entryPoints]
[entryPoints.web]
address = ":8000"
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers]
[http.routers.router]
service = "service1"
rule = "Path(`/whoami`)"
[http.services]
[http.services.service1.loadBalancer]
strategy = "leasttime"
[[http.services.service1.loadBalancer.servers]]
url = "{{ .Server1 }}"
weight = 1
[[http.services.service1.loadBalancer.servers]]
url = "{{ .Server2 }}"
weight = 1

View file

@ -35,14 +35,3 @@ services:
labels:
traefik.http.Routers.Super.Rule: Host(`my.super.host`)
traefik.http.Services.powpow.LoadBalancer.server.Port: 2375
wrr-server:
image: traefik/whoami
labels:
traefik.http.Routers.wrr-server.Rule: Host(`my.wrr.host`)
traefik.http.Services.wrr-server.LoadBalancer.server.Weight: 4
wrr-server2:
image: traefik/whoami
labels:
traefik.http.Routers.wrr-server.Rule: Host(`my.wrr.host`)
traefik.http.Services.wrr-server.LoadBalancer.server.Weight: 1

View file

@ -885,6 +885,109 @@ func (s *SimpleSuite) TestWRRServer() {
assert.Equal(s.T(), 1, repartition[whoami2IP])
}
func (s *SimpleSuite) TestLeastTimeServer() {
s.createComposeProject("base")
s.composeUp()
defer s.composeDown()
whoami1IP := s.getComposeServiceIP("whoami1")
whoami2IP := s.getComposeServiceIP("whoami2")
file := s.adaptFile("fixtures/leasttime_server.toml", struct {
Server1 string
Server2 string
}{Server1: "http://" + whoami1IP, Server2: "http://" + whoami2IP})
s.traefikCmd(withConfigFile(file))
err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("service1"))
require.NoError(s.T(), err)
// Verify leasttime strategy is configured
err = try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("leasttime"))
require.NoError(s.T(), err)
// Make requests and verify both servers respond
repartition := map[string]int{}
for range 10 {
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
require.NoError(s.T(), err)
response, err := http.DefaultClient.Do(req)
require.NoError(s.T(), err)
assert.Equal(s.T(), http.StatusOK, response.StatusCode)
body, err := io.ReadAll(response.Body)
require.NoError(s.T(), err)
if strings.Contains(string(body), whoami1IP) {
repartition[whoami1IP]++
}
if strings.Contains(string(body), whoami2IP) {
repartition[whoami2IP]++
}
}
// Both servers should have received requests
assert.Positive(s.T(), repartition[whoami1IP])
assert.Positive(s.T(), repartition[whoami2IP])
}
func (s *SimpleSuite) TestLeastTimeHeterogeneousPerformance() {
// Create test servers with different response times
var fastServerCalls, slowServerCalls atomic.Int32
fastServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
fastServerCalls.Add(1)
time.Sleep(10 * time.Millisecond) // Fast server
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write([]byte("fast-server"))
}))
defer fastServer.Close()
slowServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
slowServerCalls.Add(1)
time.Sleep(100 * time.Millisecond) // Slow server
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write([]byte("slow-server"))
}))
defer slowServer.Close()
file := s.adaptFile("fixtures/leasttime_server.toml", struct {
Server1 string
Server2 string
}{Server1: fastServer.URL, Server2: slowServer.URL})
s.traefikCmd(withConfigFile(file))
err := try.GetRequest("http://127.0.0.1:8080/api/http/services", 1000*time.Millisecond, try.BodyContains("service1"))
require.NoError(s.T(), err)
// Make 20 requests to build up response time statistics
for range 20 {
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
require.NoError(s.T(), err)
response, err := http.DefaultClient.Do(req)
require.NoError(s.T(), err)
assert.Equal(s.T(), http.StatusOK, response.StatusCode)
_, _ = io.ReadAll(response.Body)
response.Body.Close()
}
// Verify that the fast server received significantly more requests (>70%)
fastCalls := fastServerCalls.Load()
slowCalls := slowServerCalls.Load()
totalCalls := fastCalls + slowCalls
assert.Equal(s.T(), int32(20), totalCalls)
// Fast server should get >70% of traffic due to lower response time
fastPercentage := float64(fastCalls) / float64(totalCalls) * 100
assert.Greater(s.T(), fastPercentage, 70.0)
}
func (s *SimpleSuite) TestWRR() {
s.createComposeProject("base")