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

Gateway API: unable to use multiple Gateways with a shared loadBalancerIP on GKE #54453

Open
2 tasks done
vpedosyuk opened this issue Dec 23, 2024 · 6 comments · May be fixed by #54525
Open
2 tasks done

Gateway API: unable to use multiple Gateways with a shared loadBalancerIP on GKE #54453

vpedosyuk opened this issue Dec 23, 2024 · 6 comments · May be fixed by #54525

Comments

@vpedosyuk
Copy link

vpedosyuk commented Dec 23, 2024

Is this the right place to submit this?

  • This is not a security vulnerability or a crashing bug
  • This is not a question about how to use Istio

Bug Description

I have a GKE cluster where I need to expose multiple services, some are HTTP, some are TCP. I'm trying to achieve that with Istio and the new Gateway API architecture. However, I'd like all these services to have the same external cloud IP address - I believe that's a quite common use-case.

So I'm creating 2 Gateway resources:

  • one-gateway for HTTP traffic in the istio-ingress namespace.
  • two-gateway for TCP traffic in the kafka namespace.

As documented, Istio behaves a bit differently with Gateway API, i.e. it creates a separate "ingress" pod for each Gateway along with a dedicated Service resource of type LoadBalancer in respective namespaces.

The problem is that each auto-created Service exposes 15021 port by default and when configured to use a shared loadBalancerIP, one of the services just fails to boot up and reports "conflicting ports". AFAIK, there's no way to workaround that.

Reproducing the issue on a dummy GKE cluster:

$ kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v1.2.1" | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/grpcroutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/referencegrants.gateway.networking.k8s.io created

$ istioctl install --set profile=minimal -y
        |\          
        | \         
        |  \        
        |   \       
      /||    \      
     / ||     \     
    /  ||      \    
   /   ||       \   
  /    ||        \  
 /     ||         \ 
/______||__________\
____________________
  \__       _____/  
     \_____/        

✔ Istio core installed ⛵️                                                                                                                                            
- Processing resources for Istiod. Waiting for Deployment/istio-system/istiod                                                                                        
✔ Istiod installed 🧠                                                                                                                                                
✔ CNI installed 🪢                                                                                                                                                   
✔ Installation complete    

$ kubectl create namespace istio-ingress
namespace/istio-ingress created

$ kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: one-gateway
  namespace: istio-ingress
spec:
  gatewayClassName: istio
  addresses:
    - value: 34.165.74.48 # my reserved GCP Static IP
      type: IPAddress
  listeners:
    - name: default
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: All
EOF
gateway.gateway.networking.k8s.io/one-gateway created

$ kubectl create namespace kafka
namespace/kafka created

$ kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: two-gateway
  namespace: kafka
spec:
  gatewayClassName: istio
  addresses:
    - value: 34.165.74.48 # my reserved GCP Static IP
      type: IPAddress
  listeners:
    - name: kafka
      port: 9093
      protocol: TCP
      allowedRoutes:
        namespaces:
          from: All
EOF
gateway.gateway.networking.k8s.io/two-gateway created

And now checking the result:

$ kubectl get svc -n istio-ingress one-gateway-istio
NAME                TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)                        AGE
one-gateway-istio   LoadBalancer   34.118.231.188   34.165.74.48   15021:30983/TCP,80:30789/TCP   2m58s

$ kubectl get svc -n kafka two-gateway-istio
NAME                TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                          AGE
two-gateway-istio   LoadBalancer   34.118.227.66   <pending>     15021:31537/TCP,9093:32577/TCP   100s

$ kubectl describe svc -n kafka two-gateway-istio               
Name:                     two-gateway-istio
Namespace:                kafka
Labels:                   gateway.istio.io/managed=istio.io-gateway-controller
                          gateway.networking.k8s.io/gateway-name=two-gateway
Annotations:              cloud.google.com/neg: {"ingress":true}
Selector:                 gateway.networking.k8s.io/gateway-name=two-gateway
Type:                     LoadBalancer
IP Family Policy:         PreferDualStack
IP Families:              IPv4
IP:                       34.118.227.66
IPs:                      34.118.227.66
IP:                       34.165.74.48
Port:                     status-port  15021/TCP
TargetPort:               15021/TCP
NodePort:                 status-port  31537/TCP
Endpoints:                10.8.5.6:15021
Port:                     kafka  9093/TCP
TargetPort:               9093/TCP
NodePort:                 kafka  32577/TCP
Endpoints:                10.8.5.6:9093
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type     Reason                  Age                 From                Message
  ----     ------                  ----                ----                -------
  Normal   EnsuringLoadBalancer    28s (x5 over 2m5s)  service-controller  Ensuring load balancer
  Warning  SyncLoadBalancerFailed  25s (x5 over 113s)  service-controller  Error syncing load balancer: failed to ensure load balancer: failed to create forwarding rule for load balancer (ab83e004898324a30a893ad2ca90e846(kafka/two-gateway-istio)): googleapi: Error 400: Invalid value for field 'resource.IPAddress': '34.165.74.48'. Specified IP address is in-use and would result in a conflict., invalid

Once I remove the two-gateway gateway and include the needed port into the configuration of one-gateway, it works fine:

$ kubectl delete gateway -n kafka two-gateway 
gateway.gateway.networking.k8s.io "two-gateway" deleted

$ kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: one-gateway
  namespace: istio-ingress
spec:
  gatewayClassName: istio
  addresses:
    - value: 34.165.74.48 # my reserved GCP Static IP
      type: IPAddress
  listeners:
    - name: default
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: All
    - name: kafka
      port: 9093
      protocol: TCP
      allowedRoutes:
        namespaces:
          from: All
EOF
gateway.gateway.networking.k8s.io/one-gateway configured

$ kubectl get svc -n istio-ingress one-gateway-istio
NAME                TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)                                       AGE
one-gateway-istio   LoadBalancer   34.118.231.188   34.165.74.48   15021:30983/TCP,80:30789/TCP,9093:31772/TCP   11m

P.S. I'm referring to GCP as that's what I'm using currently but I think the problem is relevant for other cloud providers as well.

Version

$ istioctl version                         
client version: 1.24.2
control plane version: 1.24.2
data plane version: 1.24.2 (2 proxies)

$ kubectl version                          
Client Version: v1.30.8-dispatcher
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.30.6-gke.1125000

Additional Information

No response

@vpedosyuk vpedosyuk changed the title Gateway API: unable to use multiple Gateway with a shared loadBalancerIP on GKE Gateway API: unable to use multiple Gateways with a shared loadBalancerIP on GKE Dec 23, 2024
@howardjohn
Copy link
Member

I have a GKE cluster where I need to expose multiple services, some are HTTP, some are TCP. I'm trying to achieve that with Istio and the new Gateway API architecture. However, I'd like all these services to have the same external cloud IP address - I believe that's a quite common use-case.

This is not how Gateway API is expected to be used. Gateway represents the physical infrastructure (an IP address, a real pod serving the traffic, etc).

The expectation is sharing the same GW across multiple namespaces its done via routes and allowedRoutes. kubernetes-sigs/gateway-api#1713 would also expand this.

Obviously thats not to say it couldn't potentially be done by some implementations or with tweaks to those... but its really not the intended usage of the API

@vpedosyuk
Copy link
Author

@howardjohn, thanks, but it's somewhat confusing...

I guess both of my Gateways represent "real pods serving traffic". I don't need my Gateways to be cross-namespace merged nor to be run within a single Envoy workload as discussed in kubernetes-sigs/gateway-api#1713. But yes, I'd like to run everything with the same domain name and a TLS certificate.

Actually, I was happy that it's now easy to isolate heavy flows from regular 80/443 HTTP services, so that Kafka will have its own Envoy with its own autoscaling mechanisms attached. If Ana overloads Kafka with some tests, Chihiro wouldn't need to explain why Allison's web app is affected.

And the fact that Istio exposes the 15021 port in a LoadBalancer without any restrictions and configuration options is quite concerning. It's not mentioned in the Gateway API spec either.

@howardjohn
Copy link
Member

Does GCP not create a conflict if there is no overlap? and it will handle forwarding to the correct envoy instance by the port?

We could add an option to not expose the readiness endpoint. It's primarily added to handle some (non GCP) load balancers who perf health checks based on the first port in the service.

@vpedosyuk
Copy link
Author

According to the GCP docs (and to my experience as well) there shouldn't be any conflict:

Two or more LoadBalancer Services can reference the same static IP address if the forwarding rule for each load balancer uses a unique combination of IP address, protocol, port specification, and Network Service Tiers specification, as indicated in the table in this section.

https://cloud.google.com/kubernetes-engine/docs/concepts/service-load-balancer-parameters#spd-static-ip-sharing

To double-check that I just reproduced the original behavior once again:

$ kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: one-gateway
  namespace: istio-ingress
spec:
  gatewayClassName: istio
  addresses:
    - value: 34.165.57.128 # my second reserved GCP Static IP
      type: IPAddress
  listeners:
    - name: default
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: All
EOF
gateway.gateway.networking.k8s.io/one-gateway created

$ kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: two-gateway
  namespace: kafka
spec:
  gatewayClassName: istio
  addresses:
    - value: 34.165.57.128 # my second reserved GCP Static IP
      type: IPAddress
  listeners:
    - name: kafka
      port: 9093
      protocol: TCP
      allowedRoutes:
        namespaces:
          from: All
EOF
gateway.gateway.networking.k8s.io/two-gateway created

But this time I also added two custom Services that don't have overlapping ports but use the same IP address:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: one-gateway-custom-svc
  namespace: istio-ingress
spec:
  loadBalancerIP: 34.165.87.186 # my third reserved GCP Static IP
  type: LoadBalancer
  ports:
    - name: default
      port: 80
      protocol: TCP
      targetPort: 80
  selector:
    gateway.networking.k8s.io/gateway-name: one-gateway
EOF
service/one-gateway-custom-svc created

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: two-gateway-custom-svc
  namespace: kafka
spec:
  loadBalancerIP: 34.165.87.186 # my third reserved GCP Static IP
  type: LoadBalancer
  ports:
    - name: default
      port: 9093
      protocol: TCP
      targetPort: 9093
  selector:
    gateway.networking.k8s.io/gateway-name: two-gateway
EOF
service/two-gateway-custom-svc created

As a result, my custom Services were created successfully unlike the ones created by Istio:

$ kubectl get svc -n istio-ingress one-gateway-istio one-gateway-custom-svc 
NAME                     TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)                        AGE
one-gateway-istio        LoadBalancer   34.118.237.195   34.165.57.128   15021:30225/TCP,80:30250/TCP   4m7s
one-gateway-custom-svc   LoadBalancer   34.118.232.95    34.165.87.186   80:30130/TCP                   2m43s

$ kubectl get svc -n kafka two-gateway-istio two-gateway-custom-svc 
NAME                     TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)                          AGE
two-gateway-istio        LoadBalancer   34.118.236.129   <pending>       15021:32173/TCP,9093:30457/TCP   3m34s
two-gateway-custom-svc   LoadBalancer   34.118.229.51    34.165.87.186   9093:30243/TCP                   2m17s

@vpedosyuk
Copy link
Author

And I forgot to say that the option to not expose the readiness endpoint sounds awesome!

@vpedosyuk
Copy link
Author

@howardjohn I've created a PR with the new option that resolves the issue. Your feedback would be greatly appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants