Skip to content
Open
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
26 changes: 26 additions & 0 deletions apis/v1alpha1/upstreamsettingspolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ type UpstreamSettingsPolicySpec struct {
// +optional
KeepAlive *UpstreamKeepAlive `json:"keepAlive,omitempty"`

// LoadBalancingMethod specifies the load balancing algorithm to be used for the upstream.
// If not specified, NGINX Gateway Fabric defaults to `random two least_conn`,
// which differs from the standard NGINX default `round-robin`.
//
// +optional
LoadBalancingMethod *LoadBalancingType `json:"loadBalancingMethod,omitempty"`

// TargetRefs identifies API object(s) to apply the policy to.
// Objects must be in the same namespace as the policy.
// Support: Service
Expand Down Expand Up @@ -98,3 +105,22 @@ type UpstreamKeepAlive struct {
// +optional
Timeout *Duration `json:"timeout,omitempty"`
}

// LoadBalancingType defines the supported load balancing methods.
//
// +kubebuilder:validation:Enum=ip_hash;random two least_conn
type LoadBalancingType string

const (
// LoadBalancingTypeIPHash enables IP hash-based load balancing,
// ensuring requests from the same client IP are routed to the same upstream server.
// NGINX directive: https://nginx.org/en/docs/http/ngx_http_upstream_module.html#ip_hash
LoadBalancingTypeIPHash LoadBalancingType = "ip_hash"

// LoadBalancingTypeRandomTwoLeastConnection enables a variation of least-connections
// balancing that randomly selects two servers and forwards traffic to the one with
// fewer active connections.
// NGINX directive least_conn: https://nginx.org/en/docs/http/ngx_http_upstream_module.html#least_conn
// NGINX directive random: https://nginx.org/en/docs/http/ngx_http_upstream_module.html#random
LoadBalancingTypeRandomTwoLeastConnection LoadBalancingType = "random two least_conn"
)
5 changes: 5 additions & 0 deletions apis/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ spec:
pattern: ^[0-9]{1,4}(ms|s|m|h)?$
type: string
type: object
loadBalancingMethod:
description: |-
LoadBalancingMethod specifies the load balancing algorithm to be used for the upstream.
If not specified, NGINX Gateway Fabric defaults to `random two least_conn`,
which differs from the standard NGINX default `round-robin`.
enum:
- ip_hash
- random two least_conn
type: string
targetRefs:
description: |-
TargetRefs identifies API object(s) to apply the policy to.
Expand Down
9 changes: 9 additions & 0 deletions deploy/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9612,6 +9612,15 @@ spec:
pattern: ^[0-9]{1,4}(ms|s|m|h)?$
type: string
type: object
loadBalancingMethod:
description: |-
LoadBalancingMethod specifies the load balancing algorithm to be used for the upstream.
If not specified, NGINX Gateway Fabric defaults to `random two least_conn`,
which differs from the standard NGINX default `round-robin`.
enum:
- ip_hash
- random two least_conn
type: string
targetRefs:
description: |-
TargetRefs identifies API object(s) to apply the policy to.
Expand Down
20 changes: 15 additions & 5 deletions internal/controller/nginx/config/http/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,21 @@ const (

// Upstream holds all configuration for an HTTP upstream.
type Upstream struct {
Name string
ZoneSize string // format: 512k, 1m
StateFile string
KeepAlive UpstreamKeepAlive
Servers []UpstreamServer
SessionPersistence UpstreamSessionPersistence
Name string
ZoneSize string // format: 512k, 1m
StateFile string
LoadBalancingMethod string
KeepAlive UpstreamKeepAlive
Servers []UpstreamServer
}

// UpstreamSessionPersistence holds the session persistence configuration for an upstream.
type UpstreamSessionPersistence struct {
Name string
Expiry string
Path string
SessionType string
}

// UpstreamKeepAlive holds the keepalive configuration for an HTTP upstream.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type Processor struct{}
type UpstreamSettings struct {
// ZoneSize is the zone size setting.
ZoneSize string
// LoadBalancingMethod is the load balancing method setting.
LoadBalancingMethod string
// KeepAlive contains the keepalive settings.
KeepAlive http.UpstreamKeepAlive
}
Expand Down Expand Up @@ -61,6 +63,10 @@ func processPolicies(pols []policies.Policy) UpstreamSettings {
upstreamSettings.KeepAlive.Timeout = string(*usp.Spec.KeepAlive.Timeout)
}
}

if usp.Spec.LoadBalancingMethod != nil {
upstreamSettings.LoadBalancingMethod = string(*usp.Spec.LoadBalancingMethod)
}
}

return upstreamSettings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func TestProcess(t *testing.T) {
Time: helpers.GetPointer[ngfAPIv1alpha1.Duration]("5s"),
Timeout: helpers.GetPointer[ngfAPIv1alpha1.Duration]("10s"),
}),
LoadBalancingMethod: helpers.GetPointer(ngfAPIv1alpha1.LoadBalancingTypeIPHash),
},
},
},
Expand All @@ -48,6 +49,24 @@ func TestProcess(t *testing.T) {
Time: "5s",
Timeout: "10s",
},
LoadBalancingMethod: string(ngfAPIv1alpha1.LoadBalancingTypeIPHash),
},
},
{
name: "load balancing method set",
policies: []policies.Policy{
&ngfAPIv1alpha1.UpstreamSettingsPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "usp",
Namespace: "test",
},
Spec: ngfAPIv1alpha1.UpstreamSettingsPolicySpec{
LoadBalancingMethod: helpers.GetPointer(ngfAPIv1alpha1.LoadBalancingTypeRandomTwoLeastConnection),
},
},
},
expUpstreamSettings: UpstreamSettings{
LoadBalancingMethod: string(ngfAPIv1alpha1.LoadBalancingTypeRandomTwoLeastConnection),
},
},
{
Expand Down Expand Up @@ -220,6 +239,15 @@ func TestProcess(t *testing.T) {
}),
},
},
&ngfAPIv1alpha1.UpstreamSettingsPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "usp-loadBalancingMethod",
Namespace: "test",
},
Spec: ngfAPIv1alpha1.UpstreamSettingsPolicySpec{
LoadBalancingMethod: helpers.GetPointer(ngfAPIv1alpha1.LoadBalancingTypeIPHash),
},
},
},
expUpstreamSettings: UpstreamSettings{
ZoneSize: "2m",
Expand All @@ -229,6 +257,7 @@ func TestProcess(t *testing.T) {
Time: "5s",
Timeout: "10s",
},
LoadBalancingMethod: string(ngfAPIv1alpha1.LoadBalancingTypeIPHash),
},
},
{
Expand Down Expand Up @@ -310,6 +339,15 @@ func TestProcess(t *testing.T) {
},
},
},
&ngfAPIv1alpha1.UpstreamSettingsPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "usp-lb-method",
Namespace: "test",
},
Spec: ngfAPIv1alpha1.UpstreamSettingsPolicySpec{
LoadBalancingMethod: helpers.GetPointer(ngfAPIv1alpha1.LoadBalancingTypeIPHash),
},
},
},
expUpstreamSettings: UpstreamSettings{
ZoneSize: "2m",
Expand All @@ -319,6 +357,7 @@ func TestProcess(t *testing.T) {
Time: "5s",
Timeout: "10s",
},
LoadBalancingMethod: string(ngfAPIv1alpha1.LoadBalancingTypeIPHash),
},
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ func conflicts(a, b ngfAPI.UpstreamSettingsPolicySpec) bool {
}
}

if a.LoadBalancingMethod != nil && b.LoadBalancingMethod != nil {
return true
}

return false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func createValidPolicy() *ngfAPI.UpstreamSettingsPolicy {
Timeout: helpers.GetPointer[ngfAPI.Duration]("30s"),
Connections: helpers.GetPointer[int32](100),
},
LoadBalancingMethod: helpers.GetPointer(ngfAPI.LoadBalancingTypeRandomTwoLeastConnection),
},
Status: v1.PolicyStatus{},
}
Expand Down Expand Up @@ -176,6 +177,7 @@ func TestValidator_Conflicts(t *testing.T) {
Requests: helpers.GetPointer[int32](900),
Time: helpers.GetPointer[ngfAPI.Duration]("50s"),
},
LoadBalancingMethod: helpers.GetPointer(ngfAPI.LoadBalancingTypeRandomTwoLeastConnection),
},
},
polB: &ngfAPI.UpstreamSettingsPolicy{
Expand Down Expand Up @@ -246,6 +248,16 @@ func TestValidator_Conflicts(t *testing.T) {
},
conflicts: true,
},
{
name: "load balancing method conflicts",
polA: createValidPolicy(),
polB: &ngfAPI.UpstreamSettingsPolicy{
Spec: ngfAPI.UpstreamSettingsPolicySpec{
LoadBalancingMethod: helpers.GetPointer(ngfAPI.LoadBalancingTypeIPHash),
},
},
conflicts: true,
},
}

v := upstreamsettings.NewValidator(nil)
Expand Down
36 changes: 31 additions & 5 deletions internal/controller/nginx/config/upstreams.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const (
plusZoneSizeStream = "1m"
// stateDir is the directory for storing state files.
stateDir = "/var/lib/nginx/state"
// default load balancing method.
defaultLBMethod = "random two least_conn"
)

// keepAliveChecker takes an upstream name and returns if it has keep alive settings enabled.
Expand Down Expand Up @@ -144,6 +146,7 @@ func (g GeneratorImpl) createUpstream(
processor upstreamsettings.Processor,
) http.Upstream {
var stateFile string
var sp http.UpstreamSessionPersistence
upstreamPolicySettings := processor.Process(up.Policies)

zoneSize := ossZoneSize
Expand All @@ -154,6 +157,8 @@ func (g GeneratorImpl) createUpstream(
if !upstreamHasResolveServers(up) {
stateFile = fmt.Sprintf("%s/%s.conf", stateDir, up.Name)
}

sp = getSessionPersistenceConfiguration(up.SessionPersistence)
}

if upstreamPolicySettings.ZoneSize != "" {
Expand Down Expand Up @@ -185,12 +190,19 @@ func (g GeneratorImpl) createUpstream(
}
}

chosenLBMethod := defaultLBMethod
if upstreamPolicySettings.LoadBalancingMethod != "" {
chosenLBMethod = upstreamPolicySettings.LoadBalancingMethod
}

return http.Upstream{
Name: up.Name,
ZoneSize: zoneSize,
StateFile: stateFile,
Servers: upstreamServers,
KeepAlive: upstreamPolicySettings.KeepAlive,
Name: up.Name,
ZoneSize: zoneSize,
StateFile: stateFile,
Servers: upstreamServers,
KeepAlive: upstreamPolicySettings.KeepAlive,
LoadBalancingMethod: chosenLBMethod,
SessionPersistence: sp,
}
}

Expand All @@ -215,3 +227,17 @@ func upstreamHasResolveServers(upstream dataplane.Upstream) bool {
}
return false
}

// getSessionPersistenceConfiguration gets the session persistence configuration for an upstream.
// Supported only for NGINX Plus and cookie-based type.
func getSessionPersistenceConfiguration(sp dataplane.SessionPersistenceConfig) http.UpstreamSessionPersistence {
if sp.Name == "" {
return http.UpstreamSessionPersistence{}
}
return http.UpstreamSessionPersistence{
Name: sp.Name,
Expiry: sp.Expiry,
Path: sp.Path,
SessionType: string(sp.SessionType),
}
}
10 changes: 9 additions & 1 deletion internal/controller/nginx/config/upstreams_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@ package config
const upstreamsTemplateText = `
{{ range $u := . }}
upstream {{ $u.Name }} {
random two least_conn;
{{ if $u.LoadBalancingMethod -}}
{{ $u.LoadBalancingMethod }};
{{- end }}
{{ if $u.ZoneSize -}}
zone {{ $u.Name }} {{ $u.ZoneSize }};
{{ end -}}

{{ if $u.SessionPersistence.Name -}}
sticky {{ $u.SessionPersistence.SessionType }} {{ $u.SessionPersistence.Name }}
{{- if $u.SessionPersistence.Expiry }} expires={{ $u.SessionPersistence.Expiry }}{{- end }}
{{- if $u.SessionPersistence.Path }} path={{ $u.SessionPersistence.Path }}{{- end }};
{{ end -}}

{{- if $u.StateFile }}
state {{ $u.StateFile }};
{{- else }}
Expand Down
Loading
Loading