Getting traffic into Kubernetes: AWS vs GCP

6 min read
Share:

Why a GKE Ingress can look fine in kubectl and still have no IP — and how that differs from EKS and ALB

If you have used EKS, HTTP Ingress usually means an Application Load Balancer. Someone installs the AWS Load Balancer Controller, you set ingressClassName: alb, and AWS creates an ALB. Certificates often come from ACM. HTTP and HTTPS are listeners on that ALB.

GKE can look the same in YAML (kind: Ingress) and behave totally differently. On GKE, external Ingress with class gce is handled by GLBC (the GCE L7 / Ingress controller). It does not create an AWS-style ALB. It creates a Google HTTP(S) load balancer: forwarding rules, a URL map, backends, and often NEGs (network endpoint groups) instead of instance groups.

Same Kubernetes API. Different cloud object. That is why copying an EKS Ingress into GKE and hoping is a bad idea. We learned that the hard way: Ingress objects applied fine, Helm said success (or timed out), and there was still no IP.

The one-line map

HTTP(S) to pods

AWS / EKS
ALB plus target groups
GCP / GKE
GCE Ingress plus URL map plus backends

Ingress class

AWS / EKS
alb — controller you install
GCP / GKE
gce — GLBC; the class must exist in the cluster

TLS certificate

AWS / EKS
ACM ARN on the Ingress or annotations
GCP / GKE
Pre-shared Google cert name on the Ingress

HTTPS only

AWS / EKS
No HTTP listener, or redirect 80 to 443
GCP / GKE
Annotation so GLBC does not create HTTP port 80

TLS strength

AWS / EKS
ALB SSL policy
GCP / GKE
GCP SSL policy via FrontendConfig, not only an annotation

Point at pods

AWS / EKS
Target group to instances or IPs
GCP / GKE
NEG on the Service (ingress: true)

Internal only

AWS / EKS
Internal ALB or NLB
GCP / GKE
Class gce-internal (regional IP)

Nginx is a third option. A controller Deployment plus its own LoadBalancer Service can show CLASS <none> and still work. That is not GCE Ingress. Mixing them in your head is how you debug the wrong IP.

What we had

Each app Helm chart could create its own Ingress. Every CD run did helm upgrade. So:

  • The load balancer was the same Google LB family, not a fresh one each time.
  • SSL settings we fixed in Console vanished on the next deploy.
  • Port 80 stayed up even though we only wanted HTTPS.
  • Path routing (/api, /mobile) lived in the UI chart. Two charts owning one hostname caused conflicts.

We moved hosts, certs, HTTPS-only settings, and SSL policy into a shared ingress repo. App charts only ship Deployment and Service. On the Service we kept:

cloud.google.com/neg: ‘{“ingress”: true}’

On AWS that is closer to “this Service should be a target group for the ALB.” On GKE it means “use container-native load balancing.” It is not an Ingress. Leave it on the app Service.

Failure 1: Ingress in the cluster, no address

kubectl get ingress -n app-ns showed the resource. No IP in status.loadBalancer.

On AWS, if the ALB controller is not installed, you notice quickly. On GKE the controller is usually already running, but it only binds Ingresses whose IngressClass it owns.

kubectl get ingressclass

If gce is missing, ingressClassName: gce is a dangling pointer. No VIP.

We used a Helm option like ensureGceIngressClass: true on the cluster that had no gce class. It creates:

  • IngressClass named gce
  • controller k8s.io/ingress-gce (the official GCE IngressClass controller string)
  • helm.sh/resource-policy: keep so uninstall does not delete a cluster-wide class

Do not enable that flag on a cluster where gce already exists. You will fight apply conflicts. Also do not mix gce and gce-internal on one Ingress — that is like putting internet-facing and internal scheme on the same ALB.

Failure 2: TLS policy reset after Helm

On AWS you set an SSL policy on the ALB listener. Terraform or Helm usually owns it.

On GKE we set an SSL-policy annotation on the Ingress. It looked right in Console, then fell back after the next Helm upgrade. Google’s supported way to keep the policy is a FrontendConfig, not that annotation alone. What stuck:

apiVersion: networking.gke.io/v1beta1
kind: FrontendConfig
metadata:
name: example-app-frontend
spec:
sslPolicy: example-tls-policy

Hook that FrontendConfig on the Ingress. The policy itself is a GCP resource in the same project as the load balancer. Same idea as ACM and ALB in the same account and region.

Failure 3: HTTP still listening

On AWS: no HTTP listener, or a listener that only redirects to 443.

GKE default is still to create HTTP port 80 unless you set:

kubernetes.io/ingress.allow-http: “false”

Then it is HTTPS only. There is no magic redirect on port 80 if port 80 is not there.

Failure 4: certificates

AWS: ACM cert ARN, often alb.ingress.kubernetes.io/certificate-arn.

GKE (our setup): Google-managed or pre-shared cert resource name:

ingress.gcp.kubernetes.io/pre-shared-cert: “<cert-resource-name>”

The cert must exist in that project. The hostname on the Ingress must be on the cert. dev.app.example.com is not the same as test.app.example.com. Changing a string in YAML does not create a cert.

First create of cert plus Google HTTPS LB is slow. Helm --wait can hit context deadline exceeded while the Deployment is fine. Check the Ingress IP before you blame the app. Same class of problem as waiting for an ALB to become active, except Google’s first LB is often slower.

Helm –atomic made a mess

We pulled Ingress out of the app chart. A failed upgrade rolled back to an old revision that still had an Ingress with class gce. The cluster had no IngressClass named gce, so rollback failed with: no IngressClass with the name “gce”. The first error was often just a wait timeout. Fix the class (or do not roll back onto old Ingress YAML) before you chase Helm.

How we run it now

APP REPO
Deployment, Service, NEG annotation, secrets. No Ingress.
INGRESS REPO
Hostname, cert, HTTPS-only, SSL policy, paths.

IngressClass gce: once per cluster, if missing. App CD should not render Ingress. A leftover template that reads .Values.ingress.version when ingress is not set will fail Helm with a nil pointer. Delete the template or default the value.

Leave a Reply

Your email address will not be published. Required fields are marked *