Integrate Istio's Service Mesh ingress gateway with a GCP Ingress Load Balancer

· 3 min read

After realizing that there wasn't a comprehensive tutorial on integrating GCP's Ingress load balancer with Istio Service Mesh in a Google Kubernetes Engine (GKE) cluster, I decided to create one myself. I hope this guide saves you hours of searching and helps streamline your kubernetes networking.

Note: This tutorial expects you have already setup your DNS records (example A , CNAME records) pointing to the external IP used by GCP's Ingress load balancer.

So let's start by defining our GCP Ingress Load Balancer.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
  name: gcp-load-balancer
  namespace: istio-ingress # the k8s ns where your istio-ingressgateway helm chart is deployed
spec:
  rules:
    - host: '*.example.domain.com'
      http:
        paths:
        # This section specifies the backend service to which the traffic matching the rule should be routed.
          - backend:
              service:
                # This is the name of the Kubernetes service that will handle the traffic. Here, it points to the istio-ingressgateway service
                name: istio-ingressgateway
                port:
                  number: 80
            path: /
            pathType: Prefix

GCP's BackendConfig used by the Ingress named gcp-load-balancer to check if the defined service is healthy.

apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: load-balancer-backendconfig
  namespace: istio-ingress
spec:
  healthCheck:
    checkIntervalSec: 30
    port: 15021 # will match port defined in istio helm chart, check below
    requestPath: /healthz/ready
    type: HTTP

For this example I'm using Istio's ingress gateway helm chart with default values (as can be seen here) except for the following overrides:

defaults:
  ...
  service:
    type: NodePort # change to NodePort since the load balancer is GCP's ingress defined above
    ports:
    
    - name: status-port
      port: 30276
      protocol: TCP
      targetPort: 15021 # this needs to match PORT defined in BackendConfig above
      
    - name: http
      port: 80
      protocol: TCP
      targetPort: 80
    
    - name: https
      port: 443
      protocol: TCP
      targetPort: 443
    
      annotations: {
      # Add backend for gcp-load-balancer healthcheck 
      cloud.google.com/backend-config: '{"default": "load-balancer-backendconfig"}',
    }
    ...

Now we want to serve a simple example k8s Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: status-app
  name: status-app
  namespace: status
spec:
  revisionHistoryLimit: 1
  selector:
    matchLabels:
      app: status-app
  template:
    metadata:
      labels:
        app: status-app
    spec:
      containers:
        - args:
            - ok
        - name: nodejs-server-port-80
          # this is just a simple nodejs server that returns status 200 and string "ok" 
          image: cooervo/server-with-args
          ports:
            - containerPort: 80

The corresponding Service:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: status-app
  name: status-app
  namespace: status
spec:
  ports:
    - name: http
      port: 80
      targetPort: 80
  selector:
    app: status-app
  type: ClusterIP # type ClusterIp since external access is handled by gcp-load-balancer and the istio-ingressgateway

Now add Istio's Gateway which uses wildcard hosts since the specific paths will be handled by Istio's VirtualServices

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: default-gateway
  namespace: istio-ingress
spec:
  selector:
    istio: ingressgateway
  servers:
    - hosts:
        - '*.example.domain.com'
      port:
        name: http
        number: 80
        protocol: HTTP

Finally, let's add an Istio VirtualService for handling the status.example.domain.com path.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: status-app
  namespace: status
spec:
  gateways:
  # the gateway defined above
    - istio-ingress/default-gateway
  hosts:
    - status.example.domain.com
  http:
    - match:
        - uri:
            prefix: /
      route:
        - destination:
            # host value is {service}.{namespace}.svc.cluster.local
            host: status-app.status.svc.cluster.local
            port:
              number: 80

That's all once you have deployed all the above resources the status example app should be accessible when visiting http://status.example.domain.com

Attaching a TLS certificate

Using cert-manager certificates with the Ingress load balancer is posible.

Firstly, install cert-manager with your prefer method I used the official helm chart.

Create a ClusterIssuer as follows:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    email: my-email@domain.com
    privateKeySecretRef:
      name: letsencrypt
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
      - dns01:
          cloudDNS:
            project: your-gcp-project
        selector:
          dnsZones:
            - '*.example.domain.com'

Now you can create a wildcard certificate to use in your http load balancer:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-cert
  namespace: istio-ingress
spec:
  dnsNames:
    - '*.example.domain.com'
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt
  secretName: wildcard-cert

Finally attach it in your Ingress by adding the following block:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    ...
    # the name of the ClusterIssuer defined above
    cert-manager.io/cluster-issuer: letsencrypt
  name: gcp-load-balancer
  namespace: istio-ingress
spec:
  rules:
    ...
  tls:
    - hosts:
        - '*.example.domain.com'
      secretName: wildcard-cert

You should be able to visit https://status.example.domain.com the TLS wildcard certificate will be used to encrypt connections to your kubernetes services.