Skip to content

Commit 10fcb51

Browse files
authored
Merge pull request #11 from danehans/rr_lb
Adds nginx.ingress.kubernetes.io/load-balance Support
2 parents 7f5cc28 + 54276d4 commit 10fcb51

9 files changed

Lines changed: 458 additions & 1 deletion

File tree

pkg/i2gw/implementations/kgateway/emitter.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@ func (e *Emitter) Emit(ir *intermediate.IR) ([]client.Object, error) {
135135
backendCfg,
136136
)
137137

138+
// Apply explicit round_robin load balancing via BackendConfigPolicy.
139+
// Note: This must come AFTER applySessionAffinityPolicy so ring-hash
140+
// (session affinity) always takes precedence for a given Service.
141+
applyLoadBalancingPolicy(
142+
pol,
143+
httpRouteKey,
144+
httpRouteContext,
145+
backendCfg,
146+
)
147+
138148
// Apply enable-access-log via HTTPListenerPolicy.
139149
applyAccessLogPolicy(
140150
pol,

pkg/i2gw/implementations/kgateway/emitter_integration_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,15 @@ func TestKgatewayIngressNginxIntegration_Golden(t *testing.T) {
206206
"pkg", "i2gw", "implementations", "kgateway", "testing", "testdata", "output", "cors.yaml",
207207
),
208208
},
209+
{
210+
name: "load_balance",
211+
inputRel: filepath.Join(
212+
"pkg", "i2gw", "implementations", "kgateway", "testing", "testdata", "input", "load_balance.yaml",
213+
),
214+
goldenRel: filepath.Join(
215+
"pkg", "i2gw", "implementations", "kgateway", "testing", "testdata", "output", "load_balance.yaml",
216+
),
217+
},
209218
}
210219

211220
for _, tt := range tests {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package kgateway
18+
19+
import (
20+
"github.com/kgateway-dev/ingress2gateway/pkg/i2gw/intermediate"
21+
"github.com/kgateway-dev/kgateway/v2/api/v1alpha1/kgateway"
22+
"github.com/kgateway-dev/kgateway/v2/api/v1alpha1/shared"
23+
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/apimachinery/pkg/types"
26+
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
27+
)
28+
29+
// applyLoadBalancingPolicy projects the LoadBalancing IR policy into one or more
30+
// Kgateway BackendConfigPolicies.
31+
//
32+
// Semantics:
33+
// - We create at most one BackendConfigPolicy per Service.
34+
// - If SessionAffinity is configured for a Service, it takes precedence and
35+
// this function will not override the ring-hash configuration.
36+
// - If there is no SessionAffinity for a Service and LoadBalancing.Strategy is
37+
// "round_robin", we configure LoadBalancer.RoundRobin on the BackendConfigPolicy.
38+
// - TargetRefs are populated with all core Service backends that this Policy covers
39+
// (based on RuleBackendSources).
40+
func applyLoadBalancingPolicy(
41+
pol intermediate.Policy,
42+
httpRouteKey types.NamespacedName,
43+
httpRouteCtx intermediate.HTTPRouteContext,
44+
backendCfg map[types.NamespacedName]*kgateway.BackendConfigPolicy,
45+
) bool {
46+
// Only care about explicit round_robin strategies.
47+
if pol.LoadBalancing == nil || pol.LoadBalancing.Strategy != intermediate.LoadBalancingStrategyRoundRobin {
48+
return false
49+
}
50+
51+
touched := false
52+
53+
for _, idx := range pol.RuleBackendSources {
54+
if idx.Rule >= len(httpRouteCtx.Spec.Rules) {
55+
continue
56+
}
57+
rule := httpRouteCtx.Spec.Rules[idx.Rule]
58+
if idx.Backend >= len(rule.BackendRefs) {
59+
continue
60+
}
61+
62+
br := rule.BackendRefs[idx.Backend]
63+
64+
// Only core Service backends.
65+
if br.BackendRef.Group != nil && *br.BackendRef.Group != "" {
66+
continue
67+
}
68+
if br.BackendRef.Kind != nil && *br.BackendRef.Kind != "Service" {
69+
continue
70+
}
71+
72+
svcName := string(br.BackendRef.Name)
73+
if svcName == "" {
74+
continue
75+
}
76+
77+
svcKey := types.NamespacedName{
78+
Namespace: httpRouteKey.Namespace,
79+
Name: svcName,
80+
}
81+
82+
// Create or reuse BackendConfigPolicy per Service.
83+
bcp, exists := backendCfg[svcKey]
84+
if !exists {
85+
policyName := svcName + "-backend-config"
86+
bcp = &kgateway.BackendConfigPolicy{
87+
ObjectMeta: metav1.ObjectMeta{
88+
Name: policyName,
89+
Namespace: httpRouteKey.Namespace,
90+
},
91+
Spec: kgateway.BackendConfigPolicySpec{
92+
TargetRefs: []shared.LocalPolicyTargetReference{
93+
{
94+
Group: "",
95+
Kind: "Service",
96+
Name: gwv1.ObjectName(svcName),
97+
},
98+
},
99+
},
100+
}
101+
bcp.SetGroupVersionKind(BackendConfigPolicyGVK)
102+
backendCfg[svcKey] = bcp
103+
}
104+
105+
// Respect session affinity precedence:
106+
// If RingHash is already set (via applySessionAffinityPolicy), do not override.
107+
if bcp.Spec.LoadBalancer != nil && bcp.Spec.LoadBalancer.RingHash != nil {
108+
// TODO [danehans] add a notification that we are skipping due to session affinity.
109+
continue
110+
}
111+
112+
if bcp.Spec.LoadBalancer == nil {
113+
bcp.Spec.LoadBalancer = &kgateway.LoadBalancer{}
114+
}
115+
116+
// Set explicit round_robin.
117+
bcp.Spec.LoadBalancer.RoundRobin = &kgateway.LoadBalancerRoundRobinConfig{}
118+
119+
touched = true
120+
}
121+
122+
return touched
123+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
apiVersion: networking.k8s.io/v1
2+
kind: Ingress
3+
metadata:
4+
annotations:
5+
ingress2gateway.kubernetes.io/implementation: kgateway
6+
nginx.ingress.kubernetes.io/load-balance: "round_robin"
7+
name: ingress-myservicea1
8+
namespace: default
9+
spec:
10+
ingressClassName: nginx
11+
rules:
12+
- host: myservicea.foo.org
13+
http:
14+
paths:
15+
- backend:
16+
service:
17+
name: myservicea
18+
port:
19+
number: 80
20+
path: /
21+
pathType: Prefix
22+
---
23+
apiVersion: networking.k8s.io/v1
24+
kind: Ingress
25+
metadata:
26+
annotations:
27+
ingress2gateway.kubernetes.io/implementation: kgateway
28+
name: ingress-myservicea2
29+
namespace: default
30+
spec:
31+
ingressClassName: nginx
32+
rules:
33+
- host: myservicea.foo.org
34+
http:
35+
paths:
36+
- backend:
37+
service:
38+
name: myservicea
39+
port:
40+
number: 80
41+
path: /2
42+
pathType: Prefix
43+
---
44+
apiVersion: networking.k8s.io/v1
45+
kind: Ingress
46+
metadata:
47+
annotations:
48+
ingress2gateway.kubernetes.io/implementation: kgateway
49+
nginx.ingress.kubernetes.io/load-balance: "round_robin"
50+
name: ingress-myserviceb
51+
namespace: default
52+
spec:
53+
ingressClassName: nginx
54+
rules:
55+
- host: myserviceb.foo.org
56+
http:
57+
paths:
58+
- backend:
59+
service:
60+
name: myserviceb
61+
port:
62+
number: 80
63+
path: /
64+
pathType: Prefix
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
apiVersion: gateway.networking.k8s.io/v1
2+
kind: Gateway
3+
metadata:
4+
annotations:
5+
gateway.networking.k8s.io/generator: ingress2gateway-dev
6+
name: nginx
7+
namespace: default
8+
spec:
9+
gatewayClassName: kgateway
10+
listeners:
11+
- hostname: myservicea.foo.org
12+
name: myservicea-foo-org-http
13+
port: 80
14+
protocol: HTTP
15+
- hostname: myserviceb.foo.org
16+
name: myserviceb-foo-org-http
17+
port: 80
18+
protocol: HTTP
19+
status: {}
20+
---
21+
apiVersion: gateway.networking.k8s.io/v1
22+
kind: HTTPRoute
23+
metadata:
24+
annotations:
25+
gateway.networking.k8s.io/generator: ingress2gateway-dev
26+
name: ingress-myservicea1-myservicea-foo-org
27+
namespace: default
28+
spec:
29+
hostnames:
30+
- myservicea.foo.org
31+
parentRefs:
32+
- name: nginx
33+
rules:
34+
- backendRefs:
35+
- name: myservicea
36+
port: 80
37+
matches:
38+
- path:
39+
type: PathPrefix
40+
value: /
41+
- backendRefs:
42+
- name: myservicea
43+
port: 80
44+
matches:
45+
- path:
46+
type: PathPrefix
47+
value: /2
48+
status:
49+
parents: []
50+
---
51+
apiVersion: gateway.networking.k8s.io/v1
52+
kind: HTTPRoute
53+
metadata:
54+
annotations:
55+
gateway.networking.k8s.io/generator: ingress2gateway-dev
56+
name: ingress-myserviceb-myserviceb-foo-org
57+
namespace: default
58+
spec:
59+
hostnames:
60+
- myserviceb.foo.org
61+
parentRefs:
62+
- name: nginx
63+
rules:
64+
- backendRefs:
65+
- name: myserviceb
66+
port: 80
67+
matches:
68+
- path:
69+
type: PathPrefix
70+
value: /
71+
status:
72+
parents: []
73+
---
74+
apiVersion: gateway.kgateway.dev/v1alpha1
75+
kind: BackendConfigPolicy
76+
metadata:
77+
name: myservicea-backend-config
78+
namespace: default
79+
spec:
80+
targetRefs:
81+
- group: ""
82+
kind: Service
83+
name: myservicea
84+
loadBalancer:
85+
roundRobin: {}
86+
status:
87+
ancestors: null
88+
---
89+
apiVersion: gateway.kgateway.dev/v1alpha1
90+
kind: BackendConfigPolicy
91+
metadata:
92+
name: myserviceb-backend-config
93+
namespace: default
94+
spec:
95+
targetRefs:
96+
- group: ""
97+
kind: Service
98+
name: myserviceb
99+
loadBalancer:
100+
roundRobin: {}
101+
status:
102+
ancestors: null

pkg/i2gw/intermediate/provider_ingressnginx.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ type Policy struct {
137137
// SessionAffinity defines the session affinity policy.
138138
SessionAffinity *SessionAffinityPolicy
139139

140+
// LoadBalancing controls the upstream load-balancing algorithm. Only round_robin is supported;
141+
// other values are ignored.
142+
LoadBalancing *BackendLoadBalancingPolicy
143+
140144
// RuleBackendSources lists the (rule, backend) pairs within a merged HTTPRoute
141145
// that this policy applies to.
142146
//
@@ -171,6 +175,16 @@ type RateLimitPolicy struct {
171175
BurstMultiplier int32
172176
}
173177

178+
// LoadBalancingStrategy represents the upstream load-balancing mode requested by the Ingress NGINX annotations.
179+
// Currently only round_robin is supported; other values are ignored.
180+
type LoadBalancingStrategy string
181+
182+
const LoadBalancingStrategyRoundRobin LoadBalancingStrategy = "round_robin"
183+
184+
type BackendLoadBalancingPolicy struct {
185+
Strategy LoadBalancingStrategy
186+
}
187+
174188
// AddRuleBackendSources returns a copy of p with idxs added to
175189
// RuleBackendSources, ensuring each (Rule, Backend) pair is unique.
176190
func (p Policy) AddRuleBackendSources(idxs []PolicyIndex) Policy {

pkg/i2gw/providers/ingressnginx/converter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func newResourcesToIRConverter() *resourcesToIRConverter {
4848
extAuthFeature,
4949
basicAuthFeature,
5050
sessionAffinityFeature,
51+
loadBalancingFeature,
5152
},
5253
}
5354
}

pkg/i2gw/providers/ingressnginx/cors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
)
2929

3030
const (
31-
corsEnabledAnnotation = "nginx.ingress.kubernetes.io/enable-cors"
31+
corsEnabledAnnotation = "nginx.ingress.kubernetes.io/enable-cors"
3232
corsAllowOriginAnnotation = "nginx.ingress.kubernetes.io/cors-allow-origin"
3333
corsAllowCredentialsAnnotation = "nginx.ingress.kubernetes.io/cors-allow-credentials"
3434
corsAllowHeadersAnnotation = "nginx.ingress.kubernetes.io/cors-allow-headers"

0 commit comments

Comments
 (0)