Skip to content

Commit e45a5c5

Browse files
committed
Add IPv6 support for Kourier
Kourier failed on IPv6-only clusters: the EndpointSlice watcher filtered out IPv6 addresses so the controller couldn't discover gateway pods, the bootstrap stats listener bound to IPv4 only, and the extauthz URI construction didn't bracket IPv6 addresses. Changes: - Accept IPv6 EndpointSlices in ReadyAddressesFromSlice - Bind bootstrap stats listener to :: with ipv4_compat for dual-stack - Use net.JoinHostPort in extauthz URI for correct IPv6 bracketing Signed-off-by: Vincent Link <vlink@redhat.com>
1 parent f3e4e0c commit e45a5c5

8 files changed

Lines changed: 47 additions & 12 deletions

File tree

config/200-bootstrap.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ data:
4545
- name: stats_listener
4646
address:
4747
socket_address:
48-
address: 0.0.0.0
48+
address: "::"
4949
port_value: 9000
50+
ipv4_compat: true
5051
filter_chains:
5152
- filters:
5253
- name: envoy.filters.network.http_connection_manager

pkg/endpointslice/endpointslice.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ import (
2121
"k8s.io/apimachinery/pkg/util/sets"
2222
)
2323

24-
// ReadyAddressesFromSlice extracts ready IPv4 addresses from an EndpointSlice.
25-
// Returns nil if the slice is non-IPv4 or has no ready endpoints.
24+
// ReadyAddressesFromSlice extracts ready IP addresses from an EndpointSlice.
25+
// Returns nil if the slice uses FQDN addressing or has no ready endpoints.
2626
//
2727
// Future consideration: A more sophisticated implementation could check Serving and
2828
// Terminating conditions to support graceful draining during rolling updates, similar
2929
// to Istio, Traefik, and MetalLB implementations.
3030
func ReadyAddressesFromSlice(slice *discoveryv1.EndpointSlice) sets.Set[string] {
31-
if slice.AddressType != discoveryv1.AddressTypeIPv4 {
31+
if slice.AddressType != discoveryv1.AddressTypeIPv4 && slice.AddressType != discoveryv1.AddressTypeIPv6 {
3232
return nil
3333
}
3434

pkg/endpointslice/endpointslice_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func TestReadyAddressesFromSlice(t *testing.T) {
144144
want: nil,
145145
},
146146
{
147-
name: "IPv6 slice returns nil",
147+
name: "IPv6 with ready endpoints",
148148
slice: &discoveryv1.EndpointSlice{
149149
ObjectMeta: metav1.ObjectMeta{
150150
Name: "test-slice",
@@ -159,7 +159,7 @@ func TestReadyAddressesFromSlice(t *testing.T) {
159159
},
160160
},
161161
},
162-
want: nil,
162+
want: []string{"2001:db8::1"},
163163
},
164164
{
165165
name: "FQDN slice returns nil",

pkg/generator/endpointslice.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ import (
2828

2929
// lbEndpointsForKubeEndpointSlices converts Kubernetes EndpointSlice resources
3030
// to Envoy LbEndpoints. It aggregates endpoints from multiple slices and filters
31-
// for IPv4 addressing mode and ready endpoints only.
31+
// for IP addressing mode and ready endpoints only.
3232
func lbEndpointsForKubeEndpointSlices(slices []*discoveryv1.EndpointSlice, targetPort int32) []*endpoint.LbEndpoint {
33-
// Aggregate all ready IPv4 addresses from all slices
33+
// Aggregate all ready addresses from all slices
3434
allAddresses := sets.New[string]()
3535
for _, slice := range slices {
3636
addresses := endpointslice.ReadyAddressesFromSlice(slice)

pkg/generator/endpointslice_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ func TestLbEndpointsForKubeEndpointSlices(t *testing.T) {
157157
want: 1,
158158
},
159159
{
160-
name: "filter IPv6 and FQDN addressing",
160+
name: "include IPv4 and IPv6, filter FQDN addressing",
161161
slices: []*discoveryv1.EndpointSlice{
162162
{
163163
ObjectMeta: metav1.ObjectMeta{
@@ -207,7 +207,7 @@ func TestLbEndpointsForKubeEndpointSlices(t *testing.T) {
207207
},
208208
},
209209
targetPort: 8080,
210-
want: 1, // only IPv4
210+
want: 2, // both IPv4 and IPv6
211211
},
212212
{
213213
name: "empty slices",

pkg/reconciler/ingress/config/ext_authz.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package config
1919
import (
2020
"errors"
2121
"fmt"
22+
"net"
2223
"time"
2324

2425
v3Cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
@@ -191,7 +192,7 @@ func externalAuthzFilter(conf *ExternalAuthzConfig) *hcm.HttpFilter {
191192
extAuthConfig.Services = &extAuthService.ExtAuthz_HttpService{
192193
HttpService: &extAuthService.HttpService{
193194
ServerUri: &core.HttpUri{
194-
Uri: fmt.Sprintf("%s://%s:%d", conf.Protocol, conf.Host, conf.Port),
195+
Uri: fmt.Sprintf("%s://%s", conf.Protocol, net.JoinHostPort(conf.Host, fmt.Sprintf("%d", conf.Port))),
195196
HttpUpstreamType: &core.HttpUri_Cluster{
196197
Cluster: extAuthzClusterName,
197198
},

pkg/reconciler/ingress/config/ext_authz_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,39 @@ func Test_externalAuthZFilter_extAuthz(t *testing.T) {
303303
},
304304
},
305305
},
306+
}, {
307+
name: "http with IPv6 host",
308+
conf: &ExternalAuthzConfig{
309+
Host: "2001:db8::1",
310+
Port: 8080,
311+
MaxRequestBytes: 8192,
312+
Timeout: 2000,
313+
Protocol: "http",
314+
},
315+
extAuthzWanted: &extAuthService.ExtAuthz{
316+
TransportApiVersion: core.ApiVersion_V3,
317+
WithRequestBody: &extAuthService.BufferSettings{
318+
MaxRequestBytes: 8192,
319+
AllowPartialMessage: true,
320+
},
321+
Services: &extAuthService.ExtAuthz_HttpService{
322+
HttpService: &extAuthService.HttpService{
323+
ServerUri: &core.HttpUri{
324+
Uri: "http://[2001:db8::1]:8080",
325+
HttpUpstreamType: &core.HttpUri_Cluster{
326+
Cluster: extAuthzClusterName,
327+
},
328+
Timeout: durationpb.New(time.Duration(2000) * time.Millisecond),
329+
},
330+
AuthorizationRequest: &extAuthService.AuthorizationRequest{
331+
HeadersToAdd: []*core.HeaderValue{{
332+
Key: "client",
333+
Value: "kourier",
334+
}},
335+
},
336+
},
337+
},
338+
},
306339
}, {
307340
name: "https with path prefix",
308341
conf: &ExternalAuthzConfig{

pkg/reconciler/ingress/lister.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func (l *gatewayPodTargetLister) ListProbeTargets(_ context.Context, ing *v1alph
5555
return nil, fmt.Errorf("failed to list endpointslices for internal service: %w", err)
5656
}
5757

58-
// Aggregate ready IPv4 addresses from all slices
58+
// Aggregate ready addresses from all slices
5959
allAddresses := sets.New[string]()
6060
for _, slice := range slices {
6161
addresses := endpointslice.ReadyAddressesFromSlice(slice)

0 commit comments

Comments
 (0)