How to Assign a Static External IP to GKE LoadBalancer Service in Kubernetes


2 views

When working with Google Kubernetes Engine (GKE), the default LoadBalancer service automatically provisions an ephemeral external IP. However, production scenarios often require binding to a pre-existing static IP address, particularly when:

  • DNS records already point to specific IPs
  • Firewall rules are IP-based
  • Third-party services whitelist specific IPs

The proper approach involves three GCP resources working together:

Static IP (reserved) → Forwarding Rule → Target Pool → GKE Nodes

1. Reserve a Static IP

First, ensure your static IP exists in the same region as your GKE cluster:

gcloud compute addresses create my-static-ip \
  --region=us-central1 \
  --project=my-project

2. Create the Service with Annotation

Modify your Service manifest to include the networking.gke.io/load-balancer-type annotation:

apiVersion: v1
kind: Service
metadata:
  name: my-lb-service
  annotations:
    networking.gke.io/load-balancer-type: "Internal"
    networking.gke.io/static-ip: "my-static-ip"
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: my-app

3. Verify the Binding

After applying the service, check the assignment:

kubectl get service my-lb-service -o wide
NAME             TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)        AGE
my-lb-service    LoadBalancer   10.0.12.345   203.0.113.45    80:32456/TCP   2m

Error: "Failed to ensure load balancer"

Solution: Ensure:

  • The IP exists in the same region as your cluster
  • You have compute.addresses.get permission
  • The IP isn't already in use

For complete control, create the GCP resources manually:

# Create health check
gcloud compute health-checks create http my-health-check \
  --port=80
  
# Create backend service
gcloud compute backend-services create my-backend-service \
  --protocol=HTTP \
  --health-checks=my-health-check \
  --global
  
# Add instance groups to backend
gcloud compute backend-services add-backend my-backend-service \
  --instance-group=my-ig \
  --global

When creating a LoadBalancer service in GKE, Kubernetes automatically provisions an ephemeral external IP. This becomes problematic when:

  • You need DNS records pointing to a specific IP
  • Your organization has firewall rules tied to specific IPs
  • You want predictable IPs for whitelisting purposes

Before implementation, ensure you have:

# Check existing static IPs
gcloud compute addresses list

# Reserve new static IP (if needed)
gcloud compute addresses create my-static-ip --region=us-central1

The cleanest method uses Kubernetes annotations:

apiVersion: v1
kind: Service
metadata:
  name: my-lb-service
  annotations:
    networking.gke.io/load-balancer-type: "Internal"
    networking.gke.io/internal-load-balancer-allow-global-access: "true"
spec:
  loadBalancerIP: "YOUR_STATIC_IP_ADDRESS"
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: my-app
  type: LoadBalancer

For advanced scenarios where you need to reuse existing GCP resources:

apiVersion: v1
kind: Service
metadata:
  name: my-lb-service
  annotations:
    networking.gke.io/load-balancer-type: "External"
    networking.gke.io/existing-forwarding-rule: "projects/PROJECT_ID/regions/REGION/forwardingRules/EXISTING_RULE_NAME"
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: my-app
  type: LoadBalancer

Connection Refused Errors: Verify your backend pods are healthy and the targetPort matches your container port.

IP Not Assigned: Check IAM permissions for compute.addresses.use and ensure the IP isn't already in use.

  • Always reserve regional IPs matching your cluster's region
  • Use separate IPs for staging and production environments
  • Monitor IP quota limits in your GCP project

For infrastructure-as-code deployments:

resource "google_compute_address" "static_ip" {
  name = "my-static-ip"
  region = "us-central1"
}

resource "kubernetes_service" "lb_service" {
  metadata {
    name = "my-lb-service"
    annotations = {
      "networking.gke.io/load-balancer-type" = "External"
    }
  }
  spec {
    load_balancer_ip = google_compute_address.static_ip.address
    // ... rest of service spec
  }
}