Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --sort-endpoints-by command-line option #678

Merged
merged 1 commit into from
Oct 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion docs/content/en/docs/configuration/command-line.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The following command-line options are supported:
| [`--rate-limit-update`](#rate-limit-update) | uploads per second (float) | `0.5` | |
| [`--reload-strategy`](#reload-strategy) | [native\|reusesocket] | `reusesocket` | |
| [`--sort-backends`](#sort-backends) | [true\|false] | `false` | |
| [`--sort-endpoints-by`](#sort-endpoints-by) | [endpoint\|ip\|name\|random] | `endpoint` | v0.11 |
| [`--stats-collect-processing-period`](#stats) | time | `500ms` | v0.10 |
| [`--tcp-services-configmap`](#tcp-services-configmap) | namespace/configmapname | no tcp svc | |
| [`--verify-hostname`](#verify-hostname) | [true\|false] | `true` | |
Expand Down Expand Up @@ -235,7 +236,8 @@ describes how it works.
## --sort-backends

Defines if backend's endpoints should be sorted by name. Since v0.8 the endpoints will stay in the
same order found in the Kubernetes' endpoint objects if `--sort-backends` is missing.
same order found in the Kubernetes' endpoint objects if `--sort-backends` is missing. This option
has less precedence than `--sort-endpoints-by` if both are declared.

In v0.7 and older version, if `--sort-backends` is missing, HAProxy Ingress randomly shuffle endpoints
on each reload in order to avoid requesting always the same backends just after haproxy reloads.
Expand All @@ -246,6 +248,20 @@ option, because the default value builds the server name using a numeric sequenc
See also:

* [backend-server-naming]({{% relref "keys#backend-server-naming" %}}) configuration key
* [sort-endpoints-by]({{% relref "#sort-endpoints-by" %}}) command-line option

---

## --sort-endpoints-by

Since v0.11

Defines in which order the endpoints of a backend should be sorted.

* `endpoint`: this is the default value, uses the same order declared in the Kubernetes' Endpoint objects. `ep` is an alias to `endpoint`
* `ip`: sort endpoints by the IP and port of the destination server
* `name`: sort the endpoints by the name given to the server, see also [backend-server-naming]({{% relref "keys#backend-server-naming" %}})
* `random`: randomly shuffle the endpoints every time haproxy needs to be reloaded, this option avoids to always send requests to the same endpoints depending on the balancing algorithm

---

Expand Down
2 changes: 1 addition & 1 deletion pkg/common/ingress/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ type Configuration struct {
UpdateStatusOnShutdown bool

BackendShards int
SortBackends bool
SortEndpointsBy string
IgnoreIngressWithoutClass bool
}

Expand Down
21 changes: 19 additions & 2 deletions pkg/common/ingress/controller/launch.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,12 @@ func NewIngressController(backend ingress.Controller) *GenericController {
`Defines how much files should be used to configure the haproxy backends`)

sortBackends = flags.Bool("sort-backends", false,
`Defines if backend's endpoints should be sorted by name. It uses the same k8s endpoint order if missing`)
`Defines if backend's endpoints should be sorted by name. This option has less precedence than
--sort-endpoints-by if both are declared.`)

sortEndpointsBy = flags.String("sort-endpoints-by", "",
`Defines how to sort backend's endpoints. Allowed values are: 'endpoint' - same k8s endpoint order (default);
'name' - server/endpoint name; 'ip' - server/endpoint IP and port; 'random' - shuffle endpoints on every haproxy reload`)

useNodeInternalIP = flags.Bool("report-node-internal-ip-address", false,
`Defines if the nodes IP address to be returned in the ingress status should be the internal instead of the external IP address`)
Expand Down Expand Up @@ -296,6 +301,18 @@ func NewIngressController(backend ingress.Controller) *GenericController {
glog.Fatal("Cannot use --allow-cross-namespace if --force-namespace-isolation is true")
}

sortEndpoints := strings.ToLower(*sortEndpointsBy)
if sortEndpoints == "" {
if *sortBackends {
sortEndpoints = "name"
} else {
sortEndpoints = "endpoint"
}
}
if !stringInSlice(sortEndpoints, []string{"ep", "endpoint", "ip", "name", "random"}) {
glog.Fatalf("Unsupported --sort-endpoint-by option: %s", sortEndpoints)
}

config := &Configuration{
UpdateStatus: *updateStatus,
ElectionID: *electionID,
Expand Down Expand Up @@ -332,7 +349,7 @@ func NewIngressController(backend ingress.Controller) *GenericController {
DisablePodList: *disablePodList,
UpdateStatusOnShutdown: *updateStatusOnShutdown,
BackendShards: *backendShards,
SortBackends: *sortBackends,
SortEndpointsBy: sortEndpoints,
UseNodeInternalIP: *useNodeInternalIP,
IgnoreIngressWithoutClass: *ignoreIngressWithoutClass,
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (hc *HAProxyController) configController() {
Metrics: hc.metrics,
ReloadStrategy: *hc.reloadStrategy,
MaxOldConfigFiles: *hc.maxOldConfigFiles,
SortBackends: hc.cfg.SortBackends,
SortEndpointsBy: hc.cfg.SortEndpointsBy,
ValidateConfig: *hc.validateConfig,
}
hc.instance = haproxy.CreateInstance(hc.logger, instanceOptions)
Expand Down
10 changes: 7 additions & 3 deletions pkg/haproxy/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type InstanceOptions struct {
MaxOldConfigFiles int
Metrics types.Metrics
ReloadStrategy string
SortBackends bool
SortEndpointsBy string
ValidateConfig bool
// TODO Fake is used to skip real haproxy calls. Use a mock instead.
fake bool
Expand Down Expand Up @@ -258,8 +258,12 @@ func (i *instance) haproxyUpdate(timer *utils.Timer) {
}
updater := i.newDynUpdater()
updated := updater.update()
if i.options.SortBackends {
i.config.Backends().SortChangedEndpoints()
if i.options.SortEndpointsBy != "random" {
i.config.Backends().SortChangedEndpoints(i.options.SortEndpointsBy)
} else if !updated {
// Only shuffle if need to reload
i.config.Backends().ShuffleAllEndpoints()
timer.Tick("shuffle_endpoints")
}
if !updated || updater.cmdCnt > 0 {
// only need to rewrtite config files if:
Expand Down
25 changes: 22 additions & 3 deletions pkg/haproxy/types/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ package types

import (
"fmt"
"math/rand"
"reflect"
"sort"
"strings"
"time"
)

// BackendID ...
Expand Down Expand Up @@ -92,9 +94,26 @@ func (b *Backend) addEndpoint(ip string, port int, targetRef string) *Endpoint {
return endpoint
}

func (b *Backend) sortEndpoints() {
sort.SliceStable(b.Endpoints, func(i, j int) bool {
return b.Endpoints[i].Name < b.Endpoints[j].Name
func (b *Backend) sortEndpoints(sortBy string) {
ep := b.Endpoints
switch sortBy {
// ignoring "ep"/"endpoint" (use the k8s order) and "random" (shuffleEndpoints implements)
case "name":
sort.Slice(ep, func(i, j int) bool {
return ep[i].Name < ep[j].Name
})
case "ip":
sort.Slice(ep, func(i, j int) bool {
return ep[i].IP < ep[j].IP
})
}
}

func (b *Backend) shuffleEndpoints() {
ep := b.Endpoints
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(ep), func(i, j int) {
ep[i], ep[j] = ep[j], ep[i]
})
}

Expand Down
11 changes: 9 additions & 2 deletions pkg/haproxy/types/backends.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,16 @@ func (b *Backends) ChangedShards() []int {
}

// SortChangedEndpoints ...
func (b *Backends) SortChangedEndpoints() {
func (b *Backends) SortChangedEndpoints(sortBy string) {
for _, backend := range b.itemsAdd {
backend.sortEndpoints()
backend.sortEndpoints(sortBy)
}
}

// ShuffleAllEndpoints ...
func (b *Backends) ShuffleAllEndpoints() {
for _, backend := range b.items {
backend.shuffleEndpoints()
}
}

Expand Down