GKE Ingress Explained: How Ingress Controllers Work in Kubernetes

When you have multiple HTTP/S services in a GKE cluster, giving each one its own external load balancer gets expensive and complicated fast. Kubernetes Ingress solves this by letting you route all external HTTP/S traffic through a single Google Cloud load balancer, using rules to direct requests to the right service based on the URL path or hostname. Ingress is not a Service type; it is a separate API object that works alongside your existing ClusterIP Services.

If you are new to Kubernetes networking, it helps to first understand how Services work before diving into Ingress, since Ingress sits in front of Services rather than replacing them.

In simple terms

Think of Ingress as a single front door for all of your HTTP/S services. Every request enters through that one door. Depending on the URL (the path like /api or the hostname like api.example.com), Ingress forwards the request to the correct internal service.

Without Ingress, you would need a separate front door and a separate public IP address for every service. With Ingress, one door handles everything, and the routing rules live in a single, readable configuration file.

Note

Ingress only handles HTTP and HTTPS traffic. For TCP, UDP, or non-HTTP workloads, use a LoadBalancer Service instead.

What problem Ingress solves

Suppose your application has three services: a frontend, a REST API, and an authentication service. Without Ingress, the standard approach is to create a LoadBalancer Service for each one.

That means:

  • Three separate Google Cloud load balancers, each with an ongoing cost.
  • Three public IP addresses to manage and point DNS records at.
  • Three separate places to configure TLS certificates.
  • No shared routing logic. Each service is completely isolated at the network level.

As your application grows, this pattern becomes expensive and hard to maintain. Ingress consolidates that into one load balancer, one IP, and one set of routing rules.

Analogy

Ingress is like the reception desk of a large office building. Everyone enters through the same front door and tells reception which department they need. Reception directs them to the right floor. Without reception, each department would need its own entrance, its own door number, and its own signage. Ingress is that reception desk for your HTTP/S services.

How Ingress works on GKE

When you create an Ingress on GKE, the following chain of components handles your traffic:

Internet → Google Cloud HTTP(S) Load Balancer → GKE Ingress Controller → ClusterIP Service → Pods

Each part of the chain has a specific role:

Ingress resource: A Kubernetes API object (kind: Ingress) that declares your routing rules. It is purely declarative; it tells Kubernetes what you want, but does not do any actual routing itself.

Ingress Controller: Software running inside (or alongside) your cluster that watches for Ingress resources and makes them real. On GKE, the built-in controller reads your Ingress rules and configures the Google Cloud HTTP(S) Load Balancer accordingly.

Google Cloud HTTP(S) Load Balancer: The actual infrastructure that receives traffic from the internet. It is a fully managed, globally distributed layer-7 load balancer. GKE provisions and configures it automatically when you create an Ingress.

Backend Services: The ClusterIP Services inside your cluster that the load balancer forwards traffic to, based on the routing rules.

Pods: The containers your application actually runs in. Each Service routes to healthy, ready Pods via label selectors.

The key point: the Ingress resource is declarative and the controller makes it real. The controller continuously reconciles the state of the load balancer to match what your Ingress resources describe.

Ingress vs Service of type LoadBalancer

Both approaches expose services to external traffic, but they work at different layers and suit different scenarios.

IngressLoadBalancer Service
LayerLayer 7 (HTTP/S)Layer 4 (TCP/UDP)
Number of servicesMany services, one load balancerOne service per load balancer
Public IPsOne shared IPOne IP per Service
Path/host routingYesNo
TLS terminationYes, centrallyMust be handled per-service
GKE load balancer typeHTTP(S) Load BalancerPassthrough Network Load Balancer
Best forMultiple HTTP/S servicesA single TCP/UDP service or non-HTTP traffic

Use Ingress when you have multiple HTTP/S services that should share a load balancer, need path or host-based routing, or want centralised TLS management.

Use a LoadBalancer Service when you are exposing a single service, need non-HTTP/S traffic (TCP, UDP, gRPC without HTTP routing), or want the simpler setup.

For more on how Google Cloud load balancers work under the hood, see the external load balancers and HTTP Load Balancer setup pages.

When to use Ingress

Ingress is the right choice when you need:

  • Multiple paths on one domain: / serves your frontend and /api serves your backend API, all under www.example.com.
  • Multiple subdomains on one load balancer: api.example.com and admin.example.com pointing to different services.
  • Centralised TLS termination: manage one certificate (or one Google-managed certificate) rather than per-service TLS config.
  • Google Cloud load balancing features: Ingress integrates with Cloud CDN, Cloud Armor, and Identity-Aware Proxy through BackendConfig resources.
  • Cost control at scale: one load balancer across all your HTTP/S services is significantly cheaper than one per service.

When not to use Ingress

Warning

Ingress only handles HTTP and HTTPS. It will not work for TCP, UDP, raw gRPC (without HTTP routing), databases, or any non-HTTP protocol. Route those workloads through a LoadBalancer Service instead.

Ingress is also the wrong choice when:

  • You only have one service to expose. A LoadBalancer Service is simpler, faster to set up, and perfectly adequate for a single externally-facing service.
  • You need advanced traffic management. Features like traffic weighting, retries, circuit breaking, or fine-grained header manipulation are better served by the Gateway API or a service mesh.
  • You are running non-HTTP workloads. Databases, message brokers, and game servers should not be routed through Ingress.

GKE Ingress Controller explained

An Ingress resource is just a configuration object in the Kubernetes API. Creating one by itself does nothing. You also need an Ingress Controller watching for it.

Note

On GKE, the Ingress Controller is built in and enabled by default. You do not need to install one. On other distributions (minikube, kind, bare-metal clusters) you must install a controller separately before Ingress resources have any effect.

GKE ships with a built-in Ingress Controller. When you create an Ingress in a GKE cluster, the controller:

  1. Detects the new Ingress resource via the Kubernetes API.
  2. Provisions a Google Cloud HTTP(S) Load Balancer (if one does not already exist).
  3. Configures URL maps, backend services, health checks, and forwarding rules in Google Cloud.
  4. Assigns a public IP address and updates the Ingress object’s status.loadBalancer.ingress field.
  5. Keeps the load balancer configuration in sync as you update the Ingress resource.

The GKE controller provisions cloud infrastructure. This is different from the popular NGINX Ingress Controller, which runs an NGINX reverse proxy inside the cluster as a Pod. With NGINX Ingress, routing happens inside the cluster. With GKE Ingress, routing happens at the Google Cloud network edge before traffic even reaches your cluster.

For most GKE workloads, the built-in GKE controller is the recommended choice. It benefits from Google Cloud’s global infrastructure, integrates with Cloud Armor for security policies, and supports Cloud CDN for caching.

Path-based routing example

This Ingress routes two paths on the same hostname to different backend Services:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    kubernetes.io/ingress.class: "gce"
spec:
  defaultBackend:
    service:
      name: frontend-service
      port:
        number: 80
  rules:
    - host: www.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

What this does:

  • Requests to www.example.com/api or www.example.com/api/users go to api-service.
  • All other requests to www.example.com go to frontend-service.
  • The defaultBackend handles requests that match no rule. It acts as a fallback for unmatched traffic.

pathType: Prefix matches the specified path and any path that starts with it. pathType: Exact matches only the literal path, so /api with Exact would not match /api/users. Use Prefix for API path prefixes unless you need strict matching.

The kubernetes.io/ingress.class: "gce" annotation tells GKE to use the external HTTP(S) Load Balancer. Use "gce-internal" for an internal load balancer accessible only within your VPC.

Host-based routing example

Host-based routing lets you serve different subdomains from the same Ingress, sharing one load balancer and one public IP across all of them:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-host-ingress
  annotations:
    kubernetes.io/ingress.class: "gce"
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
    - host: admin.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: admin-service
                port:
                  number: 80

Traffic arriving with the Host: api.example.com header is routed to api-service. Traffic with Host: admin.example.com goes to admin-service. Both share the same Google Cloud HTTP(S) Load Balancer.

This is useful when you want clean subdomain-based separation between services (for example, a public API, an admin panel, and a marketing site) all without provisioning multiple load balancers.

TLS on GKE Ingress

GKE Ingress terminates TLS at the Google Cloud HTTP(S) Load Balancer. Traffic from the load balancer to your Pods travels over Google’s internal network.

Secret-based TLS: Create a Kubernetes Secret from your certificate and key files, then reference it in the Ingress:

kubectl create secret tls my-tls-secret \
  --cert=path/to/cert.crt \
  --key=path/to/cert.key
spec:
  tls:
    - hosts:
        - www.example.com
      secretName: my-tls-secret
  rules:
    - host: www.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

Google-managed certificates: A simpler alternative. Instead of managing certificate files yourself, use the ManagedCertificate custom resource and reference it via annotation:

metadata:
  annotations:
    networking.gke.io/managed-certificates: "my-managed-cert"

Google provisions and renews the certificate automatically via Google Trust Services. You do not need to rotate certificates manually.

Tip

For most public-facing workloads, Google-managed certificates are the easier long-term choice. Secret-based TLS is worth using when you need a private or internal CA, or when you already have certificates issued outside of Google Trust Services.

For a broader look at how TLS certificates work on GCP, see SSL certificates in GCP.

Useful annotations and BackendConfig

GKE Ingress behaviour is controlled through annotations on the Ingress resource and, for backend-specific settings, through BackendConfig custom resources referenced from your Services.

Ingress annotations

AnnotationWhat it does
kubernetes.io/ingress.class: "gce"Use the external GKE HTTP(S) Load Balancer (default)
kubernetes.io/ingress.class: "gce-internal"Use an internal HTTP(S) Load Balancer (VPC-only access)
networking.gke.io/managed-certificatesAttach a Google-managed TLS certificate
networking.gke.io/load-balancer-ip-addressesUse a pre-reserved static external IP address
kubernetes.io/ingress.allow-http: "false"Disable plain HTTP; only allow HTTPS connections

BackendConfig

Backend-specific settings (health check parameters, timeout values, Cloud CDN configuration, Cloud Armor security policies) are configured via BackendConfig custom resources. You attach a BackendConfig to a Service using the cloud.google.com/backend-config annotation on the Service itself, not on the Ingress.

# On your backend Service
metadata:
  annotations:
    cloud.google.com/backend-config: '{"default": "my-backend-config"}'

This separation keeps Ingress-level routing config clean and moves per-backend settings to where they belong.

How to troubleshoot GKE Ingress

When an Ingress is not working as expected, work through these checks in order:

Check the Ingress status

# Shows address (or <pending>) and the associated backends
kubectl get ingress

# Full detail including events. Start here for most problems.
kubectl describe ingress my-app-ingress

The Events section in describe output is the most useful first stop. It shows provisioning errors, missing resources, and health check failures.

Check backend Services and endpoints

# Confirm the Services exist and have the right type (ClusterIP)
kubectl get svc

# Confirm there are healthy Pods behind each Service
kubectl get endpoints api-service
kubectl get endpoints frontend-service

If an endpoints entry shows <none>, no ready Pods are matched by the Service’s selector. The load balancer cannot route to that backend.

Check Pod readiness

kubectl get pods -l app=my-app

If Pods are not in the Running state or are failing readiness probes, the Endpoints list will be empty and the load balancer backend will be marked unhealthy.

Check DNS and certificate references

  • Confirm your domain’s DNS A record points to the Ingress address.
  • If using Secret-based TLS, confirm the Secret exists in the same namespace as the Ingress: kubectl get secret my-tls-secret.
  • If using managed certificates, check the ManagedCertificate resource status: kubectl describe managedcertificate my-managed-cert.

What <pending> usually means

Warning

Do not delete and recreate an Ingress just because the address is showing as <pending>. Deleting the Ingress resets the provisioning timer. A new GKE Ingress normally takes two to five minutes to provision. If it has been more than ten minutes, run kubectl describe ingress and read the Events section to find the root cause.

If provisioning is genuinely stuck beyond ten minutes:

  • Check for events indicating provisioning errors.
  • Verify all referenced Services and Secrets exist.
  • Check that backend Pods are healthy and passing readiness probes.
  • Check your Google Cloud project quotas for forwarding rules and backend services.

Common mistakes

  1. Thinking Ingress is a Service type. Ingress is kind: Ingress in the networking.k8s.io API group. It is not a value you set in spec.type of a Service. Services and Ingresses are separate, independent objects. Your backend Services should remain type: ClusterIP.
  2. Forgetting that Ingress does nothing without an Ingress Controller. Creating an Ingress resource has no effect unless a controller is watching for it. On GKE the built-in controller is enabled by default. On other distributions (minikube, kind, bare-metal clusters) you must install a controller separately.
  3. Using the wrong Service type for backends. Ingress backends on GKE should be type: ClusterIP. Using NodePort or LoadBalancer as backends is unnecessary and can cause unexpected behaviour.
  4. Expecting non-HTTP/S traffic to work through Ingress. Ingress only handles HTTP and HTTPS. TCP, UDP, raw gRPC without HTTP routing, and database connections will not work through a Kubernetes Ingress. Use a LoadBalancer Service for those protocols.
  5. Choosing the wrong pathType. pathType: Exact matches only the literal path. If you use Exact for /api, requests to /api/users will miss and fall through to the default backend. Use pathType: Prefix for most API routing scenarios.
  6. Deleting and recreating while ADDRESS is pending. Provisioning a new Google Cloud HTTP(S) Load Balancer takes two to five minutes. Deleting and recreating the Ingress because the address is still <pending> restarts the timer. Use kubectl describe ingress to check events instead.
  7. Forgetting that backend Pods must be ready. The load balancer performs health checks against your backend Pods. If no Pods are passing their readiness probe, the backend is marked unhealthy and the load balancer returns errors. Verify kubectl get endpoints shows at least one ready address.

Frequently asked questions

What is the difference between Ingress and a Service of type LoadBalancer in Kubernetes?

A LoadBalancer Service provisions one cloud load balancer per Service, giving each service its own public IP address. An Ingress is a separate API object that sits in front of multiple ClusterIP Services and routes HTTP/S traffic to them using path and host rules, all through a single cloud load balancer with one public IP. Ingress is far more cost-effective and manageable when you have several HTTP/S services to expose.

Does GKE Ingress support TLS?

Yes. You create a Kubernetes Secret containing your TLS certificate and private key, then reference it in the spec.tls section of your Ingress. TLS is terminated at the Google Cloud HTTP(S) Load Balancer. Alternatively, you can use Google-managed certificates via the networking.gke.io/managed-certificates annotation, which automates certificate provisioning and renewal so you never have to handle cert files manually.

Why is my Ingress address showing as pending?

Run kubectl describe ingress INGRESS_NAME and check the Events section. Common causes: the backend Services do not exist or are not type ClusterIP; there are no healthy, ready Pods behind those Services; the referenced TLS Secret does not exist or has the wrong format; or a Google Cloud quota issue is preventing the load balancer from being provisioned. The GKE Ingress Controller typically takes two to five minutes on first creation, so pending during that window is normal.

Do backend Services referenced in an Ingress need to be ClusterIP?

Yes, for GKE Ingress. The backend Services should be type ClusterIP. The Ingress Controller handles exposing traffic externally, so the backend Services only need to be reachable inside the cluster. Using NodePort or LoadBalancer as Ingress backends is unnecessary and can cause unexpected behaviour with the GKE Ingress Controller.

Is Ingress the same as an Ingress Controller?

No. An Ingress is a Kubernetes API object (kind: Ingress) that declares your routing rules: which paths and hostnames should go to which Services. An Ingress Controller is the software that reads those rules and configures a real load balancer or reverse proxy to enforce them. Without a running controller, creating an Ingress resource has no effect. On GKE, the Ingress Controller is built in and enabled by default.

Last verified: 23 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.