l7mp/stunner

On GKE, how to specify an IP for udp-gateway

nhha1602 opened this issue · 11 comments

Dear All,

I tried to deploy stunner on GKE. As your docs, when apply file stunner-helm/livekit-call-stunner.yaml, it will auto create an udp-gateway service then it will auto get an IP. But I want to specify an IP for it as below that I did, but it still get a different IP.

apiVersion: stunner.l7mp.io/v1alpha1
kind: GatewayConfig
metadata:
  name: stunner-gatewayconfig
  namespace: stunner-dev
spec:
  realm: stunner.l7mp.io
  authType: plaintext
  userName: "user-1"
  password: "pass-1"
  loadBalancerServiceAnnotations:
    networking.gke.io/load-balancer-type: Internal

---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: udp-gateway
  namespace: stunner-dev
spec:
  gatewayClassName: stunner-gatewayclass
  listeners:
    - name: udp-listener
      port: 3478
      protocol: TURN-UDP
  addresses:
    - type: NamedAddress
      value: my_reserved_ip4

The "my_reserved_ip4" created by command:

gcloud compute addresses create IP_ADDRESS_NAME \
    --purpose=SHARED_LOADBALANCER_VIP \
    --region=COMPUTE_REGION \
    --subnet=SUBNET \
    --project=PROJECT_ID

Could you, please help.

Thanks and regards.

rg0now commented

Using a fix external IP is, theoretically, supported by STUNner, but this is something that is not quite standardized across cloud providers and so we haven't tested this feature extensively yet.

The problem is that the address type you're using in the Gateway spec (NamedAddress) is deprecated and so we currently do not accept NamedAddress as the address type.

I have two suggestions.

First, try to replace the NamedAddress type with IPAddress as in the below, hoping that GKE would accept it:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: udp-gateway
  namespace: stunner-dev
spec:
  gatewayClassName: stunner-gatewayclass
  listeners:
    - name: udp-listener
      port: 3478
      protocol: TURN-UDP
  addresses:
    - type: IPAddress
      value: my_reserved_ip4

The other idea would be to create the LB Service manually (just copy-paste the YAML of the udp-gateway Service) and set the fields spec.externalIPs and spec.loadBalancerIP to the IP address you got from GKE. Would this work? Can you reach STUNner now over that IP? If you do then it is a quick patch to change the code to accept NamedAddress, that would at least fix GKE.

Also, can you please copy-paste the YAML of the service STUNner has created (kubectl -n stunner-dev get svc udp-gateway -o yaml)?

rg0now commented

It just occurred to me that you could query the IP address assigned by gcloud compute addresses create ..., let the result be A.B.C.D. Then, set this IP with type IPAddress in the Gateway spec.addresses:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: udp-gateway
  namespace: stunner-dev
spec:
  gatewayClassName: stunner-gatewayclass
  listeners:
    - name: udp-listener
      port: 3478
      protocol: TURN-UDP
  addresses:
    - type: IPAddress
      value: A.B.C.D

It seems the NamedAddress type is not supported outside Google's own in-house load-balancers and, regrettably, we're not Google. Would this solution work for you?

Thank you for your helps,

I tried as your comments but I got below error on stunner operator:

2023-10-27T01:35:25.676233438Z  INFO    renderer        creating public service for gateway     {"name": "stunner-dev/udp-gateway", "gateway": "udp-gateway", "service": "{\"metadata\":{\"name\":\"udp-gateway\",\"namespace\":\"stunner-dev\",\"creationTimestamp\":null,\"labels\":{\"stunner.l7mp.io/owned-by\":\"stunner\",\"stunner.l7mp.io/related-gateway-name\":\"udp-gateway\",\"stunner.l7mp.io/related-gateway-namespace\":\"stunner-dev\"},\"annotations\":{\"networking.gke.io/load-balancer-type\":\"Internal\",\"stunner.l7mp.io/related-gateway-name\":\"stunner-dev/udp-gateway\"},\"ownerReferences\":[{\"apiVersion\":\"gateway.networking.k8s.io/v1beta1\",\"kind\":\"Gateway\",\"name\":\"udp-gateway\",\"uid\":\"21560782-753a-4e93-9689-14de0157495c\"}]},\"spec\":{\"ports\":[{\"name\":\"udp-listener\",\"protocol\":\"UDP\",\"port\":3478,\"targetPort\":0}],\"selector\":{\"app\":\"stunner\"},\"type\":\"LoadBalancer\",\"externalIPs\":[\"172.18.100.73\"],\"loadBalancerIP\":\"172.18.100.73\"},\"status\":{\"loadBalancer\":{}}}"}
2023-10-27T01:35:25.676331505Z  INFO    renderer        STUNner dataplane configuration ready   {"generation": 2762, "config": "{version=\"v1alpha1\",admin:{name=\"stunner-daemon\",logLevel=\"all:INFO\",health-check=\"http://0.0.0.0:8086\"},auth:{realm=\"stunner.l7mp.io\",type=\"plaintext\",username=\"<SECRET>\",password=\"<SECRET>\"},listeners=[\"stunner-dev/udp-gateway/udp-listener\":{turn://0.0.0.0:3478<32768-65535>,public=-:-,cert/key=-/-,routes=[stunner-dev/livekit-media-plane]}],clusters=[\"stunner-dev/livekit-media-plane\":{type=\"STATIC\",protocol=\"UDP\",endpoints=[172.18.112.32,172.31.2.26]}]}"}
2023-10-27T01:35:25.676385128Z  INFO    cds-server      processing config update event  {"generation": 2762, "update": "update (gen: 2762): upsert-queue: gway-cls: 1, gway: 1, route: 1, svc: 1, confmap: 1, dp: 0 / delete-queue: gway-cls: 0, gway: 0, route: 0, svc: 0, confmap: 0, dp: 0"}
2023-10-27T01:35:25.67641283Z   INFO    updater processing update event {"generation": 2762, "update": "update (gen: 2762): upsert-queue: gway-cls: 1, gway: 1, route: 1, svc: 1, confmap: 1, dp: 0 / delete-queue: gway-cls: 0, gway: 0, route: 0, svc: 0, confmap: 0, dp: 0"}
2023-10-27T01:35:25.729584964Z  ERROR   updater cannot update service   {"operation": "unchanged", "service": "{\"metadata\":{\"name\":\"udp-gateway\",\"namespace\":\"stunner-dev\",\"creationTimestamp\":null,\"labels\":{\"stunner.l7mp.io/owned-by\":\"stunner\",\"stunner.l7mp.io/related-gateway-name\":\"udp-gateway\",\"stunner.l7mp.io/related-gateway-namespace\":\"stunner-dev\"},\"annotations\":{\"networking.gke.io/load-balancer-type\":\"Internal\",\"stunner.l7mp.io/related-gateway-name\":\"stunner-dev/udp-gateway\"},\"ownerReferences\":[{\"apiVersion\":\"gateway.networking.k8s.io/v1beta1\",\"kind\":\"Gateway\",\"name\":\"udp-gateway\",\"uid\":\"21560782-753a-4e93-9689-14de0157495c\"}]},\"spec\":{\"ports\":[{\"name\":\"udp-listener\",\"protocol\":\"UDP\",\"port\":3478,\"targetPort\":0}],\"selector\":{\"app\":\"stunner\"},\"type\":\"LoadBalancer\",\"externalIPs\":[\"172.18.100.73\"],\"loadBalancerIP\":\"172.18.100.73\"},\"status\":{\"loadBalancer\":{}}}", "error": "cannot upsert service \"stunner-dev/udp-gateway\": services \"udp-gateway\" is forbidden: Use of external IPs is denied by admission control"}
github.com/l7mp/stunner-gateway-operator/internal/updater.(*Updater).ProcessUpdate
        /workspace/internal/updater/updater.go:115
github.com/l7mp/stunner-gateway-operator/internal/updater.(*Updater).Start.func1
        /workspace/internal/updater/updater.go:62

As I see, this error (is forbidden: Use of external IPs is denied by admission control) caused by duplicate by externalIP and loadbalacerIP ? One more thing, that I'm setting this LoadBalancer is Internal - \"networking.gke.io/load-balancer-type\":\"Internal\"

\"type\":\"LoadBalancer\",\"externalIPs\":[\"172.18.100.73\"],\"loadBalancerIP\":\"172.18.100.73\"}

Could you please advise this?

Hi,

I just found this link:
(l7mp/stunner-gateway-operator#32 (comment))

and stunner operator code:

// forward the first requested address to Kubernetes
	if len(gw.Spec.Addresses) > 0 {
		if gw.Spec.Addresses[0].Type == nil ||
			(gw.Spec.Addresses[0].Type != nil &&
				*gw.Spec.Addresses[0].Type == gwapiv1a2.IPAddressType) {
			// only the first address can be used because
			// stunner is limited to use a single public address
			// https://github.com/l7mp/stunner-gateway-operator/issues/32#issuecomment-1648035135
			svc.Spec.ExternalIPs = []string{gw.Spec.Addresses[0].Value}
			svc.Spec.LoadBalancerIP = gw.Spec.Addresses[0].Value
		}
	}

So, may be this we set externalIP to LoadBalancerIP will raised above error. Because my network team dose not allow to use externalIP in GKE - they will do NAT the internal LoadBalancerIP to public.

Please advise this.

@nhha1602 Hi,

as you mentioned your team does not allow using the externapIP field, thus you would like to set the svc.Spec.LoadBalancerIP field perhaps. I don't know what the answer should be yet however the loadBalancerIP field is deprecated and ignored by most cloud providers. Do you have any proof that GKE supports it?

    // Only applies to Service Type: LoadBalancer.
    // This feature depends on whether the underlying cloud-provider supports specifying
    // the loadBalancerIP when a load balancer is created.
    // This field will be ignored if the cloud-provider does not support the feature.
    // Deprecated: This field was under-specified and its meaning varies across implementations,
    // and it cannot support dual-stack.
    // As of Kubernetes v1.24, users are encouraged to use implementation-specific annotations when available.
    // This field may be removed in a future API version.
    // +optional
    LoadBalancerIP string `json:"loadBalancerIP,omitempty" protobuf:"bytes,8,opt,name=loadBalancerIP"`

We need to figure out first what all these fields mean actually and which of them are supported, etc. I think we need more docs about this topic, if you got some feel free to share with us.

rg0now commented

For a quick fix you can just remove the offending line below, build a new stunner image and deploy that:

svc.Spec.ExternalIPs = []string{gw.Spec.Addresses[0].Value}

Or you can remove the other problematic line and start anew:

svc.Spec.LoadBalancerIP = gw.Spec.Addresses[0].Value

Let us know which one works, if any.

Unfortunately support for static external IPs is so underspeficied in Kubernetes and the Gateway API that we don't know what's the most portable way to implement this. The official advice is to use cloud-provider specific Service annotations but we couldn't find any for GKE, let alone for EKS and the other one thousand cloud providers out there.

THank you for your helps,

I remove line: svc.Spec.ExternalIPs = []string{gw.Spec.Addresses[0].Value} and build new image for stunner operator and it worked.

Thank you.

rg0now commented

Thanks! Just for summary: svc.Spec.ExternalIPs and svc.Spec.LoadBalancerIP together are superfluous. I'm leaning towards removing svc.Spec.ExternalIPs, @davidkornel wdyt? Do you remember why we added it in the first place?

I'm afraid both of them were added in order to support this feature on different cloud-providers' clusters (without realizing that they would collide and break). Other thing is that these fields are not that used and underspecified. Just reading their API documentation reveals that it is not advised to use these, since they are either not supported or the field itself is deprecated, etc. It seems that the svc.Spec.LoadBalancerIP works on GKE, however, it is most likely to not work on other platforms. TBH this quick 'removing that line' fix might have worked but in the long game, we should do some research on this and implement that annotation-based idea that I proposed in this issue, or something like that.

svc.Spec.LoadBalancerIP works on GKE. I tested it on my machine and is also recommended on Stack Overflow. And setting external IP breaks LoadBalancers in gke.

This has been addressed in l7mp/stunner-gateway-operator@d955a1b, at least for GKE. Feel free to reopen if bug regresses.