Managing Kubernetes Ingress traffic is an essential use case. If you are running the same services across multiple GKE clusters/regions, you can use a GCP HTTP(S) Load Balancer, configured with a global anycast IP, combined with Ingress resources for your services, to route client traffic to the closest GKE cluster.
But what if you've installed Istio on your clusters, and want to leverage the policy logic of Istio Gateways
, and at the same time, enable multicluster load balancing? This sample demonstrates how to use the kubemci
tool to map a global Anycast IP to multiple Istio IngressGateways, running in three separate clusters/regions. In the end, we will show how to route client requests to the closest instance of a service, which is replicated across three clusters.
- One GCP Project with Billing enabled
- GCE, Container APIs enabled
- gcloud SDK
- kubectl
First, set your project ID:
export PROJECT_ID="<your-project-id>"
Ensure gcloud is authenticated to your account:
gcloud auth login
gcloud auth application-default login
Then, run the following script to create three GKE clusters in the us-east
, us-west
, and europe-west
regions:
./1-create-clusters.sh
Wait for all three clusters to be RUNNING
:
watch gcloud container clusters list
After a few minutes, you should see:
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERS
ION NUM_NODES STATUS
cluster3 europe-west2-b 1.14.8-gke.12 <IP> n1-standard-4 1.14.8-gke.12 3 RUNNING
cluster2 us-east4-a 1.14.8-gke.12 <IP> n1-standard-4 1.14.8-gke.12 3 RUNNING
cluster1 us-west2-a 1.14.8-gke.12 <IP> n1-standard-4 1.14.8-gke.12 3 RUNNING
Install the Istio control plane on all three clusters. Note - this script may take 5-10 minutes to complete.
./2-install-istio.sh
Deploy the Zone Printer sample application on all three clusters. This application is a simple web server that prints the GKE Region/Zone it's running in.
./3-deploy-app.sh
This script creates four resources:
- A Kubernetes Deployment,
zone-printer
- A Kubernetes Service,
zone-printer
(typeClusterIP
), mapping to thezone-printer
Deployment - An Istio Gateway, punching port
80
into the default IngressGateway - An Istio
VirtualService
to route inbound requests through the Gateway, to thezone-printer
Service on port80
.
Note that right now (and by default), the Istio IngressGateway is mapped to a service type LoadBalancer
, and has its own separate public IP. We can separately call that IngressGateway IP on all three clusters to verify that the zone printer app is running:
./4-verify-app.sh
You should see HTML output for each cluster, including reports from three separate regions:
<h1>us-west2-a!</h1>
...
<h1>Ashburn, Virginia, USA</h1>
...
<h1>London, U.K.</h1>
Now we're ready to put a global IP in front of all threy Istio IngressGateways, to enable geo-aware anycast routing.
./5-prep-mci.sh
This script does the following:
- Creates a VirtualService on all three clusters, to set up health checking for the IngressGateway. This is needed for GCP load balancer health checking. Because the IngressGateway already exposes a
/healthz
endpoint on port15020
, we just have to do a URL rewrite for requests from theGoogleHC
user-agent. - Updates the Service type on the Istio IngressGateway on all three clusters, from
LoadBalancer
toNodePort
. A NodePort service is needed to configure Ingress.
kubemci requires that all the NodePorts used for the multicluster ingress be the same. Navigate to Cloud Console > Kubernetes Engine > Services & Ingress, and for the service istio-ingressgateway
on all three services, click "Edit" and update the http2
nodeport to be 30981
:
./6-mci.sh
This script does the following:
- Reserves a global static IP in your project, named
zoneprinter-ip
. - Installs
kubemci
, then uses it to provision a multicluster Ingress, mapping to the three clusters. Thekubemci create
command takes in the following Kubernetes Ingress resource (seemanifests/ingress.yaml
):
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-zoneprinter
namespace: istio-system
annotations:
kubernetes.io/ingress.global-static-ip-name: zoneprinter-ip
kubernetes.io/ingress.class: gce-multi-cluster
spec:
backend:
serviceName: istio-ingressgateway
servicePort: 80
Here, we're creating an Ingress
for our existing NodePort backend service, istio-ingressgateway
. We add the name of the global static IP we reserved, and a multicluster ingress.class
which tells the kubemci tool to configure Ingress resources on all three clusters in our Kubeconfig. From here, the kubemci
tool provisions a GCP HTTP(S) Load Balancer, including backend services, url mappings, and firewall rules.
Two notes about this process:
- This is exactly how you'd do multicluster ingress for a single service, representing one backend workload (one Kubernetes Service) - the difference is that here, we're instead setting up ingress to an intermediate IngressGateway, allowing us to configure Istio traffic rules for many backend workloads, each srepresented by its own Kubernetes Service.
- Any multicluster ingress setup assumes that the same workloads (and in this case, traffic rules). We recommend using a Continuous Deployment tool to ensure that all clusters have ths same set of resources.
Verify that the multicluster ingress was created, by running ./kubemci list
. You should see:
NAME IP CLUSTERS
zoneprinter-ingress <your-global-IP> cluster1, cluster2, cluster3
Then, run:
./kubemci get-status zoneprinter-ingress --gcp-project=${PROJECT_ID}
You should see:
Load balancer zoneprinter-ingress has IPAddress <ip> and is spread across 3 clusters (cluster1,cluster2,cluster3)
But how does this multi-cluster zoneprinter-ingress
load balancer translate into individual cluster resources? From cluster3
, run:
kubectl get ingress ingress-zoneprinter -n istio-system -o yaml
You should see:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.gcp.kubernetes.io/instance-groups: '[{"Name":"k8s-ig--eab086beeb92f4d7","Zone":"https://www.googleapis.com/compute/v1/projects/istio-multicluster-ingress/zones/europe-west2-b"}]'
kubernetes.io/ingress.class: gce-multi-cluster
kubernetes.io/ingress.global-static-ip-name: zoneprinter-ip
...
Here, each cluster is its own instance group (a managed GKE cluster). If you navigate in the Cloud Console to Network Services > Load Balancing, and click on the load balancer beginning with mci...
, you should see:
Now that we've configure multicluster ingress for a global anycast IP, we should be able to access the global IP from clients around the world, and be routed to the ZonePrinter running in the closest GKE cluster. Note: it may take about 5 minutes to provision the load balancer.
For instance, from a laptop connected to a network on the East Coast, we're redirected to the us-east4
cluster:
mokeefe@laptop:~$ curl 34.102.158.9
<!DOCTYPE html>
<h4>Welcome from Google Cloud datacenters at:</h4>
<h1>Ashburn, Virginia, USA</h1>
<h3>You are now connected to "us-east4-a"</h3>
<img src="https://upload.wikimedia.org/wikipedia/en/a/a4/Flag_of_the_United_States.svg" style="width: 640px; height: auto; border: 1px solid black"/>
To test the geo-aware routing further, you can create Google Compute Engine instances in different regions, ssh into each one, and curl
the global IP. For instance, a VM in the Netherlands (europe-west4-a
) is routed to the London (europe-west2-b
) cluster:
mokeefe@netherlands-client:~$ curl 34.102.158.9
<!DOCTYPE html>
<h4>Welcome from Google Cloud datacenters at:</h4>
<h1>London, U.K.</h1>
<h3>You are now connected to "europe-west2-b"</h3>
<img src="https://upload.wikimedia.org/wikipedia/en/a/ae/Flag_of_the_United_Kingdom.svg" style="wid
th: 640px; height: auto; border: 1px solid black"/>
And a VM in Oregon (us-west1-b
) is routed to the Los Angeles (us-west2-a
) cluster:
mokeefe@oregon-client:~$ curl 34.102.158.9
<!DOCTYPE html>
<h4>Welcome from Google Cloud datacenters at:<h4>
<h1>us-west2-a!</h1>
🎊 Well done! You just set up geo-aware load balancing for Istio services running across three regions.
To delete the resources used in this sample (ingress, static IP, GKE clusters):
./7-cleanup.sh